Merge pull request #47 from kaitranntt/dev

feat(release): promote v5.4.1-dev.2 to production
This commit is contained in:
Kai (Tam Nhu) Tran
2025-12-02 23:45:01 -05:00
committed by GitHub
6 changed files with 120 additions and 16 deletions
+9
View File
@@ -1,3 +1,11 @@
## [5.4.2-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2-dev.1) (2025-12-03)
### Bug Fixes
* **merge:** resolve conflicts between dev and main ([8347ea6](https://github.com/kaitranntt/ccs/commit/8347ea64c6b919a79f5ab63c398b6c36f012ca2d))
* **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45)
## [5.4.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1) (2025-12-03)
@@ -5,6 +13,7 @@
* **cliproxy:** resolve windows auth browser not opening ([af4d6cf](https://github.com/kaitranntt/ccs/commit/af4d6cff89395a74e2eaf56551d3f56b95e0a6ce)), closes [#42](https://github.com/kaitranntt/ccs/issues/42)
* **doctor:** resolve windows claude cli detection failure ([cfe9ba0](https://github.com/kaitranntt/ccs/commit/cfe9ba05a4351302fbb330ca00b6025cb65a8f20)), closes [#41](https://github.com/kaitranntt/ccs/issues/41)
* **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45)
## [5.4.1-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1-dev.1) (2025-12-03)
+1 -1
View File
@@ -1 +1 @@
5.4.1
5.4.2-dev.1
+1 -1
View File
@@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (irm | iex)
# For git installations, VERSION file is read if available
$CcsVersion = "5.4.1"
$CcsVersion = "5.4.2-dev.1"
# Try to read VERSION file for git installations
if ($ScriptDir) {
+1 -1
View File
@@ -84,7 +84,7 @@ fi
# IMPORTANT: Update this version when releasing new versions!
# This hardcoded version is used for standalone installations (curl | bash)
# For git installations, VERSION file is read if available
CCS_VERSION="5.4.1"
CCS_VERSION="5.4.2-dev.1"
# Try to read VERSION file for git installations
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@kaitranntt/ccs",
"version": "5.4.1",
"version": "5.4.2-dev.1",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",
+107 -12
View File
@@ -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