mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 08:19:59 +00:00
fix(codex-auth): address persistent review focus
This commit is contained in:
@@ -193,6 +193,14 @@ function resolveActive(registry: CodexProfileData): CodexAuthActiveProfile | nul
|
|||||||
// Precedence 2: $CCS_CODEX_PROFILE env var
|
// Precedence 2: $CCS_CODEX_PROFILE env var
|
||||||
const profileEnv = (process.env.CCS_CODEX_PROFILE ?? '').trim();
|
const profileEnv = (process.env.CCS_CODEX_PROFILE ?? '').trim();
|
||||||
if (profileEnv) {
|
if (profileEnv) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(registry.profiles, profileEnv)) {
|
||||||
|
logger.warn(
|
||||||
|
'codex-auth.dashboard.stale-env-profile',
|
||||||
|
'Ignoring CCS_CODEX_PROFILE because it is not registered',
|
||||||
|
{ profileName: profileEnv }
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
name: profileEnv,
|
name: profileEnv,
|
||||||
source: 'env',
|
source: 'env',
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { resolveCodexProfileDir, ensureSharedConfigSymlink } from '../index';
|
|||||||
import { decodeAccountIdentity } from '../codex-account-identity';
|
import { decodeAccountIdentity } from '../codex-account-identity';
|
||||||
import { detectCodexCli } from '../../targets/codex-detector';
|
import { detectCodexCli } from '../../targets/codex-detector';
|
||||||
import { parseArgs, rejectUnsupportedOptions, getProfileNameError } from './types';
|
import { parseArgs, rejectUnsupportedOptions, getProfileNameError } from './types';
|
||||||
|
import type { CodexProfileMetadata } from '../types';
|
||||||
import type { CodexCommandContext } from './types';
|
import type { CodexCommandContext } from './types';
|
||||||
|
|
||||||
const logger = createLogger('codex-auth:cmd:login');
|
const logger = createLogger('codex-auth:cmd:login');
|
||||||
@@ -98,12 +99,11 @@ export async function handleLoginCodex(ctx: CodexCommandContext, args: string[])
|
|||||||
if (exitCode === 0 && fs.existsSync(authJsonPath)) {
|
if (exitCode === 0 && fs.existsSync(authJsonPath)) {
|
||||||
const identity = decodeAccountIdentity(authJsonPath);
|
const identity = decodeAccountIdentity(authJsonPath);
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
registry.updateProfile(profileName, {
|
const metadataUpdate: Partial<CodexProfileMetadata> = { last_used: now };
|
||||||
last_used: now,
|
if (identity.email !== undefined) metadataUpdate.email = identity.email;
|
||||||
email: identity.email,
|
if (identity.plan_type !== undefined) metadataUpdate.plan_type = identity.plan_type;
|
||||||
plan_type: identity.plan_type ?? null,
|
if (identity.account_id !== undefined) metadataUpdate.account_id = identity.account_id;
|
||||||
account_id: identity.account_id,
|
registry.updateProfile(profileName, metadataUpdate);
|
||||||
});
|
|
||||||
const emailStr = identity.email ?? '<unknown>';
|
const emailStr = identity.email ?? '<unknown>';
|
||||||
const planStr = identity.plan_type ? ` (plan: ${identity.plan_type})` : '';
|
const planStr = identity.plan_type ? ` (plan: ${identity.plan_type})` : '';
|
||||||
console.log(ok(`Logged in as ${emailStr}${planStr}`));
|
console.log(ok(`Logged in as ${emailStr}${planStr}`));
|
||||||
|
|||||||
@@ -363,6 +363,23 @@ describe('getCodexAuthProfilesSummary', () => {
|
|||||||
expect(result.active?.name).toBe('work');
|
expect(result.active?.name).toBe('work');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('active resolution: ignores stale CCS_CODEX_PROFILE values missing from registry', async () => {
|
||||||
|
const { getCodexAuthProfilesSummary, invalidateCodexAuthProfilesCache } = await importService();
|
||||||
|
invalidateCodexAuthProfilesCache();
|
||||||
|
|
||||||
|
process.env.CCS_CODEX_PROFILE = 'ghost';
|
||||||
|
|
||||||
|
const registryPath = path.join(ccsDir, 'codex-profiles.yaml');
|
||||||
|
fs.writeFileSync(
|
||||||
|
registryPath,
|
||||||
|
`version: "1.0"\ndefault: null\nprofiles:\n work:\n type: codex\n created: "2026-01-01T00:00:00Z"\n last_used: null\n`,
|
||||||
|
{ mode: 0o600 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await getCodexAuthProfilesSummary();
|
||||||
|
expect(result.active).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it('active resolution: registry default set -> source=default', async () => {
|
it('active resolution: registry default set -> source=default', async () => {
|
||||||
const { getCodexAuthProfilesSummary, invalidateCodexAuthProfilesCache } = await importService();
|
const { getCodexAuthProfilesSummary, invalidateCodexAuthProfilesCache } = await importService();
|
||||||
invalidateCodexAuthProfilesCache();
|
invalidateCodexAuthProfilesCache();
|
||||||
|
|||||||
@@ -217,6 +217,36 @@ describe('handleLoginCodex — clean exit updates registry', () => {
|
|||||||
expect(meta.last_used).toBeTruthy();
|
expect(meta.last_used).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('preserves cached identity metadata when a re-login token is sparse', async () => {
|
||||||
|
const detectorMod = await import('../../../../src/targets/codex-detector');
|
||||||
|
spyOn(detectorMod, 'detectCodexCli').mockReturnValue('/usr/bin/codex');
|
||||||
|
spawnReturnsCode(0, true); // writes auth.json with a valid but sparse JWT payload
|
||||||
|
|
||||||
|
const { handleLoginCodex } = await import('../../../../src/codex-auth/commands/login-command');
|
||||||
|
const ctx = await makeCtx();
|
||||||
|
ctx.registry.createProfile('preservemeta', {
|
||||||
|
created: new Date().toISOString(),
|
||||||
|
last_used: null,
|
||||||
|
email: 'cached@example.com',
|
||||||
|
plan_type: 'pro',
|
||||||
|
account_id: 'acct-cached',
|
||||||
|
});
|
||||||
|
|
||||||
|
const origLog = console.log;
|
||||||
|
console.log = () => {};
|
||||||
|
try {
|
||||||
|
await handleLoginCodex(ctx, ['preservemeta']);
|
||||||
|
} finally {
|
||||||
|
console.log = origLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = ctx.registry.getProfile('preservemeta');
|
||||||
|
expect(meta.last_used).toBeTruthy();
|
||||||
|
expect(meta.email).toBe('cached@example.com');
|
||||||
|
expect(meta.plan_type).toBe('pro');
|
||||||
|
expect(meta.account_id).toBe('acct-cached');
|
||||||
|
});
|
||||||
|
|
||||||
it('persists account_id when login token has account_id only', async () => {
|
it('persists account_id when login token has account_id only', async () => {
|
||||||
const detectorMod = await import('../../../../src/targets/codex-detector');
|
const detectorMod = await import('../../../../src/targets/codex-detector');
|
||||||
spyOn(detectorMod, 'detectCodexCli').mockReturnValue('/usr/bin/codex');
|
spyOn(detectorMod, 'detectCodexCli').mockReturnValue('/usr/bin/codex');
|
||||||
|
|||||||
Reference in New Issue
Block a user