-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #763 from diplodoc-platform/fix-changelogs-logiv
feat: new logic, changes stay
- Loading branch information
Showing
1 changed file
with
115 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
); | ||
} |