Files
OneOS1.2/scripts/snapshot-prototypes-legacy.mjs

221 lines
7.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 将 src/prototypes 下业务原型整套复制为 <id>-legacy供设计规范 v2 对照。
* 排除oneos-prototype-nav、已有 *-legacy
* 复制时跳过:.spec/acp、.spec/prototype-comment-assets体积大、非页面源码
*
* Usage: node scripts/snapshot-prototypes-legacy.mjs
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.resolve(__dirname, '..');
const prototypesDir = path.join(root, 'src/prototypes');
const sidebarPath = path.join(root, '.axhub/make/sidebar-tree.json');
const SKIP_DIRS = new Set(['oneos-prototype-nav']);
const RSYNC_EXCLUDES = [
'--exclude=.spec/acp',
'--exclude=.spec/prototype-comment-assets',
'--exclude=node_modules',
'--exclude=.DS_Store',
];
function listSourceIds() {
return fs
.readdirSync(prototypesDir, { withFileTypes: true })
.filter((d) => d.isDirectory())
.map((d) => d.name)
.filter((name) => !SKIP_DIRS.has(name) && !name.endsWith('-legacy'))
.sort();
}
function collectTitles(node, map = new Map()) {
if (!node) return map;
if (Array.isArray(node)) {
for (const child of node) collectTitles(child, map);
return map;
}
if (node.kind === 'item' && node.itemKey?.startsWith('prototypes/')) {
const id = node.itemKey.slice('prototypes/'.length);
map.set(id, node.title || id);
}
if (node.children) collectTitles(node.children, map);
if (node.prototypes) collectTitles(node.prototypes, map);
return map;
}
function walkFiles(dir, out = []) {
if (!fs.existsSync(dir)) return out;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
if (entry.name === 'node_modules' || entry.name === '.git') continue;
walkFiles(full, out);
} else if (/\.(tsx?|jsx?|css|md|json|html|js)$/i.test(entry.name)) {
out.push(full);
}
}
return out;
}
function rewriteFileContent(content, sourceIds) {
let next = content;
// Longer ids first to avoid partial replaces
const ids = [...sourceIds].sort((a, b) => b.length - a.length);
for (const id of ids) {
const legacy = `${id}-legacy`;
// path segments / imports
next = next.replaceAll(`prototypes/${id}/`, `prototypes/${legacy}/`);
next = next.replaceAll(`prototypes/${id}'`, `prototypes/${legacy}'`);
next = next.replaceAll(`prototypes/${id}"`, `prototypes/${legacy}"`);
next = next.replaceAll(`../${id}/`, `../${legacy}/`);
next = next.replaceAll(`'../${id}'`, `'../${legacy}'`);
next = next.replaceAll(`"../${id}"`, `"../${legacy}"`);
next = next.replaceAll(`/${id}/index.html`, `/${legacy}/index.html`);
}
return next;
}
function patchAnnotationTitles(filePath, legacyId, displayTitle) {
if (!fs.existsSync(filePath)) return;
try {
const raw = fs.readFileSync(filePath, 'utf8');
const doc = JSON.parse(raw);
if (doc && typeof doc === 'object') {
if (typeof doc.title === 'string') doc.title = displayTitle;
if (typeof doc.name === 'string') doc.name = displayTitle;
if (doc.meta && typeof doc.meta === 'object') {
doc.meta.legacyOf = legacyId.replace(/-legacy$/, '');
doc.meta.title = displayTitle;
}
fs.writeFileSync(filePath, `${JSON.stringify(doc, null, 2)}\n`);
}
} catch {
// leave as-is if not JSON object
}
}
function copyOne(id) {
const src = path.join(prototypesDir, id);
const dest = path.join(prototypesDir, `${id}-legacy`);
if (fs.existsSync(dest)) {
fs.rmSync(dest, { recursive: true, force: true });
}
const result = spawnSync(
'rsync',
['-a', ...RSYNC_EXCLUDES, `${src}/`, `${dest}/`],
{ encoding: 'utf8' }
);
if (result.status !== 0) {
// fallback cp
fs.cpSync(src, dest, {
recursive: true,
filter: (p) =>
!p.includes(`${path.sep}.spec${path.sep}acp`) &&
!p.includes(`${path.sep}prototype-comment-assets`) &&
!p.includes(`${path.sep}node_modules`),
});
}
return dest;
}
function addLegacySidebarFolder(sidebar, legacyEntries) {
const folderId = 'folder-ds-v2-legacy-snapshots';
const folder = {
id: folderId,
kind: 'folder',
title: '设计规范对照·旧版',
children: legacyEntries.map(({ id, title }) => ({
id: `item:prototypes:${id}`,
kind: 'item',
title,
itemKey: `prototypes/${id}`,
})),
};
// nav-menu 只同步 title === 'OneOS' 的分区,旧版必须挂在其下
const list = sidebar.prototypes || [];
const oneos = list.find((n) => n.kind === 'folder' && n.title === 'OneOS');
if (!oneos) {
throw new Error('sidebar-tree.json 中未找到 OneOS 分区');
}
oneos.children = Array.isArray(oneos.children) ? oneos.children : [];
// 移除顶层误挂的同名文件夹
sidebar.prototypes = list.filter((n) => n.id !== folderId);
const idx = oneos.children.findIndex((n) => n.id === folderId);
if (idx >= 0) oneos.children[idx] = folder;
else oneos.children.push(folder);
sidebar.updatedAt = new Date().toISOString();
}
function main() {
const sourceIds = listSourceIds();
const sidebar = JSON.parse(fs.readFileSync(sidebarPath, 'utf8'));
const titleMap = collectTitles(sidebar);
console.log(`将复制 ${sourceIds.length} 个原型 → *-legacy`);
for (const id of sourceIds) {
process.stdout.write(` copy ${id} ... `);
copyOne(id);
console.log('ok');
}
const legacyIds = sourceIds.map((id) => `${id}-legacy`);
const allSourceAndLegacy = new Set([...sourceIds, ...legacyIds]);
// Rewrite cross-refs inside legacy trees to stay frozen against live restyles
for (const legacyId of legacyIds) {
const dir = path.join(prototypesDir, legacyId);
const baseId = legacyId.replace(/-legacy$/, '');
const title = `【旧版】${titleMap.get(baseId) || baseId}`;
const files = walkFiles(dir);
for (const file of files) {
const before = fs.readFileSync(file, 'utf8');
let after = rewriteFileContent(before, sourceIds);
if (after !== before) fs.writeFileSync(file, after);
}
patchAnnotationTitles(path.join(dir, 'annotation-source.json'), legacyId, title);
fs.writeFileSync(
path.join(dir, 'LEGACY.md'),
`# ${title}
本目录为设计规范 v2 套用前的**冻结对照副本**${new Date().toISOString().slice(0, 10)})。
- 源原型:\`src/prototypes/${baseId}/\`
- 请勿在此目录继续改业务;正式改造只在源目录进行
- 已跳过复制:\`.spec/acp\`\`.spec/prototype-comment-assets\`(批注大图/会话)
- 交叉引用已尽量改写为同套 \`*-legacy\`,避免引用被改版后的正式样式
`
);
}
const legacyEntries = sourceIds.map((id) => ({
id: `${id}-legacy`,
title: `【旧版】${titleMap.get(id) || id}`,
}));
addLegacySidebarFolder(sidebar, legacyEntries);
fs.writeFileSync(sidebarPath, `${JSON.stringify(sidebar, null, 2)}\n`);
console.log(`已更新 sidebar-tree.json${legacyEntries.length} 个旧版入口)`);
const sync = spawnSync(
'npm',
['run', 'nav:sync', '--', '--note', '注册设计规范 v2 前整套 legacy 对照副本'],
{ cwd: root, encoding: 'utf8', shell: true }
);
console.log(sync.stdout || '');
if (sync.stderr) console.error(sync.stderr);
if (sync.status !== 0) {
console.error('nav:sync 失败,请手动执行 npm run nav:sync');
process.exit(sync.status || 1);
}
console.log('完成。');
}
main();