firebase-tools 10.1.5 → 10.2.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 (134) hide show
  1. package/lib/api.js +1 -0
  2. package/lib/apiv2.js +3 -0
  3. package/lib/appdistribution/options-parser-util.js +1 -1
  4. package/lib/auth.js +62 -25
  5. package/lib/command.js +1 -1
  6. package/lib/commands/apps-android-sha-create.js +2 -2
  7. package/lib/commands/apps-sdkconfig.js +1 -1
  8. package/lib/commands/auth-import.js +1 -1
  9. package/lib/commands/database-rules-list.js +2 -2
  10. package/lib/commands/emulators-start.js +1 -1
  11. package/lib/commands/ext-configure.js +1 -0
  12. package/lib/commands/ext-dev-init.js +49 -49
  13. package/lib/commands/ext-export.js +12 -2
  14. package/lib/commands/ext-install.js +104 -103
  15. package/lib/commands/ext-uninstall.js +9 -8
  16. package/lib/commands/ext-update.js +10 -9
  17. package/lib/commands/functions-config-clone.js +1 -1
  18. package/lib/commands/functions-config-export.js +1 -1
  19. package/lib/commands/functions-secrets-access.js +17 -0
  20. package/lib/commands/functions-secrets-destroy.js +40 -0
  21. package/lib/commands/functions-secrets-get.js +21 -0
  22. package/lib/commands/functions-secrets-prune.js +50 -0
  23. package/lib/commands/functions-secrets-set.js +46 -0
  24. package/lib/commands/hosting-clone.js +3 -3
  25. package/lib/commands/index.js +7 -3
  26. package/lib/commands/login.js +1 -1
  27. package/lib/commands/remoteconfig-get.js +1 -1
  28. package/lib/deploy/extensions/deploymentSummary.js +3 -3
  29. package/lib/deploy/extensions/params.js +3 -0
  30. package/lib/deploy/extensions/planner.js +2 -1
  31. package/lib/deploy/extensions/tasks.js +1 -1
  32. package/lib/deploy/functions/backend.js +20 -5
  33. package/lib/deploy/functions/checkIam.js +1 -1
  34. package/lib/deploy/functions/containerCleaner.js +3 -3
  35. package/lib/deploy/functions/ensure.js +112 -0
  36. package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
  37. package/lib/deploy/functions/functionsDeployHelper.js +2 -2
  38. package/lib/deploy/functions/prepare.js +15 -20
  39. package/lib/deploy/functions/pricing.js +1 -1
  40. package/lib/deploy/functions/prompts.js +2 -2
  41. package/lib/deploy/functions/release/fabricator.js +3 -3
  42. package/lib/deploy/functions/release/index.js +1 -1
  43. package/lib/deploy/functions/release/planner.js +11 -8
  44. package/lib/deploy/functions/release/reporter.js +3 -0
  45. package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
  46. package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
  47. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +17 -11
  48. package/lib/deploy/functions/runtimes/golang/index.js +2 -2
  49. package/lib/deploy/functions/runtimes/node/index.js +26 -0
  50. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
  51. package/lib/deploy/functions/runtimes/node/parseTriggers.js +40 -7
  52. package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
  53. package/lib/deploy/functions/validate.js +58 -3
  54. package/lib/deploy/hosting/client.js +9 -0
  55. package/lib/deploy/hosting/convertConfig.js +6 -0
  56. package/lib/deploy/hosting/deploy.js +2 -2
  57. package/lib/deploy/hosting/hashcache.js +21 -19
  58. package/lib/deploy/hosting/index.js +5 -5
  59. package/lib/deploy/hosting/prepare.js +25 -25
  60. package/lib/deploy/hosting/release.js +21 -24
  61. package/lib/deploy/hosting/uploader.js +5 -5
  62. package/lib/deploy/remoteconfig/functions.js +2 -2
  63. package/lib/emulator/auth/cloudFunctions.js +1 -1
  64. package/lib/emulator/auth/operations.js +1 -1
  65. package/lib/emulator/commandUtils.js +5 -1
  66. package/lib/emulator/constants.js +3 -0
  67. package/lib/emulator/controller.js +48 -18
  68. package/lib/emulator/download.js +18 -1
  69. package/lib/emulator/downloadableEmulators.js +30 -13
  70. package/lib/emulator/emulatorLogger.js +19 -1
  71. package/lib/emulator/extensions/validation.js +35 -0
  72. package/lib/emulator/extensionsEmulator.js +140 -0
  73. package/lib/emulator/functionsEmulator.js +175 -86
  74. package/lib/emulator/functionsEmulatorRuntime.js +108 -83
  75. package/lib/emulator/functionsEmulatorShared.js +51 -1
  76. package/lib/emulator/functionsEmulatorShell.js +1 -2
  77. package/lib/emulator/functionsEmulatorUtils.js +4 -4
  78. package/lib/emulator/functionsRuntimeWorker.js +3 -3
  79. package/lib/emulator/hub.js +4 -3
  80. package/lib/emulator/loggingEmulator.js +1 -1
  81. package/lib/emulator/pubsubEmulator.js +1 -1
  82. package/lib/emulator/registry.js +10 -2
  83. package/lib/emulator/storage/apis/firebase.js +31 -26
  84. package/lib/emulator/storage/apis/gcloud.js +7 -12
  85. package/lib/emulator/storage/files.js +36 -34
  86. package/lib/emulator/storage/index.js +2 -2
  87. package/lib/emulator/storage/metadata.js +2 -2
  88. package/lib/emulator/storage/rules/runtime.js +8 -7
  89. package/lib/emulator/types.js +3 -0
  90. package/lib/ensureApiEnabled.js +5 -1
  91. package/lib/error.js +1 -1
  92. package/lib/extensions/askUserForParam.js +2 -2
  93. package/lib/extensions/changelog.js +3 -1
  94. package/lib/extensions/checkProjectBilling.js +1 -1
  95. package/lib/extensions/diagnose.js +56 -0
  96. package/lib/extensions/displayExtensionInfo.js +1 -1
  97. package/lib/extensions/emulator/optionsHelper.js +24 -8
  98. package/lib/extensions/emulator/specHelper.js +10 -23
  99. package/lib/extensions/export.js +1 -51
  100. package/lib/extensions/extensionsApi.js +1 -1
  101. package/lib/extensions/extensionsHelper.js +23 -10
  102. package/lib/extensions/listExtensions.js +2 -0
  103. package/lib/extensions/manifest.js +48 -0
  104. package/lib/extensions/metricsUtils.js +4 -4
  105. package/lib/extensions/paramHelper.js +4 -4
  106. package/lib/extensions/refs.js +1 -1
  107. package/lib/extensions/secretsUtils.js +4 -4
  108. package/lib/functional.js +1 -1
  109. package/lib/functions/env.js +7 -8
  110. package/lib/functions/secrets.js +112 -0
  111. package/lib/gcp/cloudfunctions.js +24 -5
  112. package/lib/gcp/cloudfunctionsv2.js +18 -5
  113. package/lib/gcp/cloudtasks.js +1 -1
  114. package/lib/gcp/docker.js +2 -2
  115. package/lib/gcp/run.js +2 -2
  116. package/lib/gcp/secretManager.js +128 -46
  117. package/lib/gcp/storage.js +1 -0
  118. package/lib/hosting/api.js +1 -1
  119. package/lib/hosting/functionsProxy.js +15 -5
  120. package/lib/hosting/proxy.js +2 -2
  121. package/lib/init/features/account.js +1 -1
  122. package/lib/management/database.js +1 -1
  123. package/lib/previews.js +1 -1
  124. package/lib/responseToError.js +16 -7
  125. package/lib/serve/functions.js +2 -2
  126. package/lib/serve/hosting.js +1 -1
  127. package/lib/utils.js +7 -2
  128. package/npm-shrinkwrap.json +904 -412
  129. package/package.json +3 -3
  130. package/schema/firebase-config.json +32 -0
  131. package/templates/init/functions/javascript/package.lint.json +3 -3
  132. package/templates/init/functions/javascript/package.nolint.json +2 -2
  133. package/templates/init/functions/typescript/package.lint.json +7 -7
  134. package/templates/init/functions/typescript/package.nolint.json +3 -3
@@ -92,7 +92,7 @@ async function getIamPolicy(fnName) {
92
92
  }
93
93
  exports.getIamPolicy = getIamPolicy;
94
94
  async function setInvokerCreate(projectId, fnName, invoker) {
95
- if (invoker.length == 0) {
95
+ if (invoker.length === 0) {
96
96
  throw new error_1.FirebaseError("Invoker cannot be an empty array");
97
97
  }
98
98
  const invokerMembers = proto.getInvokerMembers(invoker, projectId);
@@ -108,7 +108,7 @@ async function setInvokerCreate(projectId, fnName, invoker) {
108
108
  exports.setInvokerCreate = setInvokerCreate;
109
109
  async function setInvokerUpdate(projectId, fnName, invoker) {
110
110
  var _a;
111
- if (invoker.length == 0) {
111
+ if (invoker.length === 0) {
112
112
  throw new error_1.FirebaseError("Invoker cannot be an empty array");
113
113
  }
114
114
  const invokerMembers = proto.getInvokerMembers(invoker, projectId);
@@ -206,6 +206,7 @@ function endpointFromFunction(gcfFunction) {
206
206
  const [, project, , region, , id] = gcfFunction.name.split("/");
207
207
  let trigger;
208
208
  let uri;
209
+ let securityLevel;
209
210
  if ((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) {
210
211
  trigger = {
211
212
  scheduleTrigger: {},
@@ -219,6 +220,7 @@ function endpointFromFunction(gcfFunction) {
219
220
  else if (gcfFunction.httpsTrigger) {
220
221
  trigger = { httpsTrigger: {} };
221
222
  uri = gcfFunction.httpsTrigger.url;
223
+ securityLevel = gcfFunction.httpsTrigger.securityLevel;
222
224
  }
223
225
  else {
224
226
  trigger = {
@@ -240,12 +242,19 @@ function endpointFromFunction(gcfFunction) {
240
242
  if (uri) {
241
243
  endpoint.uri = uri;
242
244
  }
243
- proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "labels", "environmentVariables", "sourceUploadUrl");
245
+ if (securityLevel) {
246
+ endpoint.securityLevel = securityLevel;
247
+ }
248
+ proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
249
+ if (gcfFunction.vpcConnector) {
250
+ endpoint.vpc = { connector: gcfFunction.vpcConnector };
251
+ proto.renameIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings");
252
+ }
244
253
  return endpoint;
245
254
  }
246
255
  exports.endpointFromFunction = endpointFromFunction;
247
256
  function functionFromEndpoint(endpoint, sourceUploadUrl) {
248
- if (endpoint.platform != "gcfv1") {
257
+ if (endpoint.platform !== "gcfv1") {
249
258
  throw new error_1.FirebaseError("Trying to create a v1 CloudFunction with v2 API. This should never happen");
250
259
  }
251
260
  if (!runtimes.isValidRuntime(endpoint.runtime)) {
@@ -282,8 +291,18 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
282
291
  }
283
292
  else {
284
293
  gcfFunction.httpsTrigger = {};
294
+ if (backend.isCallableTriggered(endpoint)) {
295
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callabled": "true" });
296
+ }
297
+ if (endpoint.securityLevel) {
298
+ gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel;
299
+ }
300
+ }
301
+ proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
302
+ if (endpoint.vpc) {
303
+ proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
304
+ proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
285
305
  }
286
- proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
287
306
  return gcfFunction;
288
307
  }
289
308
  exports.functionFromEndpoint = functionFromEndpoint;
@@ -106,7 +106,7 @@ async function listFunctionsInternal(projectId, region) {
106
106
  let pageToken = "";
107
107
  while (true) {
108
108
  const url = `projects/${projectId}/locations/${region}/functions`;
109
- const opts = pageToken == "" ? {} : { queryParams: { pageToken } };
109
+ const opts = pageToken === "" ? {} : { queryParams: { pageToken } };
110
110
  const res = await client.get(url, opts);
111
111
  functions.push(...(res.body.functions || []));
112
112
  for (const region of res.body.unreachable || []) {
@@ -122,9 +122,10 @@ async function listFunctionsInternal(projectId, region) {
122
122
  }
123
123
  }
124
124
  async function updateFunction(cloudFunction) {
125
+ const fieldMasks = proto.fieldMasks(cloudFunction, "labels", "serviceConfig.environmentVariables");
125
126
  try {
126
127
  const queryParams = {
127
- updateMask: proto.fieldMasks(cloudFunction).join(","),
128
+ updateMask: fieldMasks.join(","),
128
129
  };
129
130
  const res = await client.patch(cloudFunction.name, cloudFunction, { queryParams });
130
131
  return res.body;
@@ -145,7 +146,7 @@ async function deleteFunction(cloudFunction) {
145
146
  }
146
147
  exports.deleteFunction = deleteFunction;
147
148
  function functionFromEndpoint(endpoint, source) {
148
- if (endpoint.platform != "gcfv2") {
149
+ if (endpoint.platform !== "gcfv2") {
149
150
  throw new error_1.FirebaseError("Trying to create a v2 CloudFunction with v1 API. This should never happen");
150
151
  }
151
152
  if (!runtimes.isValidRuntime(endpoint.runtime)) {
@@ -165,11 +166,15 @@ function functionFromEndpoint(endpoint, source) {
165
166
  serviceConfig: {},
166
167
  };
167
168
  proto.copyIfPresent(gcfFunction, endpoint, "labels");
168
- proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "vpcConnector", "vpcConnectorEgressSettings", "serviceAccountEmail", "ingressSettings");
169
+ proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "serviceAccountEmail", "ingressSettings");
169
170
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "availableMemory", "availableMemoryMb", (mb) => `${mb}M`);
170
171
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "timeoutSeconds", "timeout", proto.secondsFromDuration);
171
172
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances");
172
173
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances");
174
+ if (endpoint.vpc) {
175
+ proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnector", "connector");
176
+ proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
177
+ }
173
178
  if (backend.isEventTriggered(endpoint)) {
174
179
  gcfFunction.eventTrigger = {
175
180
  eventType: endpoint.eventTrigger.eventType,
@@ -187,6 +192,7 @@ function functionFromEndpoint(endpoint, source) {
187
192
  if (endpoint.eventTrigger.retry) {
188
193
  logger_1.logger.warn("Cannot set a retry policy on Cloud Function", endpoint.id);
189
194
  }
195
+ gcfFunction.serviceConfig.environmentVariables = Object.assign(Object.assign({}, gcfFunction.serviceConfig.environmentVariables), { FUNCTION_SIGNATURE_TYPE: "cloudevent" });
190
196
  }
191
197
  else if (backend.isScheduleTriggered(endpoint)) {
192
198
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-scheduled": "true" });
@@ -194,6 +200,9 @@ function functionFromEndpoint(endpoint, source) {
194
200
  else if (backend.isTaskQueueTriggered(endpoint)) {
195
201
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-taskqueue": "true" });
196
202
  }
203
+ else if (backend.isCallableTriggered(endpoint)) {
204
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" });
205
+ }
197
206
  return gcfFunction;
198
207
  }
199
208
  exports.functionFromEndpoint = functionFromEndpoint;
@@ -238,12 +247,16 @@ function endpointFromFunction(gcfFunction) {
238
247
  const endpoint = Object.assign(Object.assign({ platform: "gcfv2", id,
239
248
  project,
240
249
  region }, trigger), { entryPoint: gcfFunction.buildConfig.entryPoint, runtime: gcfFunction.buildConfig.runtime, uri: gcfFunction.serviceConfig.uri });
241
- proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "vpcConnector", "vpcConnectorEgressSettings", "ingressSettings", "environmentVariables");
250
+ proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", "environmentVariables");
242
251
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "availableMemoryMb", "availableMemory", megabytes);
243
252
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "timeout", "timeoutSeconds", proto.durationFromSeconds);
244
253
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount");
245
254
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount");
246
255
  proto.copyIfPresent(endpoint, gcfFunction, "labels");
256
+ if (gcfFunction.serviceConfig.vpcConnector) {
257
+ endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector };
258
+ proto.renameIfPresent(endpoint.vpc, gcfFunction.serviceConfig, "egressSettings", "vpcConnectorEgressSettings");
259
+ }
247
260
  return endpoint;
248
261
  }
249
262
  exports.endpointFromFunction = endpointFromFunction;
@@ -102,7 +102,7 @@ async function setEnqueuer(name, invoker, assumeEmpty = false) {
102
102
  const invokerMembers = proto.getInvokerMembers(invoker, project);
103
103
  while (true) {
104
104
  const policy = {
105
- bindings: existing.bindings.filter((binding) => binding.role != ENQUEUER_ROLE),
105
+ bindings: existing.bindings.filter((binding) => binding.role !== ENQUEUER_ROLE),
106
106
  etag: existing.etag,
107
107
  version: existing.version,
108
108
  };
package/lib/gcp/docker.js CHANGED
@@ -58,7 +58,7 @@ class Client {
58
58
  if (!response.body) {
59
59
  return;
60
60
  }
61
- if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
61
+ if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) !== 0) {
62
62
  throw new error_1.FirebaseError(`Failed to delete tag ${tag} at path ${path}`, {
63
63
  children: response.body.errors,
64
64
  });
@@ -70,7 +70,7 @@ class Client {
70
70
  if (!response.body) {
71
71
  return;
72
72
  }
73
- if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
73
+ if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) !== 0) {
74
74
  throw new error_1.FirebaseError(`Failed to delete image ${digest} at path ${path}`, {
75
75
  children: response.body.errors,
76
76
  });
package/lib/gcp/run.js CHANGED
@@ -63,7 +63,7 @@ async function getIamPolicy(serviceName, httpClient = client) {
63
63
  }
64
64
  exports.getIamPolicy = getIamPolicy;
65
65
  async function setInvokerCreate(projectId, serviceName, invoker, httpClient = client) {
66
- if (invoker.length == 0) {
66
+ if (invoker.length === 0) {
67
67
  throw new error_1.FirebaseError("Invoker cannot be an empty array");
68
68
  }
69
69
  const invokerMembers = proto.getInvokerMembers(invoker, projectId);
@@ -79,7 +79,7 @@ async function setInvokerCreate(projectId, serviceName, invoker, httpClient = cl
79
79
  exports.setInvokerCreate = setInvokerCreate;
80
80
  async function setInvokerUpdate(projectId, serviceName, invoker, httpClient = client) {
81
81
  var _a;
82
- if (invoker.length == 0) {
82
+ if (invoker.length === 0) {
83
83
  throw new error_1.FirebaseError("Invoker cannot be an empty array");
84
84
  }
85
85
  const invokerMembers = proto.getInvokerMembers(invoker, projectId);
@@ -1,30 +1,88 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.grantServiceAgentRole = exports.addVersion = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.getSecretVersion = exports.getSecret = exports.listSecrets = exports.secretManagerConsoleUri = void 0;
3
+ exports.ensureServiceAgentRole = exports.setIamPolicy = exports.getIamPolicy = exports.addVersion = exports.deleteSecret = exports.patchSecret = exports.createSecret = exports.toSecretVersionResourceName = exports.parseSecretVersionResourceName = exports.parseSecretResourceName = exports.secretExists = exports.destroySecretVersion = exports.accessSecretVersion = exports.getSecretVersion = exports.listSecretVersions = exports.listSecrets = exports.getSecret = exports.secretManagerConsoleUri = void 0;
4
4
  const utils_1 = require("../utils");
5
- const api_1 = require("../api");
5
+ const error_1 = require("../error");
6
6
  const apiv2_1 = require("../apiv2");
7
+ const api_1 = require("../api");
8
+ const SECRET_NAME_REGEX = new RegExp("projects\\/" +
9
+ "(?<project>(?:\\d+)|(?:[A-Za-z]+[A-Za-z\\d-]*[A-Za-z\\d]?))\\/" +
10
+ "secrets\\/" +
11
+ "(?<secret>[A-Za-z\\d\\-_]+)");
12
+ const SECRET_VERSION_NAME_REGEX = new RegExp(SECRET_NAME_REGEX.source + "\\/versions\\/" + "(?<version>latest|[0-9]+)");
7
13
  const secretManagerConsoleUri = (projectId) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`;
8
14
  exports.secretManagerConsoleUri = secretManagerConsoleUri;
9
- const apiClient = new apiv2_1.Client({ urlPrefix: api_1.secretManagerOrigin, apiVersion: "v1beta1" });
10
- async function listSecrets(projectId) {
11
- const listRes = await apiClient.get(`/projects/${projectId}/secrets`);
12
- return listRes.body.secrets.map((s) => parseSecretResourceName(s.name));
13
- }
14
- exports.listSecrets = listSecrets;
15
+ const API_VERSION = "v1";
16
+ const client = new apiv2_1.Client({ urlPrefix: api_1.secretManagerOrigin, apiVersion: API_VERSION });
15
17
  async function getSecret(projectId, name) {
16
18
  var _a;
17
- const getRes = await apiClient.get(`/projects/${projectId}/secrets/${name}`);
19
+ const getRes = await client.get(`projects/${projectId}/secrets/${name}`);
18
20
  const secret = parseSecretResourceName(getRes.body.name);
19
21
  secret.labels = (_a = getRes.body.labels) !== null && _a !== void 0 ? _a : {};
20
22
  return secret;
21
23
  }
22
24
  exports.getSecret = getSecret;
25
+ async function listSecrets(projectId, filter) {
26
+ var _a;
27
+ const secrets = [];
28
+ const path = `projects/${projectId}/secrets`;
29
+ const baseOpts = filter ? { queryParams: { filter } } : {};
30
+ let pageToken = "";
31
+ while (true) {
32
+ const opts = pageToken === ""
33
+ ? baseOpts
34
+ : Object.assign(Object.assign({}, baseOpts), { queryParams: Object.assign(Object.assign({}, baseOpts === null || baseOpts === void 0 ? void 0 : baseOpts.queryParams), { pageToken }) });
35
+ const res = await client.get(path, opts);
36
+ for (const s of res.body.secrets) {
37
+ secrets.push(Object.assign(Object.assign({}, parseSecretResourceName(s.name)), { labels: (_a = s.labels) !== null && _a !== void 0 ? _a : {} }));
38
+ }
39
+ if (!res.body.nextPageToken) {
40
+ break;
41
+ }
42
+ pageToken = res.body.nextPageToken;
43
+ }
44
+ return secrets;
45
+ }
46
+ exports.listSecrets = listSecrets;
47
+ async function listSecretVersions(projectId, name, filter) {
48
+ const secrets = [];
49
+ const path = `projects/${projectId}/secrets/${name}/versions`;
50
+ const baseOpts = filter ? { queryParams: { filter } } : {};
51
+ let pageToken = "";
52
+ while (true) {
53
+ const opts = pageToken === ""
54
+ ? baseOpts
55
+ : Object.assign(Object.assign({}, baseOpts), { queryParams: Object.assign(Object.assign({}, baseOpts === null || baseOpts === void 0 ? void 0 : baseOpts.queryParams), { pageToken }) });
56
+ const res = await client.get(path, opts);
57
+ for (const s of res.body.versions || []) {
58
+ secrets.push(Object.assign(Object.assign({}, parseSecretVersionResourceName(s.name)), { state: s.state }));
59
+ }
60
+ if (!res.body.nextPageToken) {
61
+ break;
62
+ }
63
+ pageToken = res.body.nextPageToken;
64
+ }
65
+ return secrets;
66
+ }
67
+ exports.listSecretVersions = listSecretVersions;
23
68
  async function getSecretVersion(projectId, name, version) {
24
- const getRes = await apiClient.get(`/projects/${projectId}/secrets/${name}/versions/${version}`);
25
- return parseSecretVersionResourceName(getRes.body.name);
69
+ const getRes = await client.get(`projects/${projectId}/secrets/${name}/versions/${version}`);
70
+ return Object.assign(Object.assign({}, parseSecretVersionResourceName(getRes.body.name)), { state: getRes.body.state });
26
71
  }
27
72
  exports.getSecretVersion = getSecretVersion;
73
+ async function accessSecretVersion(projectId, name, version) {
74
+ const res = await client.get(`projects/${projectId}/secrets/${name}/versions/${version}:access`);
75
+ return Buffer.from(res.body.payload.data, "base64").toString();
76
+ }
77
+ exports.accessSecretVersion = accessSecretVersion;
78
+ async function destroySecretVersion(projectId, name, version) {
79
+ if (version === "latest") {
80
+ const sv = await getSecretVersion(projectId, name, "latest");
81
+ version = sv.versionId;
82
+ }
83
+ await client.post(`projects/${projectId}/secrets/${name}/versions/${version}:destroy`);
84
+ }
85
+ exports.destroySecretVersion = destroySecretVersion;
28
86
  async function secretExists(projectId, name) {
29
87
  try {
30
88
  await getSecret(projectId, name);
@@ -39,21 +97,27 @@ async function secretExists(projectId, name) {
39
97
  }
40
98
  exports.secretExists = secretExists;
41
99
  function parseSecretResourceName(resourceName) {
42
- const nameTokens = resourceName.split("/");
100
+ const match = SECRET_NAME_REGEX.exec(resourceName);
101
+ if (!(match === null || match === void 0 ? void 0 : match.groups)) {
102
+ throw new error_1.FirebaseError(`Invalid secret resource name [${resourceName}].`);
103
+ }
43
104
  return {
44
- projectId: nameTokens[1],
45
- name: nameTokens[3],
105
+ projectId: match.groups.project,
106
+ name: match.groups.secret,
46
107
  };
47
108
  }
48
109
  exports.parseSecretResourceName = parseSecretResourceName;
49
110
  function parseSecretVersionResourceName(resourceName) {
50
- const nameTokens = resourceName.split("/");
111
+ const match = resourceName.match(SECRET_VERSION_NAME_REGEX);
112
+ if (!(match === null || match === void 0 ? void 0 : match.groups)) {
113
+ throw new error_1.FirebaseError(`Invalid secret version resource name [${resourceName}].`);
114
+ }
51
115
  return {
52
116
  secret: {
53
- projectId: nameTokens[1],
54
- name: nameTokens[3],
117
+ projectId: match.groups.project,
118
+ name: match.groups.secret,
55
119
  },
56
- versionId: nameTokens[5],
120
+ versionId: match.groups.version,
57
121
  };
58
122
  }
59
123
  exports.parseSecretVersionResourceName = parseSecretVersionResourceName;
@@ -62,7 +126,8 @@ function toSecretVersionResourceName(secretVersion) {
62
126
  }
63
127
  exports.toSecretVersionResourceName = toSecretVersionResourceName;
64
128
  async function createSecret(projectId, name, labels) {
65
- const createRes = await apiClient.post(`/projects/${projectId}/secrets`, {
129
+ const createRes = await client.post(`projects/${projectId}/secrets`, {
130
+ name,
66
131
  replication: {
67
132
  automatic: {},
68
133
  },
@@ -71,41 +136,58 @@ async function createSecret(projectId, name, labels) {
71
136
  return parseSecretResourceName(createRes.body.name);
72
137
  }
73
138
  exports.createSecret = createSecret;
74
- async function addVersion(secret, payloadData) {
75
- const res = await apiClient.post(`/projects/${secret.projectId}/secrets/${secret.name}:addVersion`, {
139
+ async function patchSecret(projectId, name, labels) {
140
+ const fullName = `projects/${projectId}/secrets/${name}`;
141
+ const res = await client.patch(fullName, { name: fullName, labels }, { queryParams: { updateMask: "labels" } });
142
+ return parseSecretResourceName(res.body.name);
143
+ }
144
+ exports.patchSecret = patchSecret;
145
+ async function deleteSecret(projectId, name) {
146
+ const path = `projects/${projectId}/secrets/${name}`;
147
+ await client.delete(path);
148
+ }
149
+ exports.deleteSecret = deleteSecret;
150
+ async function addVersion(projectId, name, payloadData) {
151
+ const res = await client.post(`projects/${projectId}/secrets/${name}:addVersion`, {
76
152
  payload: {
77
153
  data: Buffer.from(payloadData).toString("base64"),
78
154
  },
79
155
  });
80
- const nameTokens = res.body.name.split("/");
81
- return {
82
- secret: {
83
- projectId: nameTokens[1],
84
- name: nameTokens[3],
85
- },
86
- versionId: nameTokens[5],
87
- };
156
+ return Object.assign(Object.assign({}, parseSecretVersionResourceName(res.body.name)), { state: res.body.state });
88
157
  }
89
158
  exports.addVersion = addVersion;
90
- async function grantServiceAgentRole(secret, serviceAccountEmail, role) {
91
- const getPolicyRes = await apiClient.get(`/projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`);
92
- const bindings = getPolicyRes.body.bindings || [];
93
- if (bindings.find((b) => b.role == role &&
94
- b.members.find((m) => m == `serviceAccount:${serviceAccountEmail}`))) {
95
- return;
96
- }
97
- bindings.push({
98
- role: role,
99
- members: [`serviceAccount:${serviceAccountEmail}`],
100
- });
101
- await apiClient.post(`/projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
159
+ async function getIamPolicy(secret) {
160
+ const res = await client.get(`projects/${secret.projectId}/secrets/${secret.name}:getIamPolicy`);
161
+ return res.body;
162
+ }
163
+ exports.getIamPolicy = getIamPolicy;
164
+ async function setIamPolicy(secret, bindings) {
165
+ await client.post(`projects/${secret.projectId}/secrets/${secret.name}:setIamPolicy`, {
102
166
  policy: {
103
167
  bindings,
104
168
  },
105
- updateMask: {
106
- paths: "bindings",
107
- },
169
+ updateMask: "bindings",
108
170
  });
109
- (0, utils_1.logLabeledSuccess)("SecretManager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmail}`);
110
171
  }
111
- exports.grantServiceAgentRole = grantServiceAgentRole;
172
+ exports.setIamPolicy = setIamPolicy;
173
+ async function ensureServiceAgentRole(secret, serviceAccountEmails, role) {
174
+ const policy = await module.exports.getIamPolicy(secret);
175
+ const bindings = policy.bindings || [];
176
+ let binding = bindings.find((b) => b.role === role);
177
+ if (!binding) {
178
+ binding = { role, members: [] };
179
+ bindings.push(binding);
180
+ }
181
+ let shouldShortCircuit = true;
182
+ for (const serviceAccount of serviceAccountEmails) {
183
+ if (!binding.members.find((m) => m === `serviceAccount:${serviceAccount}`)) {
184
+ binding.members.push(`serviceAccount:${serviceAccount}`);
185
+ shouldShortCircuit = false;
186
+ }
187
+ }
188
+ if (shouldShortCircuit)
189
+ return;
190
+ await module.exports.setIamPolicy(secret, bindings);
191
+ (0, utils_1.logLabeledSuccess)("secretmanager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmails.join(", ")}`);
192
+ }
193
+ exports.ensureServiceAgentRole = ensureServiceAgentRole;
@@ -29,6 +29,7 @@ async function upload(source, uploadUrl, extraHeaders) {
29
29
  method: "PUT",
30
30
  path: url.pathname,
31
31
  queryParams: url.searchParams,
32
+ responseType: "xml",
32
33
  headers: Object.assign({ "content-type": "application/zip" }, extraHeaders),
33
34
  body: source.stream,
34
35
  skipLog: { resBody: true },
@@ -192,7 +192,7 @@ async function removeAuthDomain(project, url) {
192
192
  return domains;
193
193
  }
194
194
  const targetDomain = url.replace("https://", "");
195
- const authDomains = domains.filter((domain) => domain != targetDomain);
195
+ const authDomains = domains.filter((domain) => domain !== targetDomain);
196
196
  return (0, auth_1.updateAuthDomains)(project, authDomains);
197
197
  }
198
198
  exports.removeAuthDomain = removeAuthDomain;
@@ -1,26 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.functionsProxy = void 0;
3
4
  const lodash_1 = require("lodash");
4
5
  const proxy_1 = require("./proxy");
5
6
  const projectUtils_1 = require("../projectUtils");
6
7
  const registry_1 = require("../emulator/registry");
7
8
  const types_1 = require("../emulator/types");
8
9
  const functionsEmulator_1 = require("../emulator/functionsEmulator");
9
- function default_1(options) {
10
+ const error_1 = require("../error");
11
+ function functionsProxy(options) {
10
12
  return (rewrite) => {
11
13
  return new Promise((resolve) => {
12
14
  const projectId = (0, projectUtils_1.needProjectId)(options);
13
- let url = `https://us-central1-${projectId}.cloudfunctions.net/${rewrite.function}`;
15
+ if (!("function" in rewrite)) {
16
+ throw new error_1.FirebaseError(`A non-function rewrite cannot be used in functionsProxy`, {
17
+ exit: 2,
18
+ });
19
+ }
20
+ if (!rewrite.region) {
21
+ rewrite.region = "us-central1";
22
+ }
23
+ let url = `https://${rewrite.region}-${projectId}.cloudfunctions.net/${rewrite.function}`;
14
24
  let destLabel = "live";
15
25
  if ((0, lodash_1.includes)(options.targets, "functions")) {
16
26
  destLabel = "local";
17
27
  const functionsEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
18
28
  if (functionsEmu) {
19
- url = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(functionsEmu.getInfo().host, functionsEmu.getInfo().port, projectId, rewrite.function, "us-central1");
29
+ url = functionsEmulator_1.FunctionsEmulator.getHttpFunctionUrl(functionsEmu.getInfo().host, functionsEmu.getInfo().port, projectId, rewrite.function, rewrite.region);
20
30
  }
21
31
  }
22
- resolve((0, proxy_1.proxyRequestHandler)(url, `${destLabel} Function ${rewrite.function}`));
32
+ resolve((0, proxy_1.proxyRequestHandler)(url, `${destLabel} Function ${rewrite.region}/${rewrite.function}`));
23
33
  });
24
34
  };
25
35
  }
26
- exports.default = default_1;
36
+ exports.functionsProxy = functionsProxy;
@@ -54,7 +54,7 @@ function proxyRequestHandler(url, rewriteIdentifier) {
54
54
  continue;
55
55
  }
56
56
  const value = req.headers[key];
57
- if (value == undefined) {
57
+ if (value === undefined) {
58
58
  headers.delete(key);
59
59
  }
60
60
  else if (Array.isArray(value)) {
@@ -115,7 +115,7 @@ function proxyRequestHandler(url, rewriteIdentifier) {
115
115
  if (location) {
116
116
  try {
117
117
  const locationURL = new url_1.URL(location);
118
- if (locationURL.origin == u.origin) {
118
+ if (locationURL.origin === u.origin) {
119
119
  const unborkedLocation = location.replace(locationURL.origin, "");
120
120
  proxyRes.response.headers.set("location", unborkedLocation);
121
121
  }
@@ -27,7 +27,7 @@ async function promptForAccount() {
27
27
  message: "Please select an option:",
28
28
  choices,
29
29
  });
30
- if (emailChoice == "__add__") {
30
+ if (emailChoice === "__add__") {
31
31
  const newAccount = await (0, auth_1.loginAdditionalAccount)(true);
32
32
  if (!newAccount) {
33
33
  throw new error_1.FirebaseError("Failed to add new account", { exit: 1 });
@@ -168,7 +168,7 @@ function convertDatabaseInstance(serverInstance) {
168
168
  throw new error_1.FirebaseError(`DatabaseInstance response is missing field "name"`);
169
169
  }
170
170
  const m = serverInstance.name.match(INSTANCE_RESOURCE_NAME_REGEX);
171
- if (!m || m.length != 4) {
171
+ if (!m || m.length !== 4) {
172
172
  throw new error_1.FirebaseError(`Error parsing instance resource name: ${serverInstance.name}, matches: ${m}`);
173
173
  }
174
174
  return {
package/lib/previews.js CHANGED
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.previews = void 0;
4
4
  const lodash_1 = require("lodash");
5
5
  const configstore_1 = require("./configstore");
6
- exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, dotenv: false, artifactregistry: false }, configstore_1.configstore.get("previews"));
6
+ exports.previews = Object.assign({ rtdbrules: false, ext: false, extdev: false, extensionsemulator: false, rtdbmanagement: false, functionsv2: false, golang: false, deletegcfartifacts: false, artifactregistry: false, emulatoruisnapshot: false }, configstore_1.configstore.get("previews"));
7
7
  if (process.env.FIREBASE_CLI_PREVIEWS) {
8
8
  process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach((feature) => {
9
9
  if ((0, lodash_1.has)(exports.previews, feature)) {
@@ -2,16 +2,25 @@
2
2
  const _ = require("lodash");
3
3
  const { FirebaseError } = require("./error");
4
4
  module.exports = function (response, body) {
5
- if (typeof body === "string" && response.statusCode === 404) {
6
- body = {
7
- error: {
8
- message: "Not Found",
9
- },
10
- };
11
- }
12
5
  if (response.statusCode < 400) {
13
6
  return null;
14
7
  }
8
+ if (typeof body === "string") {
9
+ if (response.statusCode === 404) {
10
+ body = {
11
+ error: {
12
+ message: "Not Found",
13
+ },
14
+ };
15
+ }
16
+ else {
17
+ body = {
18
+ error: {
19
+ message: body,
20
+ },
21
+ };
22
+ }
23
+ }
15
24
  if (typeof body !== "object") {
16
25
  try {
17
26
  body = JSON.parse(body);
@@ -27,10 +27,10 @@ class FunctionsServer {
27
27
  const nodeMajorVersion = (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.config.get("functions.runtime"));
28
28
  this.backend = {
29
29
  functionsDir,
30
- env: {},
31
30
  nodeMajorVersion,
31
+ env: {},
32
32
  };
33
- const args = Object.assign({ projectId, emulatableBackends: [this.backend], account }, partialArgs);
33
+ const args = Object.assign({ projectId, projectDir: options.config.projectDir, emulatableBackends: [this.backend], account }, partialArgs);
34
34
  if (options.host) {
35
35
  utils.assertIsStringOrUndefined(options.host);
36
36
  args.host = options.host;
@@ -46,7 +46,7 @@ function startServer(options, config, port, init) {
46
46
  },
47
47
  },
48
48
  rewriters: {
49
- function: (0, functionsProxy_1.default)(options),
49
+ function: (0, functionsProxy_1.functionsProxy)(options),
50
50
  run: (0, cloudRunProxy_1.default)(options),
51
51
  },
52
52
  }).listen(() => {
package/lib/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
3
+ exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
4
4
  const _ = require("lodash");
5
5
  const url = require("url");
6
6
  const clc = require("cli-color");
@@ -17,6 +17,7 @@ const logger_1 = require("./logger");
17
17
  const IS_WINDOWS = process.platform === "win32";
18
18
  const SUCCESS_CHAR = IS_WINDOWS ? "+" : "✔";
19
19
  const WARNING_CHAR = IS_WINDOWS ? "!" : "⚠";
20
+ const ERROR_CHAR = IS_WINDOWS ? "!!" : "⬢";
20
21
  const THIRTY_DAYS_IN_MILLISECONDS = 30 * 24 * 60 * 60 * 1000;
21
22
  exports.envOverrides = [];
22
23
  function consoleUrl(project, path) {
@@ -104,6 +105,10 @@ function logLabeledWarning(label, message, type = "warn", data = undefined) {
104
105
  logger_1.logger[type](clc.yellow.bold(`${WARNING_CHAR} ${label}:`), message, data);
105
106
  }
106
107
  exports.logLabeledWarning = logLabeledWarning;
108
+ function logLabeledError(label, message, type = "error", data = undefined) {
109
+ logger_1.logger[type](clc.red.bold(`${ERROR_CHAR} ${label}:`), message, data);
110
+ }
111
+ exports.logLabeledError = logLabeledError;
107
112
  function reject(message, options) {
108
113
  return Promise.reject(new error_1.FirebaseError(message, options));
109
114
  }
@@ -290,7 +295,7 @@ function setupLoggers() {
290
295
  logger_1.logger.add(new winston.transports.Console({
291
296
  level: "info",
292
297
  format: winston.format.printf((info) => [info.message, ...(info[triple_beam_1.SPLAT] || [])]
293
- .filter((chunk) => typeof chunk == "string")
298
+ .filter((chunk) => typeof chunk === "string")
294
299
  .join(" ")),
295
300
  }));
296
301
  }