From 9581b8e6ae38a4782cd584cbe3a1cefee766768d Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Wed, 17 Jun 2026 15:47:51 -0700 Subject: [PATCH 1/3] supports the nonEmpty param option for string and []string type params --- src/deploy/functions/params.ts | 71 ++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 16 deletions(-) diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 69ebe85212c..24e5ac2802b 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -120,11 +120,11 @@ type ParamBase = { // Default value. If not provided, a param must be supplied. default?: T | build.Expression; - // default: false - immutable?: boolean; - // Defines how the CLI will prompt for the value of the param if it's not in .env files input?: ParamInput; + + // Reject the empty string and empty array as valid input. + nonEmpty?: boolean; }; /** @@ -571,6 +571,7 @@ async function promptList( prompt, param.input, resolvedDefault, + param.nonEmpty, (res: string[]) => res, ); } else if (isTextInput(param.input)) { @@ -580,15 +581,21 @@ async function promptList( if (param.description) { prompt += ` \n(${param.description})`; } - return promptText(prompt, param.input, resolvedDefault, (res: string): string[] => { - return res.split(param.delimiter || ","); - }); + return promptText( + prompt, + param.input, + resolvedDefault, + param.nonEmpty, + (res: string): string[] => { + return res.split(param.delimiter || ","); + }, + ); } else if (isResourceInput(param.input)) { prompt = `Select values for ${param.label || param.name}:`; if (param.description) { prompt += ` \n(${param.description})`; } - return promptResourceStrings(prompt, param.input, projectId); + return promptResourceStrings(prompt, param.input, projectId, param.nonEmpty); } else { assertExhaustive(param.input); } @@ -619,7 +626,7 @@ async function promptBooleanParam( if (param.description) { prompt += ` \n(${param.description})`; } - return promptText(prompt, param.input, resolvedDefault, isTruthyInput); + return promptText(prompt, param.input, resolvedDefault, false, isTruthyInput); } else if (isResourceInput(param.input)) { throw new FirebaseError("Boolean params cannot have Cloud Resource selector inputs"); } else { @@ -658,7 +665,13 @@ async function promptStringParam( if (param.description) { prompt += ` \n(${param.description})`; } - return promptText(prompt, param.input, resolvedDefault, (res: string) => res); + return promptText( + prompt, + param.input, + resolvedDefault, + param.nonEmpty, + (res: string) => res, + ); } else { assertExhaustive(param.input); } @@ -693,7 +706,7 @@ async function promptIntParam(param: IntParam, resolvedDefault?: number): Promis if (param.description) { prompt += ` \n(${param.description})`; } - return promptText(prompt, param.input, resolvedDefault, (res: string) => { + return promptText(prompt, param.input, resolvedDefault, false, (res: string) => { if (isNaN(+res)) { return { message: `"${res}" could not be converted to a number.` }; } @@ -713,6 +726,7 @@ async function promptResourceString( prompt: string, input: ResourceInput, projectId: string, + disallowEmpty: boolean | undefined, resolvedDefault?: string, ): Promise { const notFound = new FirebaseError(`No instances of ${input.resource.type} found.`); @@ -734,7 +748,13 @@ async function promptResourceString( logger.warn( `Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`, ); - return promptText(prompt, { text: {} }, resolvedDefault, (res: string) => res); + return promptText( + prompt, + { text: {} }, + resolvedDefault, + disallowEmpty, + (res: string) => res, + ); } } @@ -742,6 +762,7 @@ async function promptResourceStrings( prompt: string, input: ResourceInput, projectId: string, + disallowEmpty: boolean | undefined, ): Promise { const notFound = new FirebaseError(`No instances of ${input.resource.type} found.`); switch (input.resource.type) { @@ -757,12 +778,20 @@ async function promptResourceStrings( }), }, }; - return promptSelectMultiple(prompt, forgedInput, undefined, (res: string[]) => res); + return promptSelectMultiple( + prompt, + forgedInput, + undefined, + disallowEmpty, + (res: string[]) => res, + ); default: logger.warn( `Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`, ); - return promptText(prompt, { text: {} }, undefined, (res: string) => res.split(",")); + return promptText(prompt, { text: {} }, undefined, disallowEmpty, (res: string) => + res.split(","), + ); } } @@ -775,6 +804,7 @@ async function promptText( prompt: string, textInput: TextInput, resolvedDefault: T | undefined, + disallowEmpty: boolean | undefined, converter: (res: string) => T | retryInput, ): Promise { const res = await input({ @@ -788,16 +818,20 @@ async function promptText( textInput.text.validationErrorMessage || `Input did not match provided validator ${userRe.toString()}, retrying...`, ); - return promptText(prompt, textInput, resolvedDefault, converter); + return promptText(prompt, textInput, resolvedDefault, disallowEmpty, converter); } } + if (disallowEmpty && res === "") { + logger.error(`Input cannot be the empty string, retrying...`); + return promptText(prompt, textInput, resolvedDefault, disallowEmpty, converter); + } // TODO(vsfan): the toString() is because PromptOnce()'s return type of string // is wrong--it will return the type of the default if selected. Remove this // hack once we fix the prompt.ts metaprogramming. const converted = converter(res.toString()); if (shouldRetry(converted)) { logger.error(converted.message); - return promptText(prompt, textInput, resolvedDefault, converter); + return promptText(prompt, textInput, resolvedDefault, disallowEmpty, converter); } return converted; } @@ -831,6 +865,7 @@ async function promptSelectMultiple( prompt: string, input: MultiSelectInput, resolvedDefault: T[] | undefined, + disallowEmpty: boolean | undefined, converter: (res: string[]) => T[] | retryInput, ): Promise { const response = await checkbox({ @@ -847,7 +882,11 @@ async function promptSelectMultiple( const converted = converter(response); if (shouldRetry(converted)) { logger.error(converted.message); - return promptSelectMultiple(prompt, input, resolvedDefault, converter); + return promptSelectMultiple(prompt, input, resolvedDefault, disallowEmpty, converter); + } + if (disallowEmpty && Array.isArray(converted) && converted.length === 0) { + logger.error(`Input cannot be the empty string, retrying...`); + return promptSelectMultiple(prompt, input, resolvedDefault, disallowEmpty, converter); } return converted; } From 05d13e923bf22e02f2565f099aeff30bb907c441 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Wed, 17 Jun 2026 15:50:28 -0700 Subject: [PATCH 2/3] forgot to hit save file --- src/deploy/functions/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index 24e5ac2802b..c22b72544dc 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -650,7 +650,7 @@ async function promptStringParam( if (param.description) { prompt += ` \n(${param.description})`; } - return promptResourceString(prompt, param.input, projectId, resolvedDefault); + return promptResourceString(prompt, param.input, projectId, param.nonEmpty, resolvedDefault); } else if (isMultiSelectInput(param.input)) { throw new FirebaseError("Non-list params cannot have multi selector inputs"); } else if (isSelectInput(param.input)) { From 5241c1f42d62423c777be961f84f16aeac8f3611 Mon Sep 17 00:00:00 2001 From: Victor Fan Date: Wed, 17 Jun 2026 15:53:28 -0700 Subject: [PATCH 3/3] clean up error string --- src/deploy/functions/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deploy/functions/params.ts b/src/deploy/functions/params.ts index c22b72544dc..271848963ed 100644 --- a/src/deploy/functions/params.ts +++ b/src/deploy/functions/params.ts @@ -885,7 +885,7 @@ async function promptSelectMultiple( return promptSelectMultiple(prompt, input, resolvedDefault, disallowEmpty, converter); } if (disallowEmpty && Array.isArray(converted) && converted.length === 0) { - logger.error(`Input cannot be the empty string, retrying...`); + logger.error(`Input cannot be empty, retrying...`); return promptSelectMultiple(prompt, input, resolvedDefault, disallowEmpty, converter); } return converted;