firebase-tools 13.17.0 → 13.19.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 (51) hide show
  1. package/README.md +10 -9
  2. package/lib/commands/dataconnect-services-list.js +4 -3
  3. package/lib/commands/dataconnect-sql-grant.js +37 -0
  4. package/lib/commands/dataconnect-sql-migrate.js +2 -1
  5. package/lib/commands/deploy.js +2 -0
  6. package/lib/commands/ext-info.js +3 -1
  7. package/lib/commands/ext-sdk-install.js +88 -0
  8. package/lib/commands/ext.js +1 -0
  9. package/lib/commands/index.js +3 -0
  10. package/lib/dataconnect/client.js +1 -1
  11. package/lib/dataconnect/dataplaneClient.js +1 -1
  12. package/lib/dataconnect/ensureApis.js +6 -1
  13. package/lib/dataconnect/load.js +3 -1
  14. package/lib/dataconnect/provisionCloudSql.js +34 -21
  15. package/lib/dataconnect/schemaMigration.js +126 -73
  16. package/lib/deploy/dataconnect/deploy.js +16 -13
  17. package/lib/deploy/dataconnect/prepare.js +36 -0
  18. package/lib/deploy/dataconnect/release.js +9 -2
  19. package/lib/deploy/extensions/deploymentSummary.js +3 -2
  20. package/lib/deploy/extensions/planner.js +32 -3
  21. package/lib/deploy/extensions/prepare.js +36 -64
  22. package/lib/deploy/extensions/release.js +11 -10
  23. package/lib/deploy/extensions/tasks.js +32 -21
  24. package/lib/deploy/firestore/prepare.js +10 -0
  25. package/lib/deploy/firestore/release.js +3 -6
  26. package/lib/deploy/functions/checkIam.js +7 -2
  27. package/lib/deploy/functions/ensure.js +10 -2
  28. package/lib/deploy/functions/prepare.js +10 -2
  29. package/lib/deploy/functions/runtimes/node/index.js +1 -1
  30. package/lib/deploy/index.js +9 -5
  31. package/lib/emulator/downloadableEmulators.js +9 -9
  32. package/lib/extensions/extensionsApi.js +3 -2
  33. package/lib/extensions/extensionsHelper.js +3 -3
  34. package/lib/extensions/localHelper.js +31 -0
  35. package/lib/extensions/runtimes/common.js +186 -38
  36. package/lib/extensions/runtimes/node.js +399 -0
  37. package/lib/extensions/types.js +10 -14
  38. package/lib/extensions/warnings.js +5 -2
  39. package/lib/firestore/checkDatabaseType.js +10 -3
  40. package/lib/frameworks/angular/index.js +15 -3
  41. package/lib/frameworks/next/utils.js +1 -1
  42. package/lib/gcp/cloudsql/permissions.js +6 -1
  43. package/lib/gcp/secretManager.js +12 -5
  44. package/lib/init/features/dataconnect/index.js +66 -53
  45. package/lib/init/features/firestore/index.js +20 -1
  46. package/lib/init/features/firestore/indexes.js +4 -4
  47. package/package.json +1 -1
  48. package/schema/connector-yaml.json +43 -17
  49. package/templates/init/dataconnect/connector.yaml +0 -1
  50. package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +12 -0
  51. package/templates/init/dataconnect/dataconnect.yaml +1 -1
@@ -17,20 +17,19 @@ const etags_1 = require("../../extensions/etags");
17
17
  const v2FunctionHelper_1 = require("./v2FunctionHelper");
18
18
  const tos_1 = require("../../extensions/tos");
19
19
  const common_1 = require("../../extensions/runtimes/common");
20
- const projectConfig_1 = require("../../functions/projectConfig");
21
20
  const functionsDeployHelper_1 = require("../functions/functionsDeployHelper");
22
- async function prepareHelper(context, options, payload, wantExtensions, noDeleteExtensions, isPrimaryCall) {
21
+ async function prepareHelper(context, options, payload, wantExtensions, haveExtensions, isDynamic) {
23
22
  var _a, _b;
24
23
  const projectId = (0, projectUtils_1.needProjectId)(options);
25
- context.have = await planner.have(projectId);
26
24
  context.want = wantExtensions;
25
+ context.have = haveExtensions;
27
26
  const etagsChanged = (0, etags_1.detectEtagChanges)(options.rc, projectId, context.have);
28
27
  if (etagsChanged.length) {
29
28
  const wantChangedIds = wantExtensions
30
29
  .map((e) => e.instanceId)
31
30
  .filter((id) => etagsChanged.includes(id));
32
31
  if (wantChangedIds.length) {
33
- (0, warnings_1.outOfBandChangesWarning)(wantChangedIds);
32
+ (0, warnings_1.outOfBandChangesWarning)(wantChangedIds, isDynamic);
34
33
  if (!(await prompt.confirm({
35
34
  message: `Do you wish to continue deploying these extension instances?`,
36
35
  default: false,
@@ -52,7 +51,7 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
52
51
  payload.instancesToCreate = context.want.filter((i) => { var _a; return !((_a = context.have) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
53
52
  payload.instancesToConfigure = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isConfigure(i)); });
54
53
  payload.instancesToUpdate = context.want.filter((i) => { var _a; return (_a = context.have) === null || _a === void 0 ? void 0 : _a.some(isUpdate(i)); });
55
- payload.instancesToDelete = context.have.filter((i) => { var _a; return !((_a = context.want) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))) && !(noDeleteExtensions === null || noDeleteExtensions === void 0 ? void 0 : noDeleteExtensions.some(matchesInstanceId(i))); });
54
+ payload.instancesToDelete = context.have.filter((i) => { var _a; return !((_a = context.want) === null || _a === void 0 ? void 0 : _a.some(matchesInstanceId(i))); });
56
55
  if (await (0, warnings_1.displayWarningsForDeploy)(payload.instancesToCreate)) {
57
56
  if (!(await prompt.confirm({
58
57
  message: `Do you wish to continue deploying these extension instances?`,
@@ -64,9 +63,6 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
64
63
  }
65
64
  }
66
65
  const permissionsNeeded = [];
67
- if (!isPrimaryCall) {
68
- payload.instancesToDelete = [];
69
- }
70
66
  if (payload.instancesToCreate.length) {
71
67
  permissionsNeeded.push("firebaseextensions.instances.create");
72
68
  logger_1.logger.info(deploymentSummary.createsSummary(payload.instancesToCreate));
@@ -80,15 +76,20 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
80
76
  logger_1.logger.info(deploymentSummary.configuresSummary(payload.instancesToConfigure));
81
77
  }
82
78
  if (payload.instancesToDelete.length) {
83
- logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete));
84
- if (!(await prompt.confirm({
85
- message: `Would you like to delete ${payload.instancesToDelete
86
- .map((i) => i.instanceId)
87
- .join(", ")}?`,
88
- default: false,
89
- nonInteractive: options.nonInteractive,
90
- force: options.force,
91
- }))) {
79
+ logger_1.logger.info(deploymentSummary.deletesSummary(payload.instancesToDelete, isDynamic));
80
+ if (options.dryRun) {
81
+ logger_1.logger.info("On your next deploy, you will be asked if you want to delete these instances.");
82
+ logger_1.logger.info("If you deploy --force, they will be deleted.");
83
+ }
84
+ if (!options.dryRun &&
85
+ !(await prompt.confirm({
86
+ message: `Would you like to delete ${payload.instancesToDelete
87
+ .map((i) => i.instanceId)
88
+ .join(", ")}?`,
89
+ default: false,
90
+ nonInteractive: options.nonInteractive,
91
+ force: options.force,
92
+ }))) {
92
93
  payload.instancesToDelete = [];
93
94
  }
94
95
  else {
@@ -96,61 +97,36 @@ async function prepareHelper(context, options, payload, wantExtensions, noDelete
96
97
  }
97
98
  }
98
99
  await (0, requirePermissions_1.requirePermissions)(options, permissionsNeeded);
99
- await (0, tos_1.acceptLatestAppDeveloperTOS)(options, projectId, context.want.map((i) => i.instanceId));
100
+ if (options.dryRun) {
101
+ const appDevTos = await (0, tos_1.getAppDeveloperTOSStatus)(projectId);
102
+ if (!appDevTos.lastAcceptedVersion) {
103
+ logger_1.logger.info("On your next deploy, you will be asked to accept the Firebase Extensions App Developer Terms of Service");
104
+ }
105
+ }
106
+ else {
107
+ await (0, tos_1.acceptLatestAppDeveloperTOS)(options, projectId, context.want.map((i) => i.instanceId));
108
+ }
100
109
  }
101
110
  async function prepareDynamicExtensions(context, options, payload, builds) {
102
111
  const filters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
103
112
  const extensions = (0, common_1.extractExtensionsFromBuilds)(builds, filters);
104
- const isApiEnabled = await (0, extensionsHelper_1.checkExtensionsApiEnabled)(options);
105
- if (Object.keys(extensions).length === 0 && !isApiEnabled) {
106
- return;
107
- }
108
113
  const projectId = (0, projectUtils_1.needProjectId)(options);
109
114
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
110
- const aliases = (0, projectUtils_1.getAliases)(options, projectId);
111
- const projectDir = options.config.projectDir;
112
- const isPrimaryCall = !!options.only && !options.only.split(",").includes("extensions");
113
115
  await (0, extensionsHelper_1.ensureExtensionsApiEnabled)(options);
114
116
  await (0, requirePermissions_1.requirePermissions)(options, ["firebaseextensions.instances.list"]);
117
+ let haveExtensions = await planner.haveDynamic(projectId);
118
+ haveExtensions = haveExtensions.filter((e) => { var _a; return (0, common_1.extensionMatchesAnyFilter)((_a = e.labels) === null || _a === void 0 ? void 0 : _a.codebase, e.instanceId, filters); });
119
+ if (Object.keys(extensions).length === 0 && haveExtensions.length === 0) {
120
+ return;
121
+ }
115
122
  const dynamicWant = await planner.wantDynamic({
116
123
  projectId,
117
124
  projectNumber,
118
125
  extensions,
119
126
  });
120
- let noDeleteExtensions = [];
121
- if (isPrimaryCall) {
122
- const firebaseJsonWant = await planner.want({
123
- projectId,
124
- projectNumber,
125
- aliases,
126
- projectDir,
127
- extensions: options.config.get("extensions", {}),
128
- });
129
- noDeleteExtensions = noDeleteExtensions.concat(firebaseJsonWant);
130
- if (hasNonDeployingCodebases(options)) {
131
- const dynamicAll = await planner.wantDynamic({
132
- projectId,
133
- projectNumber,
134
- extensions: await (0, common_1.extractAllDynamicExtensions)(options),
135
- });
136
- noDeleteExtensions = noDeleteExtensions.concat(dynamicAll);
137
- }
138
- }
139
- return prepareHelper(context, options, payload, dynamicWant, noDeleteExtensions, isPrimaryCall);
127
+ return prepareHelper(context, options, payload, dynamicWant, haveExtensions, true);
140
128
  }
141
129
  exports.prepareDynamicExtensions = prepareDynamicExtensions;
142
- function hasNonDeployingCodebases(options) {
143
- const functionFilters = (0, functionsDeployHelper_1.getEndpointFilters)(options);
144
- if (functionFilters === null || functionFilters === void 0 ? void 0 : functionFilters.length) {
145
- return true;
146
- }
147
- const functionsConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
148
- const allCodebases = (0, functionsDeployHelper_1.targetCodebases)(functionsConfig);
149
- const deployingCodebases = (0, functionsDeployHelper_1.targetCodebases)(functionsConfig, functionFilters);
150
- if (allCodebases.length > deployingCodebases.length) {
151
- return true;
152
- }
153
- }
154
130
  async function prepare(context, options, payload) {
155
131
  context.extensionsStartTime = Date.now();
156
132
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -159,19 +135,15 @@ async function prepare(context, options, payload) {
159
135
  const projectDir = options.config.projectDir;
160
136
  await (0, extensionsHelper_1.ensureExtensionsApiEnabled)(options);
161
137
  await (0, requirePermissions_1.requirePermissions)(options, ["firebaseextensions.instances.list"]);
162
- const firebaseJsonWant = await planner.want({
138
+ const wantExtensions = await planner.want({
163
139
  projectId,
164
140
  projectNumber,
165
141
  aliases,
166
142
  projectDir,
167
143
  extensions: options.config.get("extensions", {}),
168
144
  });
169
- const dynamicWant = await planner.wantDynamic({
170
- projectId,
171
- projectNumber,
172
- extensions: await (0, common_1.extractAllDynamicExtensions)(options),
173
- });
174
- return prepareHelper(context, options, payload, firebaseJsonWant, dynamicWant, true);
145
+ const haveExtensions = await planner.have(projectId);
146
+ return prepareHelper(context, options, payload, wantExtensions, haveExtensions, false);
175
147
  }
176
148
  exports.prepare = prepare;
177
149
  const matchesInstanceId = (dep) => (test) => {
@@ -24,20 +24,20 @@ async function release(context, options, payload) {
24
24
  concurrency: 5,
25
25
  handler: tasks.extensionsDeploymentHandler(errorHandler),
26
26
  });
27
- for (const creation of (_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []) {
28
- const task = tasks.createExtensionInstanceTask(projectId, creation);
27
+ for (const inst of (_a = payload.instancesToConfigure) !== null && _a !== void 0 ? _a : []) {
28
+ const task = tasks.configureExtensionInstanceTask(projectId, inst);
29
29
  void deploymentQueue.run(task);
30
30
  }
31
- for (const update of (_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []) {
32
- const task = tasks.updateExtensionInstanceTask(projectId, update);
31
+ for (const inst of (_b = payload.instancesToDelete) !== null && _b !== void 0 ? _b : []) {
32
+ const task = tasks.deleteExtensionInstanceTask(projectId, inst);
33
33
  void deploymentQueue.run(task);
34
34
  }
35
- for (const update of (_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []) {
36
- const task = tasks.configureExtensionInstanceTask(projectId, update);
35
+ for (const inst of (_c = payload.instancesToCreate) !== null && _c !== void 0 ? _c : []) {
36
+ const task = tasks.createExtensionInstanceTask(projectId, inst);
37
37
  void deploymentQueue.run(task);
38
38
  }
39
- for (const deletion of (_d = payload.instancesToDelete) !== null && _d !== void 0 ? _d : []) {
40
- const task = tasks.deleteExtensionInstanceTask(projectId, deletion);
39
+ for (const inst of (_d = payload.instancesToUpdate) !== null && _d !== void 0 ? _d : []) {
40
+ const task = tasks.updateExtensionInstanceTask(projectId, inst);
41
41
  void deploymentQueue.run(task);
42
42
  }
43
43
  const deploymentPromise = deploymentQueue.wait();
@@ -53,8 +53,9 @@ async function release(context, options, payload) {
53
53
  errors: (_o = errorHandler.errors.length) !== null && _o !== void 0 ? _o : 0,
54
54
  interactive: options.nonInteractive ? "false" : "true",
55
55
  }, duration);
56
- const newHave = await planner.have(projectId);
57
- (0, etags_1.saveEtags)(options.rc, projectId, newHave);
56
+ const have = await planner.have(projectId);
57
+ const dynamicHave = await planner.haveDynamic(projectId);
58
+ (0, etags_1.saveEtags)(options.rc, projectId, have.concat(dynamicHave));
58
59
  if (errorHandler.hasErrors()) {
59
60
  errorHandler.print();
60
61
  throw new error_1.FirebaseError(`Extensions deployment failed.`);
@@ -7,6 +7,7 @@ const extensionsApi = require("../../extensions/extensionsApi");
7
7
  const extensionsHelper_1 = require("../../extensions/extensionsHelper");
8
8
  const refs = require("../../extensions/refs");
9
9
  const utils = require("../../utils");
10
+ const types_1 = require("../../extensions/types");
10
11
  const isRetryable = (err) => err.status === 429 || err.status === 409;
11
12
  function extensionsDeploymentHandler(errorHandler) {
12
13
  return async (task) => {
@@ -27,34 +28,37 @@ function extensionsDeploymentHandler(errorHandler) {
27
28
  exports.extensionsDeploymentHandler = extensionsDeploymentHandler;
28
29
  function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
29
30
  const run = async () => {
31
+ if (!validateOnly) {
32
+ utils.logLabeledBullet("extensions", `Creating ${clc.bold(instanceSpec.instanceId)} extension instance`);
33
+ }
34
+ const createArgs = {
35
+ projectId,
36
+ instanceId: instanceSpec.instanceId,
37
+ params: instanceSpec.params,
38
+ systemParams: instanceSpec.systemParams,
39
+ allowedEventTypes: instanceSpec.allowedEventTypes,
40
+ eventarcChannel: instanceSpec.eventarcChannel,
41
+ validateOnly,
42
+ labels: instanceSpec.labels,
43
+ };
30
44
  if (instanceSpec.ref) {
31
- await extensionsApi.createInstance({
32
- projectId,
33
- instanceId: instanceSpec.instanceId,
34
- params: instanceSpec.params,
35
- systemParams: instanceSpec.systemParams,
36
- extensionVersionRef: refs.toExtensionVersionRef(instanceSpec.ref),
37
- allowedEventTypes: instanceSpec.allowedEventTypes,
38
- eventarcChannel: instanceSpec.eventarcChannel,
39
- validateOnly,
40
- });
45
+ createArgs.extensionVersionRef = refs.toExtensionVersionRef(instanceSpec.ref);
41
46
  }
42
47
  else if (instanceSpec.localPath) {
43
- const extensionSource = await (0, extensionsHelper_1.createSourceFromLocation)(projectId, instanceSpec.localPath);
44
- await extensionsApi.createInstance({
45
- projectId,
46
- instanceId: instanceSpec.instanceId,
47
- params: instanceSpec.params,
48
- systemParams: instanceSpec.systemParams,
49
- extensionSource,
50
- allowedEventTypes: instanceSpec.allowedEventTypes,
51
- eventarcChannel: instanceSpec.eventarcChannel,
52
- validateOnly,
53
- });
48
+ createArgs.extensionSource = await (0, extensionsHelper_1.createSourceFromLocation)(projectId, instanceSpec.localPath);
54
49
  }
55
50
  else {
56
51
  throw new error_1.FirebaseError(`Tried to create extension instance ${instanceSpec.instanceId} without a ref or a local path. This should never happen.`);
57
52
  }
53
+ try {
54
+ await extensionsApi.createInstance(createArgs);
55
+ }
56
+ catch (err) {
57
+ if ((0, types_1.isObject)(err) && err.status === 409) {
58
+ throw new error_1.FirebaseError(`Failed to create extension instance. Extension instance ${clc.bold(instanceSpec.instanceId)} already exists.`);
59
+ }
60
+ throw err;
61
+ }
58
62
  printSuccess(instanceSpec.instanceId, "create", validateOnly);
59
63
  return;
60
64
  };
@@ -67,6 +71,9 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
67
71
  exports.createExtensionInstanceTask = createExtensionInstanceTask;
68
72
  function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
69
73
  const run = async () => {
74
+ if (!validateOnly) {
75
+ utils.logLabeledBullet("extensions", `Updating ${clc.bold(instanceSpec.instanceId)} extension instance`);
76
+ }
70
77
  if (instanceSpec.ref) {
71
78
  await extensionsApi.updateInstanceFromRegistry({
72
79
  projectId,
@@ -109,6 +116,9 @@ function updateExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
109
116
  exports.updateExtensionInstanceTask = updateExtensionInstanceTask;
110
117
  function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly = false) {
111
118
  const run = async () => {
119
+ if (!validateOnly) {
120
+ utils.logLabeledBullet("extensions", `Configuring ${clc.bold(instanceSpec.instanceId)} extension instance`);
121
+ }
112
122
  if (instanceSpec.ref) {
113
123
  await extensionsApi.configureInstance({
114
124
  projectId,
@@ -139,6 +149,7 @@ function configureExtensionInstanceTask(projectId, instanceSpec, validateOnly =
139
149
  exports.configureExtensionInstanceTask = configureExtensionInstanceTask;
140
150
  function deleteExtensionInstanceTask(projectId, instanceSpec) {
141
151
  const run = async () => {
152
+ utils.logLabeledBullet("extensions", `Deleting ${clc.bold(instanceSpec.instanceId)} extension instance`);
142
153
  await extensionsApi.deleteInstance(projectId, instanceSpec.instanceId);
143
154
  printSuccess(instanceSpec.instanceId, "delete", false);
144
155
  return;
@@ -5,6 +5,7 @@ const loadCJSON_1 = require("../../loadCJSON");
5
5
  const rulesDeploy_1 = require("../../rulesDeploy");
6
6
  const utils = require("../../utils");
7
7
  const fsConfig = require("../../firestore/fsConfig");
8
+ const logger_1 = require("../../logger");
8
9
  function prepareRules(context, rulesDeploy, databaseId, rulesFile) {
9
10
  rulesDeploy.addFile(rulesFile);
10
11
  context.firestore.rules.push({
@@ -23,6 +24,7 @@ function prepareIndexes(context, options, databaseId, indexesFileName) {
23
24
  });
24
25
  }
25
26
  async function default_1(context, options) {
27
+ var _a;
26
28
  if (options.only) {
27
29
  const targets = options.only.split(",");
28
30
  const excludeRules = targets.indexOf("firestore:indexes") >= 0;
@@ -57,5 +59,13 @@ async function default_1(context, options) {
57
59
  if (context.firestore.rules.length > 0) {
58
60
  await rulesDeploy.compile();
59
61
  }
62
+ const rulesContext = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rules;
63
+ for (const ruleContext of rulesContext) {
64
+ const databaseId = ruleContext.databaseId;
65
+ const rulesFile = ruleContext.rulesFile;
66
+ if (!rulesFile) {
67
+ logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
68
+ }
69
+ }
60
70
  }
61
71
  exports.default = default_1;
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const rulesDeploy_1 = require("../../rulesDeploy");
4
- const logger_1 = require("../../logger");
5
- async function default_1(context, options) {
4
+ async function default_1(context) {
6
5
  var _a, _b;
7
6
  const rulesDeploy = (_a = context === null || context === void 0 ? void 0 : context.firestore) === null || _a === void 0 ? void 0 : _a.rulesDeploy;
8
7
  if (!context.firestoreRules || !rulesDeploy) {
@@ -12,11 +11,9 @@ async function default_1(context, options) {
12
11
  await Promise.all(rulesContext.map(async (ruleContext) => {
13
12
  const databaseId = ruleContext.databaseId;
14
13
  const rulesFile = ruleContext.rulesFile;
15
- if (!rulesFile) {
16
- logger_1.logger.error(`Invalid firestore config for ${databaseId} database: ${JSON.stringify(options.config.src.firestore)}`);
17
- return;
14
+ if (rulesFile) {
15
+ return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
18
16
  }
19
- return rulesDeploy.release(rulesFile, rulesDeploy_1.RulesetServiceType.CLOUD_FIRESTORE, databaseId);
20
17
  }));
21
18
  }
22
19
  exports.default = default_1;
@@ -102,7 +102,7 @@ function obtainDefaultComputeServiceAgentBindings(projectNumber) {
102
102
  return [runInvokerBinding, eventarcEventReceiverBinding];
103
103
  }
104
104
  exports.obtainDefaultComputeServiceAgentBindings = obtainDefaultComputeServiceAgentBindings;
105
- async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
105
+ async function ensureServiceAgentRoles(projectId, projectNumber, want, have, dryRun) {
106
106
  const wantServices = backend.allEndpoints(want).reduce(reduceEventsToServices, []);
107
107
  const haveServices = backend.allEndpoints(have).reduce(reduceEventsToServices, []);
108
108
  const newServices = wantServices.filter((wantS) => !haveServices.find((haveS) => wantS.name === haveS.name));
@@ -138,7 +138,12 @@ async function ensureServiceAgentRoles(projectId, projectNumber, want, have) {
138
138
  return;
139
139
  }
140
140
  try {
141
- await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
141
+ if (dryRun) {
142
+ logger_1.logger.info(`On your next deploy, the following required roles will be granted: ${requiredBindings.map((b) => `${b.members.join(", ")}: ${(0, colorette_1.bold)(b.role)}`)}`);
143
+ }
144
+ else {
145
+ await (0, resourceManager_1.setIamPolicy)(projectNumber, policy, "bindings");
146
+ }
142
147
  }
143
148
  catch (err) {
144
149
  iam.printManualIamConfig(requiredBindings, projectId, "functions");
@@ -79,11 +79,19 @@ async function secretsToServiceAccounts(b) {
79
79
  }
80
80
  return secretsToSa;
81
81
  }
82
- async function secretAccess(projectId, wantBackend, haveBackend) {
82
+ async function secretAccess(projectId, wantBackend, haveBackend, dryRun) {
83
83
  var _a, _b;
84
84
  const ensureAccess = async (secret, serviceAccounts) => {
85
85
  (0, utils_1.logLabeledBullet)("functions", `ensuring ${clc.bold(serviceAccounts.join(", "))} access to secret ${clc.bold(secret)}.`);
86
- await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
86
+ if (dryRun) {
87
+ const check = await (0, secretManager_1.checkServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
88
+ if (check.length) {
89
+ (0, utils_1.logLabeledBullet)("functions", `On your next deploy, ${clc.bold(serviceAccounts.join(", "))} will be granted access to secret ${clc.bold(secret)}.`);
90
+ }
91
+ }
92
+ else {
93
+ await (0, secretManager_1.ensureServiceAgentRole)({ name: secret, projectId }, serviceAccounts, "roles/secretmanager.secretAccessor");
94
+ }
87
95
  (0, utils_1.logLabeledSuccess)("functions", `ensured ${clc.bold(serviceAccounts.join(", "))} access to ${clc.bold(secret)}.`);
88
96
  };
89
97
  const wantSecrets = await secretsToServiceAccounts(wantBackend);
@@ -27,6 +27,7 @@ const serviceusage_1 = require("../../gcp/serviceusage");
27
27
  const applyHash_1 = require("./cache/applyHash");
28
28
  const backend_1 = require("./backend");
29
29
  const functional_1 = require("../../functional");
30
+ const prepare_1 = require("../extensions/prepare");
30
31
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
31
32
  async function prepare(context, options, payload) {
32
33
  var _a, _b;
@@ -55,6 +56,13 @@ async function prepare(context, options, payload) {
55
56
  }
56
57
  context.codebaseDeployEvents = {};
57
58
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
59
+ if (Object.values(wantBuilds).some((b) => b.extensions)) {
60
+ const extContext = {};
61
+ const extPayload = {};
62
+ await (0, prepare_1.prepareDynamicExtensions)(extContext, options, extPayload, wantBuilds);
63
+ context.extensions = extContext;
64
+ payload.extensions = extPayload;
65
+ }
58
66
  const codebaseUsesEnvs = [];
59
67
  const wantBackends = {};
60
68
  for (const [codebase, wantBuild] of Object.entries(wantBuilds)) {
@@ -183,9 +191,9 @@ async function prepare(context, options, payload) {
183
191
  await (0, prompts_1.promptForFailurePolicies)(options, matchingBackend, haveBackend);
184
192
  await (0, prompts_1.promptForMinInstances)(options, matchingBackend, haveBackend);
185
193
  await backend.checkAvailability(context, matchingBackend);
186
- await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend);
187
194
  await validate.secretsAreValid(projectId, matchingBackend);
188
- await ensure.secretAccess(projectId, matchingBackend, haveBackend);
195
+ await (0, checkIam_1.ensureServiceAgentRoles)(projectId, projectNumber, matchingBackend, haveBackend, options.dryRun);
196
+ await ensure.secretAccess(projectId, matchingBackend, haveBackend, options.dryRun);
189
197
  updateEndpointTargetedStatus(wantBackends, context.filters || []);
190
198
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
191
199
  }
@@ -18,7 +18,7 @@ const validate = require("./validate");
18
18
  const versioning = require("./versioning");
19
19
  const parseTriggers = require("./parseTriggers");
20
20
  const fsutils_1 = require("../../../../fsutils");
21
- const MIN_FUNCTIONS_SDK_VERSION = "3.20.0";
21
+ const MIN_FUNCTIONS_SDK_VERSION = "5.1.0";
22
22
  async function tryCreateDelegate(context) {
23
23
  const packageJsonPath = path.join(context.sourceDir, "package.json");
24
24
  if (!(await (0, util_1.promisify)(fs.exists)(packageJsonPath))) {
@@ -41,6 +41,7 @@ const chain = async function (fns, context, options, payload) {
41
41
  }
42
42
  };
43
43
  const deploy = async function (targetNames, options, customContext = {}) {
44
+ var _a;
44
45
  const projectId = (0, projectUtils_1.needProjectId)(options);
45
46
  const payload = {};
46
47
  const context = Object.assign({ projectId }, customContext);
@@ -85,9 +86,11 @@ const deploy = async function (targetNames, options, customContext = {}) {
85
86
  }
86
87
  predeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "predeploy"));
87
88
  prepares.push(target.prepare);
88
- deploys.push(target.deploy);
89
- releases.push(target.release);
90
- postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
89
+ if (!options.dryRun) {
90
+ deploys.push(target.deploy);
91
+ releases.push(target.release);
92
+ postdeploys.push((0, lifecycleHooks_1.lifecycleHooks)(targetName, "postdeploy"));
93
+ }
91
94
  }
92
95
  logger_1.logger.info();
93
96
  logger_1.logger.info((0, colorette_1.bold)((0, colorette_1.white)("===") + " Deploying to '" + projectId + "'..."));
@@ -110,11 +113,12 @@ const deploy = async function (targetNames, options, customContext = {}) {
110
113
  analyticsParams[t] = "true";
111
114
  }
112
115
  await (0, track_1.trackGA4)("product_deploy", analyticsParams, duration);
116
+ const successMessage = options.dryRun ? "Dry run complete!" : "Deploy complete!";
113
117
  logger_1.logger.info();
114
- (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)("Deploy complete!")));
118
+ (0, utils_1.logSuccess)((0, colorette_1.bold)((0, colorette_1.underline)(successMessage)));
115
119
  logger_1.logger.info();
116
120
  const deployedHosting = (0, lodash_1.includes)(targetNames, "hosting");
117
- logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)(options.project, "/overview"));
121
+ logger_1.logger.info((0, colorette_1.bold)("Project Console:"), (0, utils_1.consoleUrl)((_a = options.project) !== null && _a !== void 0 ? _a : "_", "/overview"));
118
122
  if (deployedHosting) {
119
123
  (0, lodash_1.each)(context.hosting.deploys, (deploy) => {
120
124
  logger_1.logger.info((0, colorette_1.bold)("Hosting URL:"), (0, utils_1.addSubdomain)((0, api_1.hostingOrigin)(), deploy.config.site));
@@ -46,20 +46,20 @@ const EMULATOR_UPDATE_DETAILS = {
46
46
  },
47
47
  dataconnect: process.platform === "darwin"
48
48
  ? {
49
- version: "1.3.6",
50
- expectedSize: 24867584,
51
- expectedChecksum: "b924d31e3620d7ed4486a95e22629fc8",
49
+ version: "1.3.8",
50
+ expectedSize: 25027328,
51
+ expectedChecksum: "903f0e8dd8212fec575dc2eba34be6e7",
52
52
  }
53
53
  : process.platform === "win32"
54
54
  ? {
55
- version: "1.3.6",
56
- expectedSize: 25292288,
57
- expectedChecksum: "45025491b43b55a94f4e4db8df903250",
55
+ version: "1.3.8",
56
+ expectedSize: 25450496,
57
+ expectedChecksum: "e09999deda7301eba06f88face5e1744",
58
58
  }
59
59
  : {
60
- version: "1.3.6",
61
- expectedSize: 24785048,
62
- expectedChecksum: "6ae5820c0470c5a954540ad97838ec01",
60
+ version: "1.3.8",
61
+ expectedSize: 24940696,
62
+ expectedChecksum: "69aeb9755d4ec2e77bdadbc40236b306",
63
63
  },
64
64
  };
65
65
  exports.DownloadDetails = {
@@ -15,10 +15,11 @@ const extensionsApiClient = new apiv2_1.Client({
15
15
  urlPrefix: (0, api_1.extensionsOrigin)(),
16
16
  apiVersion: EXTENSIONS_API_VERSION,
17
17
  });
18
- async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
18
+ async function createInstanceHelper(projectId, instanceId, config, labels, validateOnly = false) {
19
19
  const createRes = await extensionsApiClient.post(`/projects/${projectId}/instances/`, {
20
20
  name: `projects/${projectId}/instances/${instanceId}`,
21
21
  config,
22
+ labels,
22
23
  }, {
23
24
  queryParams: {
24
25
  validateOnly: validateOnly ? "true" : "false",
@@ -63,7 +64,7 @@ async function createInstance(args) {
63
64
  if (args.eventarcChannel) {
64
65
  config.eventarcChannel = args.eventarcChannel;
65
66
  }
66
- return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
67
+ return await createInstanceHelper(args.projectId, args.instanceId, config, args.labels, args.validateOnly);
67
68
  }
68
69
  exports.createInstance = createInstance;
69
70
  async function deleteInstance(projectId, instanceId) {
@@ -7,7 +7,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
7
7
  function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
10
+ exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
11
11
  const clc = require("colorette");
12
12
  const ora = require("ora");
13
13
  const semver = require("semver");
@@ -69,7 +69,7 @@ const AUTOPOPULATED_PARAM_NAMES = [
69
69
  "DATABASE_INSTANCE",
70
70
  "DATABASE_URL",
71
71
  ];
72
- exports.AUTOPOULATED_PARAM_PLACEHOLDERS = {
72
+ exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = {
73
73
  PROJECT_ID: "project-id",
74
74
  STORAGE_BUCKET: "project-id.appspot.com",
75
75
  EXT_INSTANCE_ID: "extension-id",
@@ -449,7 +449,7 @@ async function validateExtensionSpec(rootDirectory, extensionId) {
449
449
  throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
450
450
  }
451
451
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
452
- subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
452
+ subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOPULATED_PARAM_PLACEHOLDERS);
453
453
  validateSpec(subbedSpec);
454
454
  return extensionSpec;
455
455
  }
@@ -6,11 +6,20 @@ const path = require("path");
6
6
  const yaml = require("yaml");
7
7
  const fsutils_1 = require("../fsutils");
8
8
  const error_1 = require("../error");
9
+ const types_1 = require("./types");
9
10
  const logger_1 = require("../logger");
11
+ const extensionsHelper_1 = require("./extensionsHelper");
10
12
  exports.EXTENSIONS_SPEC_FILE = "extension.yaml";
11
13
  const EXTENSIONS_PREINSTALL_FILE = "PREINSTALL.md";
12
14
  async function getLocalExtensionSpec(directory) {
13
15
  const spec = await parseYAML(readFile(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE)));
16
+ if (spec.lifecycleEvents) {
17
+ spec.lifecycleEvents = fixLifecycleEvents(spec.lifecycleEvents);
18
+ }
19
+ if (!(0, types_1.isExtensionSpec)(spec)) {
20
+ (0, extensionsHelper_1.validateSpec)(spec);
21
+ throw new error_1.FirebaseError("Error: extension.yaml does not contain a valid extension specification.");
22
+ }
14
23
  try {
15
24
  const preinstall = readFile(path.resolve(directory, EXTENSIONS_PREINSTALL_FILE));
16
25
  spec.preinstallContent = preinstall;
@@ -21,6 +30,28 @@ async function getLocalExtensionSpec(directory) {
21
30
  return spec;
22
31
  }
23
32
  exports.getLocalExtensionSpec = getLocalExtensionSpec;
33
+ function fixLifecycleEvents(lifecycleEvents) {
34
+ const stages = {
35
+ onInstall: "ON_INSTALL",
36
+ onUpdate: "ON_UPDATE",
37
+ onConfigure: "ON_CONFIGURE",
38
+ stageUnspecified: "STAGE_UNSPECIFIED",
39
+ };
40
+ const arrayLifecycle = [];
41
+ if ((0, types_1.isObject)(lifecycleEvents)) {
42
+ for (const [key, val] of Object.entries(lifecycleEvents)) {
43
+ if ((0, types_1.isObject)(val) &&
44
+ typeof val.function === "string" &&
45
+ typeof val.processingMessage === "string") {
46
+ arrayLifecycle.push({
47
+ stage: stages[key] || stages["stageUnspecified"],
48
+ taskQueueTriggerFunction: val.function,
49
+ });
50
+ }
51
+ }
52
+ }
53
+ return arrayLifecycle;
54
+ }
24
55
  function findExtensionYaml(directory) {
25
56
  while (!(0, fsutils_1.fileExistsSync)(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE))) {
26
57
  const parentDir = path.dirname(directory);