mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-17 04:25:02 +00:00
fix(doctor): prefer config.yaml and make settings files optional
- Check config.yaml first (v2 format), fallback to config.json (legacy) - Make glm.settings.json and kimi.settings.json optional - Only validate settings files if they exist - Fix false positives: postinstall no longer creates these files Fixes health check reporting errors for missing files that are now optional and created on-demand when user configures a profile.
This commit is contained in:
@@ -5,13 +5,15 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { ok, fail, warn } from '../../utils/ui';
|
import { ok, fail, warn, info } from '../../utils/ui';
|
||||||
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
import { HealthCheck, IHealthChecker, createSpinner } from './types';
|
||||||
|
|
||||||
const ora = createSpinner();
|
const ora = createSpinner();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check CCS config files exist and are valid JSON
|
* Check CCS config files exist and are valid
|
||||||
|
* - Prefers config.yaml (v2) over config.json (legacy)
|
||||||
|
* - Settings files are optional (only checked if profile exists)
|
||||||
*/
|
*/
|
||||||
export class ConfigFilesChecker implements IHealthChecker {
|
export class ConfigFilesChecker implements IHealthChecker {
|
||||||
name = 'Config Files';
|
name = 'Config Files';
|
||||||
@@ -22,63 +24,139 @@ export class ConfigFilesChecker implements IHealthChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
run(results: HealthCheck): void {
|
run(results: HealthCheck): void {
|
||||||
const files = [
|
|
||||||
{ path: path.join(this.ccsDir, 'config.json'), name: 'config.json', key: 'config.json' },
|
|
||||||
{
|
|
||||||
path: path.join(this.ccsDir, 'glm.settings.json'),
|
|
||||||
name: 'glm.settings.json',
|
|
||||||
key: 'GLM Settings',
|
|
||||||
profile: 'glm',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: path.join(this.ccsDir, 'kimi.settings.json'),
|
|
||||||
name: 'kimi.settings.json',
|
|
||||||
key: 'Kimi Settings',
|
|
||||||
profile: 'kimi',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const { DelegationValidator } = require('../../utils/delegation-validator');
|
const { DelegationValidator } = require('../../utils/delegation-validator');
|
||||||
|
|
||||||
for (const file of files) {
|
// Check main config file (yaml preferred, json fallback)
|
||||||
const spinner = ora(`Checking ${file.name}`).start();
|
this.checkMainConfig(results);
|
||||||
|
|
||||||
if (!fs.existsSync(file.path)) {
|
// Check optional settings files (only if profile exists in config)
|
||||||
|
this.checkOptionalSettingsFiles(results, DelegationValidator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check main configuration file (config.yaml preferred, config.json fallback)
|
||||||
|
*/
|
||||||
|
private checkMainConfig(results: HealthCheck): void {
|
||||||
|
const configYamlPath = path.join(this.ccsDir, 'config.yaml');
|
||||||
|
const configJsonPath = path.join(this.ccsDir, 'config.json');
|
||||||
|
|
||||||
|
const yamlExists = fs.existsSync(configYamlPath);
|
||||||
|
const jsonExists = fs.existsSync(configJsonPath);
|
||||||
|
|
||||||
|
// Check config.yaml first (preferred format)
|
||||||
|
if (yamlExists) {
|
||||||
|
const spinner = ora('Checking config.yaml').start();
|
||||||
|
try {
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const content = fs.readFileSync(configYamlPath, 'utf8');
|
||||||
|
yaml.load(content);
|
||||||
|
spinner.succeed();
|
||||||
|
console.log(` ${ok('config.yaml'.padEnd(22))} Valid`);
|
||||||
|
results.addCheck('config.yaml', 'success', undefined, undefined, {
|
||||||
|
status: 'OK',
|
||||||
|
info: 'Valid',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Inform if legacy config.json also exists (purely informational, not a check)
|
||||||
|
if (jsonExists) {
|
||||||
|
console.log(` ${info('config.json'.padEnd(22))} Legacy (ignored)`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
spinner.fail();
|
spinner.fail();
|
||||||
console.log(` ${fail(file.name.padEnd(22))} Not found`);
|
console.log(` ${fail('config.yaml'.padEnd(22))} Invalid YAML`);
|
||||||
results.addCheck(
|
results.addCheck(
|
||||||
file.name,
|
'config.yaml',
|
||||||
'error',
|
'error',
|
||||||
`${file.name} not found`,
|
`Invalid YAML: ${(e as Error).message}`,
|
||||||
'Run: npm install -g @kaitranntt/ccs --force',
|
`Backup and recreate: mv ${configYamlPath} ${configYamlPath}.backup && npm install -g @kaitranntt/ccs --force`,
|
||||||
{ status: 'ERROR', info: 'Not found' }
|
{ status: 'ERROR', info: 'Invalid YAML' }
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to config.json (legacy format)
|
||||||
|
if (jsonExists) {
|
||||||
|
const spinner = ora('Checking config.json').start();
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(configJsonPath, 'utf8');
|
||||||
|
JSON.parse(content);
|
||||||
|
spinner.succeed();
|
||||||
|
console.log(` ${ok('config.json'.padEnd(22))} Valid (legacy)`);
|
||||||
|
results.addCheck('config.json', 'success', undefined, undefined, {
|
||||||
|
status: 'OK',
|
||||||
|
info: 'Valid (legacy)',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
spinner.fail();
|
||||||
|
console.log(` ${fail('config.json'.padEnd(22))} Invalid JSON`);
|
||||||
|
results.addCheck(
|
||||||
|
'config.json',
|
||||||
|
'error',
|
||||||
|
`Invalid JSON: ${(e as Error).message}`,
|
||||||
|
`Backup and recreate: mv ${configJsonPath} ${configJsonPath}.backup && npm install -g @kaitranntt/ccs --force`,
|
||||||
|
{ status: 'ERROR', info: 'Invalid JSON' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neither exists - error
|
||||||
|
const spinner = ora('Checking config').start();
|
||||||
|
spinner.fail();
|
||||||
|
console.log(` ${fail('config.yaml'.padEnd(22))} Not found`);
|
||||||
|
results.addCheck(
|
||||||
|
'config.yaml',
|
||||||
|
'error',
|
||||||
|
'No configuration file found (config.yaml or config.json)',
|
||||||
|
'Run: npm install -g @kaitranntt/ccs --force',
|
||||||
|
{ status: 'ERROR', info: 'Not found' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check optional settings files (only if corresponding profile is configured)
|
||||||
|
* These files are NOT required - they're only created when user configures a profile
|
||||||
|
*/
|
||||||
|
private checkOptionalSettingsFiles(
|
||||||
|
results: HealthCheck,
|
||||||
|
DelegationValidator: { validate: (profile: string) => { valid: boolean; error?: string } }
|
||||||
|
): void {
|
||||||
|
// Settings files to check (only if profile exists)
|
||||||
|
const settingsFiles = [
|
||||||
|
{ name: 'glm.settings.json', profile: 'glm', displayName: 'GLM Settings' },
|
||||||
|
{ name: 'kimi.settings.json', profile: 'kimi', displayName: 'Kimi Settings' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of settingsFiles) {
|
||||||
|
const filePath = path.join(this.ccsDir, file.name);
|
||||||
|
const exists = fs.existsSync(filePath);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
// Not an error - these are optional files
|
||||||
|
// Only show info if user might expect them
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate JSON
|
// File exists - validate it
|
||||||
|
const spinner = ora(`Checking ${file.name}`).start();
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(file.path, 'utf8');
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
JSON.parse(content);
|
JSON.parse(content);
|
||||||
|
|
||||||
// Extract useful info based on file type
|
// Check if API key is properly configured
|
||||||
let fileInfo = 'Valid';
|
const validation = DelegationValidator.validate(file.profile);
|
||||||
|
|
||||||
|
let fileInfo = 'Valid JSON';
|
||||||
let status: 'OK' | 'WARN' = 'OK';
|
let status: 'OK' | 'WARN' = 'OK';
|
||||||
|
|
||||||
if (file.profile) {
|
if (validation.valid) {
|
||||||
// For settings files, check if API key is configured
|
fileInfo = 'Key configured';
|
||||||
const validation = DelegationValidator.validate(file.profile);
|
status = 'OK';
|
||||||
|
} else if (validation.error && validation.error.includes('placeholder')) {
|
||||||
if (validation.valid) {
|
fileInfo = 'Placeholder key';
|
||||||
fileInfo = 'Key configured';
|
status = 'WARN';
|
||||||
status = 'OK';
|
|
||||||
} else if (validation.error && validation.error.includes('placeholder')) {
|
|
||||||
fileInfo = 'Placeholder key';
|
|
||||||
status = 'WARN';
|
|
||||||
} else {
|
|
||||||
fileInfo = 'Valid JSON';
|
|
||||||
status = 'OK';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status === 'WARN') {
|
if (status === 'WARN') {
|
||||||
@@ -100,7 +178,7 @@ export class ConfigFilesChecker implements IHealthChecker {
|
|||||||
file.name,
|
file.name,
|
||||||
'error',
|
'error',
|
||||||
`Invalid JSON: ${(e as Error).message}`,
|
`Invalid JSON: ${(e as Error).message}`,
|
||||||
`Backup and recreate: mv ${file.path} ${file.path}.backup && npm install -g @kaitranntt/ccs --force`,
|
`Backup and recreate: mv ${filePath} ${filePath}.backup`,
|
||||||
{ status: 'ERROR', info: 'Invalid JSON' }
|
{ status: 'ERROR', info: 'Invalid JSON' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user