firebase-tools 12.3.0 → 12.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,10 @@ const command_1 = require("../command");
5
5
  const logger_1 = require("../logger");
6
6
  const projectUtils_1 = require("../projectUtils");
7
7
  const secretManager_1 = require("../gcp/secretManager");
8
+ const secrets = require("../functions/secrets");
8
9
  exports.command = new command_1.Command("functions:secrets:access <KEY>[@version]")
9
10
  .description("Access secret value given secret and its version. Defaults to accessing the latest version.")
11
+ .before(secrets.ensureApi)
10
12
  .action(async (key, options) => {
11
13
  const projectId = (0, projectUtils_1.needProjectId)(options);
12
14
  let [name, version] = key.split("@");
@@ -11,6 +11,7 @@ const backend = require("../deploy/functions/backend");
11
11
  exports.command = new command_1.Command("functions:secrets:destroy <KEY>[@version]")
12
12
  .description("Destroy a secret. Defaults to destroying the latest version.")
13
13
  .withForce("Destroys a secret without confirmation.")
14
+ .before(secrets.ensureApi)
14
15
  .action(async (key, options) => {
15
16
  const projectId = (0, projectUtils_1.needProjectId)(options);
16
17
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -7,8 +7,10 @@ const logger_1 = require("../logger");
7
7
  const projectUtils_1 = require("../projectUtils");
8
8
  const secretManager_1 = require("../gcp/secretManager");
9
9
  const requirePermissions_1 = require("../requirePermissions");
10
+ const secrets = require("../functions/secrets");
10
11
  exports.command = new command_1.Command("functions:secrets:get <KEY>")
11
12
  .description("Get metadata for secret and its versions")
13
+ .before(secrets.ensureApi)
12
14
  .before(requirePermissions_1.requirePermissions, ["secretmanager.secrets.get"])
13
15
  .action(async (key, options) => {
14
16
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -4,7 +4,7 @@ exports.command = void 0;
4
4
  const backend = require("../deploy/functions/backend");
5
5
  const command_1 = require("../command");
6
6
  const projectUtils_1 = require("../projectUtils");
7
- const secrets_1 = require("../functions/secrets");
7
+ const secrets = require("../functions/secrets");
8
8
  const requirePermissions_1 = require("../requirePermissions");
9
9
  const deploymentTool_1 = require("../deploymentTool");
10
10
  const utils_1 = require("../utils");
@@ -13,6 +13,7 @@ const secretManager_1 = require("../gcp/secretManager");
13
13
  exports.command = new command_1.Command("functions:secrets:prune")
14
14
  .withForce("Destroys unused secrets without prompt")
15
15
  .description("Destroys unused secrets")
16
+ .before(secrets.ensureApi)
16
17
  .before(requirePermissions_1.requirePermissions, [
17
18
  "cloudfunctions.functions.list",
18
19
  "secretmanager.secrets.list",
@@ -27,7 +28,7 @@ exports.command = new command_1.Command("functions:secrets:prune")
27
28
  const haveEndpoints = backend
28
29
  .allEndpoints(haveBackend)
29
30
  .filter((e) => (0, deploymentTool_1.isFirebaseManaged)(e.labels || []));
30
- const pruned = await (0, secrets_1.pruneSecrets)({ projectNumber, projectId }, haveEndpoints);
31
+ const pruned = await secrets.pruneSecrets({ projectNumber, projectId }, haveEndpoints);
31
32
  if (pruned.length === 0) {
32
33
  (0, utils_1.logBullet)("All secrets are in use. Nothing to prune today.");
33
34
  return;
@@ -4,6 +4,7 @@ exports.command = void 0;
4
4
  const tty = require("tty");
5
5
  const fs = require("fs");
6
6
  const clc = require("colorette");
7
+ const logger_1 = require("../logger");
7
8
  const secrets_1 = require("../functions/secrets");
8
9
  const command_1 = require("../command");
9
10
  const requirePermissions_1 = require("../requirePermissions");
@@ -13,9 +14,11 @@ const projectUtils_1 = require("../projectUtils");
13
14
  const secretManager_1 = require("../gcp/secretManager");
14
15
  const secrets = require("../functions/secrets");
15
16
  const backend = require("../deploy/functions/backend");
17
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
16
18
  exports.command = new command_1.Command("functions:secrets:set <KEY>")
17
19
  .description("Create or update a secret for use in Cloud Functions for Firebase.")
18
20
  .withForce("Automatically updates functions to use the new secret.")
21
+ .before(secrets.ensureApi)
19
22
  .before(requirePermissions_1.requirePermissions, [
20
23
  "secretmanager.secrets.create",
21
24
  "secretmanager.secrets.get",
@@ -50,6 +53,11 @@ exports.command = new command_1.Command("functions:secrets:set <KEY>")
50
53
  clc.bold("firebase deploy --only functions"));
51
54
  return;
52
55
  }
56
+ const functionsEnabled = await (0, ensureApiEnabled_1.check)(projectId, "cloudfunctions.googleapis.com", "functions", true);
57
+ if (!functionsEnabled) {
58
+ logger_1.logger.debug("Customer set secrets before enabling functions. Exiting");
59
+ return;
60
+ }
53
61
  const haveBackend = await backend.existingBackend({ projectId });
54
62
  const endpointsToUpdate = backend
55
63
  .allEndpoints(haveBackend)
@@ -137,6 +137,8 @@ function load(client) {
137
137
  client.init = loadCommand("init");
138
138
  if (experiments.isEnabled("internaltesting")) {
139
139
  client.internaltesting = {};
140
+ client.internaltesting.frameworks = {};
141
+ client.internaltesting.frameworks.compose = loadCommand("internaltesting-frameworks-compose");
140
142
  client.internaltesting.functions = {};
141
143
  client.internaltesting.functions.discover = loadCommand("internaltesting-functions-discover");
142
144
  }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.command = void 0;
4
+ const command_1 = require("../command");
5
+ const logger_1 = require("../logger");
6
+ const driver_1 = require("../frameworks/compose/driver");
7
+ const compose_1 = require("../frameworks/compose");
8
+ const error_1 = require("../error");
9
+ exports.command = new command_1.Command("internaltesting:frameworks:compose")
10
+ .option("-m, --mode <mode>", "Composer mode (local or docker)", "local")
11
+ .description("compose framework in current directory")
12
+ .action((options) => {
13
+ const mode = options.mode;
14
+ if (!driver_1.SUPPORTED_MODES.includes(mode)) {
15
+ throw new error_1.FirebaseError(`Unsupported mode ${mode}. Supported modes are [${driver_1.SUPPORTED_MODES.join(", ")}]`);
16
+ }
17
+ const bundle = (0, compose_1.compose)(mode);
18
+ logger_1.logger.info(JSON.stringify(bundle, null, 2));
19
+ return {};
20
+ });
@@ -1,21 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.InlineExecutor = exports.QueueExecutor = void 0;
3
+ exports.InlineExecutor = exports.QueueExecutor = exports.DEFAULT_RETRY_CODES = void 0;
4
4
  const queue_1 = require("../../../throttler/queue");
5
- async function handler(op) {
5
+ exports.DEFAULT_RETRY_CODES = [429, 409, 503];
6
+ function parseErrorCode(err) {
6
7
  var _a, _b, _c, _d, _e, _f;
8
+ return (err.status ||
9
+ err.code ||
10
+ ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
11
+ ((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
12
+ ((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode));
13
+ }
14
+ async function handler(op) {
7
15
  try {
8
16
  op.result = await op.func();
9
17
  }
10
18
  catch (err) {
11
- const code = err.status ||
12
- err.code ||
13
- ((_b = (_a = err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) ||
14
- ((_c = err.original) === null || _c === void 0 ? void 0 : _c.code) ||
15
- ((_f = (_e = (_d = err.original) === null || _d === void 0 ? void 0 : _d.context) === null || _e === void 0 ? void 0 : _e.response) === null || _f === void 0 ? void 0 : _f.statusCode);
16
- if (code === 429 || code === 409 || code === 503) {
19
+ const code = parseErrorCode(err);
20
+ if (op.retryCodes.includes(code)) {
17
21
  throw err;
18
22
  }
23
+ err.code = code;
19
24
  op.error = err;
20
25
  }
21
26
  return;
@@ -24,8 +29,12 @@ class QueueExecutor {
24
29
  constructor(options) {
25
30
  this.queue = new queue_1.Queue(Object.assign(Object.assign({}, options), { handler }));
26
31
  }
27
- async run(func) {
28
- const op = { func };
32
+ async run(func, opts) {
33
+ const retryCodes = (opts === null || opts === void 0 ? void 0 : opts.retryCodes) || exports.DEFAULT_RETRY_CODES;
34
+ const op = {
35
+ func,
36
+ retryCodes,
37
+ };
29
38
  await this.queue.run(op);
30
39
  if (op.error) {
31
40
  throw op.error;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Fabricator = void 0;
4
4
  const clc = require("colorette");
5
+ const executor_1 = require("./executor");
5
6
  const error_1 = require("../../../error");
6
7
  const sourceTokenScraper_1 = require("./sourceTokenScraper");
7
8
  const timer_1 = require("./timer");
@@ -44,6 +45,7 @@ const eventarcPollerOptions = {
44
45
  masterTimeout: 25 * 60 * 1000,
45
46
  maxBackoff: 10000,
46
47
  };
48
+ const CLOUD_RUN_RESOURCE_EXHAUSTED_CODE = 8;
47
49
  const rethrowAs = (endpoint, op) => (err) => {
48
50
  logger_1.logger.error(err.message);
49
51
  throw new reporter.DeploymentError(endpoint, op, err);
@@ -269,12 +271,24 @@ class Fabricator {
269
271
  })
270
272
  .catch(rethrowAs(endpoint, "upsert eventarc channel"));
271
273
  }
272
- const resultFunction = await this.functionExecutor
273
- .run(async () => {
274
- const op = await gcfV2.createFunction(apiFunction);
275
- return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
276
- })
277
- .catch(rethrowAs(endpoint, "create"));
274
+ let resultFunction = null;
275
+ while (!resultFunction) {
276
+ resultFunction = await this.functionExecutor
277
+ .run(async () => {
278
+ const op = await gcfV2.createFunction(apiFunction);
279
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
280
+ })
281
+ .catch(async (err) => {
282
+ if (err.code === CLOUD_RUN_RESOURCE_EXHAUSTED_CODE) {
283
+ await this.deleteV2Function(endpoint);
284
+ return null;
285
+ }
286
+ else {
287
+ logger_1.logger.error(err.message);
288
+ throw new reporter.DeploymentError(endpoint, "create", err);
289
+ }
290
+ });
291
+ }
278
292
  endpoint.uri = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.uri;
279
293
  const serviceName = (_e = resultFunction.serviceConfig) === null || _e === void 0 ? void 0 : _e.service;
280
294
  endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
@@ -367,7 +381,7 @@ class Fabricator {
367
381
  .run(async () => {
368
382
  const op = await gcfV2.updateFunction(apiFunction);
369
383
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
370
- })
384
+ }, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
371
385
  .catch(rethrowAs(endpoint, "update"));
372
386
  endpoint.uri = (_c = resultFunction.serviceConfig) === null || _c === void 0 ? void 0 : _c.uri;
373
387
  const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
@@ -414,7 +428,7 @@ class Fabricator {
414
428
  const op = await gcfV2.deleteFunction(fnName);
415
429
  const pollerOptions = Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `delete-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name });
416
430
  await poller.pollOperation(pollerOptions);
417
- })
431
+ }, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
418
432
  .catch(rethrowAs(endpoint, "delete"));
419
433
  }
420
434
  async setRunTraits(serviceName, endpoint) {
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.discover = void 0;
4
+ function discover() {
5
+ return {
6
+ baseImage: "us-docker.pkg.dev/firestack-build/test/run:latest",
7
+ environmentVariables: {
8
+ NODE_ENV: "PRODUCTION",
9
+ },
10
+ installCommand: "npm install",
11
+ buildCommand: "npm run build",
12
+ startCommand: "npm run start",
13
+ afterInstall: (b) => {
14
+ console.log("HOOK: AFTER INSTALL");
15
+ return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterInstall" });
16
+ },
17
+ afterBuild(b) {
18
+ console.log("HOOK: AFTER BUILD");
19
+ return Object.assign(Object.assign({}, b), { version: "v1alpha", notes: "afterBuild" });
20
+ },
21
+ };
22
+ }
23
+ exports.discover = discover;
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DockerDriver = exports.DockerfileBuilder = void 0;
4
+ const fs = require("node:fs");
5
+ const path = require("node:path");
6
+ const spawn = require("cross-spawn");
7
+ const hooks_1 = require("./hooks");
8
+ const ADAPTER_SCRIPTS_PATH = "./.firebase/adapters";
9
+ const DOCKER_STAGE_INSTALL = "installer";
10
+ const DOCKER_STAGE_BUILD = "builder";
11
+ class DockerfileBuilder {
12
+ constructor() {
13
+ this.dockerfile = "";
14
+ this.lastStage = "";
15
+ }
16
+ from(image, name) {
17
+ this.dockerfile += `FROM ${image}`;
18
+ if (name) {
19
+ this.dockerfile += ` AS ${name}`;
20
+ this.lastStage = name;
21
+ }
22
+ this.dockerfile += "\n";
23
+ return this;
24
+ }
25
+ fromLastStage(name) {
26
+ return this.from(this.lastStage, name);
27
+ }
28
+ tempFrom(image, name) {
29
+ this.dockerfile += `FROM ${image}`;
30
+ if (name) {
31
+ this.dockerfile += ` AS ${name}`;
32
+ }
33
+ this.dockerfile += "\n";
34
+ return this;
35
+ }
36
+ workdir(dir) {
37
+ this.dockerfile += `WORKDIR ${dir}\n`;
38
+ return this;
39
+ }
40
+ copyForFirebase(src, dest, from) {
41
+ if (from) {
42
+ this.dockerfile += `COPY --chown=firebase:firebase --from=${from} ${src} ${dest}\n`;
43
+ }
44
+ else {
45
+ this.dockerfile += `COPY --chown=firebase:firebase ${src} ${dest}\n`;
46
+ }
47
+ return this;
48
+ }
49
+ copyFrom(src, dest, from) {
50
+ this.dockerfile += `COPY --from=${from} ${src} ${dest}\n`;
51
+ return this;
52
+ }
53
+ run(cmd, mount) {
54
+ if (mount) {
55
+ this.dockerfile += `RUN --mount=${mount} ${cmd}\n`;
56
+ }
57
+ else {
58
+ this.dockerfile += `RUN ${cmd}\n`;
59
+ }
60
+ return this;
61
+ }
62
+ env(key, value) {
63
+ this.dockerfile += `ENV ${key}="${value}"\n`;
64
+ return this;
65
+ }
66
+ envs(envs) {
67
+ for (const [key, value] of Object.entries(envs)) {
68
+ this.env(key, value);
69
+ }
70
+ return this;
71
+ }
72
+ cmd(cmds) {
73
+ this.dockerfile += `CMD [${cmds.map((c) => `"${c}"`).join(", ")}]\n`;
74
+ return this;
75
+ }
76
+ user(user) {
77
+ this.dockerfile += `USER ${user}\n`;
78
+ return this;
79
+ }
80
+ toString() {
81
+ return this.dockerfile;
82
+ }
83
+ }
84
+ exports.DockerfileBuilder = DockerfileBuilder;
85
+ class DockerDriver {
86
+ constructor(spec) {
87
+ this.spec = spec;
88
+ this.dockerfileBuilder = new DockerfileBuilder();
89
+ this.dockerfileBuilder.from(spec.baseImage, "base").user("firebase");
90
+ }
91
+ execDockerPush(args) {
92
+ console.log(`executing docker build: ${args.join(" ")}`);
93
+ return spawn.sync("docker", ["push", ...args], {
94
+ stdio: ["pipe", "inherit", "inherit"],
95
+ });
96
+ }
97
+ execDockerBuild(args, contextDir) {
98
+ console.log(`executing docker build: ${args.join(" ")} ${contextDir}`);
99
+ console.log(this.dockerfileBuilder.toString());
100
+ return spawn.sync("docker", ["buildx", "build", ...args, "-f", "-", contextDir], {
101
+ env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
102
+ input: this.dockerfileBuilder.toString(),
103
+ stdio: ["pipe", "inherit", "inherit"],
104
+ });
105
+ }
106
+ buildStage(stage, contextDir, tag) {
107
+ console.log(`Building stage: ${stage}`);
108
+ const args = ["--target", stage];
109
+ if (tag) {
110
+ args.push("--tag", tag);
111
+ }
112
+ const ret = this.execDockerBuild(args, contextDir);
113
+ if (ret.error || ret.status !== 0) {
114
+ throw new Error(`Failed to execute stage ${stage}: error=${ret.error} status=${ret.status}`);
115
+ }
116
+ }
117
+ exportBundle(stage, contextDir) {
118
+ const exportStage = `${stage}-export`;
119
+ this.dockerfileBuilder
120
+ .tempFrom("scratch", exportStage)
121
+ .copyFrom(hooks_1.BUNDLE_PATH, "/bundle.json", stage);
122
+ const ret = this.execDockerBuild(["--target", exportStage, "--output", ".firebase/.output"], contextDir);
123
+ if (ret.error || ret.status !== 0) {
124
+ throw new Error(`Failed to export bundle ${stage}: error=${ret.error} status=${ret.status}`);
125
+ }
126
+ return JSON.parse(fs.readFileSync("./.firebase/.output/bundle.json", "utf8"));
127
+ }
128
+ install() {
129
+ this.dockerfileBuilder
130
+ .fromLastStage(DOCKER_STAGE_INSTALL)
131
+ .workdir("/home/firebase/app")
132
+ .envs(this.spec.environmentVariables || {})
133
+ .copyForFirebase("package.json", ".")
134
+ .run(this.spec.installCommand);
135
+ this.buildStage(DOCKER_STAGE_INSTALL, ".");
136
+ }
137
+ build() {
138
+ this.dockerfileBuilder
139
+ .fromLastStage(DOCKER_STAGE_BUILD)
140
+ .copyForFirebase(".", ".")
141
+ .run(this.spec.buildCommand);
142
+ this.buildStage(DOCKER_STAGE_BUILD, ".");
143
+ }
144
+ export(bundle) {
145
+ var _a;
146
+ const startCmd = (_a = bundle.server) === null || _a === void 0 ? void 0 : _a.start.cmd;
147
+ if (startCmd) {
148
+ const exportStage = "exporter";
149
+ this.dockerfileBuilder
150
+ .from(this.spec.baseImage, exportStage)
151
+ .workdir("/home/firebase/app")
152
+ .copyForFirebase("/home/firebase/app", ".", DOCKER_STAGE_BUILD)
153
+ .cmd(startCmd);
154
+ const imageName = `us-docker.pkg.dev/${process.env.PROJECT_ID}/test/demo-nodappe`;
155
+ this.buildStage(exportStage, ".", imageName);
156
+ const ret = this.execDockerPush([imageName]);
157
+ if (ret.error || ret.status !== 0) {
158
+ throw new Error(`Failed to push image ${imageName}: error=${ret.error} status=${ret.status}`);
159
+ }
160
+ }
161
+ }
162
+ execHook(bundle, hook) {
163
+ const hookScript = `hook-${Date.now()}.js`;
164
+ const hookScriptSrc = (0, hooks_1.genHookScript)(bundle, hook);
165
+ if (!fs.existsSync(ADAPTER_SCRIPTS_PATH)) {
166
+ fs.mkdirSync(ADAPTER_SCRIPTS_PATH, { recursive: true });
167
+ }
168
+ fs.writeFileSync(path.join(ADAPTER_SCRIPTS_PATH, hookScript), hookScriptSrc);
169
+ const hookStage = path.basename(hookScript, ".js");
170
+ this.dockerfileBuilder
171
+ .fromLastStage(hookStage)
172
+ .run(`NODE_PATH=./node_modules node /framework/adapters/${hookScript}`, `source=${ADAPTER_SCRIPTS_PATH},target=/framework/adapters`);
173
+ this.buildStage(hookStage, ".");
174
+ return this.exportBundle(hookStage, ".");
175
+ }
176
+ }
177
+ exports.DockerDriver = DockerDriver;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.genHookScript = exports.BUNDLE_PATH = void 0;
4
+ exports.BUNDLE_PATH = "/home/firebase/app/.firebase/.output/bundle.json";
5
+ function genHookScript(bundle, hook) {
6
+ let hookSrc = hook.toString().trimLeft();
7
+ if (!hookSrc.startsWith("(") && !hookSrc.startsWith("function ")) {
8
+ hookSrc = `function ${hookSrc}`;
9
+ }
10
+ return `
11
+ const fs = require("node:fs");
12
+ const path = require("node:path");
13
+
14
+ const bundleDir = path.dirname("${exports.BUNDLE_PATH}");
15
+ if (!fs.existsSync(bundleDir)) {
16
+ fs.mkdirSync(bundleDir, { recursive: true });
17
+ }
18
+ const bundle = (${hookSrc})(${JSON.stringify(bundle)});
19
+ fs.writeFileSync("${exports.BUNDLE_PATH}", JSON.stringify(bundle));
20
+ `;
21
+ }
22
+ exports.genHookScript = genHookScript;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getDriver = exports.SUPPORTED_MODES = void 0;
4
+ const local_1 = require("./local");
5
+ const docker_1 = require("./docker");
6
+ exports.SUPPORTED_MODES = ["local", "docker"];
7
+ function getDriver(mode, app) {
8
+ if (mode === "local") {
9
+ return new local_1.LocalDriver(app);
10
+ }
11
+ else if (mode === "docker") {
12
+ return new docker_1.DockerDriver(app);
13
+ }
14
+ throw new Error(`Unsupported mode ${mode}`);
15
+ }
16
+ exports.getDriver = getDriver;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LocalDriver = void 0;
4
+ const fs = require("node:fs");
5
+ const spawn = require("cross-spawn");
6
+ const hooks_1 = require("./hooks");
7
+ class LocalDriver {
8
+ constructor(spec) {
9
+ this.spec = spec;
10
+ }
11
+ execCmd(cmd, args) {
12
+ const ret = spawn.sync(cmd, args, {
13
+ env: Object.assign(Object.assign({}, process.env), this.spec.environmentVariables),
14
+ stdio: ["pipe", "inherit", "inherit"],
15
+ });
16
+ if (ret.error) {
17
+ throw ret.error;
18
+ }
19
+ }
20
+ install() {
21
+ const [cmd, ...args] = this.spec.installCommand.split(" ");
22
+ this.execCmd(cmd, args);
23
+ }
24
+ build() {
25
+ const [cmd, ...args] = this.spec.buildCommand.split(" ");
26
+ this.execCmd(cmd, args);
27
+ }
28
+ export(bundle) {
29
+ }
30
+ execHook(bundle, hook) {
31
+ const script = (0, hooks_1.genHookScript)(bundle, hook);
32
+ this.execCmd("node", ["-e", script]);
33
+ if (!fs.existsSync(hooks_1.BUNDLE_PATH)) {
34
+ console.warn(`Expected hook to generate app bundle at ${hooks_1.BUNDLE_PATH} but got nothing.`);
35
+ console.warn("Returning original bundle.");
36
+ return bundle;
37
+ }
38
+ const newBundle = JSON.parse(fs.readFileSync(hooks_1.BUNDLE_PATH, "utf8"));
39
+ return newBundle;
40
+ }
41
+ }
42
+ exports.LocalDriver = LocalDriver;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = void 0;
4
+ const driver_1 = require("./driver");
5
+ const discover_1 = require("./discover");
6
+ function compose(mode) {
7
+ let bundle = { version: "v1alpha" };
8
+ const spec = (0, discover_1.discover)();
9
+ const driver = (0, driver_1.getDriver)(mode, spec);
10
+ if (spec.startCommand) {
11
+ bundle.server = {
12
+ start: {
13
+ cmd: spec.startCommand.split(" "),
14
+ },
15
+ };
16
+ }
17
+ driver.install();
18
+ if (spec.afterInstall) {
19
+ bundle = driver.execHook(bundle, spec.afterInstall);
20
+ }
21
+ driver.build();
22
+ if (spec.afterBuild) {
23
+ bundle = driver.execHook(bundle, spec.afterBuild);
24
+ }
25
+ if (bundle.server) {
26
+ driver.export(bundle);
27
+ }
28
+ return bundle;
29
+ }
30
+ exports.compose = compose;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Driver = void 0;
4
+ class Driver {
5
+ constructor(spec) {
6
+ this.spec = spec;
7
+ }
8
+ install() {
9
+ throw new Error("install() not implemented");
10
+ }
11
+ build() {
12
+ throw new Error("build() not implemented");
13
+ }
14
+ export(bundle) {
15
+ throw new Error("export() not implemented");
16
+ }
17
+ execHook(bundle, hook) {
18
+ throw new Error("execHook() not implemented");
19
+ }
20
+ }
21
+ exports.Driver = Driver;
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
3
+ exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.getSecretVersions = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.ensureApi = exports.labels = exports.isFirebaseManaged = void 0;
4
4
  const utils = require("../utils");
5
5
  const poller = require("../operation-poller");
6
6
  const gcf = require("../gcp/cloudfunctions");
7
+ const ensureApiEnabled = require("../ensureApiEnabled");
7
8
  const secretManager_1 = require("../gcp/secretManager");
8
9
  const error_1 = require("../error");
9
10
  const utils_1 = require("../utils");
@@ -12,6 +13,7 @@ const env_1 = require("./env");
12
13
  const logger_1 = require("../logger");
13
14
  const api_1 = require("../api");
14
15
  const functional_1 = require("../functional");
16
+ const projectUtils_1 = require("../projectUtils");
15
17
  const FIREBASE_MANAGED = "firebase-managed";
16
18
  function isFirebaseManaged(secret) {
17
19
  return Object.keys(secret.labels || []).includes(FIREBASE_MANAGED);
@@ -27,6 +29,11 @@ function toUpperSnakeCase(key) {
27
29
  .replace(/([a-z])([A-Z])/g, "$1_$2")
28
30
  .toUpperCase();
29
31
  }
32
+ function ensureApi(options) {
33
+ const projectId = (0, projectUtils_1.needProjectId)(options);
34
+ return ensureApiEnabled.ensure(projectId, "secretmanager.googleapis.com", "runtimeconfig", true);
35
+ }
36
+ exports.ensureApi = ensureApi;
30
37
  async function ensureValidKey(key, options) {
31
38
  const transformedKey = toUpperSnakeCase(key);
32
39
  if (transformedKey !== key) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "12.3.0",
3
+ "version": "12.3.1",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {