新增 AutoRDO 需求清洗工作台与消息中枢,迭代 OneOS V2 设计规范及租赁合同/工作台/车辆等原型,同步云效技能与导航注册;并归档一批 legacy 原型快照。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
王冕
2026-07-28 15:50:35 +08:00
parent b14425da5a
commit 5d51a6bf7a
1393 changed files with 488042 additions and 10046 deletions

View File

@@ -0,0 +1,220 @@
#!/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();