mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 06:15:03 +00:00
refactor(config): migrate to config.yaml as primary format
- Update postinstall.js to create config.yaml instead of config.json - Update recovery-manager to create config.yaml as primary config - Fix isFirstTimeInstall() to check for meaningful config content (profiles, accounts, variants, oauth_accounts, remote proxy) - Update validation to accept config.yaml OR config.json - Preserve backward compatibility: legacy config.json is migrated to config.yaml on first run via autoMigrate() - Update postinstall tests to verify config.yaml creation Fixes #142 - remote CLIProxyAPI configuration
This commit is contained in:
1 parent
cec616d530
commit
b34469d75f
4 files changed
+147
-106
No files matched your search
+71
-67
@@ -68,24 +68,13 @@ function validateConfiguration() {
|
||||
errors.push('~/.ccs/ directory not found');
|
||||
}
|
||||
|
||||
// Check required files (GLM/GLMT/Kimi are now optional - created via presets)
|
||||
const requiredFiles = [
|
||||
{ path: path.join(ccsDir, 'config.json'), name: 'config.json' }
|
||||
];
|
||||
// Check for config file - prefer config.yaml, fallback to config.json
|
||||
const configYaml = path.join(ccsDir, 'config.yaml');
|
||||
const configJson = path.join(ccsDir, 'config.json');
|
||||
const hasConfig = fs.existsSync(configYaml) || fs.existsSync(configJson);
|
||||
|
||||
for (const file of requiredFiles) {
|
||||
if (!fs.existsSync(file.path)) {
|
||||
errors.push(`${file.name} not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validate JSON syntax
|
||||
try {
|
||||
const content = fs.readFileSync(file.path, 'utf8');
|
||||
JSON.parse(content);
|
||||
} catch (e) {
|
||||
errors.push(`${file.name} has invalid JSON: ${e.message}`);
|
||||
}
|
||||
if (!hasConfig) {
|
||||
errors.push('config.yaml (or config.json) not found');
|
||||
}
|
||||
|
||||
// Check ~/.claude/settings.json (warning only, not critical)
|
||||
@@ -150,62 +139,77 @@ function createConfigFiles() {
|
||||
// Users can run "ccs sync" to install CCS commands/skills to ~/.claude/
|
||||
// This gives users control over when to modify their Claude configuration
|
||||
|
||||
// Create config.json if missing
|
||||
// Create config.yaml if missing (primary format)
|
||||
// NOTE: gemini/codex profiles NOT included - they are added on-demand when user
|
||||
// runs `ccs gemini` or `ccs codex` for first time (requires OAuth auth first)
|
||||
// NOTE: GLM/GLMT/Kimi profiles are now created via UI/CLI presets, not auto-created
|
||||
const configPath = path.join(ccsDir, 'config.json');
|
||||
if (!fs.existsSync(configPath)) {
|
||||
// NOTE: No 'default' entry - when no profile specified, CCS passes through
|
||||
// to Claude's native auth without --settings flag. This prevents env var
|
||||
// pollution from affecting the default profile.
|
||||
// Profiles are empty by default - users create via `ccs api create --preset` or UI
|
||||
const config = {
|
||||
profiles: {}
|
||||
};
|
||||
const configYamlPath = path.join(ccsDir, 'config.yaml');
|
||||
const legacyConfigPath = path.join(ccsDir, 'config.json');
|
||||
|
||||
// Atomic write: temp file → rename
|
||||
const tmpPath = `${configPath}.tmp`;
|
||||
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmpPath, configPath);
|
||||
|
||||
console.log('[OK] Created config: ~/.ccs/config.json');
|
||||
} else {
|
||||
// Update existing config (migration for older versions)
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
// Ensure profiles object exists
|
||||
if (!config.profiles) {
|
||||
config.profiles = {};
|
||||
}
|
||||
let configUpdated = false;
|
||||
|
||||
// Migration: Add glmt if missing (v3.x)
|
||||
if (!config.profiles.glmt) {
|
||||
config.profiles.glmt = '~/.ccs/glmt.settings.json';
|
||||
configUpdated = true;
|
||||
}
|
||||
|
||||
// Migration: Remove 'default' entry pointing to ~/.claude/settings.json (v5.4.0)
|
||||
// This entry caused the default profile to pass --settings flag, which could
|
||||
// pick up stale env vars (ANTHROPIC_BASE_URL) from previous profile sessions.
|
||||
// Fix: Let CCS pass through to Claude's native auth without --settings flag.
|
||||
if (config.profiles.default === '~/.claude/settings.json') {
|
||||
delete config.profiles.default;
|
||||
configUpdated = true;
|
||||
console.log('[OK] Removed legacy default profile (now uses native Claude auth)');
|
||||
}
|
||||
|
||||
// NOTE: gemini/codex profiles added on-demand, not during migration
|
||||
if (configUpdated) {
|
||||
const tmpPath = `${configPath}.tmp`;
|
||||
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmpPath, configPath);
|
||||
if (!config.profiles.glmt) {
|
||||
console.log('[OK] Updated config with glmt profile');
|
||||
}
|
||||
if (!fs.existsSync(configYamlPath)) {
|
||||
// Check for legacy config.json - autoMigrate() in ccs.ts will handle migration
|
||||
if (fs.existsSync(legacyConfigPath)) {
|
||||
console.log('[OK] Legacy config.json found - will migrate to config.yaml on first run');
|
||||
} else {
|
||||
console.log('[OK] Config exists: ~/.ccs/config.json (preserved)');
|
||||
// Try to use unified config loader if dist is available
|
||||
try {
|
||||
const { saveUnifiedConfig } = require('../dist/config/unified-config-loader');
|
||||
const { createEmptyUnifiedConfig, UNIFIED_CONFIG_VERSION } = require('../dist/config/unified-config-types');
|
||||
|
||||
const config = createEmptyUnifiedConfig();
|
||||
config.version = UNIFIED_CONFIG_VERSION;
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
console.log('[OK] Created config: ~/.ccs/config.yaml');
|
||||
} catch (loaderErr) {
|
||||
// Dist not built yet (fresh clone) - create minimal config.yaml manually
|
||||
const yaml = require('js-yaml');
|
||||
const config = {
|
||||
version: '2.0',
|
||||
profiles: {},
|
||||
accounts: {},
|
||||
cliproxy: {
|
||||
variants: {},
|
||||
oauth_accounts: {}
|
||||
},
|
||||
cliproxy_server: {
|
||||
local: {
|
||||
port: 8317,
|
||||
auto_start: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const yamlContent = yaml.dump(config, {
|
||||
indent: 2,
|
||||
lineWidth: -1,
|
||||
noRefs: true,
|
||||
sortKeys: false
|
||||
});
|
||||
const tmpPath = `${configYamlPath}.tmp`;
|
||||
fs.writeFileSync(tmpPath, yamlContent, 'utf8');
|
||||
fs.renameSync(tmpPath, configYamlPath);
|
||||
console.log('[OK] Created config: ~/.ccs/config.yaml');
|
||||
} catch (yamlErr) {
|
||||
// Final fallback: create legacy config.json
|
||||
console.warn('[!] YAML write failed, creating legacy config.json');
|
||||
const fallbackConfig = { profiles: {} };
|
||||
const tmpPath = `${legacyConfigPath}.tmp`;
|
||||
fs.writeFileSync(tmpPath, JSON.stringify(fallbackConfig, null, 2) + '\n', 'utf8');
|
||||
fs.renameSync(tmpPath, legacyConfigPath);
|
||||
console.log('[OK] Created config: ~/.ccs/config.json (fallback)');
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('[OK] Config exists: ~/.ccs/config.yaml (preserved)');
|
||||
}
|
||||
|
||||
// Handle legacy config.json migrations (for users upgrading)
|
||||
if (fs.existsSync(legacyConfigPath) && !fs.existsSync(configYamlPath)) {
|
||||
// Migration will happen via autoMigrate() in ccs.ts on first run
|
||||
console.log('[i] Legacy config.json will be migrated to config.yaml on first run');
|
||||
}
|
||||
|
||||
// NOTE: GLM, GLMT, and Kimi profiles are NO LONGER auto-created during install
|
||||
|
||||
Reference in new issue
Block a user