Skip to content

Commit f569b23

Browse files
authored
fix(dx): skip secret prompt without tty (#172)
1 parent 07144fb commit f569b23

3 files changed

Lines changed: 91 additions & 3 deletions

File tree

src/module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ export default defineNuxtModule<BetterAuthModuleOptions>({
7979
hubSecondaryStorage: false,
8080
},
8181
async onInstall(nuxt) {
82-
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola)
82+
const configuredSecret = nuxt.options.runtimeConfig?.betterAuthSecret as string | undefined
83+
const generatedSecret = await promptForSecret(nuxt.options.rootDir, consola, { configuredSecret, prepare: Boolean(nuxt.options._prepare) })
8384
if (generatedSecret)
8485
process.env.BETTER_AUTH_SECRET = generatedSecret
8586

src/module/secret.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,29 @@ function appendSecretToEnv(rootDir: string, secret: string): void {
2525
writeFileSync(envPath, content, 'utf-8')
2626
}
2727

28-
export async function promptForSecret(rootDir: string, consola: ConsolaInstance): Promise<string | undefined> {
28+
export interface PromptForSecretOptions {
29+
configuredSecret?: string
30+
prepare?: boolean
31+
}
32+
33+
export async function promptForSecret(rootDir: string, consola: ConsolaInstance, options: PromptForSecretOptions = {}): Promise<string | undefined> {
34+
const configuredSecret = options.configuredSecret?.trim()
35+
if (configuredSecret)
36+
return undefined
37+
2938
if (process.env.BETTER_AUTH_SECRET || hasEnvSecret(rootDir))
3039
return undefined
3140

41+
const hasTty = Boolean(process.stdin.isTTY && process.stdout.isTTY)
42+
if (options.prepare || !hasTty) {
43+
consola.warn('[nuxt-better-auth] Skipping BETTER_AUTH_SECRET prompt (non-interactive). Set BETTER_AUTH_SECRET or NUXT_BETTER_AUTH_SECRET.')
44+
return undefined
45+
}
46+
3247
if (isCI || isTest) {
3348
const secret = generateSecret()
3449
appendSecretToEnv(rootDir, secret)
35-
consola.info('Generated BETTER_AUTH_SECRET and added to .env (CI mode)')
50+
consola.info('Generated BETTER_AUTH_SECRET and added to .env (CI/test mode)')
3651
return secret
3752
}
3853

test/non-tty-secret-prompt.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { spawnSync } from 'node:child_process'
2+
import { fileURLToPath } from 'node:url'
3+
import { describe, expect, it } from 'vitest'
4+
5+
const repoDir = fileURLToPath(new URL('..', import.meta.url))
6+
7+
function createNonTestEnv(): NodeJS.ProcessEnv {
8+
const env: NodeJS.ProcessEnv = { ...process.env }
9+
10+
// Make std-env treat this as non-test/non-ci so we exercise the non-interactive path.
11+
delete env.CI
12+
delete env.VITEST
13+
delete env.VITEST_WORKER_ID
14+
delete env.JEST_WORKER_ID
15+
delete env.AVA_PATH
16+
delete env.TAP
17+
delete env.TEST
18+
env.NODE_ENV = 'production'
19+
delete env.npm_lifecycle_event
20+
21+
// Ensure no secret is configured via env.
22+
delete env.BETTER_AUTH_SECRET
23+
delete env.NUXT_BETTER_AUTH_SECRET
24+
25+
env.FORCE_COLOR = '0'
26+
27+
return env
28+
}
29+
30+
describe('promptForSecret', () => {
31+
it('skips prompting in non-interactive/prepare mode', () => {
32+
const env = createNonTestEnv()
33+
34+
const script = `
35+
import { createJiti } from 'jiti'
36+
import fs from 'node:fs'
37+
import os from 'node:os'
38+
import path from 'node:path'
39+
40+
const jiti = createJiti(process.cwd(), { interopDefault: true, moduleCache: false })
41+
const { promptForSecret } = await jiti.import('./src/module/secret.ts')
42+
43+
let promptCalls = 0
44+
const consola = {
45+
warn: (...args) => console.log(String(args[0] ?? '')),
46+
info: () => {},
47+
success: () => {},
48+
box: () => {},
49+
prompt: async () => {
50+
promptCalls++
51+
throw new Error('prompt called')
52+
},
53+
}
54+
55+
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nuxt-better-auth-secret-'))
56+
await promptForSecret(rootDir, consola, { prepare: true })
57+
console.log('PROMPT_CALLS=' + promptCalls)
58+
`
59+
60+
const run = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
61+
cwd: repoDir,
62+
env,
63+
encoding: 'utf8',
64+
timeout: 60_000,
65+
})
66+
67+
expect(run.status, `node script failed:\n${run.stdout}\n${run.stderr}`).toBe(0)
68+
const output = `${run.stdout}\n${run.stderr}`
69+
expect(output).toContain('Skipping BETTER_AUTH_SECRET prompt')
70+
expect(output).toContain('PROMPT_CALLS=0')
71+
})
72+
})

0 commit comments

Comments
 (0)