feat: 重构安装和升级命令以支持全局子目录的安装和排除

This commit is contained in:
catlog22
2025-12-11 11:06:15 +08:00
parent b81d1039c5
commit 19648721fc
3 changed files with 58 additions and 5 deletions

View File

@@ -14,6 +14,9 @@ const __dirname = dirname(__filename);
// Source directories to install
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
// Subdirectories that should always be installed to global (~/.claude/)
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
// Get package root directory (ccw/src/commands -> ccw)
function getPackageRoot() {
return join(__dirname, '..', '..');
@@ -122,13 +125,30 @@ export async function installCommand(options) {
let totalDirs = 0;
try {
// For Path mode, install workflows to global first
if (mode === 'Path') {
const globalPath = homedir();
for (const subdir of GLOBAL_SUBDIRS) {
const srcWorkflows = join(sourceDir, '.claude', subdir);
if (existsSync(srcWorkflows)) {
const destWorkflows = join(globalPath, '.claude', subdir);
spinner.text = `Installing ${subdir} to global...`;
const { files, directories } = await copyDirectory(srcWorkflows, destWorkflows, manifest);
totalFiles += files;
totalDirs += directories;
}
}
}
for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir);
const destPath = join(installPath, dir);
spinner.text = `Installing ${dir}...`;
const { files, directories } = await copyDirectory(srcPath, destPath, manifest);
// For Path mode on .claude, exclude global subdirs (they're already installed to global)
const excludeDirs = (mode === 'Path' && dir === '.claude') ? GLOBAL_SUBDIRS : [];
const { files, directories } = await copyDirectory(srcPath, destPath, manifest, excludeDirs);
totalFiles += files;
totalDirs += directories;
}
@@ -265,9 +285,10 @@ async function createBackup(installPath, manifest) {
* @param {string} src - Source directory
* @param {string} dest - Destination directory
* @param {Object} manifest - Manifest to track files (optional)
* @param {string[]} excludeDirs - Directory names to exclude (optional)
* @returns {Object} - Count of files and directories
*/
async function copyDirectory(src, dest, manifest = null) {
async function copyDirectory(src, dest, manifest = null, excludeDirs = []) {
let files = 0;
let directories = 0;
@@ -281,6 +302,11 @@ async function copyDirectory(src, dest, manifest = null) {
const entries = readdirSync(src);
for (const entry of entries) {
// Skip excluded directories
if (excludeDirs.includes(entry)) {
continue;
}
const srcPath = join(src, entry);
const destPath = join(dest, entry);
const stat = statSync(srcPath);

View File

@@ -1,5 +1,6 @@
import { existsSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, dirname, basename } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url';
import inquirer from 'inquirer';
import chalk from 'chalk';
@@ -12,6 +13,9 @@ const __dirname = dirname(__filename);
// Source directories to install
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen'];
// Subdirectories that should always be installed to global (~/.claude/)
const GLOBAL_SUBDIRS = ['workflows', 'scripts', 'templates'];
// Get package root directory (ccw/src/commands -> ccw)
function getPackageRoot() {
return join(__dirname, '..', '..');
@@ -210,6 +214,7 @@ export async function upgradeCommand(options) {
*/
async function performUpgrade(manifest, sourceDir, version) {
const installPath = manifest.installation_path;
const mode = manifest.installation_mode;
// Get available source directories
const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir)));
@@ -219,17 +224,33 @@ async function performUpgrade(manifest, sourceDir, version) {
}
// Create new manifest
const newManifest = createManifest(manifest.installation_mode, installPath);
const newManifest = createManifest(mode, installPath);
let totalFiles = 0;
let totalDirs = 0;
// For Path mode, upgrade workflows to global first
if (mode === 'Path') {
const globalPath = homedir();
for (const subdir of GLOBAL_SUBDIRS) {
const srcWorkflows = join(sourceDir, '.claude', subdir);
if (existsSync(srcWorkflows)) {
const destWorkflows = join(globalPath, '.claude', subdir);
const { files, directories } = await copyDirectory(srcWorkflows, destWorkflows, newManifest);
totalFiles += files;
totalDirs += directories;
}
}
}
// Copy each directory
for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir);
const destPath = join(installPath, dir);
const { files, directories } = await copyDirectory(srcPath, destPath, newManifest);
// For Path mode on .claude, exclude global subdirs (they're already installed to global)
const excludeDirs = (mode === 'Path' && dir === '.claude') ? GLOBAL_SUBDIRS : [];
const { files, directories } = await copyDirectory(srcPath, destPath, newManifest, excludeDirs);
totalFiles += files;
totalDirs += directories;
}
@@ -263,9 +284,10 @@ async function performUpgrade(manifest, sourceDir, version) {
* @param {string} src - Source directory
* @param {string} dest - Destination directory
* @param {Object} manifest - Manifest to track files
* @param {string[]} excludeDirs - Directory names to exclude (optional)
* @returns {Object} - Count of files and directories
*/
async function copyDirectory(src, dest, manifest) {
async function copyDirectory(src, dest, manifest, excludeDirs = []) {
let files = 0;
let directories = 0;
@@ -279,6 +301,11 @@ async function copyDirectory(src, dest, manifest) {
const entries = readdirSync(src);
for (const entry of entries) {
// Skip excluded directories
if (excludeDirs.includes(entry)) {
continue;
}
const srcPath = join(src, entry);
const destPath = join(dest, entry);
const stat = statSync(srcPath);