diff --git a/CHANGELOG.md b/CHANGELOG.md index 48bdcbac17f..e2842c8d93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,3 +2,4 @@ - Fixes Storage Emulator to support JSON uploads larger than 100KB without hanging or throwing 413 error (#8355) - Add `extdeprecationwarnings` experiment to display phased deprecation notices and guidance across `ext:*` CLI commands. - Fixes Data Connect emulator crash when in-flight GraphQL requests are cancelled (#10821) +- Automatically enable non-interactive mode when an AI agent is detected in the environment to prevent the CLI from hanging on interactive prompts. diff --git a/src/command.ts b/src/command.ts index 0eb63361943..96250370eea 100644 --- a/src/command.ts +++ b/src/command.ts @@ -15,7 +15,7 @@ import { getProject } from "./management/projects"; import { reconcileStudioFirebaseProject } from "./management/studio"; import { requireAuth } from "./requireAuth"; import { Options } from "./options"; -import { isFirebaseStudio } from "./env"; +import { isFirebaseStudio, detectAIAgent } from "./env"; import * as experiments from "./experiments"; import { showDeprecationWarningBefore, showDeprecationWarningAfter } from "./extensions/warnings"; @@ -314,7 +314,8 @@ export class Command { if ( !process.stdin.isTTY || getInheritedOption(options, "nonInteractive") || - getInheritedOption(options, "json") // --json implies --non-interactive. + getInheritedOption(options, "json") || // --json implies --non-interactive. + detectAIAgent() !== "unknown" ) { options.nonInteractive = true; } @@ -324,6 +325,12 @@ export class Command { options.nonInteractive = false; } + if (options.nonInteractive) { + process.env.NON_INTERACTIVE = "true"; + } else { + delete process.env.NON_INTERACTIVE; + } + if (getInheritedOption(options, "debug")) { options.debug = true; } diff --git a/src/dataconnect/types.ts b/src/dataconnect/types.ts index bf724028a06..d1c49910702 100644 --- a/src/dataconnect/types.ts +++ b/src/dataconnect/types.ts @@ -249,16 +249,17 @@ export function toDatasource( ds: DatasourceYaml, ): Datasource { if (ds?.postgresql) { - return { - postgresql: { - database: ds.postgresql.database, - schema: ds.postgresql.schema, - cloudSql: { - instance: `projects/${projectId}/locations/${locationId}/instances/${ds.postgresql.cloudSql.instanceId}`, - }, - schemaValidation: ds.postgresql.schemaValidation, - }, + const postgresql: PostgreSql = { + database: ds.postgresql.database, + schema: ds.postgresql.schema, + schemaValidation: ds.postgresql.schemaValidation, }; + if (ds.postgresql.cloudSql) { + postgresql.cloudSql = { + instance: `projects/${projectId}/locations/${locationId}/instances/${ds.postgresql.cloudSql.instanceId}`, + }; + } + return { postgresql }; } if (ds?.httpGraphql) { return { diff --git a/src/prompt.spec.ts b/src/prompt.spec.ts index 3f662324a8c..99e9d2c4491 100644 --- a/src/prompt.spec.ts +++ b/src/prompt.spec.ts @@ -4,6 +4,21 @@ import { FirebaseError } from "./error"; import * as prompt from "./prompt"; describe("prompt", () => { + let originalNonInteractiveEnv: string | undefined; + + beforeEach(() => { + originalNonInteractiveEnv = process.env.NON_INTERACTIVE; + delete process.env.NON_INTERACTIVE; + }); + + afterEach(() => { + if (typeof originalNonInteractiveEnv !== "undefined") { + process.env.NON_INTERACTIVE = originalNonInteractiveEnv; + } else { + delete process.env.NON_INTERACTIVE; + } + }); + describe("guard", () => { it("returns default in non-interactive if present", () => { const { shouldReturn, value } = prompt.guard({ @@ -25,6 +40,17 @@ describe("prompt", () => { expect(value).to.be.undefined; }); + it("returns default if process.env.NON_INTERACTIVE is true", () => { + process.env.NON_INTERACTIVE = "true"; + const { shouldReturn, value } = prompt.guard({ + message: "message", + nonInteractive: false, + default: 42, + }); + expect(shouldReturn).to.be.true; + expect(value).to.equal(42); + }); + it("throws if non-interactive without default", () => { expect(() => prompt.guard({ diff --git a/src/prompt.ts b/src/prompt.ts index fdf9865d731..17fb0f5fd1c 100644 --- a/src/prompt.ts +++ b/src/prompt.ts @@ -23,7 +23,7 @@ export interface BasicOptions { export function guard( opts: BasicOptions, ): { shouldReturn: true; value: T } | { shouldReturn: false; value: undefined } { - if (!opts.nonInteractive) { + if (!opts.nonInteractive && process.env.NON_INTERACTIVE !== "true") { return { shouldReturn: false, value: undefined }; } if (typeof opts.default !== "undefined") {