firebase-tools 9.21.0 → 9.23.2

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 (82) hide show
  1. package/CHANGELOG.md +7 -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 +8 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/commands/remoteconfig-get.js +6 -5
  16. package/lib/config.js +3 -2
  17. package/lib/deploy/extensions/args.js +2 -0
  18. package/lib/deploy/extensions/deploy.js +49 -0
  19. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  20. package/lib/deploy/extensions/errors.js +31 -0
  21. package/lib/deploy/extensions/index.js +8 -0
  22. package/lib/deploy/extensions/params.js +39 -0
  23. package/lib/deploy/extensions/planner.js +94 -0
  24. package/lib/deploy/extensions/prepare.js +111 -0
  25. package/lib/deploy/extensions/release.js +43 -0
  26. package/lib/deploy/extensions/secrets.js +150 -0
  27. package/lib/deploy/extensions/tasks.js +98 -0
  28. package/lib/deploy/extensions/validate.js +17 -0
  29. package/lib/deploy/functions/backend.js +8 -1
  30. package/lib/deploy/functions/checkIam.js +65 -4
  31. package/lib/deploy/functions/containerCleaner.js +97 -50
  32. package/lib/deploy/functions/eventTypes.js +10 -0
  33. package/lib/deploy/functions/prepare.js +12 -1
  34. package/lib/deploy/functions/release/fabricator.js +75 -10
  35. package/lib/deploy/functions/release/index.js +9 -1
  36. package/lib/deploy/functions/release/planner.js +3 -0
  37. package/lib/deploy/functions/release/reporter.js +8 -1
  38. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  39. package/lib/deploy/functions/runtimes/node/parseTriggers.js +10 -11
  40. package/lib/deploy/functions/services/index.js +38 -0
  41. package/lib/deploy/functions/services/storage.js +43 -0
  42. package/lib/deploy/functions/triggerRegionHelper.js +6 -30
  43. package/lib/deploy/index.js +10 -1
  44. package/lib/emulator/auth/handlers.js +1 -1
  45. package/lib/emulator/auth/operations.js +27 -9
  46. package/lib/emulator/auth/widget_ui.js +17 -3
  47. package/lib/emulator/functionsEmulator.js +18 -2
  48. package/lib/emulator/functionsEmulatorShared.js +1 -0
  49. package/lib/emulator/pubsubEmulator.js +58 -45
  50. package/lib/emulator/storage/cloudFunctions.js +13 -6
  51. package/lib/ensureApiEnabled.js +11 -14
  52. package/lib/extensions/askUserForParam.js +42 -10
  53. package/lib/extensions/checkProjectBilling.js +7 -7
  54. package/lib/extensions/emulator/triggerHelper.js +1 -0
  55. package/lib/extensions/export.js +107 -0
  56. package/lib/extensions/extensionsApi.js +103 -21
  57. package/lib/extensions/extensionsHelper.js +4 -1
  58. package/lib/extensions/listExtensions.js +16 -11
  59. package/lib/extensions/paramHelper.js +6 -4
  60. package/lib/extensions/provisioningHelper.js +16 -3
  61. package/lib/extensions/refs.js +9 -1
  62. package/lib/extensions/secretsUtils.js +10 -9
  63. package/lib/extensions/updateHelper.js +12 -2
  64. package/lib/extensions/versionHelper.js +14 -0
  65. package/lib/extensions/warnings.js +33 -1
  66. package/lib/functions/env.js +2 -2
  67. package/lib/gcp/artifactregistry.js +16 -0
  68. package/lib/gcp/cloudfunctions.js +27 -7
  69. package/lib/gcp/cloudfunctionsv2.js +45 -5
  70. package/lib/gcp/cloudtasks.js +143 -0
  71. package/lib/gcp/docker.js +36 -2
  72. package/lib/gcp/location.js +44 -0
  73. package/lib/gcp/proto.js +2 -2
  74. package/lib/gcp/secretManager.js +27 -6
  75. package/lib/gcp/storage.js +48 -32
  76. package/lib/init/features/functions/index.js +3 -3
  77. package/lib/init/features/hosting/github.js +3 -0
  78. package/lib/init/features/project.js +2 -1
  79. package/lib/previews.js +1 -1
  80. package/lib/projectUtils.js +10 -1
  81. package/package.json +5 -4
  82. package/schema/firebase-config.json +9 -0
@@ -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");
@@ -77,6 +77,7 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
77
77
  "You may only select one option.",
78
78
  choices: utils_1.convertExtensionOptionToLabeledList(paramSpec.options),
79
79
  });
80
+ valid = checkResponse(response, paramSpec);
80
81
  break;
81
82
  case extensionsApi_1.ParamType.MULTISELECT:
82
83
  response = await utils_1.onceWithJoin({
@@ -95,11 +96,13 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
95
96
  "You may select multiple options.",
96
97
  choices: utils_1.convertExtensionOptionToLabeledList(paramSpec.options),
97
98
  });
99
+ valid = checkResponse(response, paramSpec);
98
100
  break;
99
101
  case extensionsApi_1.ParamType.SECRET:
100
102
  response = reconfiguring
101
103
  ? await promptReconfigureSecret(projectId, instanceId, paramSpec)
102
104
  : await promptCreateSecret(projectId, instanceId, paramSpec);
105
+ valid = true;
103
106
  break;
104
107
  default:
105
108
  response = await prompt_1.promptOnce({
@@ -108,8 +111,8 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
108
111
  default: paramSpec.default,
109
112
  message: `Enter a value for ${label}:`,
110
113
  });
114
+ valid = checkResponse(response, paramSpec);
111
115
  }
112
- valid = checkResponse(response, paramSpec);
113
116
  }
114
117
  return response;
115
118
  }
@@ -139,26 +142,55 @@ async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
139
142
  type: "password",
140
143
  message: `This secret will be stored in Cloud Secret Manager as ${secretName}.\nEnter new value for ${paramSpec.label.trim()}:`,
141
144
  });
142
- if (!secret) {
143
- secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
145
+ if (secretValue === "" && paramSpec.required) {
146
+ logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
147
+ return promptReconfigureSecret(projectId, instanceId, paramSpec);
148
+ }
149
+ else if (secretValue !== "") {
150
+ if (checkResponse(secretValue, paramSpec)) {
151
+ if (!secret) {
152
+ secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
153
+ }
154
+ return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
155
+ }
156
+ else {
157
+ return promptReconfigureSecret(projectId, instanceId, paramSpec);
158
+ }
159
+ }
160
+ else {
161
+ return "";
144
162
  }
145
- return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
146
163
  case SecretUpdateAction.LEAVE:
147
164
  default:
148
165
  return paramSpec.default || "";
149
166
  }
150
167
  }
151
- async function promptCreateSecret(projectId, instanceId, paramSpec) {
152
- const secretName = await generateSecretName(projectId, instanceId, paramSpec.param);
168
+ async function promptCreateSecret(projectId, instanceId, paramSpec, secretName) {
169
+ const name = secretName !== null && secretName !== void 0 ? secretName : (await generateSecretName(projectId, instanceId, paramSpec.param));
153
170
  const secretValue = await prompt_1.promptOnce({
154
171
  name: paramSpec.param,
155
172
  type: "password",
156
173
  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()}:`,
174
+ 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
175
  });
159
- const secret = await secretManagerApi.createSecret(projectId, secretName, secretsUtils.getSecretLabels(instanceId));
160
- return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
176
+ if (secretValue === "" && paramSpec.required) {
177
+ logger_1.logger.info(`Secret value cannot be empty for required param ${paramSpec.param}`);
178
+ return promptCreateSecret(projectId, instanceId, paramSpec, name);
179
+ }
180
+ else if (secretValue !== "") {
181
+ if (checkResponse(secretValue, paramSpec)) {
182
+ const secret = await secretManagerApi.createSecret(projectId, name, secretsUtils.getSecretLabels(instanceId));
183
+ return addNewSecretVersion(projectId, instanceId, secret, paramSpec, secretValue);
184
+ }
185
+ else {
186
+ return promptCreateSecret(projectId, instanceId, paramSpec, name);
187
+ }
188
+ }
189
+ else {
190
+ return "";
191
+ }
161
192
  }
193
+ exports.promptCreateSecret = promptCreateSecret;
162
194
  async function generateSecretName(projectId, instanceId, paramName) {
163
195
  let secretName = `ext-${instanceId}-${paramName}`;
164
196
  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;
@@ -9,6 +9,7 @@ function functionResourceToEmulatedTriggerDefintion(resource) {
9
9
  const etd = {
10
10
  name: resource.name,
11
11
  entryPoint: resource.name,
12
+ platform: "gcfv1",
12
13
  };
13
14
  const properties = _.get(resource, "properties", {});
14
15
  if (properties.timeout) {
@@ -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;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.registerPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
3
+ exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
4
4
  const yaml = require("js-yaml");
5
5
  const _ = require("lodash");
6
6
  const clc = require("cli-color");
@@ -32,7 +32,7 @@ var ParamType;
32
32
  ParamType["MULTISELECT"] = "MULTISELECT";
33
33
  ParamType["SECRET"] = "SECRET";
34
34
  })(ParamType = exports.ParamType || (exports.ParamType = {}));
35
- async function createInstanceHelper(projectId, instanceId, config) {
35
+ async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
36
36
  const createRes = await api.request("POST", `/${VERSION}/projects/${projectId}/instances/`, {
37
37
  auth: true,
38
38
  origin: api.extensionsOrigin,
@@ -40,7 +40,13 @@ async function createInstanceHelper(projectId, instanceId, config) {
40
40
  name: `projects/${projectId}/instances/${instanceId}`,
41
41
  config: config,
42
42
  },
43
+ query: {
44
+ validateOnly,
45
+ },
43
46
  });
47
+ if (validateOnly) {
48
+ return createRes;
49
+ }
44
50
  const pollRes = await operationPoller.pollOperation({
45
51
  apiOrigin: api.extensionsOrigin,
46
52
  apiVersion: VERSION,
@@ -68,7 +74,7 @@ async function createInstance(args) {
68
74
  else {
69
75
  throw new error_1.FirebaseError("No ExtensionVersion or ExtensionSource provided but one is required.");
70
76
  }
71
- return createInstanceHelper(args.projectId, args.instanceId, config);
77
+ return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
72
78
  }
73
79
  exports.createInstance = createInstance;
74
80
  async function deleteInstance(projectId, instanceId) {
@@ -115,31 +121,46 @@ async function listInstances(projectId) {
115
121
  return instances;
116
122
  }
117
123
  exports.listInstances = listInstances;
118
- async function configureInstance(projectId, instanceId, params) {
119
- const res = await patchInstance(projectId, instanceId, "config.params", {
120
- config: {
121
- params,
124
+ async function configureInstance(args) {
125
+ var _a;
126
+ const res = await patchInstance({
127
+ projectId: args.projectId,
128
+ instanceId: args.instanceId,
129
+ updateMask: "config.params",
130
+ validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
131
+ data: {
132
+ config: {
133
+ params: args.params,
134
+ },
122
135
  },
123
136
  });
124
137
  return res;
125
138
  }
126
139
  exports.configureInstance = configureInstance;
127
- async function updateInstance(projectId, instanceId, extensionSource, params) {
140
+ async function updateInstance(args) {
141
+ var _a;
128
142
  const body = {
129
143
  config: {
130
- source: { name: extensionSource.name },
144
+ source: { name: args.extensionSource.name },
131
145
  },
132
146
  };
133
147
  let updateMask = "config.source.name";
134
- if (params) {
135
- body.config.params = params;
148
+ if (args.params) {
149
+ body.config.params = args.params;
136
150
  updateMask += ",config.params";
137
151
  }
138
- return await patchInstance(projectId, instanceId, updateMask, body);
152
+ return await patchInstance({
153
+ projectId: args.projectId,
154
+ instanceId: args.instanceId,
155
+ updateMask,
156
+ validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
157
+ data: body,
158
+ });
139
159
  }
140
160
  exports.updateInstance = updateInstance;
141
- async function updateInstanceFromRegistry(projectId, instanceId, extRef, params) {
142
- const ref = refs.parse(extRef);
161
+ async function updateInstanceFromRegistry(args) {
162
+ var _a;
163
+ const ref = refs.parse(args.extRef);
143
164
  const body = {
144
165
  config: {
145
166
  extensionRef: refs.toExtensionRef(ref),
@@ -147,22 +168,32 @@ async function updateInstanceFromRegistry(projectId, instanceId, extRef, params)
147
168
  },
148
169
  };
149
170
  let updateMask = "config.extension_ref,config.extension_version";
150
- if (params) {
151
- body.config.params = params;
171
+ if (args.params) {
172
+ body.config.params = args.params;
152
173
  updateMask += ",config.params";
153
174
  }
154
- return await patchInstance(projectId, instanceId, updateMask, body);
175
+ return await patchInstance({
176
+ projectId: args.projectId,
177
+ instanceId: args.instanceId,
178
+ updateMask,
179
+ validateOnly: (_a = args.validateOnly) !== null && _a !== void 0 ? _a : false,
180
+ data: body,
181
+ });
155
182
  }
156
183
  exports.updateInstanceFromRegistry = updateInstanceFromRegistry;
157
- async function patchInstance(projectId, instanceId, updateMask, data) {
158
- const updateRes = await api.request("PATCH", `/${VERSION}/projects/${projectId}/instances/${instanceId}`, {
184
+ async function patchInstance(args) {
185
+ const updateRes = await api.request("PATCH", `/${VERSION}/projects/${args.projectId}/instances/${args.instanceId}`, {
159
186
  auth: true,
160
187
  origin: api.extensionsOrigin,
161
188
  query: {
162
- updateMask,
189
+ updateMask: args.updateMask,
190
+ validateOnly: args.validateOnly,
163
191
  },
164
- data,
192
+ data: args.data,
165
193
  });
194
+ if (args.validateOnly) {
195
+ return updateRes;
196
+ }
166
197
  const pollRes = await operationPoller.pollOperation({
167
198
  apiOrigin: api.extensionsOrigin,
168
199
  apiVersion: VERSION,
@@ -302,6 +333,57 @@ async function registerPublisherProfile(projectId, publisherId) {
302
333
  return res.body;
303
334
  }
304
335
  exports.registerPublisherProfile = registerPublisherProfile;
336
+ async function deprecateExtensionVersion(extensionRef, deprecationMessage) {
337
+ const ref = refs.parse(extensionRef);
338
+ try {
339
+ const res = await api.request("POST", `/${VERSION}/${refs.toExtensionVersionName(ref)}:deprecate`, {
340
+ auth: true,
341
+ origin: api.extensionsOrigin,
342
+ data: { deprecationMessage },
343
+ });
344
+ return res.body;
345
+ }
346
+ catch (err) {
347
+ if (err.status === 403) {
348
+ throw new error_1.FirebaseError(`You are not the owner of extension '${clc.bold(extensionRef)}' and don’t have the correct permissions to deprecate this extension version.` + err, { status: err.status });
349
+ }
350
+ else if (err.status === 404) {
351
+ throw new error_1.FirebaseError(`Extension version ${clc.bold(extensionRef)} was not found.`);
352
+ }
353
+ else if (err instanceof error_1.FirebaseError) {
354
+ throw err;
355
+ }
356
+ throw new error_1.FirebaseError(`Error occurred deprecating extension version '${extensionRef}': ${err}`, {
357
+ status: err.status,
358
+ });
359
+ }
360
+ }
361
+ exports.deprecateExtensionVersion = deprecateExtensionVersion;
362
+ async function undeprecateExtensionVersion(extensionRef) {
363
+ const ref = refs.parse(extensionRef);
364
+ try {
365
+ const res = await api.request("POST", `/${VERSION}/${refs.toExtensionVersionName(ref)}:undeprecate`, {
366
+ auth: true,
367
+ origin: api.extensionsOrigin,
368
+ });
369
+ return res.body;
370
+ }
371
+ catch (err) {
372
+ if (err.status === 403) {
373
+ throw new error_1.FirebaseError(`You are not the owner of extension '${clc.bold(extensionRef)}' and don’t have the correct permissions to undeprecate this extension version.`, { status: err.status });
374
+ }
375
+ else if (err.status === 404) {
376
+ throw new error_1.FirebaseError(`Extension version ${clc.bold(extensionRef)} was not found.`);
377
+ }
378
+ else if (err instanceof error_1.FirebaseError) {
379
+ throw err;
380
+ }
381
+ throw new error_1.FirebaseError(`Error occurred undeprecating extension version '${extensionRef}': ${err}`, {
382
+ status: err.status,
383
+ });
384
+ }
385
+ }
386
+ exports.undeprecateExtensionVersion = undeprecateExtensionVersion;
305
387
  async function publishExtensionVersion(extensionVersionRef, packageUri, extensionRoot) {
306
388
  const ref = refs.parse(extensionVersionRef);
307
389
  if (!ref.version) {
@@ -27,6 +27,7 @@ const prompt_1 = require("../prompt");
27
27
  const logger_1 = require("../logger");
28
28
  const utils_2 = require("../utils");
29
29
  const changelog_1 = require("./changelog");
30
+ const getProjectNumber_1 = require("../getProjectNumber");
30
31
  var SpecParamType;
31
32
  (function (SpecParamType) {
32
33
  SpecParamType["SELECT"] = "select";
@@ -76,6 +77,7 @@ function getDBInstanceFromURL(databaseUrl = "") {
76
77
  exports.getDBInstanceFromURL = getDBInstanceFromURL;
77
78
  async function getFirebaseProjectParams(projectId) {
78
79
  const body = await functionsConfig_1.getFirebaseConfig({ project: projectId });
80
+ const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId });
79
81
  const FIREBASE_CONFIG = JSON.stringify({
80
82
  projectId: body.projectId,
81
83
  databaseURL: body.databaseURL,
@@ -83,6 +85,7 @@ async function getFirebaseProjectParams(projectId) {
83
85
  });
84
86
  return {
85
87
  PROJECT_ID: body.projectId,
88
+ PROJECT_NUMBER: projectNumber,
86
89
  DATABASE_URL: body.databaseURL,
87
90
  STORAGE_BUCKET: body.storageBucket,
88
91
  FIREBASE_CONFIG,
@@ -108,7 +111,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
108
111
  const newParams = paramVars;
109
112
  for (const param of paramSpecs) {
110
113
  if (!paramVars[param.param]) {
111
- if (param.default != undefined) {
114
+ if (param.default != undefined && param.required) {
112
115
  newParams[param.param] = param.default;
113
116
  }
114
117
  else if (param.required) {
@@ -8,19 +8,20 @@ const extensionsApi_1 = require("./extensionsApi");
8
8
  const extensionsHelper_1 = require("./extensionsHelper");
9
9
  const utils = require("../utils");
10
10
  const extensionsUtils = require("./utils");
11
- const logger_1 = require("../logger");
12
11
  async function listExtensions(projectId) {
13
12
  const instances = await extensionsApi_1.listInstances(projectId);
14
13
  if (instances.length < 1) {
15
14
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `there are no extensions installed on project ${clc.bold(projectId)}.`);
16
- return { instances: [] };
15
+ return [];
17
16
  }
18
17
  const table = new Table({
19
18
  head: ["Extension", "Publisher", "Instance ID", "State", "Version", "Your last update"],
20
19
  style: { head: ["yellow"] },
21
20
  });
22
21
  const sorted = _.sortBy(instances, "createTime", "asc").reverse();
22
+ const formatted = [];
23
23
  sorted.forEach((instance) => {
24
+ var _a, _b, _c, _d;
24
25
  let extension = _.get(instance, "config.extensionRef", "");
25
26
  let publisher;
26
27
  if (extension === "") {
@@ -30,18 +31,22 @@ async function listExtensions(projectId) {
30
31
  else {
31
32
  publisher = extension.split("/")[0];
32
33
  }
33
- table.push([
34
+ const instanceId = (_a = _.last(instance.name.split("/"))) !== null && _a !== void 0 ? _a : "";
35
+ const state = instance.state +
36
+ (_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : "");
37
+ const version = (_d = (_c = (_b = instance === null || instance === void 0 ? void 0 : instance.config) === null || _b === void 0 ? void 0 : _b.source) === null || _c === void 0 ? void 0 : _c.spec) === null || _d === void 0 ? void 0 : _d.version;
38
+ const updateTime = extensionsUtils.formatTimestamp(instance.updateTime);
39
+ table.push([extension, publisher, instanceId, state, version, updateTime]);
40
+ formatted.push({
34
41
  extension,
35
42
  publisher,
36
- _.last(instance.name.split("/")),
37
- instance.state +
38
- (_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : ""),
39
- _.get(instance, "config.source.spec.version", ""),
40
- extensionsUtils.formatTimestamp(instance.updateTime),
41
- ]);
43
+ instanceId,
44
+ state,
45
+ version,
46
+ updateTime,
47
+ });
42
48
  });
43
49
  utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
44
- logger_1.logger.info(table.toString());
45
- return { instances: sorted };
50
+ return formatted;
46
51
  }
47
52
  exports.listExtensions = listExtensions;
@@ -4,13 +4,13 @@ exports.readEnvFile = exports.getParamsFromFile = exports.promptForNewParams = e
4
4
  const _ = require("lodash");
5
5
  const path = require("path");
6
6
  const clc = require("cli-color");
7
- const dotenv = require("dotenv");
8
7
  const fs = require("fs-extra");
9
8
  const error_1 = require("../error");
10
9
  const logger_1 = require("../logger");
11
10
  const extensionsHelper_1 = require("./extensionsHelper");
12
11
  const askUserForParam = require("./askUserForParam");
13
12
  const track = require("../track");
13
+ const env = require("../functions/env");
14
14
  function setNewDefaults(params, newDefaults) {
15
15
  params.forEach((param) => {
16
16
  if (newDefaults[param.param.toUpperCase()]) {
@@ -42,7 +42,6 @@ async function getParams(args) {
42
42
  params = getParamsFromFile({
43
43
  projectId: args.projectId,
44
44
  paramSpecs: args.paramSpecs,
45
- noninteractive: args.nonInteractive,
46
45
  paramsEnvPath: args.paramsEnvPath,
47
46
  });
48
47
  }
@@ -71,7 +70,6 @@ async function getParamsForUpdate(args) {
71
70
  params = getParamsFromFile({
72
71
  projectId: args.projectId,
73
72
  paramSpecs: args.newSpec.params,
74
- noninteractive: args.nonInteractive,
75
73
  paramsEnvPath: args.paramsEnvPath,
76
74
  });
77
75
  }
@@ -132,6 +130,10 @@ function getParamsFromFile(args) {
132
130
  exports.getParamsFromFile = getParamsFromFile;
133
131
  function readEnvFile(envPath) {
134
132
  const buf = fs.readFileSync(path.resolve(envPath), "utf8");
135
- return dotenv.parse(buf.toString().trim(), { debug: true });
133
+ const result = env.parse(buf.toString().trim());
134
+ if (result.errors.length) {
135
+ throw new error_1.FirebaseError(`Error while parsing ${envPath} - unable to parse following lines:\n${result.errors.join("\n")}`);
136
+ }
137
+ return result.envs;
136
138
  }
137
139
  exports.readEnvFile = readEnvFile;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUsedProducts = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
3
+ exports.getUsedProducts = exports.bulkCheckProductsProvisioned = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
4
4
  const marked = require("marked");
5
5
  const api = require("../api");
6
+ const functional_1 = require("../functional");
6
7
  const error_1 = require("../error");
8
+ const planner_1 = require("../deploy/extensions/planner");
7
9
  var DeferredProduct;
8
10
  (function (DeferredProduct) {
9
11
  DeferredProduct[DeferredProduct["STORAGE"] = 0] = "STORAGE";
@@ -11,6 +13,18 @@ var DeferredProduct;
11
13
  })(DeferredProduct = exports.DeferredProduct || (exports.DeferredProduct = {}));
12
14
  async function checkProductsProvisioned(projectId, spec) {
13
15
  const usedProducts = getUsedProducts(spec);
16
+ await checkProducts(projectId, usedProducts);
17
+ }
18
+ exports.checkProductsProvisioned = checkProductsProvisioned;
19
+ async function bulkCheckProductsProvisioned(projectId, instanceSpecs) {
20
+ const usedProducts = await Promise.all(instanceSpecs.map(async (i) => {
21
+ const extensionVersion = await planner_1.getExtensionVersion(i);
22
+ return getUsedProducts(extensionVersion.spec);
23
+ }));
24
+ await checkProducts(projectId, [...functional_1.flattenArray(usedProducts)]);
25
+ }
26
+ exports.bulkCheckProductsProvisioned = bulkCheckProductsProvisioned;
27
+ async function checkProducts(projectId, usedProducts) {
14
28
  const needProvisioning = [];
15
29
  let isStorageProvisionedPromise;
16
30
  let isAuthProvisionedPromise;
@@ -29,7 +43,7 @@ async function checkProductsProvisioned(projectId, spec) {
29
43
  if (needProvisioning.length > 0) {
30
44
  let errorMessage = "Some services used by this extension have not been set up on your " +
31
45
  "Firebase project. To ensure this extension works as intended, you must enable these " +
32
- "services by following the provided links, then retry installing the extension\n\n";
46
+ "services by following the provided links, then retry this command\n\n";
33
47
  if (needProvisioning.includes(DeferredProduct.STORAGE)) {
34
48
  errorMessage +=
35
49
  " - Firebase Storage: store and retrieve user-generated files like images, audio, and " +
@@ -46,7 +60,6 @@ async function checkProductsProvisioned(projectId, spec) {
46
60
  throw new error_1.FirebaseError(marked(errorMessage), { exit: 2 });
47
61
  }
48
62
  }
49
- exports.checkProductsProvisioned = checkProductsProvisioned;
50
63
  function getUsedProducts(spec) {
51
64
  var _a, _b;
52
65
  const usedProducts = [];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
3
+ exports.equal = exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
4
4
  const semver = require("semver");
5
5
  const error_1 = require("../error");
6
6
  const refRegex = new RegExp(/^([^/@\n]+)\/{1}([^/@\n]+)(@{1}([^\n]+)|)$/);
@@ -57,3 +57,11 @@ function toExtensionVersionName(ref) {
57
57
  return `publishers/${ref.publisherId}/extensions/${ref.extensionId}/versions/${ref.version}`;
58
58
  }
59
59
  exports.toExtensionVersionName = toExtensionVersionName;
60
+ function equal(a, b) {
61
+ return (!!a &&
62
+ !!b &&
63
+ a.publisherId === b.publisherId &&
64
+ a.extensionId === b.extensionId &&
65
+ a.version === b.version);
66
+ }
67
+ exports.equal = equal;