123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.resolveParams = exports.ParamValue = exports.isMultiSelectInput = exports.isResourceInput = exports.isSelectInput = exports.isTextInput = exports.resolveBoolean = exports.resolveList = exports.resolveString = exports.resolveInt = void 0;
- const logger_1 = require("../../logger");
- const error_1 = require("../../error");
- const prompt_1 = require("../../prompt");
- const functional_1 = require("../../functional");
- const secretManager = require("../../gcp/secretManager");
- const storage_1 = require("../../gcp/storage");
- const cel_1 = require("./cel");
- function dependenciesCEL(expr) {
- const deps = [];
- const paramCapture = /{{ params\.(\w+) }}/g;
- let match;
- while ((match = paramCapture.exec(expr)) != null) {
- deps.push(match[1]);
- }
- return deps;
- }
- function resolveInt(from, paramValues) {
- if (typeof from === "number") {
- return from;
- }
- return (0, cel_1.resolveExpression)("number", from, paramValues);
- }
- exports.resolveInt = resolveInt;
- function resolveString(from, paramValues) {
- let output = from;
- const celCapture = /{{ .+? }}/g;
- const subExprs = from.match(celCapture);
- if (!subExprs || subExprs.length === 0) {
- return output;
- }
- for (const expr of subExprs) {
- const resolved = (0, cel_1.resolveExpression)("string", expr, paramValues);
- output = output.replace(expr, resolved);
- }
- return output;
- }
- exports.resolveString = resolveString;
- function resolveList(from, paramValues) {
- if (!from) {
- return [];
- }
- else if (Array.isArray(from)) {
- return from.map((entry) => resolveString(entry, paramValues));
- }
- else if (typeof from === "string") {
- return (0, cel_1.resolveExpression)("string[]", from, paramValues);
- }
- else {
- (0, functional_1.assertExhaustive)(from);
- }
- }
- exports.resolveList = resolveList;
- function resolveBoolean(from, paramValues) {
- if (typeof from === "boolean") {
- return from;
- }
- return (0, cel_1.resolveExpression)("boolean", from, paramValues);
- }
- exports.resolveBoolean = resolveBoolean;
- function isTextInput(input) {
- return {}.hasOwnProperty.call(input, "text");
- }
- exports.isTextInput = isTextInput;
- function isSelectInput(input) {
- return {}.hasOwnProperty.call(input, "select");
- }
- exports.isSelectInput = isSelectInput;
- function isResourceInput(input) {
- return {}.hasOwnProperty.call(input, "resource");
- }
- exports.isResourceInput = isResourceInput;
- function isMultiSelectInput(input) {
- return {}.hasOwnProperty.call(input, "multiSelect");
- }
- exports.isMultiSelectInput = isMultiSelectInput;
- class ParamValue {
- constructor(rawValue, internal, types) {
- this.rawValue = rawValue;
- this.internal = internal;
- this.legalString = types.string || false;
- this.legalBoolean = types.boolean || false;
- this.legalNumber = types.number || false;
- this.legalList = types.list || false;
- this.delimiter = ",";
- }
- static fromList(ls, delimiter = ",") {
- const pv = new ParamValue(ls.join(delimiter), false, { list: true });
- pv.setDelimiter(delimiter);
- return pv;
- }
- setDelimiter(delimiter) {
- this.delimiter = delimiter;
- }
- toString() {
- return this.rawValue;
- }
- toSDK() {
- return this.legalList ? JSON.stringify(this.asList()) : this.toString();
- }
- asString() {
- return this.rawValue;
- }
- asBoolean() {
- return ["true", "y", "yes", "1"].includes(this.rawValue);
- }
- asList() {
- return this.rawValue.split(this.delimiter);
- }
- asNumber() {
- return +this.rawValue;
- }
- }
- exports.ParamValue = ParamValue;
- function resolveDefaultCEL(type, expr, currentEnv) {
- const deps = dependenciesCEL(expr);
- const allDepsFound = deps.every((dep) => !!currentEnv[dep]);
- if (!allDepsFound) {
- throw new error_1.FirebaseError("Build specified parameter with un-resolvable default value " +
- expr +
- "; dependencies missing.");
- }
- switch (type) {
- case "boolean":
- return resolveBoolean(expr, currentEnv);
- case "string":
- return resolveString(expr, currentEnv);
- case "int":
- return resolveInt(expr, currentEnv);
- case "list":
- return resolveList(expr, currentEnv);
- default:
- throw new error_1.FirebaseError("Build specified parameter with default " + expr + " of unsupported type");
- }
- }
- function canSatisfyParam(param, value) {
- if (param.type === "string") {
- return typeof value === "string";
- }
- else if (param.type === "int") {
- return typeof value === "number" && Number.isInteger(value);
- }
- else if (param.type === "boolean") {
- return typeof value === "boolean";
- }
- else if (param.type === "list") {
- return Array.isArray(value);
- }
- else if (param.type === "secret") {
- return false;
- }
- (0, functional_1.assertExhaustive)(param);
- }
- async function resolveParams(params, firebaseConfig, userEnvs, nonInteractive) {
- const paramValues = populateDefaultParams(firebaseConfig);
- const [resolved, outstanding] = (0, functional_1.partition)(params, (param) => {
- return {}.hasOwnProperty.call(userEnvs, param.name);
- });
- for (const param of resolved) {
- paramValues[param.name] = userEnvs[param.name];
- }
- const [needSecret, needPrompt] = (0, functional_1.partition)(outstanding, (param) => param.type === "secret");
- for (const param of needSecret) {
- await handleSecret(param, firebaseConfig.projectId);
- }
- if (nonInteractive && needPrompt.length > 0) {
- const envNames = outstanding.map((p) => p.name).join(", ");
- throw new error_1.FirebaseError(`In non-interactive mode but have no value for the following environment variables: ${envNames}\n` +
- "To continue, either run `firebase deploy` with an interactive terminal, or add values to a dotenv file. " +
- "For information regarding how to use dotenv files, see https://firebase.google.com/docs/functions/config-env");
- }
- for (const param of needPrompt) {
- const promptable = param;
- let paramDefault = promptable.default;
- if (paramDefault && (0, cel_1.isCelExpression)(paramDefault)) {
- paramDefault = resolveDefaultCEL(param.type, paramDefault, paramValues);
- }
- if (paramDefault && !canSatisfyParam(param, paramDefault)) {
- throw new error_1.FirebaseError("Parameter " + param.name + " has default value " + paramDefault + " of wrong type");
- }
- paramValues[param.name] = await promptParam(param, firebaseConfig.projectId, paramDefault);
- }
- return paramValues;
- }
- exports.resolveParams = resolveParams;
- function populateDefaultParams(config) {
- const defaultParams = {};
- if (config.databaseURL && config.databaseURL !== "") {
- defaultParams["DATABASE_URL"] = new ParamValue(config.databaseURL, true, {
- string: true,
- boolean: false,
- number: false,
- });
- }
- defaultParams["PROJECT_ID"] = new ParamValue(config.projectId, true, {
- string: true,
- boolean: false,
- number: false,
- });
- defaultParams["GCLOUD_PROJECT"] = new ParamValue(config.projectId, true, {
- string: true,
- boolean: false,
- number: false,
- });
- if (config.storageBucket && config.storageBucket !== "") {
- defaultParams["STORAGE_BUCKET"] = new ParamValue(config.storageBucket, true, {
- string: true,
- boolean: false,
- number: false,
- });
- }
- return defaultParams;
- }
- async function handleSecret(secretParam, projectId) {
- const metadata = await secretManager.getSecretMetadata(projectId, secretParam.name, "latest");
- if (!metadata.secret) {
- const secretValue = await (0, prompt_1.promptOnce)({
- name: secretParam.name,
- type: "password",
- message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretParam.name}. Enter a value for ${secretParam.label || secretParam.name}:`,
- });
- const secretLabel = { "firebase-hosting-managed": "yes" };
- await secretManager.createSecret(projectId, secretParam.name, secretLabel);
- await secretManager.addVersion(projectId, secretParam.name, secretValue);
- return secretValue;
- }
- else if (!metadata.secretVersion) {
- throw new error_1.FirebaseError(`Cloud Secret Manager has no latest version of the secret defined by param ${secretParam.label || secretParam.name}`);
- }
- else if (metadata.secretVersion.state === "DESTROYED" ||
- metadata.secretVersion.state === "DISABLED") {
- throw new error_1.FirebaseError(`Cloud Secret Manager's latest version of secret '${secretParam.label || secretParam.name} is in illegal state ${metadata.secretVersion.state}`);
- }
- }
- async function promptParam(param, projectId, resolvedDefault) {
- if (param.type === "string") {
- const provided = await promptStringParam(param, projectId, resolvedDefault);
- return new ParamValue(provided.toString(), false, { string: true });
- }
- else if (param.type === "int") {
- const provided = await promptIntParam(param, resolvedDefault);
- return new ParamValue(provided.toString(), false, { number: true });
- }
- else if (param.type === "boolean") {
- const provided = await promptBooleanParam(param, resolvedDefault);
- return new ParamValue(provided.toString(), false, { boolean: true });
- }
- else if (param.type === "list") {
- const provided = await promptList(param, projectId, resolvedDefault);
- return ParamValue.fromList(provided, param.delimiter);
- }
- else if (param.type === "secret") {
- throw new error_1.FirebaseError(`Somehow ended up trying to interactively prompt for secret parameter ${param.name}, which should never happen.`);
- }
- (0, functional_1.assertExhaustive)(param);
- }
- async function promptList(param, projectId, resolvedDefault) {
- if (!param.input) {
- const defaultToText = { text: {} };
- param.input = defaultToText;
- }
- let prompt;
- if (isSelectInput(param.input)) {
- throw new error_1.FirebaseError("List params cannot have non-list selector inputs");
- }
- else if (isMultiSelectInput(param.input)) {
- prompt = `Select a value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
- return promptSelectMultiple(prompt, param.input, resolvedDefault, (res) => res);
- }
- else if (isTextInput(param.input)) {
- prompt = `Enter a list of strings (delimiter: ${param.delimiter ? param.delimiter : ","}) for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- return promptText(prompt, param.input, resolvedDefault, (res) => {
- 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);
- }
- else {
- (0, functional_1.assertExhaustive)(param.input);
- }
- }
- async function promptBooleanParam(param, resolvedDefault) {
- if (!param.input) {
- const defaultToText = { text: {} };
- param.input = defaultToText;
- }
- const isTruthyInput = (res) => ["true", "y", "yes", "1"].includes(res.toLowerCase());
- let prompt;
- if (isSelectInput(param.input)) {
- prompt = `Select a value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
- return promptSelect(prompt, param.input, resolvedDefault, isTruthyInput);
- }
- else if (isMultiSelectInput(param.input)) {
- throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
- }
- else if (isTextInput(param.input)) {
- prompt = `Enter a boolean value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- return promptText(prompt, param.input, resolvedDefault, isTruthyInput);
- }
- else if (isResourceInput(param.input)) {
- throw new error_1.FirebaseError("Boolean params cannot have Cloud Resource selector inputs");
- }
- else {
- (0, functional_1.assertExhaustive)(param.input);
- }
- }
- async function promptStringParam(param, projectId, resolvedDefault) {
- if (!param.input) {
- const defaultToText = { text: {} };
- param.input = defaultToText;
- }
- let prompt;
- if (isResourceInput(param.input)) {
- prompt = `Select a value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- return promptResourceString(prompt, param.input, projectId, resolvedDefault);
- }
- else if (isMultiSelectInput(param.input)) {
- throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
- }
- else if (isSelectInput(param.input)) {
- prompt = `Select a value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
- return promptSelect(prompt, param.input, resolvedDefault, (res) => res);
- }
- else if (isTextInput(param.input)) {
- prompt = `Enter a string value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- return promptText(prompt, param.input, resolvedDefault, (res) => res);
- }
- else {
- (0, functional_1.assertExhaustive)(param.input);
- }
- }
- async function promptIntParam(param, resolvedDefault) {
- if (!param.input) {
- const defaultToText = { text: {} };
- param.input = defaultToText;
- }
- let prompt;
- if (isSelectInput(param.input)) {
- prompt = `Select a value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- prompt += "\nSelect an option with the arrow keys, and use Enter to confirm your choice. ";
- return promptSelect(prompt, param.input, resolvedDefault, (res) => {
- if (isNaN(+res)) {
- return { message: `"${res}" could not be converted to a number.` };
- }
- if (res.includes(".")) {
- return { message: `${res} is not an integer value.` };
- }
- return +res;
- });
- }
- else if (isMultiSelectInput(param.input)) {
- throw new error_1.FirebaseError("Non-list params cannot have multi selector inputs");
- }
- else if (isTextInput(param.input)) {
- prompt = `Enter an integer value for ${param.label || param.name}:`;
- if (param.description) {
- prompt += ` \n(${param.description})`;
- }
- return promptText(prompt, param.input, resolvedDefault, (res) => {
- if (isNaN(+res)) {
- return { message: `"${res}" could not be converted to a number.` };
- }
- if (res.includes(".")) {
- return { message: `${res} is not an integer value.` };
- }
- return +res;
- });
- }
- else if (isResourceInput(param.input)) {
- throw new error_1.FirebaseError("Numeric params cannot have Cloud Resource selector inputs");
- }
- else {
- (0, functional_1.assertExhaustive)(param.input);
- }
- }
- async function promptResourceString(prompt, input, projectId, resolvedDefault) {
- const notFound = new error_1.FirebaseError(`No instances of ${input.resource.type} found.`);
- switch (input.resource.type) {
- case "storage.googleapis.com/Bucket":
- const buckets = await (0, storage_1.listBuckets)(projectId);
- if (buckets.length === 0) {
- throw notFound;
- }
- const forgedInput = {
- select: {
- options: buckets.map((bucketName) => {
- return { label: bucketName, value: bucketName };
- }),
- },
- };
- return promptSelect(prompt, forgedInput, resolvedDefault, (res) => res);
- default:
- logger_1.logger.warn(`Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`);
- return promptText(prompt, { text: {} }, resolvedDefault, (res) => res);
- }
- }
- async function promptResourceStrings(prompt, input, projectId) {
- const notFound = new error_1.FirebaseError(`No instances of ${input.resource.type} found.`);
- switch (input.resource.type) {
- case "storage.googleapis.com/Bucket":
- const buckets = await (0, storage_1.listBuckets)(projectId);
- if (buckets.length === 0) {
- throw notFound;
- }
- const forgedInput = {
- multiSelect: {
- options: buckets.map((bucketName) => {
- return { label: bucketName, value: bucketName };
- }),
- },
- };
- return promptSelectMultiple(prompt, forgedInput, undefined, (res) => res);
- default:
- logger_1.logger.warn(`Warning: unknown resource type ${input.resource.type}; defaulting to raw text input...`);
- return promptText(prompt, { text: {} }, undefined, (res) => res.split(","));
- }
- }
- function shouldRetry(obj) {
- return typeof obj === "object" && obj.message !== undefined;
- }
- async function promptText(prompt, input, resolvedDefault, converter) {
- const res = await (0, prompt_1.promptOnce)({
- type: "input",
- default: resolvedDefault,
- message: prompt,
- });
- if (input.text.validationRegex) {
- const userRe = new RegExp(input.text.validationRegex);
- if (!userRe.test(res)) {
- logger_1.logger.error(input.text.validationErrorMessage ||
- `Input did not match provided validator ${userRe.toString()}, retrying...`);
- return promptText(prompt, input, resolvedDefault, converter);
- }
- }
- const converted = converter(res.toString());
- if (shouldRetry(converted)) {
- logger_1.logger.error(converted.message);
- return promptText(prompt, input, resolvedDefault, converter);
- }
- return converted;
- }
- async function promptSelect(prompt, input, resolvedDefault, converter) {
- const response = await (0, prompt_1.promptOnce)({
- name: "input",
- type: "list",
- default: resolvedDefault,
- message: prompt,
- choices: input.select.options.map((option) => {
- return {
- checked: false,
- name: option.label,
- value: option.value.toString(),
- };
- }),
- });
- const converted = converter(response);
- if (shouldRetry(converted)) {
- logger_1.logger.error(converted.message);
- return promptSelect(prompt, input, resolvedDefault, converter);
- }
- return converted;
- }
- async function promptSelectMultiple(prompt, input, resolvedDefault, converter) {
- const response = await (0, prompt_1.promptOnce)({
- name: "input",
- type: "checkbox",
- default: resolvedDefault,
- message: prompt,
- choices: input.multiSelect.options.map((option) => {
- return {
- checked: false,
- name: option.label,
- value: option.value.toString(),
- };
- }),
- });
- const converted = converter(response);
- if (shouldRetry(converted)) {
- logger_1.logger.error(converted.message);
- return promptSelectMultiple(prompt, input, resolvedDefault, converter);
- }
- return converted;
- }
|