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 // Source directories to install
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen']; 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) // Get package root directory (ccw/src/commands -> ccw)
function getPackageRoot() { function getPackageRoot() {
return join(__dirname, '..', '..'); return join(__dirname, '..', '..');
@@ -122,13 +125,30 @@ export async function installCommand(options) {
let totalDirs = 0; let totalDirs = 0;
try { 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) { for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir); const srcPath = join(sourceDir, dir);
const destPath = join(installPath, dir); const destPath = join(installPath, dir);
spinner.text = `Installing ${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; totalFiles += files;
totalDirs += directories; totalDirs += directories;
} }
@@ -265,9 +285,10 @@ async function createBackup(installPath, manifest) {
* @param {string} src - Source directory * @param {string} src - Source directory
* @param {string} dest - Destination directory * @param {string} dest - Destination directory
* @param {Object} manifest - Manifest to track files (optional) * @param {Object} manifest - Manifest to track files (optional)
* @param {string[]} excludeDirs - Directory names to exclude (optional)
* @returns {Object} - Count of files and directories * @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 files = 0;
let directories = 0; let directories = 0;
@@ -281,6 +302,11 @@ async function copyDirectory(src, dest, manifest = null) {
const entries = readdirSync(src); const entries = readdirSync(src);
for (const entry of entries) { for (const entry of entries) {
// Skip excluded directories
if (excludeDirs.includes(entry)) {
continue;
}
const srcPath = join(src, entry); const srcPath = join(src, entry);
const destPath = join(dest, entry); const destPath = join(dest, entry);
const stat = statSync(srcPath); const stat = statSync(srcPath);

View File

@@ -1,5 +1,6 @@
import { existsSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { existsSync, readdirSync, statSync, copyFileSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, dirname, basename } from 'path'; import { join, dirname, basename } from 'path';
import { homedir } from 'os';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import chalk from 'chalk'; import chalk from 'chalk';
@@ -12,6 +13,9 @@ const __dirname = dirname(__filename);
// Source directories to install // Source directories to install
const SOURCE_DIRS = ['.claude', '.codex', '.gemini', '.qwen']; 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) // Get package root directory (ccw/src/commands -> ccw)
function getPackageRoot() { function getPackageRoot() {
return join(__dirname, '..', '..'); return join(__dirname, '..', '..');
@@ -210,6 +214,7 @@ export async function upgradeCommand(options) {
*/ */
async function performUpgrade(manifest, sourceDir, version) { async function performUpgrade(manifest, sourceDir, version) {
const installPath = manifest.installation_path; const installPath = manifest.installation_path;
const mode = manifest.installation_mode;
// Get available source directories // Get available source directories
const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir))); const availableDirs = SOURCE_DIRS.filter(dir => existsSync(join(sourceDir, dir)));
@@ -219,17 +224,33 @@ async function performUpgrade(manifest, sourceDir, version) {
} }
// Create new manifest // Create new manifest
const newManifest = createManifest(manifest.installation_mode, installPath); const newManifest = createManifest(mode, installPath);
let totalFiles = 0; let totalFiles = 0;
let totalDirs = 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 // Copy each directory
for (const dir of availableDirs) { for (const dir of availableDirs) {
const srcPath = join(sourceDir, dir); const srcPath = join(sourceDir, dir);
const destPath = join(installPath, 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; totalFiles += files;
totalDirs += directories; totalDirs += directories;
} }
@@ -263,9 +284,10 @@ async function performUpgrade(manifest, sourceDir, version) {
* @param {string} src - Source directory * @param {string} src - Source directory
* @param {string} dest - Destination directory * @param {string} dest - Destination directory
* @param {Object} manifest - Manifest to track files * @param {Object} manifest - Manifest to track files
* @param {string[]} excludeDirs - Directory names to exclude (optional)
* @returns {Object} - Count of files and directories * @returns {Object} - Count of files and directories
*/ */
async function copyDirectory(src, dest, manifest) { async function copyDirectory(src, dest, manifest, excludeDirs = []) {
let files = 0; let files = 0;
let directories = 0; let directories = 0;
@@ -279,6 +301,11 @@ async function copyDirectory(src, dest, manifest) {
const entries = readdirSync(src); const entries = readdirSync(src);
for (const entry of entries) { for (const entry of entries) {
// Skip excluded directories
if (excludeDirs.includes(entry)) {
continue;
}
const srcPath = join(src, entry); const srcPath = join(src, entry);
const destPath = join(dest, entry); const destPath = join(dest, entry);
const stat = statSync(srcPath); const stat = statSync(srcPath);