diff --git a/build.ts b/build.ts index 68472ae..c2140ab 100644 --- a/build.ts +++ b/build.ts @@ -29,9 +29,14 @@ async function main(): Promise { // regardless of dotted or bracket access, so a build machine that happened to // have it set (e.g. .env.local) would otherwise stamp one run id into the // bundle for every user. Exclude it from the sweep so no define ever matches. - // POLYLANE_TELEMETRY_NOTICE_ACK is likewise a per-run runtime value (the - // installer sets it after printing the telemetry disclosure itself). - const DEFINE_EXCLUDE = new Set(['POLYLANE_ONBOARDING_RUN', 'POLYLANE_TELEMETRY_NOTICE_ACK']); + // The installer also sets both notice acknowledgements for its CLI child. + // They must remain runtime values so a build cannot suppress notices for + // everyone who later runs the published CLI directly. + const DEFINE_EXCLUDE = new Set([ + 'POLYLANE_ONBOARDING_RUN', + 'POLYLANE_TELEMETRY_NOTICE_ACK', + 'POLYLANE_TERMS_NOTICE_ACK', + ]); // Bake every other POLYLANE_* env var visible at build time into the bundle, so // the produced binary works without needing those vars set at runtime. // - Locally: comes from .env.local (gitignored — your dev domain / dev OAuth) diff --git a/src/commands/auth/signup.ts b/src/commands/auth/signup.ts index 31beea6..62a7331 100644 --- a/src/commands/auth/signup.ts +++ b/src/commands/auth/signup.ts @@ -78,6 +78,10 @@ const TERMS_NOTICE = [ ' https://polylane.com/privacy/', ].join('\n'); +// The installer prints the notice before its login picker and sets this only +// for the CLI process it starts, so an email choice does not print it twice. +const showTermsNotice = (): boolean => process.env.POLYLANE_TERMS_NOTICE_ACK !== '1'; + // Shown exactly once, on stderr, right after the server accepted it. Password // reset from the console sign-in page is the way to pick a different one. function announceGeneratedPassword(config: Config, email: string, password: string): void { @@ -166,8 +170,7 @@ async function oauthSignup(config: Config, provider: 'google' | 'github'): Promi [ `Your browser will open the Polylane signup page.`, `Pick "${label}" there, then approve the CLI's access when asked.`, - ``, - TERMS_NOTICE, + ...(showTermsNotice() ? ['', TERMS_NOTICE] : []), ].join('\n'), `Sign up with ${label}` ); @@ -255,7 +258,7 @@ export async function emailSignup(config: Config, args: Record) // the console (nominal#465). No separate confirm step — that was tried in // cli#53 and reverted. Scripted runs get it on stderr, never blocking. The // --code completion path creates nothing, so it stays silent. - if (!codeArg) { + if (!codeArg && showTermsNotice()) { if (isInteractive(config.nonInteractive)) note(TERMS_NOTICE); else process.stderr.write(`\n${TERMS_NOTICE}\n\n`); } diff --git a/test/signup.test.ts b/test/signup.test.ts index e02461f..2d8c92d 100644 --- a/test/signup.test.ts +++ b/test/signup.test.ts @@ -122,6 +122,7 @@ describe('auth signup terms notice', () => { }); beforeEach(() => { + delete process.env.POLYLANE_TERMS_NOTICE_ACK; rmSync(CONFIG_FILE, { force: true }); rmSync(CREDENTIALS_FILE, { force: true }); }); @@ -168,6 +169,26 @@ describe('auth signup terms notice', () => { assert.ok(!output.includes('Continue?')); }); + it('keeps the email signup prompts but does not repeat a notice printed by the installer', async () => { + mockApi({ '/v1/auth/signup': signupResponse }); + process.env.POLYLANE_TERMS_NOTICE_ACK = '1'; + + captureOutput(); + try { + await authSignupCommand.execute( + mockConfig({ telemetry: false, nonInteractive: false }), + {} as GlobalFlags, + { email: 'dev@acme.com' } + ); + } finally { + restoreOutput(); + delete process.env.POLYLANE_TERMS_NOTICE_ACK; + } + + assert.ok(!output.includes(TERMS_LINE)); + assert.ok(output.includes(PASSWORD_PROMPT_MARKER)); + }); + it('shows the notice without a gate when --password is passed interactively', async () => { mockApi({ '/v1/auth/signup': signupResponse });