bookworm-smart-assistant/scripts/archive/migrate-to-user.js

368 lines
12 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* Bookworm 系统迁移脚本
* 将完整的 Bookworm Smart Assistant 系统复刻到目标用户账户
*
* 用法:
* node migrate-to-user.js <目标用户名>
* node migrate-to-user.js janson9527us
*
* 可选参数:
* --dry-run 仅预览不实际复制
* --force 覆盖已存在的目标文件
*/
const fs = require('fs');
const path = require('path');
// ─── 参数解析 ────────────────────────────────────────────
const args = process.argv.slice(2);
const flags = args.filter(a => a.startsWith('--'));
const positional = args.filter(a => !a.startsWith('--'));
const DRY_RUN = flags.includes('--dry-run');
const FORCE = flags.includes('--force');
const TARGET_USER = positional[0];
if (!TARGET_USER) {
console.error('用法: node migrate-to-user.js <目标用户名> [--dry-run] [--force]');
console.error('示例: node migrate-to-user.js janson9527us');
process.exit(1);
}
// ─── 路径定义 ────────────────────────────────────────────
const SOURCE_ROOT = 'C:\\Users\\janson9527us\\.claude';
const TARGET_ROOT = `C:\\Users\\${TARGET_USER}\\.claude`;
const SOURCE_HOME = 'C:\\Users\\janson9527us';
const TARGET_HOME = `C:\\Users\\${TARGET_USER}`;
// 检查源目录
if (!fs.existsSync(SOURCE_ROOT)) {
console.error(`错误: 源目录不存在: ${SOURCE_ROOT}`);
process.exit(1);
}
// 检查目标用户目录
if (!fs.existsSync(TARGET_HOME)) {
console.error(`错误: 目标用户目录不存在: ${TARGET_HOME}`);
process.exit(1);
}
// 检查目标是否已存在
if (fs.existsSync(TARGET_ROOT) && !FORCE) {
console.error(`警告: 目标目录已存在: ${TARGET_ROOT}`);
console.error('使用 --force 参数覆盖');
process.exit(1);
}
// ─── 需要复制的目录/文件清单 ──────────────────────────────
// 核心配置文件
const CORE_FILES = [
'CLAUDE.md',
'settings.json',
'skills-index.json',
'stats-compiled.json',
'SKILL-REGISTRY.md',
'stats-cache.json',
];
// 需要完整复制的目录(排除规则在下方)
const COPY_DIRS = [
'hooks',
'scripts',
'agents',
'skills',
'docs',
'mcp-servers',
'plugins',
'cache',
'debug',
];
// 需要排除的目录/文件模式
const EXCLUDE_PATTERNS = [
// 会话相关(不需要复制)
/^statsig[/\\]/,
/^tasks[/\\]/,
/^teams[/\\]/,
/^projects[/\\]/,
/^plans[/\\]/,
/^te[/\\]/,
/\.update\.lock$/,
// node_modules太大目标端重新安装
/node_modules[/\\]/,
/node_modules$/,
// git 对象(太大,且不需要)
/\.git[/\\]objects[/\\]/,
/\.git[/\\]index$/,
// debug 日志数据(运行时生成,可跳过大文件)
/^debug[/\\]weights-history[/\\]/,
// 二进制和平台特定文件
/\.exe$/,
/\.dll$/,
/\.node$/,
/esbuild[/\\]/,
/@esbuild[/\\]/,
/@rollup[/\\]rollup-/,
];
// ─── 工具函数 ────────────────────────────────────────────
function shouldExclude(relativePath) {
return EXCLUDE_PATTERNS.some(pattern => pattern.test(relativePath));
}
/**
* 递归获取目录下所有文件相对路径
*/
function walkDir(dir, baseDir = dir) {
const results = [];
if (!fs.existsSync(dir)) return results;
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relPath = path.relative(baseDir, fullPath);
if (entry.isDirectory()) {
// 快速跳过已知大目录
if (entry.name === 'node_modules' || entry.name === '.pnpm') continue;
results.push(...walkDir(fullPath, baseDir));
} else {
results.push(relPath);
}
}
return results;
}
/**
* 确保目录存在
*/
function ensureDir(dirPath) {
if (!DRY_RUN) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
/**
* 复制文件
*/
function copyFile(src, dest) {
if (DRY_RUN) {
console.log(` [DRY] ${src}${dest}`);
return;
}
ensureDir(path.dirname(dest));
fs.copyFileSync(src, dest);
}
/**
* 替换文件中的路径引用
*/
function rewritePaths(content) {
// 替换各种格式的路径:
// C:/Users/janson9527us/.claude → C:/Users/<target>/.claude
// C:\\Users\\janson9527us\\.claude → C:\\Users\\<target>\\.claude
// C:\Users\janson9527us\.claude → C:\Users\<target>\.claude
// /c/Users/janson9527us/.claude → /c/Users/<target>/.claude (WSL/Git Bash)
// /mnt/c/Users/janson9527us/.claude → /mnt/c/Users/<target>/.claude (WSL)
let result = content;
// JSON 双反斜杠格式: C:\\Users\\janson9527us
result = result.replace(
/C:\\\\Users\\\\home/g,
`C:\\\\Users\\\\${TARGET_USER}`
);
// 正斜杠格式: C:/Users/janson9527us
result = result.replace(
/C:\/Users\/home/g,
`C:/Users/${TARGET_USER}`
);
// 单反斜杠格式: C:\Users\janson9527us (非JSON文本中)
result = result.replace(
/C:\\Users\\janson9527us(?!\\\\)/g,
`C:\\Users\\${TARGET_USER}`
);
// WSL 路径: /mnt/c/Users/janson9527us
result = result.replace(
/\/mnt\/c\/Users\/home/g,
`/mnt/c/Users/${TARGET_USER}`
);
// Git Bash 路径: /c/Users/janson9527us
result = result.replace(
/\/c\/Users\/home/g,
`/c/Users/${TARGET_USER}`
);
return result;
}
// ─── 主逻辑 ─────────────────────────────────────────────
console.log('╔════════════════════════════════════════════════╗');
console.log('║ Bookworm Smart Assistant - 系统迁移工具 ║');
console.log('╠════════════════════════════════════════════════╣');
console.log(`║ 源: ${SOURCE_ROOT.padEnd(40)}`);
console.log(`║ 目标: ${TARGET_ROOT.padEnd(38)}`);
console.log(`║ 模式: ${DRY_RUN ? '预览 (dry-run)' : FORCE ? '覆盖写入' : '正常写入'}${''.padEnd(DRY_RUN ? 24 : FORCE ? 28 : 28)}`);
console.log('╚════════════════════════════════════════════════╝');
console.log('');
let copied = 0;
let skipped = 0;
let rewritten = 0;
let errors = 0;
// 需要路径重写的文件扩展名
const REWRITE_EXTENSIONS = new Set(['.json', '.js', '.md', '.sh', '.py', '.yaml', '.yml']);
/**
* 处理单个文件的复制
*/
function processFile(relPath, srcBase, destBase) {
const srcFile = path.join(srcBase, relPath);
const destFile = path.join(destBase, relPath);
// 排除检查
const checkPath = path.relative(SOURCE_ROOT, srcFile).replace(/\\/g, '/');
if (shouldExclude(checkPath)) {
skipped++;
return;
}
try {
const ext = path.extname(relPath).toLowerCase();
if (REWRITE_EXTENSIONS.has(ext)) {
// 文本文件: 读取 → 路径替换 → 写入
const content = fs.readFileSync(srcFile, 'utf-8');
const newContent = rewritePaths(content);
if (DRY_RUN) {
if (content !== newContent) {
console.log(` [REWRITE] ${relPath}`);
rewritten++;
} else {
console.log(` [COPY] ${relPath}`);
}
} else {
ensureDir(path.dirname(destFile));
fs.writeFileSync(destFile, newContent, 'utf-8');
if (content !== newContent) rewritten++;
}
} else {
// 二进制文件: 直接复制
copyFile(srcFile, destFile);
}
copied++;
} catch (err) {
console.error(` [ERROR] ${relPath}: ${err.message}`);
errors++;
}
}
// Step 1: 复制核心配置文件
console.log('▸ Step 1/3: 复制核心配置文件...');
for (const file of CORE_FILES) {
const srcFile = path.join(SOURCE_ROOT, file);
if (fs.existsSync(srcFile)) {
processFile(file, SOURCE_ROOT, TARGET_ROOT);
console.log(`${file}`);
} else {
console.log(` - ${file} (不存在,跳过)`);
}
}
console.log('');
// Step 2: 复制目录
console.log('▸ Step 2/3: 复制目录结构...');
for (const dir of COPY_DIRS) {
const srcDir = path.join(SOURCE_ROOT, dir);
if (!fs.existsSync(srcDir)) {
console.log(` - ${dir}/ (不存在,跳过)`);
continue;
}
const files = walkDir(srcDir, SOURCE_ROOT);
const validFiles = files.filter(f => !shouldExclude(f));
console.log(`${dir}/ (${validFiles.length} 文件)`);
for (const file of files) {
processFile(file, SOURCE_ROOT, TARGET_ROOT);
}
}
console.log('');
// Step 3: 创建必要的空目录
console.log('▸ Step 3/3: 初始化运行时目录...');
const INIT_DIRS = [
'debug',
'debug/weights-history',
'backups',
];
for (const dir of INIT_DIRS) {
const target = path.join(TARGET_ROOT, dir);
if (!DRY_RUN) {
ensureDir(target);
console.log(`${dir}/`);
} else {
console.log(` [DRY] mkdir ${dir}/`);
}
}
console.log('');
// ─── 特殊处理: settings.json 的 env.HOME ────────────────
const targetSettings = path.join(TARGET_ROOT, 'settings.json');
if (!DRY_RUN && fs.existsSync(targetSettings)) {
try {
const settings = JSON.parse(fs.readFileSync(targetSettings, 'utf-8'));
// 确保 HOME 指向新用户目录
if (settings.env && settings.env.HOME) {
settings.env.HOME = `C:\\Users\\${TARGET_USER}`;
}
fs.writeFileSync(targetSettings, JSON.stringify(settings, null, 2), 'utf-8');
console.log('▸ 特殊处理: settings.json env.HOME 已更新');
} catch (e) {
console.error(` [WARN] settings.json 后处理失败: ${e.message}`);
}
}
// ─── 汇总报告 ────────────────────────────────────────────
console.log('');
console.log('═══════════════════════════════════════════════');
console.log(' 迁移完成!');
console.log(` 复制文件: ${copied}`);
console.log(` 路径重写: ${rewritten}`);
console.log(` 跳过文件: ${skipped}`);
console.log(` 错误: ${errors}`);
console.log('═══════════════════════════════════════════════');
console.log('');
if (!DRY_RUN && errors === 0) {
console.log('后续步骤:');
console.log('');
console.log(` 1. 切换到 ${TARGET_USER} 账户`);
console.log(` 2. 打开终端,进入 C:\\Users\\${TARGET_USER}`);
console.log(' 3. 运行 claude 启动 Claude Code');
console.log(' 4. 如果 hooks/__tests__ 需要测试,在该目录运行 pnpm install');
console.log(' 5. 如果 mcp-servers/browserbase 需要使用,在该目录运行 npm install');
console.log('');
console.log('环境变量 (可选paths.config.js 已自动适配):');
console.log(` set CLAUDE_HOME=C:\\Users\\${TARGET_USER}\\.claude`);
console.log('');
}
if (DRY_RUN) {
console.log('这是预览模式。移除 --dry-run 参数执行实际迁移。');
}
process.exit(errors > 0 ? 1 : 0);