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
这个提交包含在:
@@ -0,0 +1,132 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
|
||||
import {
|
||||
loadPathAliasManifest,
|
||||
parsePathAliasManifest,
|
||||
resolveChangelogPath,
|
||||
transitionAliasedFrontendState,
|
||||
validatePathAliases,
|
||||
} from '../scripts/changelog-path-aliases.mjs';
|
||||
import { parseFrontmatter } from '../scripts/validate-changelog-frontmatter.mjs';
|
||||
|
||||
const OLD_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-前端待处理-管理后台.md';
|
||||
const CURRENT_5216 = 'changelogs-v2/2026-07/24_5216_派车看板补充槽位接送路线与就绪摘要-修改接口-管理后台.md';
|
||||
|
||||
function document({ canonicalPath = '', status = 'claimed', owner = 'frontend-team', frontendRef = '' } = {}) {
|
||||
const canonicalLine = canonicalPath ? `canonical_path: "${canonicalPath}"\n` : '';
|
||||
return `---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "5216"
|
||||
title: "路径兼容测试"
|
||||
consumer: "admin"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "${status}"
|
||||
frontend_owner: "${owner}"
|
||||
frontend_ref: "${frontendRef}"
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
${canonicalLine}updated_at: "2026-07-26"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 路径兼容测试
|
||||
|
||||
## 变更接口
|
||||
|
||||
- 无业务接口变化。
|
||||
|
||||
## 验证证据
|
||||
|
||||
- 自动化测试。
|
||||
`;
|
||||
}
|
||||
|
||||
function createFixture() {
|
||||
const root = mkdtempSync(path.join(tmpdir(), 'hl-changelog-alias-'));
|
||||
const manifest = {
|
||||
schema: 'hl-changelog-path-aliases/v1',
|
||||
aliases: [{
|
||||
ticket: '5216',
|
||||
alias: OLD_5216,
|
||||
canonical: CURRENT_5216,
|
||||
reason: '测试旧路径兼容',
|
||||
}],
|
||||
};
|
||||
writeFileSync(path.join(root, 'changelog-path-aliases.json'), JSON.stringify(manifest, null, 2));
|
||||
for (const repoPath of [OLD_5216, CURRENT_5216]) {
|
||||
const absolute = path.join(root, ...repoPath.split('/'));
|
||||
mkdirSync(path.dirname(absolute), { recursive: true });
|
||||
writeFileSync(absolute, document({
|
||||
canonicalPath: repoPath === OLD_5216 ? CURRENT_5216 : '',
|
||||
}));
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
test('repository #5216 compatibility entry resolves to its canonical document', () => {
|
||||
const manifest = loadPathAliasManifest();
|
||||
assert.equal(resolveChangelogPath(OLD_5216, manifest), CURRENT_5216);
|
||||
assert.equal(resolveChangelogPath(CURRENT_5216, manifest), CURRENT_5216);
|
||||
assert.deepEqual(validatePathAliases().errors, []);
|
||||
});
|
||||
|
||||
test('rejects unsafe paths, duplicate aliases and alias chains', () => {
|
||||
assert.throws(() => parsePathAliasManifest(JSON.stringify({
|
||||
schema: 'hl-changelog-path-aliases/v1',
|
||||
aliases: [{
|
||||
ticket: '5216',
|
||||
alias: '../outside.md',
|
||||
canonical: CURRENT_5216,
|
||||
reason: 'unsafe',
|
||||
}],
|
||||
})), /POSIX|规范路径/);
|
||||
assert.throws(() => parsePathAliasManifest(JSON.stringify({
|
||||
schema: 'hl-changelog-path-aliases/v1',
|
||||
aliases: [
|
||||
{ ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'one' },
|
||||
{ ticket: '5216', alias: OLD_5216, canonical: CURRENT_5216, reason: 'two' },
|
||||
],
|
||||
})), /重复/);
|
||||
});
|
||||
|
||||
test('frontend status transition updates alias group once and is idempotent', () => {
|
||||
const root = createFixture();
|
||||
try {
|
||||
const first = transitionAliasedFrontendState(root, OLD_5216, {
|
||||
status: 'implemented',
|
||||
owner: 'frontend-team',
|
||||
frontendRef: 'mmg/hl-ui@abc1234',
|
||||
}, {
|
||||
now: new Date('2026-07-26T04:00:00Z'),
|
||||
write: true,
|
||||
});
|
||||
assert.deepEqual(new Set(first.changedPaths), new Set([OLD_5216, CURRENT_5216]));
|
||||
assert.deepEqual(validatePathAliases(root).errors, []);
|
||||
|
||||
for (const repoPath of [OLD_5216, CURRENT_5216]) {
|
||||
const text = readFileSync(path.join(root, ...repoPath.split('/')), 'utf8');
|
||||
const { metadata } = parseFrontmatter(text);
|
||||
assert.equal(metadata.frontend_status, 'implemented');
|
||||
assert.equal(metadata.frontend_owner, 'frontend-team');
|
||||
assert.equal(metadata.frontend_ref, 'mmg/hl-ui@abc1234');
|
||||
}
|
||||
|
||||
const second = transitionAliasedFrontendState(root, OLD_5216, {
|
||||
status: 'implemented',
|
||||
owner: 'frontend-team',
|
||||
frontendRef: 'mmg/hl-ui@abc1234',
|
||||
}, {
|
||||
now: new Date('2026-07-26T04:00:00Z'),
|
||||
write: true,
|
||||
});
|
||||
assert.deepEqual(second.changedPaths, []);
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户