fix(changelog): restore stable path aliases for #5252
changelog-filename-gate / validate (pull_request) Successful in 2s
changelog-filename-gate / validate (pull_request) Successful in 2s
这个提交包含在:
@@ -9,6 +9,7 @@ const CONTROLLED_ROOTS = new Map([
|
||||
['changelogs-v2-mp', '小程序端'],
|
||||
]);
|
||||
const CHANGE_TYPES = new Set(['新增接口', '修改接口', '删除接口']);
|
||||
const PATH_ALIAS_MANIFEST = 'changelog-path-aliases.json';
|
||||
|
||||
function ruleError(code, path, message, expected) {
|
||||
return { code, path, message, expected };
|
||||
@@ -76,6 +77,40 @@ export function collectNewTargetPaths(records) {
|
||||
.map(({ targetPath }) => targetPath);
|
||||
}
|
||||
|
||||
export function pathAliasMapFromManifest(value) {
|
||||
if (value?.schema !== 'hl-changelog-path-aliases/v1' || !Array.isArray(value.aliases)) {
|
||||
throw new Error('changelog-path-aliases.json schema 或 aliases 非法');
|
||||
}
|
||||
const aliases = new Map();
|
||||
for (const [index, entry] of value.aliases.entries()) {
|
||||
if (
|
||||
!entry
|
||||
|| typeof entry.alias !== 'string'
|
||||
|| typeof entry.canonical !== 'string'
|
||||
|| entry.alias.length === 0
|
||||
|| entry.canonical.length === 0
|
||||
) {
|
||||
throw new Error(`changelog-path-aliases.json aliases[${index}] 缺少 alias/canonical`);
|
||||
}
|
||||
if (aliases.has(entry.alias)) {
|
||||
throw new Error(`changelog-path-aliases.json alias 重复: ${entry.alias}`);
|
||||
}
|
||||
aliases.set(entry.alias, entry.canonical);
|
||||
}
|
||||
return aliases;
|
||||
}
|
||||
|
||||
export function loadPathAliasMap(root = process.cwd()) {
|
||||
try {
|
||||
return pathAliasMapFromManifest(JSON.parse(readFileSync(`${root}/${PATH_ALIAS_MANIFEST}`, 'utf8')));
|
||||
} catch (error) {
|
||||
if (error?.code === 'ENOENT') {
|
||||
return new Map();
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function controlledRootForPath(inputPath) {
|
||||
const candidate = String(inputPath);
|
||||
for (const root of CONTROLLED_ROOTS.keys()) {
|
||||
@@ -178,10 +213,32 @@ export function validateChangelogPath(inputPath, now = new Date()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
export function runValidation(records, now = new Date()) {
|
||||
export function runValidation(records, now = new Date(), { pathAliases = new Map() } = {}) {
|
||||
const targetPaths = collectNewTargetPaths(records);
|
||||
const controlledPaths = targetPaths.filter((targetPath) => controlledRootForPath(targetPath));
|
||||
const errors = controlledPaths.flatMap((targetPath) => validateChangelogPath(targetPath, now));
|
||||
const errors = controlledPaths.flatMap((targetPath) => (
|
||||
pathAliases.has(targetPath) ? [] : validateChangelogPath(targetPath, now)
|
||||
));
|
||||
for (const record of records) {
|
||||
const sourcePath = /^R\d{1,3}$/.test(record.status)
|
||||
? record.sourcePath
|
||||
: record.status === 'D' ? record.targetPath : undefined;
|
||||
if (!sourcePath || !controlledRootForPath(sourcePath) || !sourcePath.endsWith('.md')) {
|
||||
continue;
|
||||
}
|
||||
const expectedTarget = pathAliases.get(sourcePath);
|
||||
const actualTarget = /^R\d{1,3}$/.test(record.status) ? record.targetPath : undefined;
|
||||
if (!expectedTarget || (actualTarget && expectedTarget !== actualTarget)) {
|
||||
errors.push(ruleError(
|
||||
'E_PATH_STABILITY',
|
||||
sourcePath,
|
||||
'已发布 changelog 不得在缺少精确 alias 关系时删除或重命名',
|
||||
actualTarget
|
||||
? `在 ${PATH_ALIAS_MANIFEST} 登记 ${sourcePath} -> ${actualTarget} 并保留兼容入口`
|
||||
: `在 ${PATH_ALIAS_MANIFEST} 登记迁移关系并保留兼容入口`,
|
||||
));
|
||||
}
|
||||
}
|
||||
return {
|
||||
targetCount: targetPaths.length,
|
||||
checkedCount: controlledPaths.length,
|
||||
@@ -294,7 +351,9 @@ export function main(argv = process.argv.slice(2)) {
|
||||
try {
|
||||
const options = parseArguments(argv);
|
||||
const records = parseNameStatusZ(diffFromOptions(options));
|
||||
const result = runValidation(records, new Date());
|
||||
const result = runValidation(records, new Date(), {
|
||||
pathAliases: loadPathAliasMap(),
|
||||
});
|
||||
if (result.errors.length > 0) {
|
||||
for (const error of result.errors) {
|
||||
console.error(`[${error.code}] ${error.path}: ${error.message}; expected ${error.expected}`);
|
||||
|
||||
在新工单中引用
屏蔽一个用户