Skip to content

Commit

Permalink
feat: new logic, changes stay
Browse files Browse the repository at this point in the history
  • Loading branch information
v8tenko committed May 16, 2024
1 parent 8382042 commit 176ba71
Showing 1 changed file with 115 additions and 116 deletions.
231 changes: 115 additions & 116 deletions src/steps/processChangelogs.ts
Original file line number Diff line number Diff line change
@@ -1,150 +1,149 @@
import {glob} from '../utils/glob';
import {join} from 'node:path';
import {ArgvService, TocService} from '../services';
import {readFile, unlink, writeFile} from 'node:fs/promises';
import {Lang} from '../constants';

type MergedChangelogs = {
[pathToProjectToc: string]: {
[language: string]: {
[pathToFile: string]: unknown;
};
};
import {dirname, join, normalize, resolve} from 'node:path';
import {ArgvService} from '../services';
import {copyFile, mkdir, readFile, writeFile} from 'node:fs/promises';
import {groupBy} from 'lodash';
import {isExternalHref} from '../utils';

export const CHANGELOG_LIMIT = 50;
export const LANG_SERVICE_RE =
/(?<lang>[^/]+)\/(?<service>[^/]+)\/changelogs\/__changes-(?<name>[^.]+)\.json$/;

type FileItem = {
lang: string;
service: string;
name: string;
filepath: string;
index?: number;
};

type ParsedPath = {
language: string;
path: string;
level: number;
type ChangelogItem = {
date: string;
title: string;
storyId?: string;
image?: {
src: string;
alt?: string;
ratio?: number;
};
description: string;
[x: string]: unknown;
};

const hasSingleLanguage = () => {
const {langs} = ArgvService.getConfig();
return typeof langs === 'undefined' || langs.length === 1;
};
export async function processChangelogs() {
const {output: outputFolderPath, changelogs} = ArgvService.getConfig();

const project = (path: string): ParsedPath => {
const parts = path.split('/').slice(0, -1);
const language = hasSingleLanguage() ? Lang.EN : parts.shift()!;
const level = parts.length;
if (!changelogs) {
return;
}

return {
path: '/' + parts.join('/'),
language,
level,
};
};
const result = await glob('**/**/__changes-*.json', {
cwd: outputFolderPath,
});

const file = (path: string): ParsedPath => {
const parts = path.split('/');
const language = hasSingleLanguage() ? Lang.EN : parts.shift()!;
const files = result.state.found;

return {
path: '/' + parts.join('/'),
language,
level: -1,
};
};
if (!files.length) {
return;
}

type Project = {
path: string;
languages: string[];
level: number;
};
const changeFileItems: FileItem[] = [];

/*
This function collects all the project's subprojects and sorts them by depth level.
This is done to make it easier to find which toc.yaml file is responsible
for the necessary changes file: the earlier the project is in the list, the deeper it is.
The first project whose path to toc.yaml shares a common prefix with the path to changes
will be considered responsible for it.
*/
const uniqueProjects = (tocs: string[]): [string, Project][] => {
const projects = tocs.map(project);
const composed = projects.reduce(
(acc, curr) => {
if (acc[curr.path]) {
acc[curr.path].languages.push(curr.language);

return acc;
}

acc[curr.path] = {
languages: [curr.language],
path: curr.path,
level: curr.level,
};

return acc;
},
{} as Record<string, Project>,
);
files.forEach((relPath) => {
const filepath = join(outputFolderPath, relPath);
const m = relPath.match(LANG_SERVICE_RE);
if (!m) {
return;
}

const entries = Object.entries(composed).sort((a, b) => {
return b[1].level - a[1].level;
const {lang, service, name} = m.groups as {lang: string; service: string; name: string};
let index;
if (/^\d+$/.test(name)) {
index = Number(name);
}

changeFileItems.push({lang, service, filepath, name, index});
});

return entries;
};
const usedFileItems: FileItem[][] = [];

export async function processChangelogs() {
const {output: outputFolderPath} = ArgvService.getConfig();
const tocs = TocService.getAllTocs();
const projects = uniqueProjects(tocs);
const langServiceFileItems = groupBy(
changeFileItems,
({lang, service}) => `${lang}_${service}`,
);

const result = await glob('**/**/__changes-*.json', {
cwd: outputFolderPath,
Object.values(langServiceFileItems).forEach((fileItems) => {
const hasIdx = fileItems.every(({index}) => index !== undefined);
fileItems
.sort(({name: a, index: ai}, {name: b, index: bi}) => {
if (hasIdx && ai !== undefined && bi !== undefined) {
return bi - ai;
}
return b.localeCompare(a);
})
.splice(CHANGELOG_LIMIT);

usedFileItems.push(fileItems);
});

const files = result.state.found;
const processed = await Promise.all(
usedFileItems.map(async (items) => {
const {lang, service} = items[0];

if (!files.length) {
return;
}
const basePath = join(outputFolderPath, lang, service);

const merged: MergedChangelogs = {};
const changelogs: ChangelogItem[] = await Promise.all(
items.map(async ({filepath}) => readFile(filepath, 'utf8').then(JSON.parse)),
);

const changes = await Promise.all(
files.map((path) => {
const filePath = join(outputFolderPath, path);
changelogs.forEach((changelog) => {
const {source: sourceName} = changelog as {source?: string};
if (sourceName) {
// eslint-disable-next-line no-param-reassign
changelog.link = `/${service}/changelogs/${
sourceName === 'index' ? '' : sourceName
}`;
}
});

return readFile(filePath).then(
(buffer) => [path, JSON.parse(buffer.toString())] as [string, unknown],
);
}),
);
const imgSet = new Set();

changes.forEach(([path, value]) => {
const {language, path: pathToFile} = file(path);
await Promise.all(
changelogs.map(async ({image}, idx) => {
if (!image) {
return undefined;
}

const project = projects.find(([pathToProject, project]) => {
return pathToFile.startsWith(pathToProject) && project.languages.includes(language);
});
const {filepath} = items[idx];
const {src} = image;

if (!project) {
return;
}
if (isExternalHref(src)) {
return undefined;
}

const [projectPath] = project;
const imgPath = resolve(dirname(filepath), src);
const normSrc = normalize(`/${src}`);
const newImagePath = join(basePath, '_changelogs', normSrc);

merged[projectPath] ??= {};
merged[projectPath][language] ??= {};
if (!imgSet.has(newImagePath)) {
imgSet.add(newImagePath);

Object.assign(merged[projectPath][language], {
[path]: value,
});
});
await mkdir(dirname(newImagePath), {recursive: true});
await copyFile(imgPath, newImagePath);
}

image.src = join(lang, service, '_changelogs', normSrc);

await Promise.all(
files.map((path) => {
const filePath = join(outputFolderPath, path);
return image;
}),
);

return unlink(filePath);
return [service, changelogs];
}),
);

const changelogPath = join(outputFolderPath, 'changelog.json');

await writeFile(changelogPath, JSON.stringify(merged, null, 4));
await writeFile(
join(outputFolderPath, 'changelogs.minified.json'),
JSON.stringify(Object.fromEntries(processed), null, 4),
);
}

0 comments on commit 176ba71

Please sign in to comment.