firebase-tools 9.21.0 → 9.22.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/lib/api.js +2 -0
  3. package/lib/apiv2.js +3 -2
  4. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +1 -1
  7. package/lib/commands/ext-dev-deprecate.js +63 -0
  8. package/lib/commands/ext-dev-undeprecate.js +56 -0
  9. package/lib/commands/ext-export.js +44 -0
  10. package/lib/commands/ext-install.js +1 -1
  11. package/lib/commands/ext-update.js +1 -1
  12. package/lib/commands/functions-delete.js +2 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/config.js +3 -2
  16. package/lib/deploy/extensions/args.js +2 -0
  17. package/lib/deploy/extensions/deploy.js +49 -0
  18. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  19. package/lib/deploy/extensions/errors.js +31 -0
  20. package/lib/deploy/extensions/index.js +8 -0
  21. package/lib/deploy/extensions/planner.js +95 -0
  22. package/lib/deploy/extensions/prepare.js +103 -0
  23. package/lib/deploy/extensions/release.js +43 -0
  24. package/lib/deploy/extensions/secrets.js +150 -0
  25. package/lib/deploy/extensions/tasks.js +98 -0
  26. package/lib/deploy/extensions/validate.js +17 -0
  27. package/lib/deploy/functions/backend.js +8 -1
  28. package/lib/deploy/functions/containerCleaner.js +77 -21
  29. package/lib/deploy/functions/release/fabricator.js +69 -9
  30. package/lib/deploy/functions/release/index.js +5 -1
  31. package/lib/deploy/functions/release/planner.js +3 -0
  32. package/lib/deploy/functions/release/reporter.js +4 -1
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  34. package/lib/deploy/functions/runtimes/node/parseTriggers.js +7 -2
  35. package/lib/deploy/index.js +1 -0
  36. package/lib/emulator/functionsEmulator.js +3 -1
  37. package/lib/extensions/askUserForParam.js +14 -6
  38. package/lib/extensions/checkProjectBilling.js +7 -7
  39. package/lib/extensions/export.js +107 -0
  40. package/lib/extensions/extensionsApi.js +103 -21
  41. package/lib/extensions/extensionsHelper.js +4 -1
  42. package/lib/extensions/listExtensions.js +16 -11
  43. package/lib/extensions/paramHelper.js +6 -4
  44. package/lib/extensions/provisioningHelper.js +16 -3
  45. package/lib/extensions/refs.js +9 -1
  46. package/lib/extensions/secretsUtils.js +10 -9
  47. package/lib/extensions/updateHelper.js +12 -2
  48. package/lib/extensions/versionHelper.js +14 -0
  49. package/lib/extensions/warnings.js +33 -1
  50. package/lib/gcp/artifactregistry.js +16 -0
  51. package/lib/gcp/cloudfunctions.js +25 -7
  52. package/lib/gcp/cloudfunctionsv2.js +10 -2
  53. package/lib/gcp/cloudtasks.js +143 -0
  54. package/lib/gcp/docker.js +7 -1
  55. package/lib/gcp/proto.js +2 -2
  56. package/lib/gcp/secretManager.js +27 -6
  57. package/lib/previews.js +1 -1
  58. package/package.json +2 -1
  59. package/schema/firebase-config.json +9 -0
@@ -10,6 +10,7 @@ const runtimes_1 = require("../runtimes");
10
10
  const api_1 = require("../../../api");
11
11
  const logger_1 = require("../../../logger");
12
12
  const backend = require("../backend");
13
+ const cloudtasks = require("../../../gcp/cloudtasks");
13
14
  const deploymentTool = require("../../../deploymentTool");
14
15
  const gcf = require("../../../gcp/cloudfunctions");
15
16
  const gcfV2 = require("../../../gcp/cloudfunctionsv2");
@@ -24,11 +25,13 @@ const gcfV1PollerOptions = {
24
25
  apiOrigin: api_1.functionsOrigin,
25
26
  apiVersion: gcf.API_VERSION,
26
27
  masterTimeout: 25 * 60 * 1000,
28
+ maxBackoff: 10000,
27
29
  };
28
30
  const gcfV2PollerOptions = {
29
31
  apiOrigin: api_1.functionsV2Origin,
30
32
  apiVersion: gcfV2.API_VERSION,
31
33
  masterTimeout: 25 * 60 * 1000,
34
+ maxBackoff: 10000,
32
35
  };
33
36
  const DEFAULT_GCFV2_CONCURRENCY = 80;
34
37
  const rethrowAs = (endpoint, op) => (err) => {
@@ -172,6 +175,16 @@ class Fabricator {
172
175
  .catch(rethrowAs(endpoint, "set invoker"));
173
176
  }
174
177
  }
178
+ else if (backend.isTaskQueueTriggered(endpoint)) {
179
+ const invoker = endpoint.taskQueueTrigger.invoker;
180
+ if (invoker && !invoker.includes("private")) {
181
+ await this.executor
182
+ .run(async () => {
183
+ await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
184
+ })
185
+ .catch(rethrowAs(endpoint, "set invoker"));
186
+ }
187
+ }
175
188
  }
176
189
  async createV2Function(endpoint) {
177
190
  var _a;
@@ -214,6 +227,16 @@ class Fabricator {
214
227
  .catch(rethrowAs(endpoint, "set invoker"));
215
228
  }
216
229
  }
230
+ else if (backend.isTaskQueueTriggered(endpoint)) {
231
+ const invoker = endpoint.taskQueueTrigger.invoker;
232
+ if (invoker && !invoker.includes("private")) {
233
+ await this.executor
234
+ .run(async () => {
235
+ await gcf.setInvokerCreate(endpoint.project, backend.functionName(endpoint), invoker);
236
+ })
237
+ .catch(rethrowAs(endpoint, "set invoker"));
238
+ }
239
+ }
217
240
  await this.setConcurrency(endpoint, serviceName, endpoint.concurrency || DEFAULT_GCFV2_CONCURRENCY);
218
241
  }
219
242
  async updateV1Function(endpoint, scraper) {
@@ -231,12 +254,16 @@ class Fabricator {
231
254
  })
232
255
  .catch(rethrowAs(endpoint, "update"));
233
256
  endpoint.uri = (_a = resultFunction === null || resultFunction === void 0 ? void 0 : resultFunction.httpsTrigger) === null || _a === void 0 ? void 0 : _a.url;
234
- if (backend.isHttpsTriggered(endpoint) && endpoint.httpsTrigger.invoker) {
257
+ let invoker;
258
+ if (backend.isHttpsTriggered(endpoint)) {
259
+ invoker = endpoint.httpsTrigger.invoker;
260
+ }
261
+ else if (backend.isTaskQueueTriggered(endpoint)) {
262
+ invoker = endpoint.taskQueueTrigger.invoker;
263
+ }
264
+ if (invoker) {
235
265
  await this.executor
236
- .run(async () => {
237
- await gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), endpoint.httpsTrigger.invoker);
238
- return;
239
- })
266
+ .run(() => gcf.setInvokerUpdate(endpoint.project, backend.functionName(endpoint), invoker))
240
267
  .catch(rethrowAs(endpoint, "set invoker"));
241
268
  }
242
269
  }
@@ -250,17 +277,24 @@ class Fabricator {
250
277
  if ((_a = apiFunction.eventTrigger) === null || _a === void 0 ? void 0 : _a.pubsubTopic) {
251
278
  delete apiFunction.eventTrigger.pubsubTopic;
252
279
  }
253
- const resultFunction = (await this.functionExecutor
280
+ const resultFunction = await this.functionExecutor
254
281
  .run(async () => {
255
282
  const op = await gcfV2.updateFunction(apiFunction);
256
283
  return await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { pollerName: `update-${endpoint.region}-${endpoint.id}`, operationResourceName: op.name }));
257
284
  })
258
- .catch(rethrowAs(endpoint, "update")));
285
+ .catch(rethrowAs(endpoint, "update"));
259
286
  endpoint.uri = resultFunction.serviceConfig.uri;
260
287
  const serviceName = resultFunction.serviceConfig.service;
261
- if (backend.isHttpsTriggered(endpoint) && endpoint.httpsTrigger.invoker) {
288
+ let invoker;
289
+ if (backend.isHttpsTriggered(endpoint)) {
290
+ invoker = endpoint.httpsTrigger.invoker;
291
+ }
292
+ else if (backend.isTaskQueueTriggered(endpoint)) {
293
+ invoker = endpoint.taskQueueTrigger.invoker;
294
+ }
295
+ if (invoker) {
262
296
  await this.executor
263
- .run(() => run.setInvokerUpdate(endpoint.project, serviceName, endpoint.httpsTrigger.invoker))
297
+ .run(() => run.setInvokerUpdate(endpoint.project, serviceName, invoker))
264
298
  .catch(rethrowAs(endpoint, "set invoker"));
265
299
  }
266
300
  if (endpoint.concurrency) {
@@ -314,6 +348,9 @@ class Fabricator {
314
348
  }
315
349
  functional_1.assertExhaustive(endpoint.platform);
316
350
  }
351
+ else if (backend.isTaskQueueTriggered(endpoint)) {
352
+ await this.upsertTaskQueue(endpoint);
353
+ }
317
354
  }
318
355
  async deleteTrigger(endpoint) {
319
356
  if (backend.isScheduleTriggered(endpoint)) {
@@ -327,6 +364,9 @@ class Fabricator {
327
364
  }
328
365
  functional_1.assertExhaustive(endpoint.platform);
329
366
  }
367
+ else if (backend.isTaskQueueTriggered(endpoint)) {
368
+ await this.disableTaskQueue(endpoint);
369
+ }
330
370
  }
331
371
  async upsertScheduleV1(endpoint) {
332
372
  const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
@@ -337,6 +377,17 @@ class Fabricator {
337
377
  upsertScheduleV2(endpoint) {
338
378
  return Promise.reject(new reporter.DeploymentError(endpoint, "upsert schedule", new Error("Not implemented")));
339
379
  }
380
+ async upsertTaskQueue(endpoint) {
381
+ const queue = cloudtasks.queueFromEndpoint(endpoint);
382
+ await this.executor
383
+ .run(() => cloudtasks.upsertQueue(queue))
384
+ .catch(rethrowAs(endpoint, "upsert task queue"));
385
+ if (endpoint.taskQueueTrigger.invoker) {
386
+ await this.executor
387
+ .run(() => cloudtasks.setEnqueuer(queue.name, endpoint.taskQueueTrigger.invoker))
388
+ .catch(rethrowAs(endpoint, "set invoker"));
389
+ }
390
+ }
340
391
  async deleteScheduleV1(endpoint) {
341
392
  const job = scheduler.jobFromEndpoint(endpoint, this.appEngineLocation);
342
393
  await this.executor
@@ -349,6 +400,15 @@ class Fabricator {
349
400
  deleteScheduleV2(endpoint) {
350
401
  return Promise.reject(new reporter.DeploymentError(endpoint, "delete schedule", new Error("Not implemented")));
351
402
  }
403
+ async disableTaskQueue(endpoint) {
404
+ const update = {
405
+ name: cloudtasks.queueNameForEndpoint(endpoint),
406
+ state: "DISABLED",
407
+ };
408
+ await this.executor
409
+ .run(() => cloudtasks.updateQueue(update))
410
+ .catch(rethrowAs(endpoint, "disable task queue"));
411
+ }
352
412
  logOpStart(op, endpoint) {
353
413
  const runtime = runtimes_1.getHumanFriendlyRuntimeName(endpoint.runtime);
354
414
  const label = helper.getFunctionLabel(endpoint);
@@ -45,7 +45,11 @@ async function release(context, options, payload) {
45
45
  await reporter.logAndTrackDeployStats(summary);
46
46
  reporter.printErrors(summary);
47
47
  printTriggerUrls(payload.functions.backend);
48
- await containerCleaner.cleanupBuildImages(backend.allEndpoints(payload.functions.backend));
48
+ const haveEndpoints = backend.allEndpoints(payload.functions.backend);
49
+ const deletedEndpoints = Object.values(plan)
50
+ .map((r) => r.endpointsToDelete)
51
+ .reduce(functional_1.reduceFlat, []);
52
+ await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints);
49
53
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
50
54
  if (allErrors.length) {
51
55
  const opts = allErrors.length == 1 ? { original: allErrors[0] } : { children: allErrors };
@@ -138,6 +138,9 @@ function checkForIllegalUpdate(want, have) {
138
138
  else if (backend.isScheduleTriggered(e)) {
139
139
  return "a scheduled";
140
140
  }
141
+ else if (backend.isTaskQueueTriggered(e)) {
142
+ return "a task queue";
143
+ }
141
144
  throw Error("Functions release planner is not able to handle an unknown trigger type");
142
145
  };
143
146
  const wantType = triggerType(want);
@@ -135,7 +135,7 @@ function printQuotaErrors(results) {
135
135
  }
136
136
  function printAbortedErrors(results) {
137
137
  const aborted = results.filter((r) => r.error instanceof AbortedDeploymentError);
138
- if (!aborted) {
138
+ if (!aborted.length) {
139
139
  return;
140
140
  }
141
141
  logger_1.logger.info("");
@@ -151,6 +151,9 @@ function triggerTag(endpoint) {
151
151
  if (backend.isScheduleTriggered(endpoint)) {
152
152
  return `${prefix}.scheduled`;
153
153
  }
154
+ if (backend.isTaskQueueTriggered(endpoint)) {
155
+ return `${prefix}.taskQueue`;
156
+ }
154
157
  if (backend.isHttpsTriggered(endpoint)) {
155
158
  if ((_a = endpoint.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
156
159
  return `${prefix}.callable`;
@@ -59,6 +59,7 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
59
59
  httpsTrigger: "object",
60
60
  eventTrigger: "object",
61
61
  scheduleTrigger: "object",
62
+ taskQueueTrigger: "object",
62
63
  });
63
64
  let triggerCount = 0;
64
65
  if (ep.httpsTrigger) {
@@ -70,6 +71,9 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
70
71
  if (ep.scheduleTrigger) {
71
72
  triggerCount++;
72
73
  }
74
+ if (ep.taskQueueTrigger) {
75
+ triggerCount++;
76
+ }
73
77
  if (!triggerCount) {
74
78
  throw new error_1.FirebaseError("Expected trigger in endpoint" + id);
75
79
  }
@@ -111,6 +115,30 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
111
115
  });
112
116
  triggered = { scheduleTrigger: ep.scheduleTrigger };
113
117
  }
118
+ else if (backend.isTaskQueueTriggered(ep)) {
119
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger", ep.taskQueueTrigger, {
120
+ rateLimits: "object",
121
+ retryConfig: "object",
122
+ invoker: "array",
123
+ });
124
+ if (ep.taskQueueTrigger.rateLimits) {
125
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger.rateLimits", ep.taskQueueTrigger.rateLimits, {
126
+ maxBurstSize: "number",
127
+ maxConcurrentDispatches: "number",
128
+ maxDispatchesPerSecond: "number",
129
+ });
130
+ }
131
+ if (ep.taskQueueTrigger.retryConfig) {
132
+ parsing_1.assertKeyTypes(prefix + ".taskQueueTrigger.retryConfig", ep.taskQueueTrigger.retryConfig, {
133
+ maxAttempts: "number",
134
+ maxRetryDuration: "string",
135
+ minBackoff: "string",
136
+ maxBackoff: "string",
137
+ maxDoublings: "number",
138
+ });
139
+ }
140
+ triggered = { taskQueueTrigger: ep.taskQueueTrigger };
141
+ }
114
142
  else {
115
143
  throw new error_1.FirebaseError(`Do not recognize trigger type for endpoint ${id}. Try upgrading ` +
116
144
  "firebase-tools with npm install -g firebase-tools@latest");
@@ -68,10 +68,15 @@ function addResourcesToBackend(projectId, runtime, annotation, want) {
68
68
  Object.freeze(annotation);
69
69
  for (const region of annotation.regions || [api.functionsDefaultRegion]) {
70
70
  let triggered;
71
- if (!!annotation.httpsTrigger == !!annotation.eventTrigger) {
71
+ const triggerCount = +!!annotation.httpsTrigger + +!!annotation.eventTrigger + +!!annotation.taskQueueTrigger;
72
+ if (triggerCount != 1) {
72
73
  throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
73
74
  }
74
- if (annotation.httpsTrigger) {
75
+ if (annotation.taskQueueTrigger) {
76
+ triggered = { taskQueueTrigger: annotation.taskQueueTrigger };
77
+ want.requiredAPIs["cloudtasks"] = "cloudtasks.googleapis.com";
78
+ }
79
+ else if (annotation.httpsTrigger) {
75
80
  const trigger = {};
76
81
  if (annotation.failurePolicy) {
77
82
  logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
@@ -15,6 +15,7 @@ var TARGETS = {
15
15
  functions: require("./functions"),
16
16
  storage: require("./storage"),
17
17
  remoteconfig: require("./remoteconfig"),
18
+ extensions: require("./extensions"),
18
19
  };
19
20
  var _noop = function () {
20
21
  return Promise.resolve();
@@ -526,6 +526,8 @@ class FunctionsEmulator {
526
526
  const envs = {};
527
527
  envs.FUNCTIONS_EMULATOR = "true";
528
528
  envs.TZ = "UTC";
529
+ envs.FIREBASE_DEBUG_MODE = "true";
530
+ envs.FIREBASE_DEBUG_FEATURES = JSON.stringify({ skipTokenVerification: true });
529
531
  const firestoreEmulator = this.getEmulatorInfo(types_1.Emulators.FIRESTORE);
530
532
  if (firestoreEmulator != null) {
531
533
  envs[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = functionsEmulatorShared_1.formatHost(firestoreEmulator);
@@ -730,7 +732,7 @@ class FunctionsEmulator {
730
732
  const reqBody = req.rawBody;
731
733
  const isCallable = trigger.labels && trigger.labels["deployment-callable"] === "true";
732
734
  const authHeader = req.header("Authorization");
733
- if (authHeader && isCallable) {
735
+ if (authHeader && isCallable && trigger.platform !== "gcfv2") {
734
736
  const token = this.tokenFromAuthHeader(authHeader);
735
737
  if (token) {
736
738
  const contextAuth = {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ask = exports.getInquirerDefault = exports.askForParam = exports.checkResponse = void 0;
3
+ exports.ask = exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.checkResponse = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const marked = require("marked");
@@ -148,17 +148,25 @@ async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
148
148
  return paramSpec.default || "";
149
149
  }
150
150
  }
151
- async function promptCreateSecret(projectId, instanceId, paramSpec) {
152
- const secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
151
+ async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
152
+ const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
153
153
  const secretValue = await prompt_1.promptOnce({
154
154
  name: paramSpec.param,
155
155
  type: "password",
156
156
  default: paramSpec.default,
157
- message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${secretName} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
157
+ message: `This secret will be stored in Cloud Secret Manager (https://cloud.google.com/secret-manager/pricing) as ${name} and managed by Firebase Extensions (Firebase Extensions Service Agent will be granted Secret Admin role on this secret).\nEnter a value for ${paramSpec.label.trim()}:`,
158
158
  });
159
- const secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
160
- return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
159
+ if (secretValue === "" && paramSpec.required) {
160
+ logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
161
+ return await promptCreateSecret(projectId, instanceId, paramSpec, name);
162
+ }
163
+ else if (secretValue !== "") {
164
+ const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
165
+ return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
166
+ }
167
+ return secretValue;
161
168
  }
169
+ exports.promptCreateSecret = promptCreateSecret;
162
170
  async function generateSecretName(projectId, instanceId, paramName) {
163
171
  let secretName = `ext-${instanceId}-${paramName}`;
164
172
  while (await secretManagerApi.secretExists(projectId, secretName)) {
@@ -33,13 +33,13 @@ async function openBillingAccount(projectId, url, open) {
33
33
  });
34
34
  return cloudbilling.checkBillingEnabled(projectId);
35
35
  }
36
- async function chooseBillingAccount(projectId, extensionName, accounts) {
36
+ async function chooseBillingAccount(projectId, accounts) {
37
37
  const choices = accounts.map((m) => m.displayName);
38
38
  choices.push(ADD_BILLING_ACCOUNT);
39
39
  const answer = await prompt.promptOnce({
40
40
  name: "billing",
41
41
  type: "list",
42
- message: `The extension ${clc.underline(extensionName)} requires your project to be upgraded to the Blaze plan. You have access to the following billing accounts.
42
+ message: `Extensions require your project to be upgraded to the Blaze plan. You have access to the following billing accounts.
43
43
  Please select the one that you would like to associate with this project:`,
44
44
  choices: choices,
45
45
  });
@@ -54,10 +54,10 @@ Please select the one that you would like to associate with this project:`,
54
54
  }
55
55
  return logBillingStatus(billingEnabled, projectId);
56
56
  }
57
- async function setUpBillingAccount(projectId, extensionName) {
57
+ async function setUpBillingAccount(projectId) {
58
58
  const billingURL = `https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`;
59
59
  logger_1.logger.info();
60
- logger_1.logger.info(`The extension ${clc.bold(extensionName)} requires your project to be upgraded to the Blaze plan. Please visit the following link to add a billing account:`);
60
+ logger_1.logger.info(`Extension require your project to be upgraded to the Blaze plan. Please visit the following link to add a billing account:`);
61
61
  logger_1.logger.info();
62
62
  logger_1.logger.info(clc.bold.underline(billingURL));
63
63
  logger_1.logger.info();
@@ -70,13 +70,13 @@ async function setUpBillingAccount(projectId, extensionName) {
70
70
  const billingEnabled = await openBillingAccount(projectId, billingURL, open);
71
71
  return logBillingStatus(billingEnabled, projectId);
72
72
  }
73
- async function enableBilling(projectId, extensionName) {
73
+ async function enableBilling(projectId) {
74
74
  const billingAccounts = await cloudbilling.listBillingAccounts();
75
75
  if (billingAccounts) {
76
76
  const accounts = billingAccounts.filter((account) => account.open);
77
77
  return accounts.length > 0
78
- ? chooseBillingAccount(projectId, extensionName, accounts)
79
- : setUpBillingAccount(projectId, extensionName);
78
+ ? chooseBillingAccount(projectId, accounts)
79
+ : setUpBillingAccount(projectId);
80
80
  }
81
81
  }
82
82
  exports.enableBilling = enableBilling;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeFiles = exports.displayExportInfo = exports.setSecretParamsToLatest = exports.parameterizeProject = void 0;
4
+ const clc = require("cli-color");
5
+ const refs = require("./refs");
6
+ const config_1 = require("../config");
7
+ const planner_1 = require("../deploy/extensions/planner");
8
+ const deploymentSummary_1 = require("../deploy/extensions/deploymentSummary");
9
+ const logger_1 = require("../logger");
10
+ const error_1 = require("../error");
11
+ const prompt_1 = require("../prompt");
12
+ const secretManager_1 = require("../gcp/secretManager");
13
+ const secretsUtils_1 = require("./secretsUtils");
14
+ function parameterizeProject(projectId, projectNumber, spec) {
15
+ const newParams = {};
16
+ for (const [key, val] of Object.entries(spec.params)) {
17
+ const p1 = val.replace(projectId, "${param:PROJECT_ID}");
18
+ const p2 = p1.replace(projectNumber, "${param:PROJECT_NUMBER}");
19
+ newParams[key] = p2;
20
+ }
21
+ const newSpec = Object.assign({}, spec);
22
+ newSpec.params = newParams;
23
+ return newSpec;
24
+ }
25
+ exports.parameterizeProject = parameterizeProject;
26
+ async function setSecretParamsToLatest(spec) {
27
+ const newParams = Object.assign({}, spec.params);
28
+ const extensionVersion = await planner_1.getExtensionVersion(spec);
29
+ const activeSecrets = secretsUtils_1.getActiveSecrets(extensionVersion.spec, newParams);
30
+ for (const [key, val] of Object.entries(newParams)) {
31
+ if (activeSecrets.includes(val)) {
32
+ const parsed = secretManager_1.parseSecretVersionResourceName(val);
33
+ parsed.versionId = "latest";
34
+ newParams[key] = secretManager_1.toSecretVersionResourceName(parsed);
35
+ }
36
+ }
37
+ return Object.assign(Object.assign({}, spec), { params: newParams });
38
+ }
39
+ exports.setSecretParamsToLatest = setSecretParamsToLatest;
40
+ function displayExportInfo(withRef, withoutRef) {
41
+ logger_1.logger.info("The following Extension instances will be saved locally:");
42
+ logger_1.logger.info("");
43
+ displaySpecs(withRef);
44
+ if (withoutRef.length) {
45
+ logger_1.logger.info(`Your project also has the following instances installed from local sources. These will not be saved to firebase.json:`);
46
+ for (const spec of withoutRef) {
47
+ logger_1.logger.info(spec.instanceId);
48
+ }
49
+ }
50
+ }
51
+ exports.displayExportInfo = displayExportInfo;
52
+ function displaySpecs(specs) {
53
+ for (let i = 0; i < specs.length; i++) {
54
+ const spec = specs[i];
55
+ logger_1.logger.info(`${i + 1}. ${deploymentSummary_1.humanReadable(spec)}`);
56
+ logger_1.logger.info(`Configuration will be written to 'extensions/${spec.instanceId}.env'`);
57
+ for (const p of Object.entries(spec.params)) {
58
+ logger_1.logger.info(`\t${p[0]}=${p[1]}`);
59
+ }
60
+ logger_1.logger.info("");
61
+ }
62
+ }
63
+ function writeExtensionsToFirebaseJson(have, existingConfig) {
64
+ const extensions = existingConfig.get("extensions", {});
65
+ for (const s of have) {
66
+ extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
67
+ }
68
+ existingConfig.set("extensions", extensions);
69
+ logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
70
+ existingConfig.writeProjectFile("firebase.json", existingConfig.src);
71
+ }
72
+ async function writeEnvFile(spec, existingConfig, force) {
73
+ const content = Object.entries(spec.params)
74
+ .map((r) => `${r[0]}=${r[1]}`)
75
+ .join("\n");
76
+ await existingConfig.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
77
+ }
78
+ async function writeFiles(have, options) {
79
+ const existingConfig = config_1.Config.load(options, true);
80
+ if (!existingConfig) {
81
+ throw new error_1.FirebaseError("Not currently in a Firebase directory. Please run `firebase init` to create a Firebase directory.");
82
+ }
83
+ if (existingConfig.has("extensions") &&
84
+ Object.keys(existingConfig.get("extensions")).length &&
85
+ !options.nonInteractive &&
86
+ !options.force) {
87
+ const currentExtensions = Object.entries(existingConfig.get("extensions"))
88
+ .map((i) => `${i[0]}: ${i[1]}`)
89
+ .join("\n\t");
90
+ const overwrite = await prompt_1.promptOnce({
91
+ type: "list",
92
+ message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
93
+ choices: [
94
+ { name: "Overwrite", value: true },
95
+ { name: "Merge", value: false },
96
+ ],
97
+ });
98
+ if (overwrite) {
99
+ existingConfig.set("extensions", {});
100
+ }
101
+ }
102
+ writeExtensionsToFirebaseJson(have, existingConfig);
103
+ for (const spec of have) {
104
+ await writeEnvFile(spec, existingConfig, options.force);
105
+ }
106
+ }
107
+ exports.writeFiles = writeFiles;