firebase-tools 12.5.3 → 12.6.0

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.
package/lib/command.js CHANGED
@@ -83,10 +83,12 @@ class Command {
83
83
  runner(...args)
84
84
  .then(async (result) => {
85
85
  if ((0, utils_1.getInheritedOption)(options, "json")) {
86
- console.log(JSON.stringify({
87
- status: "success",
88
- result: result,
89
- }, null, 2));
86
+ await new Promise((resolve) => {
87
+ process.stdout.write(JSON.stringify({
88
+ status: "success",
89
+ result: result,
90
+ }, null, 2), resolve);
91
+ });
90
92
  }
91
93
  const duration = Math.floor((process.uptime() - start) * 1000);
92
94
  const trackSuccess = (0, track_1.trackGA4)("command_execution", {
@@ -111,10 +113,12 @@ class Command {
111
113
  })
112
114
  .catch(async (err) => {
113
115
  if ((0, utils_1.getInheritedOption)(options, "json")) {
114
- console.log(JSON.stringify({
115
- status: "error",
116
- error: err.message,
117
- }, null, 2));
116
+ await new Promise((resolve) => {
117
+ process.stdout.write(JSON.stringify({
118
+ status: "error",
119
+ error: err.message,
120
+ }, null, 2), resolve);
121
+ });
118
122
  }
119
123
  const duration = Math.floor((process.uptime() - start) * 1000);
120
124
  await (0, utils_1.withTimeout)(5000, Promise.all([
@@ -6,7 +6,9 @@ const Table = require("cli-table");
6
6
  const experiments = require("../experiments");
7
7
  const functional_1 = require("../functional");
8
8
  const logger_1 = require("../logger");
9
- exports.command = new command_1.Command("experiments:list").action(() => {
9
+ exports.command = new command_1.Command("experiments:list")
10
+ .description("list all experiments, along with a description of each experiment and whether it is currently enabled")
11
+ .action(() => {
10
12
  const table = new Table({
11
13
  head: ["Enabled", "Name", "Description"],
12
14
  style: { head: ["yellow"] },
@@ -95,17 +95,18 @@ class Fabricator {
95
95
  deployResults.push(result);
96
96
  };
97
97
  const upserts = [];
98
- const scraper = new sourceTokenScraper_1.SourceTokenScraper();
98
+ const scraperV1 = new sourceTokenScraper_1.SourceTokenScraper();
99
+ const scraperV2 = new sourceTokenScraper_1.SourceTokenScraper();
99
100
  for (const endpoint of changes.endpointsToCreate) {
100
101
  this.logOpStart("creating", endpoint);
101
- upserts.push(handle("create", endpoint, () => this.createEndpoint(endpoint, scraper)));
102
+ upserts.push(handle("create", endpoint, () => this.createEndpoint(endpoint, scraperV1, scraperV2)));
102
103
  }
103
104
  for (const endpoint of changes.endpointsToSkip) {
104
105
  utils.logSuccess(this.getLogSuccessMessage("skip", endpoint));
105
106
  }
106
107
  for (const update of changes.endpointsToUpdate) {
107
108
  this.logOpStart("updating", update.endpoint);
108
- upserts.push(handle("update", update.endpoint, () => this.updateEndpoint(update, scraper)));
109
+ upserts.push(handle("update", update.endpoint, () => this.updateEndpoint(update, scraperV1, scraperV2)));
109
110
  }
110
111
  await utils.allSettled(upserts);
111
112
  if (deployResults.find((r) => r.error)) {
@@ -126,31 +127,31 @@ class Fabricator {
126
127
  await utils.allSettled(deletes);
127
128
  return deployResults;
128
129
  }
129
- async createEndpoint(endpoint, scraper) {
130
+ async createEndpoint(endpoint, scraperV1, scraperV2) {
130
131
  endpoint.labels = Object.assign(Object.assign({}, endpoint.labels), deploymentTool.labels());
131
132
  if (endpoint.platform === "gcfv1") {
132
- await this.createV1Function(endpoint, scraper);
133
+ await this.createV1Function(endpoint, scraperV1);
133
134
  }
134
135
  else if (endpoint.platform === "gcfv2") {
135
- await this.createV2Function(endpoint);
136
+ await this.createV2Function(endpoint, scraperV2);
136
137
  }
137
138
  else {
138
139
  (0, functional_1.assertExhaustive)(endpoint.platform);
139
140
  }
140
141
  await this.setTrigger(endpoint);
141
142
  }
142
- async updateEndpoint(update, scraper) {
143
+ async updateEndpoint(update, scraperV1, scraperV2) {
143
144
  update.endpoint.labels = Object.assign(Object.assign({}, update.endpoint.labels), deploymentTool.labels());
144
145
  if (update.deleteAndRecreate) {
145
146
  await this.deleteEndpoint(update.deleteAndRecreate);
146
- await this.createEndpoint(update.endpoint, scraper);
147
+ await this.createEndpoint(update.endpoint, scraperV1, scraperV2);
147
148
  return;
148
149
  }
149
150
  if (update.endpoint.platform === "gcfv1") {
150
- await this.updateV1Function(update.endpoint, scraper);
151
+ await this.updateV1Function(update.endpoint, scraperV1);
151
152
  }
152
153
  else if (update.endpoint.platform === "gcfv2") {
153
- await this.updateV2Function(update.endpoint);
154
+ await this.updateV2Function(update.endpoint, scraperV2);
154
155
  }
155
156
  else {
156
157
  (0, functional_1.assertExhaustive)(update.endpoint.platform);
@@ -221,7 +222,7 @@ class Fabricator {
221
222
  .catch(rethrowAs(endpoint, "set invoker"));
222
223
  }
223
224
  }
224
- async createV2Function(endpoint) {
225
+ async createV2Function(endpoint, scraper) {
225
226
  var _a, _b, _c, _d, _e;
226
227
  const storageSource = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
227
228
  if (!storageSource) {
@@ -275,10 +276,12 @@ class Fabricator {
275
276
  while (!resultFunction) {
276
277
  resultFunction = await this.functionExecutor
277
278
  .run(async () => {
279
+ apiFunction.buildConfig.sourceToken = await scraper.getToken();
278
280
  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 }));
281
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `create-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
280
282
  })
281
283
  .catch(async (err) => {
284
+ scraper.abort();
282
285
  if (err.code === CLOUD_RUN_RESOURCE_EXHAUSTED_CODE) {
283
286
  await this.deleteV2Function(endpoint);
284
287
  return null;
@@ -366,7 +369,7 @@ class Fabricator {
366
369
  .catch(rethrowAs(endpoint, "set invoker"));
367
370
  }
368
371
  }
369
- async updateV2Function(endpoint) {
372
+ async updateV2Function(endpoint, scraper) {
370
373
  var _a, _b, _c, _d;
371
374
  const storageSource = (_a = this.sources[endpoint.codebase]) === null || _a === void 0 ? void 0 : _a.storage;
372
375
  if (!storageSource) {
@@ -379,10 +382,15 @@ class Fabricator {
379
382
  }
380
383
  const resultFunction = await this.functionExecutor
381
384
  .run(async () => {
385
+ apiFunction.buildConfig.sourceToken = await scraper.getToken();
382
386
  const op = await gcfV2.updateFunction(apiFunction);
383
- return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
387
+ return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.codebase}-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name, onPoll: scraper.poller }));
384
388
  }, { retryCodes: [...executor_1.DEFAULT_RETRY_CODES, CLOUD_RUN_RESOURCE_EXHAUSTED_CODE] })
385
- .catch(rethrowAs(endpoint, "update"));
389
+ .catch((err) => {
390
+ scraper.abort();
391
+ logger_1.logger.error(err.message);
392
+ throw new reporter.DeploymentError(endpoint, "update", err);
393
+ });
386
394
  endpoint.uri = (_c = resultFunction.serviceConfig) === null || _c === void 0 ? void 0 : _c.uri;
387
395
  const serviceName = (_d = resultFunction.serviceConfig) === null || _d === void 0 ? void 0 : _d.service;
388
396
  endpoint.runServiceId = utils.last(serviceName === null || serviceName === void 0 ? void 0 : serviceName.split("/"));
@@ -10,21 +10,30 @@ class SourceTokenScraper {
10
10
  this.promise = new Promise((resolve) => (this.resolve = resolve));
11
11
  this.fetchState = "NONE";
12
12
  }
13
+ abort() {
14
+ this.resolve({ aborted: true });
15
+ }
13
16
  async getToken() {
14
17
  if (this.fetchState === "NONE") {
15
18
  this.fetchState = "FETCHING";
16
19
  return undefined;
17
20
  }
18
21
  else if (this.fetchState === "FETCHING") {
19
- return this.promise;
22
+ const tokenResult = await this.promise;
23
+ if (tokenResult.aborted) {
24
+ this.promise = new Promise((resolve) => (this.resolve = resolve));
25
+ return undefined;
26
+ }
27
+ return tokenResult.token;
20
28
  }
21
29
  else if (this.fetchState === "VALID") {
30
+ const tokenResult = await this.promise;
22
31
  if (this.isTokenExpired()) {
23
32
  this.fetchState = "FETCHING";
24
33
  this.promise = new Promise((resolve) => (this.resolve = resolve));
25
34
  return undefined;
26
35
  }
27
- return this.promise;
36
+ return tokenResult.token;
28
37
  }
29
38
  else {
30
39
  (0, functional_1.assertExhaustive)(this.fetchState);
@@ -45,7 +54,10 @@ class SourceTokenScraper {
45
54
  if (((_a = op.metadata) === null || _a === void 0 ? void 0 : _a.sourceToken) || op.done) {
46
55
  const [, , , region] = ((_c = (_b = op.metadata) === null || _b === void 0 ? void 0 : _b.target) === null || _c === void 0 ? void 0 : _c.split("/")) || [];
47
56
  logger_1.logger.debug(`Got source token ${(_d = op.metadata) === null || _d === void 0 ? void 0 : _d.sourceToken} for region ${region}`);
48
- this.resolve((_e = op.metadata) === null || _e === void 0 ? void 0 : _e.sourceToken);
57
+ this.resolve({
58
+ token: (_e = op.metadata) === null || _e === void 0 ? void 0 : _e.sourceToken,
59
+ aborted: false,
60
+ });
49
61
  this.fetchState = "VALID";
50
62
  this.expiry = Date.now() + this.tokenValidDurationMs;
51
63
  }
@@ -620,13 +620,15 @@ async function exportEmulatorData(exportPath, options, initiatedBy) {
620
620
  fs.mkdirSync(exportAbsPath);
621
621
  }
622
622
  const existingMetadata = hubExport_1.HubExport.readMetadata(exportAbsPath);
623
- if (existingMetadata && !(options.force || options.exportOnExit)) {
623
+ const isExportDirEmpty = fs.readdirSync(exportAbsPath).length === 0;
624
+ if ((existingMetadata || !isExportDirEmpty) && !(options.force || options.exportOnExit)) {
624
625
  if (options.noninteractive) {
625
626
  throw new error_1.FirebaseError("Export already exists in the target directory, re-run with --force to overwrite.", { exit: 1 });
626
627
  }
627
- const prompt = await (0, prompt_1.promptOnce)({
628
- type: "confirm",
629
- message: `The directory ${exportAbsPath} already contains export data. Exporting again to the same directory will overwrite all data. Do you want to continue?`,
628
+ const prompt = await (0, prompt_1.confirm)({
629
+ message: `The directory ${exportAbsPath} is not empty. Existing files in this directory will be overwritten. Do you want to continue?`,
630
+ nonInteractive: options.nonInteractive,
631
+ force: options.force,
630
632
  default: false,
631
633
  });
632
634
  if (!prompt) {
@@ -23,9 +23,9 @@ const EMULATOR_UPDATE_DETAILS = {
23
23
  expectedChecksum: "2fd771101c0e1f7898c04c9204f2ce63",
24
24
  },
25
25
  firestore: {
26
- version: "1.18.1",
27
- expectedSize: 64866257,
28
- expectedChecksum: "743211a3e33217fe71dc20aff1fa26a5",
26
+ version: "1.18.2",
27
+ expectedSize: 63929486,
28
+ expectedChecksum: "7b066cd684baf9bcd4a56a258be344a5",
29
29
  },
30
30
  storage: {
31
31
  version: "1.1.3",
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveHostAndAssignPorts = exports.waitForPortClosed = exports.checkListenable = void 0;
3
+ exports.resolveHostAndAssignPorts = exports.waitForPortUsed = exports.checkListenable = void 0;
4
4
  const clc = require("colorette");
5
5
  const tcpport = require("tcp-port-used");
6
6
  const node_net_1 = require("node:net");
@@ -131,7 +131,7 @@ async function checkListenable(arg1, port) {
131
131
  });
132
132
  }
133
133
  exports.checkListenable = checkListenable;
134
- async function waitForPortClosed(port, host) {
134
+ async function waitForPortUsed(port, host) {
135
135
  const interval = 250;
136
136
  const timeout = 60000;
137
137
  try {
@@ -141,7 +141,7 @@ async function waitForPortClosed(port, host) {
141
141
  throw new error_1.FirebaseError(`TIMEOUT: Port ${port} on ${host} was not active within ${timeout}ms`);
142
142
  }
143
143
  }
144
- exports.waitForPortClosed = waitForPortClosed;
144
+ exports.waitForPortUsed = waitForPortUsed;
145
145
  const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
146
146
  database: true,
147
147
  firestore: true,
@@ -18,7 +18,7 @@ class EmulatorRegistry {
18
18
  await instance.start();
19
19
  if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
20
20
  const info = instance.getInfo();
21
- await portUtils.waitForPortClosed(info.port, (0, utils_1.connectableHostname)(info.host));
21
+ await portUtils.waitForPortUsed(info.port, (0, utils_1.connectableHostname)(info.host));
22
22
  }
23
23
  }
24
24
  static async stop(name) {
@@ -27,8 +27,13 @@ class EmulatorRegistry {
27
27
  if (!instance) {
28
28
  return;
29
29
  }
30
- await instance.stop();
31
- this.clear(instance.getName());
30
+ try {
31
+ await instance.stop();
32
+ this.clear(instance.getName());
33
+ }
34
+ catch (e) {
35
+ emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("WARN", name, `Error stopping ${constants_1.Constants.description(name)}`);
36
+ }
32
37
  }
33
38
  static async stopAll() {
34
39
  const stopPriority = {
@@ -49,12 +54,7 @@ class EmulatorRegistry {
49
54
  return stopPriority[a] - stopPriority[b];
50
55
  });
51
56
  for (const name of emulatorsToStop) {
52
- try {
53
- await this.stop(name);
54
- }
55
- catch (e) {
56
- emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("WARN", name, `Error stopping ${constants_1.Constants.description(name)}`);
57
- }
57
+ await this.stop(name);
58
58
  }
59
59
  }
60
60
  static isRunning(emulator) {
@@ -387,7 +387,7 @@ async function getConfig(dir) {
387
387
  if ((0, semver_1.gte)(version, "12.0.0")) {
388
388
  const { default: loadConfig } = (0, utils_1.relativeRequire)(dir, "next/dist/server/config");
389
389
  const { PHASE_PRODUCTION_BUILD } = (0, utils_1.relativeRequire)(dir, "next/constants");
390
- config = await loadConfig(PHASE_PRODUCTION_BUILD, dir, null);
390
+ config = await loadConfig(PHASE_PRODUCTION_BUILD, dir);
391
391
  }
392
392
  else {
393
393
  try {
@@ -13,7 +13,8 @@ const fsutils_1 = require("../fsutils");
13
13
  const url_1 = require("url");
14
14
  const constants_1 = require("./constants");
15
15
  const { dynamicImport } = require(true && "../dynamicImport");
16
- const NPM_ROOT_TIMEOUT_MILLIES = 2000;
16
+ const NPM_ROOT_TIMEOUT_MILLIES = 5000;
17
+ const NPM_ROOT_MEMO = new Map();
17
18
  function isUrl(url) {
18
19
  return /^https?:\/\//.test(url);
19
20
  }
@@ -139,8 +140,16 @@ function scanDependencyTree(searchingFor, dependencies = {}) {
139
140
  }
140
141
  function getNpmRoot(cwd) {
141
142
  var _a;
142
- return (_a = (0, cross_spawn_1.sync)("npm", ["root"], { cwd, timeout: NPM_ROOT_TIMEOUT_MILLIES })
143
+ let npmRoot = NPM_ROOT_MEMO.get(cwd);
144
+ if (npmRoot)
145
+ return npmRoot;
146
+ npmRoot = (_a = (0, cross_spawn_1.sync)("npm", ["root"], {
147
+ cwd,
148
+ timeout: NPM_ROOT_TIMEOUT_MILLIES,
149
+ })
143
150
  .stdout) === null || _a === void 0 ? void 0 : _a.toString().trim();
151
+ NPM_ROOT_MEMO.set(cwd, npmRoot);
152
+ return npmRoot;
144
153
  }
145
154
  exports.getNpmRoot = getNpmRoot;
146
155
  function getNodeModuleBin(name, cwd) {
@@ -91,6 +91,7 @@ async function createFunction(cloudFunction) {
91
91
  const components = cloudFunction.name.split("/");
92
92
  const functionId = components.splice(-1, 1)[0];
93
93
  cloudFunction.buildConfig.environmentVariables = Object.assign(Object.assign({}, cloudFunction.buildConfig.environmentVariables), { GOOGLE_NODE_RUN_SCRIPTS: "" });
94
+ cloudFunction.serviceConfig.environmentVariables = Object.assign(Object.assign({}, cloudFunction.serviceConfig.environmentVariables), { FUNCTION_TARGET: functionId });
94
95
  try {
95
96
  const res = await client.post(components.join("/"), cloudFunction, { queryParams: { functionId } });
96
97
  return res.body;
@@ -143,9 +144,12 @@ async function listFunctionsInternal(projectId, region) {
143
144
  }
144
145
  }
145
146
  async function updateFunction(cloudFunction) {
147
+ const components = cloudFunction.name.split("/");
148
+ const functionId = components.splice(-1, 1)[0];
146
149
  const fieldMasks = proto.fieldMasks(cloudFunction, "labels", "serviceConfig.environmentVariables", "serviceConfig.secretEnvironmentVariables");
147
150
  cloudFunction.buildConfig.environmentVariables = Object.assign(Object.assign({}, cloudFunction.buildConfig.environmentVariables), { GOOGLE_NODE_RUN_SCRIPTS: "" });
148
151
  fieldMasks.push("buildConfig.buildEnvironmentVariables");
152
+ cloudFunction.serviceConfig.environmentVariables = Object.assign(Object.assign({}, cloudFunction.serviceConfig.environmentVariables), { FUNCTION_TARGET: functionId });
149
153
  try {
150
154
  const queryParams = {
151
155
  updateMask: fieldMasks.join(","),
@@ -211,6 +215,7 @@ function functionFromEndpoint(endpoint) {
211
215
  if (backend.isEventTriggered(endpoint)) {
212
216
  gcfFunction.eventTrigger = {
213
217
  eventType: endpoint.eventTrigger.eventType,
218
+ retryPolicy: "RETRY_POLICY_UNSPECIFIED",
214
219
  };
215
220
  if (gcfFunction.eventTrigger.eventType === v2_1.PUBSUB_PUBLISH_EVENT) {
216
221
  if (!((_b = endpoint.eventTrigger.eventFilters) === null || _b === void 0 ? void 0 : _b.topic)) {
@@ -240,9 +245,9 @@ function functionFromEndpoint(endpoint) {
240
245
  }
241
246
  proto.renameIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "triggerRegion", "region");
242
247
  proto.copyIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "channel");
243
- if (endpoint.eventTrigger.retry) {
244
- logger_1.logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id);
245
- }
248
+ endpoint.eventTrigger.retry
249
+ ? (gcfFunction.eventTrigger.retryPolicy = "RETRY_POLICY_RETRY")
250
+ : (gcfFunction.eventTrigger.retryPolicy = "RETRY_POLICY_DO_NOT_RETRY");
246
251
  gcfFunction.serviceConfig.environmentVariables = Object.assign(Object.assign({}, gcfFunction.serviceConfig.environmentVariables), { FUNCTION_SIGNATURE_TYPE: "cloudevent" });
247
252
  }
248
253
  else if (backend.isScheduleTriggered(endpoint)) {
@@ -315,7 +320,7 @@ function endpointFromFunction(gcfFunction) {
315
320
  trigger = {
316
321
  eventTrigger: {
317
322
  eventType: gcfFunction.eventTrigger.eventType,
318
- retry: false,
323
+ retry: gcfFunction.eventTrigger.retryPolicy === "RETRY_POLICY_RETRY" ? true : false,
319
324
  },
320
325
  };
321
326
  if (Object.keys(eventFilters).length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "12.5.3",
3
+ "version": "12.6.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -19,7 +19,8 @@
19
19
  },
20
20
  "pkg": {
21
21
  "scripts": [
22
- "node_modules/npm/src/**/*.js"
22
+ "node_modules/npm/lib/*.js",
23
+ "node_modules/npm/lib/**/*.js"
23
24
  ],
24
25
  "assets": [
25
26
  "node_modules/.bin/**",