firebase-tools 10.4.1 → 10.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/lib/bin/firebase.js +1 -1
  2. package/lib/commands/emulators-start.js +6 -1
  3. package/lib/commands/ext-configure.js +4 -4
  4. package/lib/commands/ext-dev-emulators-start.js +5 -1
  5. package/lib/commands/ext-install.js +5 -4
  6. package/lib/commands/ext-update.js +2 -1
  7. package/lib/commands/functions-secrets-destroy.js +23 -3
  8. package/lib/commands/functions-secrets-prune.js +15 -12
  9. package/lib/commands/functions-secrets-set.js +51 -4
  10. package/lib/deploy/functions/backend.js +1 -5
  11. package/lib/deploy/functions/checkIam.js +44 -1
  12. package/lib/deploy/functions/prepare.js +13 -3
  13. package/lib/deploy/functions/release/fabricator.js +1 -3
  14. package/lib/deploy/functions/release/index.js +21 -0
  15. package/lib/deploy/functions/release/planner.js +1 -2
  16. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +11 -10
  17. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  18. package/lib/deploy/functions/runtimes/node/parseTriggers.js +5 -19
  19. package/lib/deploy/functions/services/firebaseAlerts.js +1 -17
  20. package/lib/deploy/functions/services/index.js +2 -1
  21. package/lib/deploy/functions/services/storage.js +1 -6
  22. package/lib/emulator/auth/operations.js +21 -20
  23. package/lib/emulator/auth/state.js +79 -43
  24. package/lib/emulator/commandUtils.js +72 -2
  25. package/lib/emulator/controller.js +9 -0
  26. package/lib/emulator/downloadableEmulators.js +13 -6
  27. package/lib/emulator/extensions/postinstall.js +41 -0
  28. package/lib/emulator/functionsEmulator.js +8 -18
  29. package/lib/emulator/functionsEmulatorShared.js +41 -19
  30. package/lib/emulator/shared/request.js +19 -0
  31. package/lib/emulator/storage/apis/firebase.js +25 -33
  32. package/lib/emulator/storage/apis/gcloud.js +78 -63
  33. package/lib/emulator/storage/files.js +48 -52
  34. package/lib/emulator/storage/index.js +23 -3
  35. package/lib/emulator/storage/metadata.js +18 -8
  36. package/lib/emulator/storage/rules/manager.js +7 -17
  37. package/lib/emulator/storage/rules/utils.js +11 -3
  38. package/lib/emulator/storage/server.js +38 -12
  39. package/lib/extensions/askUserForParam.js +25 -20
  40. package/lib/extensions/emulator/optionsHelper.js +5 -7
  41. package/lib/extensions/emulator/triggerHelper.js +11 -14
  42. package/lib/extensions/extensionsApi.js +2 -1
  43. package/lib/extensions/extensionsHelper.js +11 -2
  44. package/lib/extensions/manifest.js +1 -1
  45. package/lib/extensions/paramHelper.js +23 -13
  46. package/lib/functions/env.js +10 -2
  47. package/lib/functions/runtimeConfigExport.js +10 -6
  48. package/lib/functions/secrets.js +99 -6
  49. package/lib/gcp/cloudfunctions.js +6 -13
  50. package/lib/gcp/cloudfunctionsv2.js +14 -23
  51. package/lib/gcp/cloudtasks.js +5 -3
  52. package/lib/gcp/secretManager.js +1 -1
  53. package/lib/requirePermissions.js +4 -1
  54. package/lib/utils.js +30 -1
  55. package/npm-shrinkwrap.json +2 -2
  56. package/package.json +1 -1
  57. package/lib/emulator/storage/list.js +0 -18
@@ -1,32 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.functionResourceToEmulatedTriggerDefintion = void 0;
4
- const _ = require("lodash");
5
4
  const functionsEmulatorShared_1 = require("../../emulator/functionsEmulatorShared");
6
5
  const emulatorLogger_1 = require("../../emulator/emulatorLogger");
7
6
  const types_1 = require("../../emulator/types");
7
+ const proto = require("../../gcp/proto");
8
8
  function functionResourceToEmulatedTriggerDefintion(resource) {
9
9
  const etd = {
10
10
  name: resource.name,
11
11
  entryPoint: resource.name,
12
12
  platform: "gcfv1",
13
13
  };
14
- const properties = _.get(resource, "properties", {});
15
- if (properties.timeout) {
16
- etd.timeout = properties.timeout;
17
- }
18
- if (properties.location) {
19
- etd.regions = [properties.location];
20
- }
21
- if (properties.availableMemoryMb) {
22
- etd.availableMemoryMb = properties.availableMemoryMb;
23
- }
14
+ const properties = resource.properties || {};
15
+ proto.renameIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
16
+ proto.renameIfPresent(etd, properties, "regions", "location", (str) => [str]);
17
+ proto.copyIfPresent(etd, properties, "availableMemoryMb");
24
18
  if (properties.httpsTrigger) {
25
19
  etd.httpsTrigger = properties.httpsTrigger;
26
20
  }
27
- else if (properties.eventTrigger) {
28
- properties.eventTrigger.service = (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType);
29
- etd.eventTrigger = properties.eventTrigger;
21
+ if (properties.eventTrigger) {
22
+ etd.eventTrigger = {
23
+ eventType: properties.eventTrigger.eventType,
24
+ resource: properties.eventTrigger.resource,
25
+ service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
26
+ };
30
27
  }
31
28
  else {
32
29
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
@@ -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.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.getPublisherProfile = 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.getPublisherProfile = 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.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
4
4
  const yaml = require("js-yaml");
5
5
  const clc = require("cli-color");
6
6
  const { marked } = require("marked");
@@ -26,6 +26,7 @@ var Visibility;
26
26
  Visibility["UNLISTED"] = "unlisted";
27
27
  Visibility["PUBLIC"] = "public";
28
28
  })(Visibility = exports.Visibility || (exports.Visibility = {}));
29
+ exports.FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function";
29
30
  var ParamType;
30
31
  (function (ParamType) {
31
32
  ParamType["STRING"] = "STRING";
@@ -80,6 +80,9 @@ function getDBInstanceFromURL(databaseUrl = "") {
80
80
  exports.getDBInstanceFromURL = getDBInstanceFromURL;
81
81
  async function getFirebaseProjectParams(projectId, emulatorMode = false) {
82
82
  var _a, _b;
83
+ if (!projectId) {
84
+ return {};
85
+ }
83
86
  const body = emulatorMode
84
87
  ? await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)
85
88
  : await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
@@ -263,7 +266,10 @@ async function promptForValidInstanceId(instanceId) {
263
266
  }
264
267
  exports.promptForValidInstanceId = promptForValidInstanceId;
265
268
  async function ensureExtensionsApiEnabled(options) {
266
- const projectId = (0, projectUtils_1.needProjectId)(options);
269
+ const projectId = (0, projectUtils_1.getProjectId)(options);
270
+ if (!projectId) {
271
+ return;
272
+ }
267
273
  return await (0, ensureApiEnabled_1.ensure)(projectId, "firebaseextensions.googleapis.com", "extensions", options.markdown);
268
274
  }
269
275
  exports.ensureExtensionsApiEnabled = ensureExtensionsApiEnabled;
@@ -519,7 +525,10 @@ async function confirm(args) {
519
525
  }
520
526
  exports.confirm = confirm;
521
527
  async function diagnoseAndFixProject(options) {
522
- const projectId = (0, projectUtils_1.needProjectId)(options);
528
+ const projectId = (0, projectUtils_1.getProjectId)(options);
529
+ if (!projectId) {
530
+ return;
531
+ }
523
532
  const ok = await (0, diagnose_1.diagnose)(projectId);
524
533
  if (!ok) {
525
534
  throw new error_1.FirebaseError("Unable to proceed until all issues are resolved.");
@@ -170,7 +170,7 @@ function showDeprecationWarning() {
170
170
  }
171
171
  exports.showDeprecationWarning = showDeprecationWarning;
172
172
  function showPreviewWarning() {
173
- utils.logLabeledWarning(extensionsHelper_1.logPrefix, "These changes will be reflected in your Firebase Emulator after restart. " +
173
+ utils.logLabeledWarning(extensionsHelper_1.logPrefix, `See these changes in your Firebase Emulator by running "firebase emulators:start". ` +
174
174
  `Run ${clc.bold("firebase deploy (--only extensions)")} to deploy the changes to your Firebase project. `);
175
175
  }
176
176
  exports.showPreviewWarning = showPreviewWarning;
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
4
- const _ = require("lodash");
5
4
  const path = require("path");
6
5
  const clc = require("cli-color");
7
6
  const fs = require("fs-extra");
@@ -11,6 +10,7 @@ const extensionsHelper_1 = require("./extensionsHelper");
11
10
  const askUserForParam = require("./askUserForParam");
12
11
  const track = require("../track");
13
12
  const env = require("../functions/env");
13
+ const utils_1 = require("../utils");
14
14
  function getBaseParamBindings(params) {
15
15
  let ret = {};
16
16
  for (const [k, v] of Object.entries(params)) {
@@ -37,8 +37,9 @@ function setNewDefaults(params, newDefaults) {
37
37
  }
38
38
  exports.setNewDefaults = setNewDefaults;
39
39
  function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
40
- const specParams = _.cloneDeep(_.get(extensionInstance, "config.source.spec.params", []));
41
- const currentParams = _.cloneDeep(_.get(extensionInstance, "config.params", {}));
40
+ var _a, _b, _c, _d;
41
+ const specParams = (0, utils_1.cloneDeep)(((_c = (_b = (_a = extensionInstance === null || extensionInstance === void 0 ? void 0 : extensionInstance.config) === null || _a === void 0 ? void 0 : _a.source) === null || _b === void 0 ? void 0 : _b.spec) === null || _c === void 0 ? void 0 : _c.params) || []);
42
+ const currentParams = (0, utils_1.cloneDeep)(((_d = extensionInstance === null || extensionInstance === void 0 ? void 0 : extensionInstance.config) === null || _d === void 0 ? void 0 : _d.params) || {});
42
43
  return setNewDefaults(specParams, currentParams);
43
44
  }
44
45
  exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
@@ -57,16 +58,22 @@ async function getParams(args) {
57
58
  }
58
59
  else if (args.paramsEnvPath) {
59
60
  params = getParamsFromFile({
60
- projectId: args.projectId,
61
61
  paramSpecs: args.paramSpecs,
62
62
  paramsEnvPath: args.paramsEnvPath,
63
63
  });
64
64
  }
65
65
  else {
66
66
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
67
- params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
67
+ params = await askUserForParam.ask({
68
+ projectId: args.projectId,
69
+ instanceId: args.instanceId,
70
+ paramSpecs: args.paramSpecs,
71
+ firebaseProjectParams,
72
+ reconfiguring: !!args.reconfiguring,
73
+ });
68
74
  }
69
- void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
75
+ const paramNames = Object.keys(params);
76
+ void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
70
77
  return params;
71
78
  }
72
79
  exports.getParams = getParams;
@@ -85,7 +92,6 @@ async function getParamsForUpdate(args) {
85
92
  }
86
93
  else if (args.paramsEnvPath) {
87
94
  params = getParamsFromFile({
88
- projectId: args.projectId,
89
95
  paramSpecs: args.newSpec.params,
90
96
  paramsEnvPath: args.paramsEnvPath,
91
97
  });
@@ -99,27 +105,31 @@ async function getParamsForUpdate(args) {
99
105
  instanceId: args.instanceId,
100
106
  });
101
107
  }
102
- void track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
108
+ const paramNames = Object.keys(params);
109
+ void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
103
110
  return params;
104
111
  }
105
112
  exports.getParamsForUpdate = getParamsForUpdate;
106
113
  async function promptForNewParams(args) {
107
114
  const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
108
115
  const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
109
- const comparer = (param1, param2) => {
116
+ const sameParam = (param1) => (param2) => {
110
117
  return param1.type === param2.type && param1.param === param2.param;
111
118
  };
119
+ const paramDiff = (left, right) => {
120
+ return left.filter((aLeft) => !right.find(sameParam(aLeft)));
121
+ };
112
122
  const oldParams = args.spec.params.filter((p) => Object.keys(args.currentParams).includes(p.param));
113
- let paramsDiffDeletions = _.differenceWith(oldParams, args.newSpec.params, comparer);
123
+ let paramsDiffDeletions = paramDiff(oldParams, args.newSpec.params);
114
124
  paramsDiffDeletions = (0, extensionsHelper_1.substituteParams)(paramsDiffDeletions, firebaseProjectParams);
115
- let paramsDiffAdditions = _.differenceWith(args.newSpec.params, oldParams, comparer);
125
+ let paramsDiffAdditions = paramDiff(args.newSpec.params, oldParams);
116
126
  paramsDiffAdditions = (0, extensionsHelper_1.substituteParams)(paramsDiffAdditions, firebaseProjectParams);
117
127
  if (paramsDiffDeletions.length) {
118
128
  logger_1.logger.info("The following params will no longer be used:");
119
- paramsDiffDeletions.forEach((param) => {
129
+ for (const param of paramsDiffDeletions) {
120
130
  logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
121
131
  delete newParamBindingOptions[param.param.toUpperCase()];
122
- });
132
+ }
123
133
  }
124
134
  if (paramsDiffAdditions.length) {
125
135
  logger_1.logger.info("To update this instance, configure the following new parameters:");
@@ -42,6 +42,15 @@ const LINE_RE = new RegExp("^" +
42
42
  "\\s*" +
43
43
  "(?:#[^\\n]*)?" +
44
44
  "$", "gms");
45
+ const ESCAPE_SEQUENCES_TO_CHARACTERS = {
46
+ "\\n": "\n",
47
+ "\\r": "\r",
48
+ "\\t": "\t",
49
+ "\\v": "\v",
50
+ "\\\\": "\\",
51
+ "\\'": "'",
52
+ '\\"': '"',
53
+ };
45
54
  function parse(data) {
46
55
  const envs = {};
47
56
  const errors = [];
@@ -54,8 +63,7 @@ function parse(data) {
54
63
  if ((quotesMatch = /^(["'])(.*)\1$/ms.exec(v)) != null) {
55
64
  v = quotesMatch[2];
56
65
  if (quotesMatch[1] === '"') {
57
- v = v.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\v", "\v");
58
- v = v.replace(/\\([\\'"])/g, "$1");
66
+ v = v.replace(/\\[nrtv\\'"]/g, (match) => ESCAPE_SEQUENCES_TO_CHARACTERS[match]);
59
67
  }
60
68
  }
61
69
  envs[k] = v;
@@ -115,13 +115,17 @@ function hydrateEnvs(pInfos, prefix) {
115
115
  return errMsg;
116
116
  }
117
117
  exports.hydrateEnvs = hydrateEnvs;
118
+ const CHARACTERS_TO_ESCAPE_SEQUENCES = {
119
+ "\n": "\\n",
120
+ "\r": "\\r",
121
+ "\t": "\\t",
122
+ "\v": "\\v",
123
+ "\\": "\\\\",
124
+ '"': '\\"',
125
+ "'": "\\'",
126
+ };
118
127
  function escape(s) {
119
- const result = s
120
- .replace("\n", "\\n")
121
- .replace("\r", "\\r")
122
- .replace("\t", "\\t")
123
- .replace("\v", "\\v");
124
- return result.replace(/(['"])/g, "\\$1");
128
+ return s.replace(/[\n\r\t\v\\"']/g, (ch) => CHARACTERS_TO_ESCAPE_SEQUENCES[ch]);
125
129
  }
126
130
  function toDotenvFormat(envs, header = "") {
127
131
  const lines = envs.map(({ newKey, value }) => `${newKey}="${escape(value)}"`);
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pruneSecrets = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
3
+ exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
4
+ const utils = require("../utils");
5
+ const poller = require("../operation-poller");
6
+ const gcf = require("../gcp/cloudfunctions");
4
7
  const secretManager_1 = require("../gcp/secretManager");
5
8
  const error_1 = require("../error");
6
9
  const utils_1 = require("../utils");
7
10
  const prompt_1 = require("../prompt");
8
11
  const env_1 = require("./env");
12
+ const logger_1 = require("../logger");
13
+ const api_1 = require("../api");
14
+ const functional_1 = require("../functional");
9
15
  const FIREBASE_MANGED = "firebase-managed";
10
16
  function isFirebaseManaged(secret) {
11
17
  return Object.keys(secret.labels || []).includes(FIREBASE_MANGED);
@@ -17,8 +23,7 @@ function labels() {
17
23
  exports.labels = labels;
18
24
  function toUpperSnakeCase(key) {
19
25
  return key
20
- .replace("-", "_")
21
- .replace(".", "_")
26
+ .replace(/[.-]/g, "_")
22
27
  .replace(/([a-z])([A-Z])/g, "$1_$2")
23
28
  .toUpperCase();
24
29
  }
@@ -80,19 +85,40 @@ function of(endpoints) {
80
85
  return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
81
86
  }
82
87
  exports.of = of;
88
+ function inUse(projectInfo, secret, endpoint) {
89
+ const { projectId, projectNumber } = projectInfo;
90
+ for (const sev of of([endpoint])) {
91
+ if ((sev.projectId === projectId || sev.projectId === projectNumber) &&
92
+ sev.secret === secret.name) {
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+ exports.inUse = inUse;
83
99
  async function pruneSecrets(projectInfo, endpoints) {
84
100
  const { projectId, projectNumber } = projectInfo;
85
101
  const pruneKey = (name, version) => `${name}@${version}`;
86
102
  const prunedSecrets = new Set();
87
103
  const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${FIREBASE_MANGED}=true`);
88
104
  for (const secret of haveSecrets) {
89
- const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `state: ENABLED`);
105
+ const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `NOT state: DESTROYED`);
90
106
  for (const version of versions) {
91
107
  prunedSecrets.add(pruneKey(secret.name, version.versionId));
92
108
  }
93
109
  }
94
- const sevs = of(endpoints).filter((sev) => sev.projectId === projectId || sev.projectId === projectNumber);
95
- for (const sev of sevs) {
110
+ const secrets = [];
111
+ for (const secret of of(endpoints)) {
112
+ if (!secret.version) {
113
+ throw new error_1.FirebaseError(`Secret ${secret.secret} version is unexpectedly empty.`);
114
+ }
115
+ if (secret.projectId === projectId || secret.projectId === projectNumber) {
116
+ if (secret.version) {
117
+ secrets.push(Object.assign(Object.assign({}, secret), { version: secret.version }));
118
+ }
119
+ }
120
+ }
121
+ for (const sev of secrets) {
96
122
  let name = sev.secret;
97
123
  if (name.includes("/")) {
98
124
  const secret = (0, secretManager_1.parseSecretResourceName)(name);
@@ -110,3 +136,70 @@ async function pruneSecrets(projectInfo, endpoints) {
110
136
  .map(([secret, version]) => ({ projectId, version, secret, key: secret }));
111
137
  }
112
138
  exports.pruneSecrets = pruneSecrets;
139
+ async function pruneAndDestroySecrets(projectInfo, endpoints) {
140
+ const { projectId, projectNumber } = projectInfo;
141
+ logger_1.logger.debug("Pruning secrets to find unused secret versions...");
142
+ const unusedSecrets = await module.exports.pruneSecrets({ projectId, projectNumber }, endpoints);
143
+ if (unusedSecrets.length === 0) {
144
+ return { destroyed: [], erred: [] };
145
+ }
146
+ const destroyed = [];
147
+ const erred = [];
148
+ const msg = unusedSecrets.map((s) => `${s.secret}@${s.version}`);
149
+ logger_1.logger.debug(`Found unused secret versions: ${msg}. Destroying them...`);
150
+ const destroyResults = await utils.allSettled(unusedSecrets.map(async (sev) => {
151
+ await (0, secretManager_1.destroySecretVersion)(sev.projectId, sev.secret, sev.version);
152
+ return sev;
153
+ }));
154
+ for (const result of destroyResults) {
155
+ if (result.status === "fulfilled") {
156
+ destroyed.push(result.value);
157
+ }
158
+ else {
159
+ erred.push(result.reason);
160
+ }
161
+ }
162
+ return { destroyed, erred };
163
+ }
164
+ exports.pruneAndDestroySecrets = pruneAndDestroySecrets;
165
+ async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
166
+ const { projectId, projectNumber } = projectInfo;
167
+ if (!inUse(projectInfo, secretVersion.secret, endpoint)) {
168
+ return endpoint;
169
+ }
170
+ const updatedSevs = [];
171
+ for (const sev of of([endpoint])) {
172
+ const updatedSev = Object.assign({}, sev);
173
+ if ((updatedSev.projectId === projectId || updatedSev.projectId === projectNumber) &&
174
+ updatedSev.secret === secretVersion.secret.name) {
175
+ updatedSev.version = secretVersion.versionId;
176
+ }
177
+ updatedSevs.push(updatedSev);
178
+ }
179
+ if (endpoint.platform === "gcfv1") {
180
+ const fn = gcf.functionFromEndpoint(endpoint, "");
181
+ const op = await gcf.updateFunction({
182
+ name: fn.name,
183
+ runtime: fn.runtime,
184
+ entryPoint: fn.entryPoint,
185
+ secretEnvironmentVariables: updatedSevs,
186
+ });
187
+ const gcfV1PollerOptions = {
188
+ apiOrigin: api_1.functionsOrigin,
189
+ apiVersion: gcf.API_VERSION,
190
+ masterTimeout: 25 * 60 * 1000,
191
+ maxBackoff: 10000,
192
+ pollerName: `update-${endpoint.region}-${endpoint.id}`,
193
+ operationResourceName: op.name,
194
+ };
195
+ const cfn = await poller.pollOperation(gcfV1PollerOptions);
196
+ return gcf.endpointFromFunction(cfn);
197
+ }
198
+ else if (endpoint.platform === "gcfv2") {
199
+ throw new error_1.FirebaseError(`Unsupported platform ${endpoint.platform}`);
200
+ }
201
+ else {
202
+ (0, functional_1.assertExhaustive)(endpoint.platform);
203
+ }
204
+ }
205
+ exports.updateEndpointSecret = updateEndpointSecret;
@@ -232,12 +232,7 @@ function endpointFromFunction(gcfFunction) {
232
232
  trigger = {
233
233
  eventTrigger: {
234
234
  eventType: gcfFunction.eventTrigger.eventType,
235
- eventFilters: [
236
- {
237
- attribute: "resource",
238
- value: gcfFunction.eventTrigger.resource,
239
- },
240
- ],
235
+ eventFilters: { resource: gcfFunction.eventTrigger.resource },
241
236
  retry: !!((_e = gcfFunction.eventTrigger.failurePolicy) === null || _e === void 0 ? void 0 : _e.retry),
242
237
  },
243
238
  };
@@ -254,7 +249,8 @@ function endpointFromFunction(gcfFunction) {
254
249
  if (securityLevel) {
255
250
  endpoint.securityLevel = securityLevel;
256
251
  }
257
- proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "timeout", "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
252
+ proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
253
+ proto.renameIfPresent(endpoint, gcfFunction, "timeoutSeconds", "timeout", proto.secondsFromDuration);
258
254
  if (gcfFunction.vpcConnector) {
259
255
  endpoint.vpc = { connector: gcfFunction.vpcConnector };
260
256
  proto.renameIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings");
@@ -278,13 +274,9 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
278
274
  };
279
275
  proto.copyIfPresent(gcfFunction, endpoint, "labels");
280
276
  if (backend.isEventTriggered(endpoint)) {
281
- const resourceFilter = backend.findEventFilter(endpoint, "resource");
282
- if (!resourceFilter) {
283
- throw new error_1.FirebaseError("Invalid event trigger definition. Expected event filter with 'resource' attribute.");
284
- }
285
277
  gcfFunction.eventTrigger = {
286
278
  eventType: endpoint.eventTrigger.eventType,
287
- resource: resourceFilter.value,
279
+ resource: endpoint.eventTrigger.eventFilters.resource,
288
280
  };
289
281
  gcfFunction.eventTrigger.failurePolicy = endpoint.eventTrigger.retry
290
282
  ? { retry: {} }
@@ -311,7 +303,8 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
311
303
  gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel;
312
304
  }
313
305
  }
314
- proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "timeout", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
306
+ proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
307
+ proto.renameIfPresent(gcfFunction, endpoint, "timeout", "timeoutSeconds", proto.durationFromSeconds);
315
308
  if (endpoint.vpc) {
316
309
  proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
317
310
  proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
@@ -166,9 +166,8 @@ function functionFromEndpoint(endpoint, source) {
166
166
  serviceConfig: {},
167
167
  };
168
168
  proto.copyIfPresent(gcfFunction, endpoint, "labels");
169
- proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "serviceAccountEmail", "ingressSettings");
169
+ proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "serviceAccountEmail", "ingressSettings", "timeoutSeconds");
170
170
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "availableMemory", "availableMemoryMb", (mb) => `${mb}M`);
171
- proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "timeoutSeconds", "timeout", proto.secondsFromDuration);
172
171
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances");
173
172
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances");
174
173
  if (endpoint.vpc) {
@@ -180,23 +179,19 @@ function functionFromEndpoint(endpoint, source) {
180
179
  eventType: endpoint.eventTrigger.eventType,
181
180
  };
182
181
  if (gcfFunction.eventTrigger.eventType === v2_1.PUBSUB_PUBLISH_EVENT) {
183
- const pubsubFilter = backend.findEventFilter(endpoint, "topic");
184
- if (!pubsubFilter) {
185
- throw new error_1.FirebaseError("Invalid pubsub endpoint. Expected eventFilter with 'topic' attribute but found none.");
186
- }
187
- gcfFunction.eventTrigger.pubsubTopic = pubsubFilter.value;
188
- for (const filter of endpoint.eventTrigger.eventFilters) {
189
- if (filter.attribute === "topic") {
182
+ gcfFunction.eventTrigger.pubsubTopic = endpoint.eventTrigger.eventFilters.topic;
183
+ gcfFunction.eventTrigger.eventFilters = [];
184
+ for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
185
+ if (attribute === "topic")
190
186
  continue;
191
- }
192
- if (!gcfFunction.eventTrigger.eventFilters) {
193
- gcfFunction.eventTrigger.eventFilters = [];
194
- }
195
- gcfFunction.eventTrigger.eventFilters.push(filter);
187
+ gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
196
188
  }
197
189
  }
198
190
  else {
199
- gcfFunction.eventTrigger.eventFilters = endpoint.eventTrigger.eventFilters;
191
+ gcfFunction.eventTrigger.eventFilters = [];
192
+ for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
193
+ gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
194
+ }
200
195
  }
201
196
  proto.renameIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "triggerRegion", "region");
202
197
  if (endpoint.eventTrigger.retry) {
@@ -239,19 +234,16 @@ function endpointFromFunction(gcfFunction) {
239
234
  trigger = {
240
235
  eventTrigger: {
241
236
  eventType: gcfFunction.eventTrigger.eventType,
242
- eventFilters: [],
237
+ eventFilters: {},
243
238
  retry: false,
244
239
  },
245
240
  };
246
241
  if (gcfFunction.eventTrigger.pubsubTopic) {
247
- trigger.eventTrigger.eventFilters.push({
248
- attribute: "topic",
249
- value: gcfFunction.eventTrigger.pubsubTopic,
250
- });
242
+ trigger.eventTrigger.eventFilters.topic = gcfFunction.eventTrigger.pubsubTopic;
251
243
  }
252
244
  else {
253
245
  for (const { attribute, value } of gcfFunction.eventTrigger.eventFilters || []) {
254
- trigger.eventTrigger.eventFilters.push({ attribute, value });
246
+ trigger.eventTrigger.eventFilters[attribute] = value;
255
247
  }
256
248
  }
257
249
  proto.renameIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "region", "triggerRegion");
@@ -265,9 +257,8 @@ function endpointFromFunction(gcfFunction) {
265
257
  const endpoint = Object.assign(Object.assign({ platform: "gcfv2", id,
266
258
  project,
267
259
  region }, trigger), { entryPoint: gcfFunction.buildConfig.entryPoint, runtime: gcfFunction.buildConfig.runtime, uri: gcfFunction.serviceConfig.uri });
268
- proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", "environmentVariables");
260
+ proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", "environmentVariables", "timeoutSeconds");
269
261
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "availableMemoryMb", "availableMemory", megabytes);
270
- proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "timeout", "timeoutSeconds", proto.durationFromSeconds);
271
262
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount");
272
263
  proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount");
273
264
  proto.copyIfPresent(endpoint, gcfFunction, "labels");
@@ -13,7 +13,6 @@ const client = new apiv2_1.Client({
13
13
  exports.DEFAULT_SETTINGS = {
14
14
  rateLimits: {
15
15
  maxConcurrentDispatches: 1000,
16
- maxBurstSize: 100,
17
16
  maxDispatchesPerSecond: 500,
18
17
  },
19
18
  state: "RUNNING",
@@ -133,10 +132,13 @@ exports.queueNameForEndpoint = queueNameForEndpoint;
133
132
  function queueFromEndpoint(endpoint) {
134
133
  const queue = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(exports.DEFAULT_SETTINGS))), { name: queueNameForEndpoint(endpoint) });
135
134
  if (endpoint.taskQueueTrigger.rateLimits) {
136
- proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxBurstSize", "maxConcurrentDispatches", "maxDispatchesPerSecond");
135
+ proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxConcurrentDispatches", "maxDispatchesPerSecond");
137
136
  }
138
137
  if (endpoint.taskQueueTrigger.retryConfig) {
139
- proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxBackoff", "maxDoublings", "maxRetryDuration", "minBackoff");
138
+ proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxDoublings");
139
+ proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxRetryDuration", "maxRetrySeconds", proto.durationFromSeconds);
140
+ proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxBackoff", "maxBackoffSeconds", proto.durationFromSeconds);
141
+ proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "minBackoff", "minBackoffSeconds", proto.durationFromSeconds);
140
142
  }
141
143
  return queue;
142
144
  }
@@ -133,7 +133,7 @@ async function createSecret(projectId, name, labels) {
133
133
  },
134
134
  labels,
135
135
  }, { queryParams: { secretId: name } });
136
- return parseSecretResourceName(createRes.body.name);
136
+ return Object.assign(Object.assign({}, parseSecretResourceName(createRes.body.name)), { labels });
137
137
  }
138
138
  exports.createSecret = createSecret;
139
139
  async function patchSecret(projectId, name, labels) {
@@ -9,7 +9,10 @@ const error_1 = require("./error");
9
9
  const iam_1 = require("./gcp/iam");
10
10
  const BASE_PERMISSIONS = ["firebase.projects.get"];
11
11
  async function requirePermissions(options, permissions = []) {
12
- const projectId = (0, projectUtils_1.needProjectId)(options);
12
+ const projectId = (0, projectUtils_1.getProjectId)(options);
13
+ if (!projectId) {
14
+ return;
15
+ }
13
16
  const requiredPermissions = BASE_PERMISSIONS.concat(permissions).sort();
14
17
  await (0, requireAuth_1.requireAuth)(options);
15
18
  logger_1.logger.debug(`[iam] checking project ${projectId} for permissions ${JSON.stringify(requiredPermissions)}`);
package/lib/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.groupBy = 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;
3
+ exports.cloneDeep = exports.groupBy = 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");
@@ -406,3 +406,32 @@ function groupBy(arr, f) {
406
406
  }, {});
407
407
  }
408
408
  exports.groupBy = groupBy;
409
+ function cloneArray(arr) {
410
+ return arr.map((e) => cloneDeep(e));
411
+ }
412
+ function cloneObject(obj) {
413
+ const clone = {};
414
+ for (const [k, v] of Object.entries(obj)) {
415
+ clone[k] = cloneDeep(v);
416
+ }
417
+ return clone;
418
+ }
419
+ function cloneDeep(obj) {
420
+ if (typeof obj !== "object" || !obj) {
421
+ return obj;
422
+ }
423
+ if (obj instanceof RegExp) {
424
+ return RegExp(obj, obj.flags);
425
+ }
426
+ if (obj instanceof Date) {
427
+ return new Date(obj);
428
+ }
429
+ if (Array.isArray(obj)) {
430
+ return cloneArray(obj);
431
+ }
432
+ if (obj instanceof Map) {
433
+ return new Map(obj.entries());
434
+ }
435
+ return cloneObject(obj);
436
+ }
437
+ exports.cloneDeep = cloneDeep;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.4.1",
3
+ "version": "10.6.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "firebase-tools",
9
- "version": "10.4.1",
9
+ "version": "10.6.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@google-cloud/pubsub": "^2.18.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "10.4.1",
3
+ "version": "10.6.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {