firebase-tools 9.18.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 (114) hide show
  1. package/CHANGELOG.md +3 -6
  2. package/lib/api.js +3 -0
  3. package/lib/apiv2.js +8 -5
  4. package/lib/command.js +1 -1
  5. package/lib/commands/crashlytics-symbols-upload.js +146 -0
  6. package/lib/commands/deploy.js +9 -1
  7. package/lib/commands/ext-configure.js +9 -2
  8. package/lib/commands/ext-dev-deprecate.js +63 -0
  9. package/lib/commands/ext-dev-extension-delete.js +2 -1
  10. package/lib/commands/ext-dev-publish.js +10 -4
  11. package/lib/commands/ext-dev-undeprecate.js +56 -0
  12. package/lib/commands/ext-dev-unpublish.js +12 -4
  13. package/lib/commands/ext-export.js +44 -0
  14. package/lib/commands/ext-install.js +50 -13
  15. package/lib/commands/ext-uninstall.js +6 -0
  16. package/lib/commands/ext-update.js +60 -18
  17. package/lib/commands/functions-config-export.js +115 -0
  18. package/lib/commands/functions-delete.js +47 -25
  19. package/lib/commands/functions-list.js +12 -12
  20. package/lib/commands/index.js +9 -0
  21. package/lib/commands/init.js +3 -0
  22. package/lib/config.js +3 -2
  23. package/lib/deploy/extensions/args.js +2 -0
  24. package/lib/deploy/extensions/deploy.js +49 -0
  25. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  26. package/lib/deploy/extensions/errors.js +31 -0
  27. package/lib/deploy/extensions/index.js +8 -0
  28. package/lib/deploy/extensions/planner.js +95 -0
  29. package/lib/deploy/extensions/prepare.js +103 -0
  30. package/lib/deploy/extensions/release.js +43 -0
  31. package/lib/deploy/extensions/secrets.js +150 -0
  32. package/lib/deploy/extensions/tasks.js +98 -0
  33. package/lib/deploy/extensions/validate.js +17 -0
  34. package/lib/deploy/functions/backend.js +93 -115
  35. package/lib/deploy/functions/checkIam.js +8 -8
  36. package/lib/deploy/functions/containerCleaner.js +82 -22
  37. package/lib/deploy/functions/deploy.js +4 -10
  38. package/lib/deploy/functions/functionsDeployHelper.js +3 -68
  39. package/lib/deploy/functions/prepare.js +62 -27
  40. package/lib/deploy/functions/pricing.js +17 -17
  41. package/lib/deploy/functions/prompts.js +22 -21
  42. package/lib/deploy/functions/release/executor.js +39 -0
  43. package/lib/deploy/functions/release/fabricator.js +422 -0
  44. package/lib/deploy/functions/release/index.js +73 -0
  45. package/lib/deploy/functions/release/planner.js +162 -0
  46. package/lib/deploy/functions/release/reporter.js +165 -0
  47. package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
  48. package/lib/deploy/functions/release/timer.js +14 -0
  49. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
  50. package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
  51. package/lib/deploy/functions/triggerRegionHelper.js +40 -0
  52. package/lib/deploy/functions/validate.js +1 -24
  53. package/lib/deploy/index.js +1 -0
  54. package/lib/downloadUtils.js +37 -0
  55. package/lib/emulator/auth/apiSpec.js +1788 -403
  56. package/lib/emulator/auth/handlers.js +6 -5
  57. package/lib/emulator/auth/operations.js +439 -40
  58. package/lib/emulator/auth/server.js +32 -11
  59. package/lib/emulator/auth/state.js +205 -5
  60. package/lib/emulator/auth/widget_ui.js +2 -2
  61. package/lib/emulator/download.js +2 -31
  62. package/lib/emulator/downloadableEmulators.js +7 -7
  63. package/lib/emulator/emulatorLogger.js +0 -3
  64. package/lib/emulator/events/types.js +16 -0
  65. package/lib/emulator/functionsEmulator.js +120 -21
  66. package/lib/emulator/functionsEmulatorRuntime.js +46 -121
  67. package/lib/emulator/functionsEmulatorShared.js +51 -7
  68. package/lib/emulator/functionsEmulatorShell.js +1 -1
  69. package/lib/emulator/pubsubEmulator.js +61 -40
  70. package/lib/emulator/storage/cloudFunctions.js +37 -7
  71. package/lib/extensions/askUserForConsent.js +14 -1
  72. package/lib/extensions/askUserForParam.js +81 -4
  73. package/lib/extensions/billingMigrationHelper.js +1 -11
  74. package/lib/extensions/changelog.js +2 -1
  75. package/lib/extensions/checkProjectBilling.js +7 -7
  76. package/lib/extensions/displayExtensionInfo.js +35 -33
  77. package/lib/extensions/emulator/optionsHelper.js +3 -3
  78. package/lib/extensions/emulator/triggerHelper.js +2 -32
  79. package/lib/extensions/export.js +107 -0
  80. package/lib/extensions/extensionsApi.js +149 -97
  81. package/lib/extensions/extensionsHelper.js +36 -32
  82. package/lib/extensions/listExtensions.js +16 -11
  83. package/lib/extensions/paramHelper.js +73 -40
  84. package/lib/extensions/provisioningHelper.js +16 -3
  85. package/lib/extensions/refs.js +67 -0
  86. package/lib/extensions/secretsUtils.js +59 -0
  87. package/lib/extensions/updateHelper.js +33 -47
  88. package/lib/extensions/versionHelper.js +14 -0
  89. package/lib/extensions/warnings.js +33 -1
  90. package/lib/functional.js +64 -0
  91. package/lib/functions/env.js +26 -13
  92. package/lib/functions/runtimeConfigExport.js +137 -0
  93. package/lib/gcp/artifactregistry.js +16 -0
  94. package/lib/gcp/cloudfunctions.js +65 -35
  95. package/lib/gcp/cloudfunctionsv2.js +56 -43
  96. package/lib/gcp/cloudscheduler.js +22 -16
  97. package/lib/gcp/cloudtasks.js +143 -0
  98. package/lib/gcp/docker.js +7 -1
  99. package/lib/gcp/proto.js +2 -2
  100. package/lib/gcp/pubsub.js +1 -9
  101. package/lib/gcp/secretManager.js +132 -0
  102. package/lib/gcp/storage.js +16 -0
  103. package/lib/previews.js +1 -1
  104. package/lib/requireInteractive.js +12 -0
  105. package/lib/utils.js +30 -1
  106. package/package.json +6 -4
  107. package/schema/firebase-config.json +9 -0
  108. package/lib/deploy/functions/deploymentPlanner.js +0 -113
  109. package/lib/deploy/functions/deploymentTimer.js +0 -23
  110. package/lib/deploy/functions/errorHandler.js +0 -75
  111. package/lib/deploy/functions/release.js +0 -116
  112. package/lib/deploy/functions/tasks.js +0 -324
  113. package/lib/functions/listFunctions.js +0 -10
  114. package/lib/functionsDelete.js +0 -60
@@ -19,8 +19,8 @@ class PubsubEmulator {
19
19
  apiEndpoint: `${host}:${port}`,
20
20
  projectId: this.args.projectId,
21
21
  });
22
- this.triggers = new Map();
23
- this.subscriptions = new Map();
22
+ this.triggersForTopic = new Map();
23
+ this.subscriptionForTopic = new Map();
24
24
  }
25
25
  async start() {
26
26
  return downloadableEmulators.start(types_1.Emulators.PUBSUB, this.args);
@@ -44,10 +44,11 @@ class PubsubEmulator {
44
44
  getName() {
45
45
  return types_1.Emulators.PUBSUB;
46
46
  }
47
- async addTrigger(topicName, trigger) {
48
- this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${trigger})`);
49
- const topicTriggers = this.triggers.get(topicName) || new Set();
50
- if (topicTriggers.has(topicName) && this.subscriptions.has(topicName)) {
47
+ async addTrigger(topicName, triggerKey, signatureType) {
48
+ this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
49
+ const triggers = this.triggersForTopic.get(topicName) || [];
50
+ if (triggers.some((t) => t.triggerKey === triggerKey) &&
51
+ this.subscriptionForTopic.has(topicName)) {
51
52
  this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
52
53
  return;
53
54
  }
@@ -82,53 +83,73 @@ class PubsubEmulator {
82
83
  sub.on("message", (message) => {
83
84
  this.onMessage(topicName, message);
84
85
  });
85
- topicTriggers.add(trigger);
86
- this.triggers.set(topicName, topicTriggers);
87
- this.subscriptions.set(topicName, sub);
86
+ triggers.push({ triggerKey, signatureType });
87
+ this.triggersForTopic.set(topicName, triggers);
88
+ this.subscriptionForTopic.set(topicName, sub);
89
+ }
90
+ getRequestOptions(topic, message, signatureType) {
91
+ const baseOpts = {
92
+ origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS).getInfo())}`,
93
+ };
94
+ if (signatureType === "event") {
95
+ return Object.assign(Object.assign({}, baseOpts), { data: {
96
+ context: {
97
+ eventId: uuid.v4(),
98
+ resource: {
99
+ service: "pubsub.googleapis.com",
100
+ name: `projects/${this.args.projectId}/topics/${topic}`,
101
+ },
102
+ eventType: "google.pubsub.topic.publish",
103
+ timestamp: message.publishTime.toISOString(),
104
+ },
105
+ data: {
106
+ data: message.data,
107
+ attributes: message.attributes,
108
+ },
109
+ } });
110
+ }
111
+ else if (signatureType === "cloudevent") {
112
+ const data = {
113
+ message: {
114
+ messageId: message.id,
115
+ publishTime: message.publishTime,
116
+ attributes: message.attributes,
117
+ orderingKey: message.orderingKey,
118
+ data: message.data.toString("base64"),
119
+ },
120
+ subscription: this.subscriptionForTopic.get(topic).name,
121
+ };
122
+ const ce = {
123
+ specVersion: 1,
124
+ type: "google.cloud.pubsub.topic.v1.messagePublished",
125
+ source: `//pubsub.googleapis.com/projects/${this.args.projectId}/topics/${topic}`,
126
+ data,
127
+ };
128
+ return Object.assign(Object.assign({}, baseOpts), { headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" }, data: ce });
129
+ }
130
+ throw new error_1.FirebaseError(`Unsupported trigger signature: ${signatureType}`);
88
131
  }
89
132
  async onMessage(topicName, message) {
90
133
  this.logger.logLabeled("DEBUG", "pubsub", `onMessage(${topicName}, ${message.id})`);
91
- const topicTriggers = this.triggers.get(topicName);
92
- if (!topicTriggers || topicTriggers.size === 0) {
134
+ const triggers = this.triggersForTopic.get(topicName);
135
+ if (!triggers || triggers.length === 0) {
93
136
  throw new error_1.FirebaseError(`No trigger for topic: ${topicName}`);
94
137
  }
95
- const functionsEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
96
- if (!functionsEmu) {
138
+ if (!registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS)) {
97
139
  throw new error_1.FirebaseError(`Attempted to execute pubsub trigger for topic ${topicName} but could not find Functions emulator`);
98
140
  }
99
- this.logger.logLabeled("DEBUG", "pubsub", `Executing ${topicTriggers.size} matching triggers (${JSON.stringify(Array.from(topicTriggers))})`);
100
- let remaining = topicTriggers.size;
101
- for (const trigger of topicTriggers) {
102
- const body = {
103
- context: {
104
- eventId: uuid.v4(),
105
- resource: {
106
- service: "pubsub.googleapis.com",
107
- name: `projects/${this.args.projectId}/topics/${topicName}`,
108
- },
109
- eventType: "google.pubsub.topic.publish",
110
- timestamp: message.publishTime.toISOString(),
111
- },
112
- data: {
113
- data: message.data,
114
- attributes: message.attributes,
115
- },
116
- };
141
+ this.logger.logLabeled("DEBUG", "pubsub", `Executing ${triggers.length} matching triggers (${JSON.stringify(triggers.map((t) => t.triggerKey))})`);
142
+ for (const { triggerKey, signatureType } of triggers) {
143
+ const reqOpts = this.getRequestOptions(topicName, message, signatureType);
117
144
  try {
118
- await api.request("POST", `/functions/projects/${this.args.projectId}/triggers/${trigger}`, {
119
- origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(functionsEmu.getInfo())}`,
120
- data: body,
121
- });
145
+ await api.request("POST", `/functions/projects/${this.args.projectId}/triggers/${triggerKey}`, reqOpts);
122
146
  }
123
147
  catch (e) {
124
148
  this.logger.logLabeled("DEBUG", "pubsub", e);
125
149
  }
126
- remaining--;
127
- if (remaining <= 0) {
128
- this.logger.logLabeled("DEBUG", "pubsub", `Acking message ${message.id}`);
129
- message.ack();
130
- }
131
150
  }
151
+ this.logger.logLabeled("DEBUG", "pubsub", `Acking message ${message.id}`);
152
+ message.ack();
132
153
  }
133
154
  }
134
155
  exports.PubsubEmulator = PubsubEmulator;
@@ -6,6 +6,12 @@ const types_1 = require("../types");
6
6
  const emulatorLogger_1 = require("../emulatorLogger");
7
7
  const metadata_1 = require("./metadata");
8
8
  const apiv2_1 = require("../../apiv2");
9
+ const STORAGE_V2_ACTION_MAP = {
10
+ finalize: "finalized",
11
+ metadataUpdate: "metadataUpdated",
12
+ delete: "deleted",
13
+ archive: "archived",
14
+ };
9
15
  class StorageCloudFunctions {
10
16
  constructor(projectId) {
11
17
  this.projectId = projectId;
@@ -19,26 +25,37 @@ class StorageCloudFunctions {
19
25
  this.functionsEmulatorInfo = functionsEmulator.getInfo();
20
26
  this.multicastOrigin = `http://${registry_1.EmulatorRegistry.getInfoHostString(this.functionsEmulatorInfo)}`;
21
27
  this.multicastPath = `/functions/projects/${projectId}/trigger_multicast`;
28
+ this.client = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
22
29
  }
23
30
  }
24
31
  async dispatch(action, object) {
25
- if (!this.enabled)
32
+ if (!this.enabled) {
26
33
  return;
27
- const multicastEventBody = this.createEventRequestBody(action, object);
28
- const c = new apiv2_1.Client({ urlPrefix: this.multicastOrigin, auth: false });
29
- let res;
34
+ }
35
+ const errStatus = [];
30
36
  let err;
31
37
  try {
32
- res = await c.post(this.multicastPath, multicastEventBody);
38
+ const eventBody = this.createLegacyEventRequestBody(action, object);
39
+ const eventRes = await this.client.post(this.multicastPath, eventBody);
40
+ if (eventRes.status !== 200) {
41
+ errStatus.push(eventRes.status);
42
+ }
43
+ const cloudEventBody = this.createCloudEventRequestBody(action, object);
44
+ const cloudEventRes = await this.client.post(this.multicastPath, cloudEventBody, {
45
+ headers: { "Content-Type": "application/cloudevents+json; charset=UTF-8" },
46
+ });
47
+ if (cloudEventRes.status !== 200) {
48
+ errStatus.push(cloudEventRes.status);
49
+ }
33
50
  }
34
51
  catch (e) {
35
52
  err = e;
36
53
  }
37
- if (err || (res === null || res === void 0 ? void 0 : res.status) != 200) {
54
+ if (err || errStatus.length > 0) {
38
55
  this.logger.logLabeled("WARN", "functions", `Firebase Storage function was not triggered due to emulation error. Please file a bug.`);
39
56
  }
40
57
  }
41
- createEventRequestBody(action, objectMetadataPayload) {
58
+ createLegacyEventRequestBody(action, objectMetadataPayload) {
42
59
  const timestamp = new Date();
43
60
  return JSON.stringify({
44
61
  eventId: `${timestamp.getTime()}`,
@@ -52,5 +69,18 @@ class StorageCloudFunctions {
52
69
  data: objectMetadataPayload,
53
70
  });
54
71
  }
72
+ createCloudEventRequestBody(action, objectMetadataPayload) {
73
+ const ceAction = STORAGE_V2_ACTION_MAP[action];
74
+ if (!ceAction) {
75
+ throw new Error("Action is not definied as a CloudEvents action");
76
+ }
77
+ const data = objectMetadataPayload;
78
+ return JSON.stringify({
79
+ specVersion: 1,
80
+ type: `google.cloud.storage.object.v1.${ceAction}`,
81
+ source: `//storage.googleapis.com/projects/_/buckets/${objectMetadataPayload.bucket}/objects/${objectMetadataPayload.name}`,
82
+ data,
83
+ });
84
+ }
55
85
  }
56
86
  exports.StorageCloudFunctions = StorageCloudFunctions;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.promptForPublisherTOS = exports.displayRoles = exports.retrieveRoleInfo = exports.formatDescription = void 0;
3
+ exports.promptForPublisherTOS = exports.displayApis = exports.displayRoles = exports.retrieveRoleInfo = exports.formatDescription = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const marked = require("marked");
@@ -35,6 +35,19 @@ async function displayRoles(extensionName, projectId, roles) {
35
35
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
36
36
  }
37
37
  exports.displayRoles = displayRoles;
38
+ function displayApis(extensionName, projectId, apis) {
39
+ if (!apis.length) {
40
+ return;
41
+ }
42
+ const question = `${clc.bold(extensionName)} will enable the following APIs for project ${clc.bold(projectId)}`;
43
+ const results = apis.map((api) => {
44
+ return `- ${api.apiName}: ${api.reason}`;
45
+ });
46
+ results.unshift(question);
47
+ const message = results.join("\n");
48
+ utils.logLabeledBullet(extensionsHelper_1.logPrefix, message);
49
+ }
50
+ exports.displayApis = displayApis;
38
51
  async function promptForPublisherTOS() {
39
52
  const termsOfServiceMsg = "By registering as a publisher, you confirm that you have read the Firebase Extensions Publisher Terms and Conditions (linked below) and you, on behalf of yourself and the organization you represent, agree to comply with it. Here is a brief summary of the highlights of our terms and conditions:\n" +
40
53
  " - You ensure extensions you publish comply with all laws and regulations; do not include any viruses, spyware, Trojan horses, or other malicious code; and do not violate any person’s rights, including intellectual property, privacy, and security rights.\n" +
@@ -1,15 +1,22 @@
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");
7
7
  const extensionsApi_1 = require("./extensionsApi");
8
+ const secretManagerApi = require("../gcp/secretManager");
9
+ const secretsUtils = require("./secretsUtils");
8
10
  const extensionsHelper_1 = require("./extensionsHelper");
9
11
  const utils_1 = require("./utils");
10
12
  const logger_1 = require("../logger");
11
13
  const prompt_1 = require("../prompt");
12
14
  const utils = require("../utils");
15
+ var SecretUpdateAction;
16
+ (function (SecretUpdateAction) {
17
+ SecretUpdateAction[SecretUpdateAction["LEAVE"] = 0] = "LEAVE";
18
+ SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 1] = "SET_NEW";
19
+ })(SecretUpdateAction || (SecretUpdateAction = {}));
13
20
  function checkResponse(response, spec) {
14
21
  let valid = true;
15
22
  let responses;
@@ -48,7 +55,7 @@ function checkResponse(response, spec) {
48
55
  return valid;
49
56
  }
50
57
  exports.checkResponse = checkResponse;
51
- async function askForParam(paramSpec) {
58
+ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
52
59
  let valid = false;
53
60
  let response = "";
54
61
  const description = paramSpec.description || "";
@@ -89,6 +96,11 @@ async function askForParam(paramSpec) {
89
96
  choices: utils_1.convertExtensionOptionToLabeledList(paramSpec.options),
90
97
  });
91
98
  break;
99
+ case extensionsApi_1.ParamType.SECRET:
100
+ response = reconfiguring
101
+ ? await promptReconfigureSecret(projectId, instanceId, paramSpec)
102
+ : await promptCreateSecret(projectId, instanceId, paramSpec);
103
+ break;
92
104
  default:
93
105
  response = await prompt_1.promptOnce({
94
106
  name: paramSpec.param,
@@ -102,6 +114,71 @@ async function askForParam(paramSpec) {
102
114
  return response;
103
115
  }
104
116
  exports.askForParam = askForParam;
117
+ async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
118
+ const action = await prompt_1.promptOnce({
119
+ type: "list",
120
+ message: `Choose what you would like to do with this secret:`,
121
+ choices: [
122
+ { name: "Leave unchanged", value: SecretUpdateAction.LEAVE },
123
+ { name: "Set new value", value: SecretUpdateAction.SET_NEW },
124
+ ],
125
+ });
126
+ switch (action) {
127
+ case SecretUpdateAction.SET_NEW:
128
+ let secret;
129
+ let secretName;
130
+ if (paramSpec.default) {
131
+ secret = secretManagerApi.parseSecretResourceName(paramSpec.default);
132
+ secretName = secret.name;
133
+ }
134
+ else {
135
+ secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
136
+ }
137
+ const secretValue = await prompt_1.promptOnce({
138
+ name: paramSpec.param,
139
+ type: "password",
140
+ message: `This secret will be stored in Cloud Secret Manager as ${secretName}.\nEnter new value for ${paramSpec.label.trim()}:`,
141
+ });
142
+ if (!secret) {
143
+ secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
144
+ }
145
+ return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
146
+ case SecretUpdateAction.LEAVE:
147
+ default:
148
+ return paramSpec.default || "";
149
+ }
150
+ }
151
+ async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
152
+ const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
153
+ const secretValue = await prompt_1.promptOnce({
154
+ name: paramSpec.param,
155
+ type: "password",
156
+ default: paramSpec.default,
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
+ });
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;
168
+ }
169
+ exports.promptCreateSecret = promptCreateSecret;
170
+ async function generateSecretName(projectId, instanceId, paramName) {
171
+ let secretName = `ext-${instanceId}-${paramName}`;
172
+ while (await secretManagerApi.secretExists(projectId, secretName)) {
173
+ secretName += `-${utils_1.getRandomString(3)}`;
174
+ }
175
+ return secretName;
176
+ }
177
+ async function addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue) {
178
+ const version = await secretManagerApi.addVersion(secret, secretValue);
179
+ await secretsUtils.grantFirexServiceAgentSecretAdminRole(secret);
180
+ return `projects/${version.secret.projectId}/secrets/${version.secret.name}/versions/${version.versionId}`;
181
+ }
105
182
  function getInquirerDefault(options, def) {
106
183
  const defaultOption = _.find(options, (option) => {
107
184
  return option.value === def;
@@ -109,7 +186,7 @@ function getInquirerDefault(options, def) {
109
186
  return defaultOption ? defaultOption.label || defaultOption.value : "";
110
187
  }
111
188
  exports.getInquirerDefault = getInquirerDefault;
112
- async function ask(paramSpecs, firebaseProjectParams) {
189
+ async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
113
190
  if (_.isEmpty(paramSpecs)) {
114
191
  logger_1.logger.debug("No params were specified for this extension.");
115
192
  return {};
@@ -119,7 +196,7 @@ async function ask(paramSpecs, firebaseProjectParams) {
119
196
  const result = {};
120
197
  const promises = _.map(substituted, (paramSpec) => {
121
198
  return async () => {
122
- result[paramSpec.param] = await askForParam(paramSpec);
199
+ result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
123
200
  };
124
201
  });
125
202
  await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
@@ -36,19 +36,9 @@ function hasRuntime(spec, runtime) {
36
36
  const resources = spec.resources || [];
37
37
  return resources.some((r) => { var _a; return runtime === (((_a = r.properties) === null || _a === void 0 ? void 0 : _a.runtime) || defaultRuntime); });
38
38
  }
39
- async function displayNode10UpdateBillingNotice(curSpec, newSpec, prompt) {
39
+ function displayNode10UpdateBillingNotice(curSpec, newSpec) {
40
40
  if (hasRuntime(curSpec, "nodejs8") && hasRuntime(newSpec, "nodejs10")) {
41
41
  utils.logLabeledWarning(extensionsHelper_1.logPrefix, marked(billingMsgUpdate));
42
- if (prompt) {
43
- const continueUpdate = await prompt_1.promptOnce({
44
- type: "confirm",
45
- message: "Do you wish to continue?",
46
- default: true,
47
- });
48
- if (!continueUpdate) {
49
- throw new error_1.FirebaseError(`Cancelled.`, { exit: 2 });
50
- }
51
- }
52
42
  }
53
43
  }
54
44
  exports.displayNode10UpdateBillingNotice = displayNode10UpdateBillingNotice;
@@ -10,6 +10,7 @@ const Table = require("cli-table");
10
10
  const extensionsApi_1 = require("./extensionsApi");
11
11
  const localHelper_1 = require("./localHelper");
12
12
  const logger_1 = require("../logger");
13
+ const refs = require("./refs");
13
14
  const utils_1 = require("../utils");
14
15
  marked.setOptions({
15
16
  renderer: new TerminalRenderer(),
@@ -25,7 +26,7 @@ async function getReleaseNotesForUpdate(args) {
25
26
  });
26
27
  for (const extensionVersion of extensionVersions) {
27
28
  if (extensionVersion.releaseNotes) {
28
- const version = extensionsApi_1.parseRef(extensionVersion.ref).version;
29
+ const version = refs.parse(extensionVersion.ref).version;
29
30
  releaseNotes[version] = extensionVersion.releaseNotes;
30
31
  }
31
32
  }
@@ -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;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printSourceDownloadLink = exports.getConsent = exports.displayUpdateChangesRequiringConfirmation = exports.displayUpdateChangesNoInput = exports.displayExtInfo = void 0;
3
+ exports.printSourceDownloadLink = exports.displayUpdateChangesRequiringConfirmation = exports.displayUpdateChangesNoInput = exports.displayExtInfo = void 0;
4
4
  const _ = require("lodash");
5
5
  const clc = require("cli-color");
6
6
  const marked = require("marked");
@@ -9,7 +9,6 @@ const utils = require("../utils");
9
9
  const extensionsHelper_1 = require("./extensionsHelper");
10
10
  const logger_1 = require("../logger");
11
11
  const error_1 = require("../error");
12
- const prompt_1 = require("../prompt");
13
12
  marked.setOptions({
14
13
  renderer: new TerminalRenderer(),
15
14
  });
@@ -69,17 +68,22 @@ function displayUpdateChangesNoInput(spec, newSpec) {
69
68
  return lines;
70
69
  }
71
70
  exports.displayUpdateChangesNoInput = displayUpdateChangesNoInput;
72
- async function displayUpdateChangesRequiringConfirmation(spec, newSpec) {
73
- if (spec.license !== newSpec.license) {
71
+ async function displayUpdateChangesRequiringConfirmation(args) {
72
+ const equals = (a, b) => {
73
+ return _.isEqual(a, b);
74
+ };
75
+ if (args.spec.license !== args.newSpec.license) {
74
76
  const message = "\n" +
75
77
  "**License**\n" +
76
- deletionColor(spec.license ? `- ${spec.license}\n` : "- None\n") +
77
- additionColor(newSpec.license ? `+ ${newSpec.license}\n` : "+ None\n") +
78
- "Do you wish to continue?";
79
- await getConsent("license", marked(message));
78
+ deletionColor(args.spec.license ? `- ${args.spec.license}\n` : "- None\n") +
79
+ additionColor(args.newSpec.license ? `+ ${args.newSpec.license}\n` : "+ None\n");
80
+ logger_1.logger.info(message);
81
+ if (!(await extensionsHelper_1.confirm({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
82
+ throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'License'.");
83
+ }
80
84
  }
81
- const apisDiffDeletions = _.differenceWith(spec.apis, _.get(newSpec, "apis", []), _.isEqual);
82
- const apisDiffAdditions = _.differenceWith(newSpec.apis, _.get(spec, "apis", []), _.isEqual);
85
+ const apisDiffDeletions = _.differenceWith(args.spec.apis, _.get(args.newSpec, "apis", []), equals);
86
+ const apisDiffAdditions = _.differenceWith(args.newSpec.apis, _.get(args.spec, "apis", []), equals);
83
87
  if (apisDiffDeletions.length || apisDiffAdditions.length) {
84
88
  let message = "\n**APIs:**\n";
85
89
  apisDiffDeletions.forEach((api) => {
@@ -88,11 +92,13 @@ async function displayUpdateChangesRequiringConfirmation(spec, newSpec) {
88
92
  apisDiffAdditions.forEach((api) => {
89
93
  message += additionColor(`+ ${api.apiName} (${api.reason})\n`);
90
94
  });
91
- message += "Do you wish to continue?";
92
- await getConsent("apis", marked(message));
95
+ logger_1.logger.info(message);
96
+ if (!(await extensionsHelper_1.confirm({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
97
+ throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'APIs'.");
98
+ }
93
99
  }
94
- const resourcesDiffDeletions = _.differenceWith(spec.resources, _.get(newSpec, "resources", []), compareResources);
95
- const resourcesDiffAdditions = _.differenceWith(newSpec.resources, _.get(spec, "resources", []), compareResources);
100
+ const resourcesDiffDeletions = _.differenceWith(args.spec.resources, _.get(args.newSpec, "resources", []), compareResources);
101
+ const resourcesDiffAdditions = _.differenceWith(args.newSpec.resources, _.get(args.spec, "resources", []), compareResources);
96
102
  if (resourcesDiffDeletions.length || resourcesDiffAdditions.length) {
97
103
  let message = "\n**Resources:**\n";
98
104
  resourcesDiffDeletions.forEach((resource) => {
@@ -101,11 +107,13 @@ async function displayUpdateChangesRequiringConfirmation(spec, newSpec) {
101
107
  resourcesDiffAdditions.forEach((resource) => {
102
108
  message += additionColor(`+ ${getResourceReadableName(resource)}`);
103
109
  });
104
- message += "Do you wish to continue?";
105
- await getConsent("resources", marked(message));
110
+ logger_1.logger.info(message);
111
+ if (!(await extensionsHelper_1.confirm({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
112
+ throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'Resources'.");
113
+ }
106
114
  }
107
- const rolesDiffDeletions = _.differenceWith(spec.roles, _.get(newSpec, "roles", []), _.isEqual);
108
- const rolesDiffAdditions = _.differenceWith(newSpec.roles, _.get(spec, "roles", []), _.isEqual);
115
+ const rolesDiffDeletions = _.differenceWith(args.spec.roles, _.get(args.newSpec, "roles", []), equals);
116
+ const rolesDiffAdditions = _.differenceWith(args.newSpec.roles, _.get(args.spec, "roles", []), equals);
109
117
  if (rolesDiffDeletions.length || rolesDiffAdditions.length) {
110
118
  let message = "\n**Permissions:**\n";
111
119
  rolesDiffDeletions.forEach((role) => {
@@ -114,11 +122,16 @@ async function displayUpdateChangesRequiringConfirmation(spec, newSpec) {
114
122
  rolesDiffAdditions.forEach((role) => {
115
123
  message += additionColor(`+ ${role.role} (${role.reason})\n`);
116
124
  });
117
- message += "Do you wish to continue?";
118
- await getConsent("apis", marked(message));
125
+ logger_1.logger.info(message);
126
+ if (!(await extensionsHelper_1.confirm({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
127
+ throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'Permissions'.");
128
+ }
119
129
  }
120
- if (!spec.billingRequired && newSpec.billingRequired) {
121
- await getConsent("billingRequired", "Billing is now required for the new version of this extension. Would you like to continue?");
130
+ if (!args.spec.billingRequired && args.newSpec.billingRequired) {
131
+ logger_1.logger.info("Billing is now required for the new version of this extension.");
132
+ if (!(await extensionsHelper_1.confirm({ nonInteractive: args.nonInteractive, force: args.force, default: true }))) {
133
+ throw new error_1.FirebaseError("Unable to update this extension instance without explicit consent for the change to 'BillingRequired'.");
134
+ }
122
135
  }
123
136
  }
124
137
  exports.displayUpdateChangesRequiringConfirmation = displayUpdateChangesRequiringConfirmation;
@@ -130,17 +143,6 @@ function getResourceReadableName(resource) {
130
143
  ? `${resource.name} (Cloud Function): ${resource.description}\n`
131
144
  : `${resource.name} (${resource.type})\n`;
132
145
  }
133
- async function getConsent(field, message) {
134
- const consent = await prompt_1.promptOnce({
135
- type: "confirm",
136
- message,
137
- default: true,
138
- });
139
- if (!consent) {
140
- throw new error_1.FirebaseError(`Without explicit consent for the change to ${field}, we cannot update this extension instance.`, { exit: 2 });
141
- }
142
- }
143
- exports.getConsent = getConsent;
144
146
  function printSourceDownloadLink(sourceDownloadUri) {
145
147
  const sourceDownloadMsg = `Want to review the source code that will be installed? Download it here: ${sourceDownloadUri}`;
146
148
  utils.logBullet(marked(sourceDownloadMsg));
@@ -19,7 +19,7 @@ async function buildOptions(options) {
19
19
  options.extensionDir = extensionDir;
20
20
  const spec = await specHelper.readExtensionYaml(extensionDir);
21
21
  extensionsHelper.validateSpec(spec);
22
- const params = await getParams(options, spec);
22
+ const params = getParams(options, spec);
23
23
  extensionsHelper.validateCommandLineParams(params, spec.params);
24
24
  const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, params);
25
25
  let testConfig;
@@ -35,9 +35,9 @@ async function buildOptions(options) {
35
35
  return options;
36
36
  }
37
37
  exports.buildOptions = buildOptions;
38
- async function getParams(options, extensionSpec) {
38
+ function getParams(options, extensionSpec) {
39
39
  const projectId = projectUtils_1.needProjectId(options);
40
- const userParams = await paramHelper.readParamsFile(options.testParams);
40
+ const userParams = paramHelper.readEnvFile(options.testParams);
41
41
  const autoParams = {
42
42
  PROJECT_ID: projectId,
43
43
  EXT_INSTANCE_ID: extensionSpec.name,
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.functionResourceToEmulatedTriggerDefintion = void 0;
4
4
  const _ = require("lodash");
5
- const constants_1 = require("../../emulator/constants");
5
+ const functionsEmulatorShared_1 = require("../../emulator/functionsEmulatorShared");
6
6
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
7
7
  const types_1 = require("../../emulator/types");
8
8
  function functionResourceToEmulatedTriggerDefintion(resource) {
@@ -24,7 +24,7 @@ function functionResourceToEmulatedTriggerDefintion(resource) {
24
24
  etd.httpsTrigger = properties.httpsTrigger;
25
25
  }
26
26
  else if (properties.eventTrigger) {
27
- properties.eventTrigger.service = getServiceFromEventType(properties.eventTrigger.eventType);
27
+ properties.eventTrigger.service = functionsEmulatorShared_1.getServiceFromEventType(properties.eventTrigger.eventType);
28
28
  etd.eventTrigger = properties.eventTrigger;
29
29
  }
30
30
  else {
@@ -33,33 +33,3 @@ function functionResourceToEmulatedTriggerDefintion(resource) {
33
33
  return etd;
34
34
  }
35
35
  exports.functionResourceToEmulatedTriggerDefintion = functionResourceToEmulatedTriggerDefintion;
36
- function getServiceFromEventType(eventType) {
37
- if (eventType.includes("firestore")) {
38
- return constants_1.Constants.SERVICE_FIRESTORE;
39
- }
40
- if (eventType.includes("database")) {
41
- return constants_1.Constants.SERVICE_REALTIME_DATABASE;
42
- }
43
- if (eventType.includes("pubsub")) {
44
- return constants_1.Constants.SERVICE_PUBSUB;
45
- }
46
- if (eventType.includes("storage")) {
47
- return constants_1.Constants.SERVICE_STORAGE;
48
- }
49
- if (eventType.includes("analytics")) {
50
- return constants_1.Constants.SERVICE_ANALYTICS;
51
- }
52
- if (eventType.includes("auth")) {
53
- return constants_1.Constants.SERVICE_AUTH;
54
- }
55
- if (eventType.includes("crashlytics")) {
56
- return constants_1.Constants.SERVICE_CRASHLYTICS;
57
- }
58
- if (eventType.includes("remoteconfig")) {
59
- return constants_1.Constants.SERVICE_REMOTE_CONFIG;
60
- }
61
- if (eventType.includes("testing")) {
62
- return constants_1.Constants.SERVICE_TEST_LAB;
63
- }
64
- return "";
65
- }