firebase-tools 9.21.0 → 9.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +3 -3
  2. package/lib/api.js +2 -0
  3. package/lib/apiv2.js +3 -2
  4. package/lib/commands/crashlytics-symbols-upload.js +1 -1
  5. package/lib/commands/deploy.js +9 -1
  6. package/lib/commands/ext-configure.js +1 -1
  7. package/lib/commands/ext-dev-deprecate.js +63 -0
  8. package/lib/commands/ext-dev-undeprecate.js +56 -0
  9. package/lib/commands/ext-export.js +44 -0
  10. package/lib/commands/ext-install.js +1 -1
  11. package/lib/commands/ext-update.js +1 -1
  12. package/lib/commands/functions-delete.js +2 -0
  13. package/lib/commands/index.js +6 -5
  14. package/lib/commands/init.js +3 -0
  15. package/lib/config.js +3 -2
  16. package/lib/deploy/extensions/args.js +2 -0
  17. package/lib/deploy/extensions/deploy.js +49 -0
  18. package/lib/deploy/extensions/deploymentSummary.js +52 -0
  19. package/lib/deploy/extensions/errors.js +31 -0
  20. package/lib/deploy/extensions/index.js +8 -0
  21. package/lib/deploy/extensions/planner.js +95 -0
  22. package/lib/deploy/extensions/prepare.js +103 -0
  23. package/lib/deploy/extensions/release.js +43 -0
  24. package/lib/deploy/extensions/secrets.js +150 -0
  25. package/lib/deploy/extensions/tasks.js +98 -0
  26. package/lib/deploy/extensions/validate.js +17 -0
  27. package/lib/deploy/functions/backend.js +8 -1
  28. package/lib/deploy/functions/containerCleaner.js +77 -21
  29. package/lib/deploy/functions/release/fabricator.js +69 -9
  30. package/lib/deploy/functions/release/index.js +5 -1
  31. package/lib/deploy/functions/release/planner.js +3 -0
  32. package/lib/deploy/functions/release/reporter.js +4 -1
  33. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +28 -0
  34. package/lib/deploy/functions/runtimes/node/parseTriggers.js +7 -2
  35. package/lib/deploy/index.js +1 -0
  36. package/lib/emulator/functionsEmulator.js +3 -1
  37. package/lib/extensions/askUserForParam.js +14 -6
  38. package/lib/extensions/checkProjectBilling.js +7 -7
  39. package/lib/extensions/export.js +107 -0
  40. package/lib/extensions/extensionsApi.js +103 -21
  41. package/lib/extensions/extensionsHelper.js +4 -1
  42. package/lib/extensions/listExtensions.js +16 -11
  43. package/lib/extensions/paramHelper.js +6 -4
  44. package/lib/extensions/provisioningHelper.js +16 -3
  45. package/lib/extensions/refs.js +9 -1
  46. package/lib/extensions/secretsUtils.js +10 -9
  47. package/lib/extensions/updateHelper.js +12 -2
  48. package/lib/extensions/versionHelper.js +14 -0
  49. package/lib/extensions/warnings.js +33 -1
  50. package/lib/gcp/artifactregistry.js +16 -0
  51. package/lib/gcp/cloudfunctions.js +25 -7
  52. package/lib/gcp/cloudfunctionsv2.js +10 -2
  53. package/lib/gcp/cloudtasks.js +143 -0
  54. package/lib/gcp/docker.js +7 -1
  55. package/lib/gcp/proto.js +2 -2
  56. package/lib/gcp/secretManager.js +27 -6
  57. package/lib/previews.js +1 -1
  58. package/package.json +2 -1
  59. package/schema/firebase-config.json +9 -0
@@ -158,13 +158,16 @@ function functionFromEndpoint(endpoint, source) {
158
158
  }
159
159
  }
160
160
  else if (backend.isScheduleTriggered(endpoint)) {
161
- gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { ["deployment-scheduled"]: "true" });
161
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-scheduled": "true" });
162
+ }
163
+ else if (backend.isTaskQueueTriggered(endpoint)) {
164
+ gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-taskqueue": "true" });
162
165
  }
163
166
  return gcfFunction;
164
167
  }
165
168
  exports.functionFromEndpoint = functionFromEndpoint;
166
169
  function endpointFromFunction(gcfFunction) {
167
- var _a;
170
+ var _a, _b;
168
171
  const [, project, , region, , id] = gcfFunction.name.split("/");
169
172
  let trigger;
170
173
  if (((_a = gcfFunction.labels) === null || _a === void 0 ? void 0 : _a["deployment-scheduled"]) === "true") {
@@ -172,6 +175,11 @@ function endpointFromFunction(gcfFunction) {
172
175
  scheduleTrigger: {},
173
176
  };
174
177
  }
178
+ else if (((_b = gcfFunction.labels) === null || _b === void 0 ? void 0 : _b["deployment-taskqueue"]) === "true") {
179
+ trigger = {
180
+ taskQueueTrigger: {},
181
+ };
182
+ }
175
183
  else if (gcfFunction.eventTrigger) {
176
184
  trigger = {
177
185
  eventTrigger: {
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queueFromEndpoint = exports.queueNameForEndpoint = exports.setEnqueuer = exports.getIamPolicy = exports.setIamPolicy = exports.deleteQueue = exports.purgeQueue = exports.upsertQueue = exports.updateQueue = exports.getQueue = exports.createQueue = exports.DEFAULT_SETTINGS = void 0;
4
+ const proto = require("./proto");
5
+ const apiv2_1 = require("../apiv2");
6
+ const api_1 = require("../api");
7
+ const API_VERSION = "v2";
8
+ const client = new apiv2_1.Client({
9
+ urlPrefix: api_1.cloudTasksOrigin,
10
+ auth: true,
11
+ apiVersion: API_VERSION,
12
+ });
13
+ exports.DEFAULT_SETTINGS = {
14
+ rateLimits: {
15
+ maxConcurrentDispatches: 1000,
16
+ maxBurstSize: 100,
17
+ maxDispatchesPerSecond: 500,
18
+ },
19
+ state: "RUNNING",
20
+ retryConfig: {
21
+ maxDoublings: 16,
22
+ maxAttempts: 3,
23
+ maxBackoff: "3600s",
24
+ minBackoff: "0.100s",
25
+ },
26
+ };
27
+ async function createQueue(queue) {
28
+ const path = queue.name.substring(0, queue.name.lastIndexOf("/"));
29
+ const res = await client.post(path, queue);
30
+ return res.body;
31
+ }
32
+ exports.createQueue = createQueue;
33
+ async function getQueue(name) {
34
+ const res = await client.get(name);
35
+ return res.body;
36
+ }
37
+ exports.getQueue = getQueue;
38
+ async function updateQueue(queue) {
39
+ const res = await client.patch(queue.name, queue, {
40
+ queryParams: { updateMask: proto.fieldMasks(queue).join(",") },
41
+ });
42
+ return res.body;
43
+ }
44
+ exports.updateQueue = updateQueue;
45
+ async function upsertQueue(queue) {
46
+ var _a, _b;
47
+ try {
48
+ const existing = await module.exports.getQueue(queue.name);
49
+ if (JSON.stringify(queue) === JSON.stringify(existing)) {
50
+ return false;
51
+ }
52
+ if (existing.state === "DISABLED") {
53
+ await module.exports.purgeQueue(queue.name);
54
+ }
55
+ await module.exports.updateQueue(queue);
56
+ return false;
57
+ }
58
+ catch (err) {
59
+ if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 404) {
60
+ await module.exports.createQueue(queue);
61
+ return true;
62
+ }
63
+ throw err;
64
+ }
65
+ }
66
+ exports.upsertQueue = upsertQueue;
67
+ async function purgeQueue(name) {
68
+ await client.post(`${name}:purge`);
69
+ }
70
+ exports.purgeQueue = purgeQueue;
71
+ async function deleteQueue(name) {
72
+ await client.delete(name);
73
+ }
74
+ exports.deleteQueue = deleteQueue;
75
+ async function setIamPolicy(name, policy) {
76
+ const res = await client.post(`${name}:setIamPolicy`, {
77
+ policy,
78
+ });
79
+ return res.body;
80
+ }
81
+ exports.setIamPolicy = setIamPolicy;
82
+ async function getIamPolicy(name) {
83
+ const res = await client.post(`${name}:getIamPolicy`);
84
+ return res.body;
85
+ }
86
+ exports.getIamPolicy = getIamPolicy;
87
+ const ENQUEUER_ROLE = "roles/cloudtasks.enqueuer";
88
+ async function setEnqueuer(name, invoker, assumeEmpty = false) {
89
+ var _a, _b;
90
+ let existing;
91
+ if (assumeEmpty) {
92
+ existing = {
93
+ bindings: [],
94
+ etag: "",
95
+ version: 3,
96
+ };
97
+ }
98
+ else {
99
+ existing = await module.exports.getIamPolicy(name);
100
+ }
101
+ const [, project] = name.split("/");
102
+ const invokerMembers = proto.getInvokerMembers(invoker, project);
103
+ while (true) {
104
+ const policy = {
105
+ bindings: existing.bindings.filter((binding) => binding.role != ENQUEUER_ROLE),
106
+ etag: existing.etag,
107
+ version: existing.version,
108
+ };
109
+ if (invokerMembers.length) {
110
+ policy.bindings.push({ role: ENQUEUER_ROLE, members: invokerMembers });
111
+ }
112
+ if (JSON.stringify(policy) === JSON.stringify(existing)) {
113
+ return;
114
+ }
115
+ try {
116
+ await module.exports.setIamPolicy(name, policy);
117
+ return;
118
+ }
119
+ catch (err) {
120
+ if (((_b = (_a = err === null || err === void 0 ? void 0 : err.context) === null || _a === void 0 ? void 0 : _a.response) === null || _b === void 0 ? void 0 : _b.statusCode) === 429) {
121
+ existing = await module.exports.getIamPolicy(name);
122
+ continue;
123
+ }
124
+ throw err;
125
+ }
126
+ }
127
+ }
128
+ exports.setEnqueuer = setEnqueuer;
129
+ function queueNameForEndpoint(endpoint) {
130
+ return `projects/${endpoint.project}/locations/${endpoint.region}/queues/${endpoint.id}`;
131
+ }
132
+ exports.queueNameForEndpoint = queueNameForEndpoint;
133
+ function queueFromEndpoint(endpoint) {
134
+ const queue = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(exports.DEFAULT_SETTINGS))), { name: queueNameForEndpoint(endpoint) });
135
+ if (endpoint.taskQueueTrigger.rateLimits) {
136
+ proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxBurstSize", "maxConcurrentDispatches", "maxDispatchesPerSecond");
137
+ }
138
+ if (endpoint.taskQueueTrigger.retryConfig) {
139
+ proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxBackoff", "maxDoublings", "maxRetryDuration", "minBackoff");
140
+ }
141
+ return queue;
142
+ }
143
+ exports.queueFromEndpoint = queueFromEndpoint;
package/lib/gcp/docker.js CHANGED
@@ -4,7 +4,7 @@ exports.Client = void 0;
4
4
  const error_1 = require("../error");
5
5
  const api = require("../apiv2");
6
6
  function isErrors(response) {
7
- return Object.prototype.hasOwnProperty.call(response, "errors");
7
+ return !!response && Object.prototype.hasOwnProperty.call(response, "errors");
8
8
  }
9
9
  const API_VERSION = "v2";
10
10
  class Client {
@@ -27,6 +27,9 @@ class Client {
27
27
  async deleteTag(path, tag) {
28
28
  var _a;
29
29
  const response = await this.client.delete(`${path}/manifests/${tag}`);
30
+ if (!response.body) {
31
+ return;
32
+ }
30
33
  if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
31
34
  throw new error_1.FirebaseError(`Failed to delete tag ${tag} at path ${path}`, {
32
35
  children: response.body.errors,
@@ -36,6 +39,9 @@ class Client {
36
39
  async deleteImage(path, digest) {
37
40
  var _a;
38
41
  const response = await this.client.delete(`${path}/manifests/${digest}`);
42
+ if (!response.body) {
43
+ return;
44
+ }
39
45
  if (((_a = response.body.errors) === null || _a === void 0 ? void 0 : _a.length) != 0) {
40
46
  throw new error_1.FirebaseError(`Failed to delete image ${digest} at path ${path}`, {
41
47
  children: response.body.errors,
package/lib/gcp/proto.js CHANGED
@@ -67,10 +67,10 @@ function fieldMasksHelper(prefixes, cursor, doNotRecurseIn, masks) {
67
67
  }
68
68
  }
69
69
  function getInvokerMembers(invoker, projectId) {
70
- if (invoker[0] === "private") {
70
+ if (invoker.includes("private")) {
71
71
  return [];
72
72
  }
73
- if (invoker[0] === "public") {
73
+ if (invoker.includes("public")) {
74
74
  return ["allUsers"];
75
75
  }
76
76
  return invoker.map((inv) => formatServiceAccount(inv, projectId));
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.grantServiceAgentRole = exports.addVersion = exports.createSecret = exports.parseSecretResourceName = exports.secretExists = exports.getSecretLabels = exports.getSecret = exports.listSecrets = void 0;
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;
4
+ const utils_1 = require("../utils");
4
5
  const api = require("../api");
6
+ exports.secretManagerConsoleUri = (projectId) => `https://console.cloud.google.com/security/secret-manager?project=${projectId}`;
5
7
  async function listSecrets(projectId) {
6
8
  const listRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets`, {
7
9
  auth: true,
@@ -11,21 +13,24 @@ async function listSecrets(projectId) {
11
13
  }
12
14
  exports.listSecrets = listSecrets;
13
15
  async function getSecret(projectId, name) {
16
+ var _a;
14
17
  const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, {
15
18
  auth: true,
16
19
  origin: api.secretManagerOrigin,
17
20
  });
18
- return parseSecretResourceName(getRes.body.name);
21
+ const secret = parseSecretResourceName(getRes.body.name);
22
+ secret.labels = (_a = getRes.body.labels) !== null && _a !== void 0 ? _a : {};
23
+ return secret;
19
24
  }
20
25
  exports.getSecret = getSecret;
21
- async function getSecretLabels(projectId, name) {
22
- const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}`, {
26
+ async function getSecretVersion(projectId, name, version) {
27
+ const getRes = await api.request("GET", `/v1beta1/projects/${projectId}/secrets/${name}/versions/${version}`, {
23
28
  auth: true,
24
29
  origin: api.secretManagerOrigin,
25
30
  });
26
- return getRes.body.labels;
31
+ return parseSecretVersionResourceName(getRes.body.name);
27
32
  }
28
- exports.getSecretLabels = getSecretLabels;
33
+ exports.getSecretVersion = getSecretVersion;
29
34
  async function secretExists(projectId, name) {
30
35
  try {
31
36
  await getSecret(projectId, name);
@@ -47,6 +52,21 @@ function parseSecretResourceName(resourceName) {
47
52
  };
48
53
  }
49
54
  exports.parseSecretResourceName = parseSecretResourceName;
55
+ function parseSecretVersionResourceName(resourceName) {
56
+ const nameTokens = resourceName.split("/");
57
+ return {
58
+ secret: {
59
+ projectId: nameTokens[1],
60
+ name: nameTokens[3],
61
+ },
62
+ versionId: nameTokens[5],
63
+ };
64
+ }
65
+ exports.parseSecretVersionResourceName = parseSecretVersionResourceName;
66
+ function toSecretVersionResourceName(secretVersion) {
67
+ return `projects/${secretVersion.secret.projectId}/secrets/${secretVersion.secret.name}/versions/${secretVersion.versionId}`;
68
+ }
69
+ exports.toSecretVersionResourceName = toSecretVersionResourceName;
50
70
  async function createSecret(projectId, name, labels) {
51
71
  const createRes = await api.request("POST", `/v1beta1/projects/${projectId}/secrets?secretId=${name}`, {
52
72
  auth: true,
@@ -107,5 +127,6 @@ async function grantServiceAgentRole(secret, serviceAccountEmail, role) {
107
127
  },
108
128
  },
109
129
  });
130
+ utils_1.logLabeledSuccess("SecretManager", `Granted ${role} on projects/${secret.projectId}/secrets/${secret.name} to ${serviceAccountEmail}`);
110
131
  }
111
132
  exports.grantServiceAgentRole = grantServiceAgentRole;
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, crashlyticsSymbolsUpload: false }, configstore_1.configstore.get("previews"));
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"));
7
7
  if (process.env.FIREBASE_CLI_PREVIEWS) {
8
8
  process.env.FIREBASE_CLI_PREVIEWS.split(",").forEach((feature) => {
9
9
  if (lodash_1.has(exports.previews, feature)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "9.21.0",
3
+ "version": "9.22.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -26,6 +26,7 @@
26
26
  "test:client-integration": "./scripts/client-integration-tests/run.sh",
27
27
  "test:compile": "tsc --project tsconfig.compile.json",
28
28
  "test:emulator": "./scripts/emulator-tests/run.sh",
29
+ "test:extensions-deploy": "./scripts/extensions-deploy-tests/run.sh",
29
30
  "test:extensions-emulator": "./scripts/extensions-emulator-tests/run.sh",
30
31
  "test:hosting": "./scripts/hosting-tests/run.sh",
31
32
  "test:triggers-end-to-end": "./scripts/triggers-end-to-end-tests/run.sh",
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "additionalProperties": false,
4
+ "definitions": {
5
+ "ExtensionsConfig": {
6
+ "additionalProperties": false,
7
+ "type": "object"
8
+ }
9
+ },
4
10
  "properties": {
5
11
  "database": {
6
12
  "anyOf": [
@@ -273,6 +279,9 @@
273
279
  },
274
280
  "type": "object"
275
281
  },
282
+ "extensions": {
283
+ "$ref": "#/definitions/ExtensionsConfig"
284
+ },
276
285
  "firestore": {
277
286
  "additionalProperties": false,
278
287
  "properties": {