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

368 lines
12 KiB
JavaScript
Raw 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
/**
* 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);