diff --git a/src/lib/credentials.test.ts b/src/lib/credentials.test.ts index fa7f744e..d6d27add 100644 --- a/src/lib/credentials.test.ts +++ b/src/lib/credentials.test.ts @@ -90,6 +90,30 @@ unknown_key = ignored `; expect(parseCredentials(content)).toEqual({ default: { apiKey: 'sk-real' } }); }); + + it('reads a section header that carries a trailing comment', () => { + const content = `[default] +api_key = sk-default +[prod] # production account +api_key = sk-prod +[dev] ; dev account +api_key = sk-dev +`; + expect(parseCredentials(content)).toEqual({ + default: { apiKey: 'sk-default' }, + prod: { apiKey: 'sk-prod' }, + dev: { apiKey: 'sk-dev' }, + }); + }); + + it('never moves keys under an unreadable header into the section above it', () => { + const content = `[default] +api_key = sk-default +[prod] stray text +api_key = sk-prod +`; + expect(parseCredentials(content)).toEqual({ default: { apiKey: 'sk-default' } }); + }); }); describe('serializeCredentials', () => { diff --git a/src/lib/credentials.ts b/src/lib/credentials.ts index b0eec1f0..3061bd79 100644 --- a/src/lib/credentials.ts +++ b/src/lib/credentials.ts @@ -136,7 +136,8 @@ export function parseCredentials(content: string): CredentialsFile { for (const rawLine of content.split('\n')) { const line = rawLine.trim(); if (line === '' || line.startsWith('#') || line.startsWith(';')) continue; - const sectionMatch = /^\[([^\]]+)\]$/.exec(line); + // A header may carry a trailing comment (`[prod] # production account`). + const sectionMatch = /^\[([^\]]+)\]\s*(?:[#;].*)?$/.exec(line); if (sectionMatch) { const sectionName = sectionMatch[1]!.trim(); const existing = result[sectionName]; @@ -149,6 +150,13 @@ export function parseCredentials(content: string): CredentialsFile { } continue; } + // Any other `[`-line is a header we can't read. Its keys must not land in + // the section above it (that silently swapped one profile's key into + // another, and the next write lost the original key). + if (line.startsWith('[')) { + currentEntry = null; + continue; + } if (currentEntry === null) continue; const eqIndex = line.indexOf('='); if (eqIndex < 0) continue;