mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 18:18:43 +00:00
Merge pull request #372 from kaitranntt/kai/fix/first-time-install-detection
fix(setup): persist setup_completed flag to prevent repeated first-time notice
This commit is contained in:
@@ -113,12 +113,14 @@ async function selectOption(
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a first-time install (config exists but is empty/unconfigured)
|
||||
* Returns true if user should be prompted to run setup wizard
|
||||
* Check if this is a first-time install (config exists but is empty/unconfigured).
|
||||
* Returns true if user should be prompted to run setup wizard.
|
||||
*
|
||||
* IMPORTANT: Also checks legacy config.json for existing profiles to avoid
|
||||
* treating users with existing GLM/Kimi setups as "first-time installs"
|
||||
* (Fix for issue #195 - GLM auth persistence regression)
|
||||
* Detection priority:
|
||||
* 1. setup_completed flag (explicit wizard completion)
|
||||
* 2. Unified config content (profiles, accounts, variants, oauth_accounts, remote proxy)
|
||||
* 3. Legacy config.json profiles (GLM, Kimi)
|
||||
* 4. Legacy profiles.json accounts
|
||||
*/
|
||||
export function isFirstTimeInstall(): boolean {
|
||||
// Check unified config first (config.yaml)
|
||||
@@ -132,6 +134,11 @@ export function isFirstTimeInstall(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If setup wizard was completed, NOT first-time install
|
||||
if (loaded.setup_completed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for any meaningful configuration in unified config
|
||||
const hasProfiles = Object.keys(loaded.profiles || {}).length > 0;
|
||||
const hasAccounts = Object.keys(loaded.accounts || {}).length > 0;
|
||||
@@ -370,6 +377,7 @@ async function runSetupWizard(force: boolean = false): Promise<void> {
|
||||
}
|
||||
|
||||
// Save config
|
||||
config.setup_completed = true;
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
// Final summary
|
||||
|
||||
@@ -139,6 +139,7 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
const defaults = createEmptyUnifiedConfig();
|
||||
return {
|
||||
version: partial.version ?? defaults.version,
|
||||
setup_completed: partial.setup_completed,
|
||||
default: partial.default ?? defaults.default,
|
||||
accounts: partial.accounts ?? defaults.accounts,
|
||||
profiles: partial.profiles ?? defaults.profiles,
|
||||
@@ -326,6 +327,9 @@ function generateYamlWithComments(config: UnifiedConfig): string {
|
||||
|
||||
// Version
|
||||
lines.push(`version: ${config.version}`);
|
||||
if (config.setup_completed !== undefined) {
|
||||
lines.push(`setup_completed: ${config.setup_completed}`);
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
// Default
|
||||
|
||||
@@ -521,6 +521,8 @@ export const DEFAULT_DASHBOARD_AUTH_CONFIG: DashboardAuthConfig = {
|
||||
export interface UnifiedConfig {
|
||||
/** Config version (7 for quota management) */
|
||||
version: number;
|
||||
/** Flag indicating setup wizard has been completed */
|
||||
setup_completed?: boolean;
|
||||
/** Default profile name to use when none specified */
|
||||
default?: string;
|
||||
/** Account-based profiles (isolated Claude instances) */
|
||||
|
||||
@@ -115,6 +115,51 @@ cliproxy_server:
|
||||
});
|
||||
});
|
||||
|
||||
describe('setup_completed flag detection', () => {
|
||||
it('should detect setup_completed flag in unified config', () => {
|
||||
createConfigYaml(`
|
||||
version: 8
|
||||
setup_completed: true
|
||||
profiles: {}
|
||||
accounts: {}
|
||||
`);
|
||||
|
||||
const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8');
|
||||
const hasSetupCompleted = content.includes('setup_completed: true');
|
||||
|
||||
expect(hasSetupCompleted).toBe(true);
|
||||
});
|
||||
|
||||
it('should treat missing setup_completed as first-time eligible', () => {
|
||||
createConfigYaml(`
|
||||
version: 8
|
||||
profiles: {}
|
||||
accounts: {}
|
||||
`);
|
||||
|
||||
const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8');
|
||||
const hasSetupCompleted = content.includes('setup_completed: true');
|
||||
|
||||
expect(hasSetupCompleted).toBe(false);
|
||||
});
|
||||
|
||||
it('should treat setup_completed: false as first-time eligible', () => {
|
||||
createConfigYaml(`
|
||||
version: 8
|
||||
setup_completed: false
|
||||
profiles: {}
|
||||
accounts: {}
|
||||
`);
|
||||
|
||||
const content = fs.readFileSync(path.join(testDir, 'config.yaml'), 'utf8');
|
||||
const hasSetupCompletedTrue = content.includes('setup_completed: true');
|
||||
const hasSetupCompletedFalse = content.includes('setup_completed: false');
|
||||
|
||||
expect(hasSetupCompletedTrue).toBe(false);
|
||||
expect(hasSetupCompletedFalse).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('corrupted config handling', () => {
|
||||
it('should handle corrupted config.json gracefully', () => {
|
||||
fs.writeFileSync(path.join(testDir, 'config.json'), 'not valid json{{{', 'utf8');
|
||||
|
||||
Reference in New Issue
Block a user