mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 20:24:21 +00:00
Merge pull request #47 from kaitranntt/dev
feat(release): promote v5.4.1-dev.2 to production
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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