mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-10 06:20:13 +00:00
chore(merge): sync dev with main v5.4.3
This commit is contained in:
@@ -164,12 +164,9 @@ export class ClaudeSymlinkManager {
|
||||
if (!silent) console.log(`[OK] Symlinked ${item.target}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Windows fallback: stub for now, full implementation in v4.2
|
||||
// Windows fallback: copy instead of symlink when symlinks unavailable
|
||||
if (process.platform === 'win32') {
|
||||
if (!silent) {
|
||||
console.log(`[!] Symlink failed for ${item.target} (Windows fallback deferred to v4.2)`);
|
||||
console.log(`[i] Enable Developer Mode or wait for next update`);
|
||||
}
|
||||
return this.copyFallback(sourcePath, targetPath, item, silent);
|
||||
} else {
|
||||
const error = err as Error;
|
||||
if (!silent) console.log(`[!] Failed to symlink ${item.target}: ${error.message}`);
|
||||
@@ -178,6 +175,61 @@ export class ClaudeSymlinkManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Windows fallback: copy files/directories when symlinks unavailable
|
||||
* Note: Changes won't auto-sync; user must run 'ccs sync' after updates
|
||||
*/
|
||||
private copyFallback(
|
||||
sourcePath: string,
|
||||
targetPath: string,
|
||||
item: CcsItem,
|
||||
silent = false
|
||||
): boolean {
|
||||
try {
|
||||
if (item.type === 'directory') {
|
||||
// Copy directory recursively
|
||||
this.copyDirRecursive(sourcePath, targetPath);
|
||||
} else {
|
||||
// Copy single file
|
||||
fs.copyFileSync(sourcePath, targetPath);
|
||||
}
|
||||
if (!silent) {
|
||||
console.log(`[OK] Copied ${item.target} (symlink unavailable)`);
|
||||
console.log(`[i] Run 'ccs sync' after CCS updates to refresh`);
|
||||
}
|
||||
return true;
|
||||
} catch (copyErr) {
|
||||
const error = copyErr as Error;
|
||||
if (!silent) {
|
||||
console.log(`[!] Failed to copy ${item.target}: ${error.message}`);
|
||||
console.log(`[i] Enable Developer Mode for symlinks, or check permissions`);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copy directory (for Windows fallback)
|
||||
*/
|
||||
private copyDirRecursive(src: string, dest: string): void {
|
||||
if (!fs.existsSync(dest)) {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
}
|
||||
|
||||
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
this.copyDirRecursive(srcPath, destPath);
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if target is already the correct symlink pointing to source
|
||||
*/
|
||||
@@ -224,8 +276,8 @@ export class ClaudeSymlinkManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall CCS items from ~/.claude/ (remove symlinks only)
|
||||
* Safe: only removes items that are CCS symlinks
|
||||
* Uninstall CCS items from ~/.claude/ (remove symlinks or copied files)
|
||||
* Safe: only removes items that are CCS symlinks or valid copies
|
||||
*/
|
||||
uninstall(): void {
|
||||
let removed = 0;
|
||||
@@ -234,10 +286,20 @@ export class ClaudeSymlinkManager {
|
||||
const targetPath = path.join(this.userClaudeDir, item.target);
|
||||
const sourcePath = path.join(this.ccsClaudeDir, item.source);
|
||||
|
||||
// Only remove if it's our symlink
|
||||
if (fs.existsSync(targetPath) && this.isOurSymlink(targetPath, sourcePath)) {
|
||||
// Check if it's our symlink or a valid copy (Windows fallback)
|
||||
const isSymlink = this.isOurSymlink(targetPath, sourcePath);
|
||||
const isCopy =
|
||||
process.platform === 'win32' && this.isCopiedItem(targetPath, sourcePath, item.type);
|
||||
|
||||
if (fs.existsSync(targetPath) && (isSymlink || isCopy)) {
|
||||
try {
|
||||
fs.unlinkSync(targetPath);
|
||||
if (item.type === 'directory' && !isSymlink) {
|
||||
// Remove copied directory recursively
|
||||
fs.rmSync(targetPath, { recursive: true, force: true });
|
||||
} else {
|
||||
// Remove symlink or file
|
||||
fs.unlinkSync(targetPath);
|
||||
}
|
||||
console.log(`[OK] Removed ${item.target}`);
|
||||
removed++;
|
||||
} catch (err) {
|
||||
@@ -286,14 +348,47 @@ export class ClaudeSymlinkManager {
|
||||
issues.push(`Not installed: ${item.target} (run 'ccs sync' to install)`);
|
||||
healthy = false;
|
||||
} else if (!this.isOurSymlink(targetPath, sourcePath)) {
|
||||
issues.push(`Not a CCS symlink: ${item.target} (run 'ccs sync' to fix)`);
|
||||
healthy = false;
|
||||
// On Windows, copied files are valid (symlink fallback)
|
||||
if (process.platform === 'win32' && this.isCopiedItem(targetPath, sourcePath, item.type)) {
|
||||
// Copied file is valid on Windows, but note it's not a symlink
|
||||
issues.push(`${item.target} is a copy (not symlink) - run 'ccs sync' after updates`);
|
||||
// Still healthy, just a warning
|
||||
} else {
|
||||
issues.push(`Not a CCS symlink: ${item.target} (run 'ccs sync' to fix)`);
|
||||
healthy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { healthy, issues };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if target is a valid copy of source (Windows fallback check)
|
||||
*/
|
||||
private isCopiedItem(
|
||||
targetPath: string,
|
||||
sourcePath: string,
|
||||
type: 'file' | 'directory'
|
||||
): boolean {
|
||||
try {
|
||||
const targetStats = fs.statSync(targetPath);
|
||||
const sourceStats = fs.statSync(sourcePath);
|
||||
|
||||
if (type === 'directory') {
|
||||
// For directories, just check both exist and are directories
|
||||
return targetStats.isDirectory() && sourceStats.isDirectory();
|
||||
} else {
|
||||
// For files, compare size as basic validation
|
||||
return (
|
||||
targetStats.isFile() && sourceStats.isFile() && targetStats.size === sourceStats.size
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync delegation commands and skills to ~/.claude/ (used by 'ccs sync' command)
|
||||
* Same as install() but with explicit sync message
|
||||
|
||||
Reference in New Issue
Block a user