firebase-tools 13.6.0 → 13.7.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 (61) hide show
  1. package/lib/api.js +1 -1
  2. package/lib/apphosting/config.js +31 -0
  3. package/lib/apphosting/githubConnections.js +261 -0
  4. package/lib/{init/features/apphosting → apphosting}/index.js +21 -17
  5. package/lib/{init/features/apphosting → apphosting}/repo.js +9 -9
  6. package/lib/apphosting/secrets/dialogs.js +169 -0
  7. package/lib/apphosting/secrets/index.js +98 -0
  8. package/lib/commands/apphosting-backends-create.js +4 -2
  9. package/lib/commands/apphosting-backends-delete.js +1 -1
  10. package/lib/commands/apphosting-secrets-access.js +24 -0
  11. package/lib/commands/apphosting-secrets-describe.js +29 -0
  12. package/lib/commands/apphosting-secrets-grantaccess.js +45 -0
  13. package/lib/commands/apphosting-secrets-set.js +105 -0
  14. package/lib/commands/functions-secrets-access.js +2 -2
  15. package/lib/commands/functions-secrets-describe.js +14 -0
  16. package/lib/commands/functions-secrets-destroy.js +2 -2
  17. package/lib/commands/functions-secrets-get.js +3 -17
  18. package/lib/commands/functions-secrets-prune.js +2 -1
  19. package/lib/commands/functions-secrets-set.js +2 -2
  20. package/lib/commands/index.js +6 -0
  21. package/lib/deploy/functions/checkIam.js +3 -6
  22. package/lib/deploy/functions/containerCleaner.js +1 -11
  23. package/lib/deploy/functions/params.js +2 -2
  24. package/lib/deploy/functions/prepare.js +12 -3
  25. package/lib/deploy/functions/prompts.js +39 -7
  26. package/lib/deploy/functions/release/fabricator.js +5 -5
  27. package/lib/deploy/functions/release/index.js +17 -2
  28. package/lib/deploy/functions/release/planner.js +11 -3
  29. package/lib/deploy/functions/runtimes/index.js +6 -43
  30. package/lib/deploy/functions/runtimes/node/index.js +3 -2
  31. package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +15 -34
  32. package/lib/deploy/functions/runtimes/python/index.js +11 -7
  33. package/lib/deploy/functions/runtimes/supported.js +135 -0
  34. package/lib/deploy/functions/services/index.js +4 -0
  35. package/lib/emulator/controller.js +8 -1
  36. package/lib/emulator/functionsEmulator.js +2 -2
  37. package/lib/emulator/hub.js +5 -0
  38. package/lib/experiments.js +12 -0
  39. package/lib/extensions/emulator/specHelper.js +4 -3
  40. package/lib/frameworks/next/constants.js +2 -1
  41. package/lib/frameworks/next/index.js +22 -12
  42. package/lib/frameworks/next/utils.js +32 -3
  43. package/lib/functional.js +2 -2
  44. package/lib/functions/events/v2.js +7 -1
  45. package/lib/functions/secrets.js +40 -22
  46. package/lib/gcp/apphosting.js +15 -2
  47. package/lib/gcp/cloudbuild.js +7 -3
  48. package/lib/gcp/cloudfunctions.js +5 -5
  49. package/lib/gcp/cloudfunctionsv2.js +3 -3
  50. package/lib/gcp/cloudscheduler.js +2 -2
  51. package/lib/gcp/computeEngine.js +7 -0
  52. package/lib/gcp/devConnect.js +24 -11
  53. package/lib/gcp/iam.js +9 -1
  54. package/lib/gcp/secretManager.js +53 -13
  55. package/lib/gcp/serviceusage.js +21 -5
  56. package/lib/init/features/functions/python.js +4 -3
  57. package/lib/init/features/index.js +1 -1
  58. package/lib/utils.js +6 -6
  59. package/package.json +1 -1
  60. package/schema/firebase-config.json +12 -2
  61. /package/lib/{init/features/apphosting → apphosting}/constants.js +0 -0
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.promptForMinInstances = exports.promptForFunctionDeletion = exports.promptForFailurePolicies = void 0;
3
+ exports.promptForMinInstances = exports.promptForUnsafeMigration = exports.promptForFunctionDeletion = exports.promptForFailurePolicies = void 0;
4
4
  const clc = require("colorette");
5
5
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
6
6
  const error_1 = require("../../error");
@@ -49,16 +49,16 @@ async function promptForFailurePolicies(options, want, have) {
49
49
  }
50
50
  }
51
51
  exports.promptForFailurePolicies = promptForFailurePolicies;
52
- async function promptForFunctionDeletion(functionsToDelete, force, nonInteractive) {
52
+ async function promptForFunctionDeletion(functionsToDelete, options) {
53
53
  let shouldDeleteFns = true;
54
- if (functionsToDelete.length === 0 || force) {
54
+ if (functionsToDelete.length === 0 || options.force) {
55
55
  return true;
56
56
  }
57
57
  const deleteList = functionsToDelete
58
58
  .sort(backend.compareFunctions)
59
59
  .map((fn) => "\t" + (0, functionsDeployHelper_1.getFunctionLabel)(fn))
60
60
  .join("\n");
61
- if (nonInteractive) {
61
+ if (options.nonInteractive) {
62
62
  const deleteCommands = functionsToDelete
63
63
  .map((func) => {
64
64
  return "\tfirebase functions:delete " + func.id + " --region " + func.region;
@@ -75,9 +75,7 @@ async function promptForFunctionDeletion(functionsToDelete, force, nonInteractiv
75
75
  "\n\nIf you are renaming a function or changing its region, it is recommended that you create the new " +
76
76
  "function first before deleting the old one to prevent event loss. For more info, visit " +
77
77
  clc.underline("https://firebase.google.com/docs/functions/manage-functions#modify" + "\n"));
78
- shouldDeleteFns = await (0, prompt_1.promptOnce)({
79
- type: "confirm",
80
- name: "confirm",
78
+ shouldDeleteFns = await (0, prompt_1.confirm)({
81
79
  default: false,
82
80
  message: "Would you like to proceed with deletion? Selecting no will continue the rest of the deployments.",
83
81
  });
@@ -85,6 +83,40 @@ async function promptForFunctionDeletion(functionsToDelete, force, nonInteractiv
85
83
  return shouldDeleteFns;
86
84
  }
87
85
  exports.promptForFunctionDeletion = promptForFunctionDeletion;
86
+ async function promptForUnsafeMigration(fnsToUpdate, options) {
87
+ const unsafeUpdates = fnsToUpdate.filter((eu) => eu.unsafe);
88
+ if (unsafeUpdates.length === 0 || options.force) {
89
+ return fnsToUpdate;
90
+ }
91
+ const warnMessage = "The following functions are unsafely changing event types: " +
92
+ clc.bold(unsafeUpdates
93
+ .map((eu) => eu.endpoint)
94
+ .sort(backend.compareFunctions)
95
+ .map(functionsDeployHelper_1.getFunctionLabel)
96
+ .join(", ")) +
97
+ ". " +
98
+ "While automatic migration is allowed for these functions, updating the underlying event type may result in data loss. " +
99
+ "To avoid this, consider the best practices outlined in the migration guide: https://firebase.google.com/docs/functions/manage-functions?gen=2nd#modify-trigger";
100
+ utils.logLabeledWarning("functions", warnMessage);
101
+ const safeUpdates = fnsToUpdate.filter((eu) => !eu.unsafe);
102
+ if (options.nonInteractive) {
103
+ utils.logLabeledWarning("functions", "Skipping updates for functions that may be unsafe to update. To update these functions anyway, deploy again in interactive mode or use the --force option.");
104
+ return safeUpdates;
105
+ }
106
+ for (const eu of unsafeUpdates) {
107
+ const shouldUpdate = await (0, prompt_1.promptOnce)({
108
+ type: "confirm",
109
+ name: "confirm",
110
+ default: false,
111
+ message: `[${(0, functionsDeployHelper_1.getFunctionLabel)(eu.endpoint)}] Would you like to proceed with the unsafe migration?`,
112
+ });
113
+ if (shouldUpdate) {
114
+ safeUpdates.push(eu);
115
+ }
116
+ }
117
+ return safeUpdates;
118
+ }
119
+ exports.promptForUnsafeMigration = promptForUnsafeMigration;
88
120
  async function promptForMinInstances(options, want, have) {
89
121
  if (options.force) {
90
122
  return;
@@ -7,7 +7,7 @@ const error_1 = require("../../../error");
7
7
  const sourceTokenScraper_1 = require("./sourceTokenScraper");
8
8
  const timer_1 = require("./timer");
9
9
  const functional_1 = require("../../../functional");
10
- const runtimes_1 = require("../runtimes");
10
+ const supported_1 = require("../runtimes/supported");
11
11
  const api_1 = require("../../../api");
12
12
  const logger_1 = require("../../../logger");
13
13
  const backend = require("../backend");
@@ -25,7 +25,7 @@ const scheduler = require("../../../gcp/cloudscheduler");
25
25
  const utils = require("../../../utils");
26
26
  const services = require("../services");
27
27
  const v1_1 = require("../../../functions/events/v1");
28
- const checkIam_1 = require("../checkIam");
28
+ const gce = require("../../../gcp/computeEngine");
29
29
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
30
30
  const gcfV1PollerOptions = {
31
31
  apiOrigin: (0, api_1.functionsOrigin)(),
@@ -332,7 +332,7 @@ class Fabricator {
332
332
  else if (backend.isScheduleTriggered(endpoint)) {
333
333
  const invoker = endpoint.serviceAccount
334
334
  ? [endpoint.serviceAccount]
335
- : [(0, checkIam_1.getDefaultComputeServiceAgent)(this.projectNumber)];
335
+ : [gce.getDefaultServiceAccount(this.projectNumber)];
336
336
  await this.executor
337
337
  .run(() => run.setInvokerCreate(endpoint.project, serviceName, invoker))
338
338
  .catch(rethrowAs(endpoint, "set invoker"));
@@ -415,7 +415,7 @@ class Fabricator {
415
415
  else if (backend.isScheduleTriggered(endpoint)) {
416
416
  invoker = endpoint.serviceAccount
417
417
  ? [endpoint.serviceAccount]
418
- : [(0, checkIam_1.getDefaultComputeServiceAgent)(this.projectNumber)];
418
+ : [gce.getDefaultServiceAccount(this.projectNumber)];
419
419
  }
420
420
  if (invoker) {
421
421
  await this.executor
@@ -562,7 +562,7 @@ class Fabricator {
562
562
  .catch(rethrowAs(endpoint, "unregister blocking trigger"));
563
563
  }
564
564
  logOpStart(op, endpoint) {
565
- const runtime = (0, runtimes_1.getHumanFriendlyRuntimeName)(endpoint.runtime);
565
+ const runtime = supported_1.RUNTIMES[endpoint.runtime].friendly;
566
566
  const platform = (0, functionsDeployHelper_1.getHumanFriendlyPlatformName)(endpoint.platform);
567
567
  const label = helper.getFunctionLabel(endpoint);
568
568
  utils.logLabeledBullet("functions", `${op} ${runtime} (${platform}) function ${clc.bold(label)}...`);
@@ -11,6 +11,7 @@ const fabricator = require("./fabricator");
11
11
  const reporter = require("./reporter");
12
12
  const executor = require("./executor");
13
13
  const prompts = require("../prompts");
14
+ const experiments = require("../../../experiments");
14
15
  const functionsConfig_1 = require("../../../functionsConfig");
15
16
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
16
17
  const error_1 = require("../../../error");
@@ -37,12 +38,24 @@ async function release(context, options, payload) {
37
38
  const fnsToDelete = Object.values(plan)
38
39
  .map((regionalChanges) => regionalChanges.endpointsToDelete)
39
40
  .reduce(functional_1.reduceFlat, []);
40
- const shouldDelete = await prompts.promptForFunctionDeletion(fnsToDelete, options.force, options.nonInteractive);
41
+ const shouldDelete = await prompts.promptForFunctionDeletion(fnsToDelete, options);
41
42
  if (!shouldDelete) {
42
43
  for (const change of Object.values(plan)) {
43
44
  change.endpointsToDelete = [];
44
45
  }
45
46
  }
47
+ const fnsToUpdate = Object.values(plan)
48
+ .map((regionalChanges) => regionalChanges.endpointsToUpdate)
49
+ .reduce(functional_1.reduceFlat, []);
50
+ const fnsToUpdateSafe = await prompts.promptForUnsafeMigration(fnsToUpdate, options);
51
+ for (const key of Object.keys(plan)) {
52
+ plan[key].endpointsToUpdate = [];
53
+ }
54
+ for (const eu of fnsToUpdateSafe) {
55
+ const e = eu.endpoint;
56
+ const key = `${e.codebase || ""}-${e.region}-${e.availableMemoryMb || "default"}`;
57
+ plan[key].endpointsToUpdate.push(eu);
58
+ }
46
59
  const throttlerOptions = {
47
60
  retries: 30,
48
61
  backoff: 20000,
@@ -65,7 +78,9 @@ async function release(context, options, payload) {
65
78
  const deletedEndpoints = Object.values(plan)
66
79
  .map((r) => r.endpointsToDelete)
67
80
  .reduce(functional_1.reduceFlat, []);
68
- await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints);
81
+ if (experiments.isEnabled("automaticallydeletegcfartifacts")) {
82
+ await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints);
83
+ }
69
84
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
70
85
  if (allErrors.length) {
71
86
  const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0;
4
- const clc = require("colorette");
3
+ exports.checkForV2Upgrade = exports.checkForIllegalUpdate = exports.checkForUnsafeUpdate = exports.upgradedScheduleFromV1ToV2 = exports.changedV2PubSubTopic = exports.changedTriggerRegion = exports.upgradedToGCFv2WithoutSettingConcurrency = exports.createDeploymentPlan = exports.calculateUpdate = exports.calculateChangesets = void 0;
5
4
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
6
5
  const deploymentTool_1 = require("../../../deploymentTool");
7
6
  const error_1 = require("../../../error");
8
7
  const utils = require("../../../utils");
9
8
  const backend = require("../backend");
10
9
  const v2events = require("../../../functions/events/v2");
10
+ const v2_1 = require("../../../functions/events/v2");
11
11
  function calculateChangesets(want, have, keyFn, deleteAll) {
12
12
  const toCreate = utils.groupBy(Object.keys(want)
13
13
  .filter((id) => !have[id])
@@ -29,7 +29,7 @@ function calculateChangesets(want, have, keyFn, deleteAll) {
29
29
  }, {});
30
30
  const toSkip = utils.groupBy(Object.values(toSkipEndpointsMap), keyFn);
31
31
  if (Object.keys(toSkip).length) {
32
- utils.logLabeledBullet("functions", `Skipping the deploy of unchanged functions with ${clc.bold("experimental")} support for skipdeployingnoopfunctions`);
32
+ utils.logLabeledBullet("functions", "Skipping the deploy of unchanged functions.");
33
33
  }
34
34
  const toUpdate = utils.groupBy(Object.keys(want)
35
35
  .filter((id) => have[id])
@@ -57,6 +57,7 @@ function calculateUpdate(want, have) {
57
57
  checkForIllegalUpdate(want, have);
58
58
  const update = {
59
59
  endpoint: want,
60
+ unsafe: checkForUnsafeUpdate(want, have),
60
61
  };
61
62
  const needsDelete = changedTriggerRegion(want, have) ||
62
63
  changedV2PubSubTopic(want, have) ||
@@ -164,6 +165,13 @@ function upgradedScheduleFromV1ToV2(want, have) {
164
165
  return true;
165
166
  }
166
167
  exports.upgradedScheduleFromV1ToV2 = upgradedScheduleFromV1ToV2;
168
+ function checkForUnsafeUpdate(want, have) {
169
+ return (backend.isEventTriggered(want) &&
170
+ v2_1.FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX.test(want.eventTrigger.eventType) &&
171
+ backend.isEventTriggered(have) &&
172
+ v2_1.FIRESTORE_EVENT_REGEX.test(have.eventTrigger.eventType));
173
+ }
174
+ exports.checkForUnsafeUpdate = checkForUnsafeUpdate;
167
175
  function checkForIllegalUpdate(want, have) {
168
176
  const triggerType = (e) => {
169
177
  if (backend.isHttpsTriggered(e)) {
@@ -1,61 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRuntimeDelegate = exports.getHumanFriendlyRuntimeName = exports.isValidRuntime = exports.isDeprecatedRuntime = void 0;
3
+ exports.getRuntimeDelegate = void 0;
4
4
  const node = require("./node");
5
5
  const python = require("./python");
6
6
  const validate = require("../validate");
7
7
  const error_1 = require("../../../error");
8
- const RUNTIMES = [
9
- "nodejs10",
10
- "nodejs12",
11
- "nodejs14",
12
- "nodejs16",
13
- "nodejs18",
14
- "nodejs20",
15
- "python310",
16
- "python311",
17
- "python312",
18
- ];
19
- const EXPERIMENTAL_RUNTIMES = [];
20
- const DEPRECATED_RUNTIMES = ["nodejs6", "nodejs8"];
21
- function isDeprecatedRuntime(runtime) {
22
- return DEPRECATED_RUNTIMES.includes(runtime);
23
- }
24
- exports.isDeprecatedRuntime = isDeprecatedRuntime;
25
- function isValidRuntime(runtime) {
26
- return RUNTIMES.includes(runtime) || EXPERIMENTAL_RUNTIMES.includes(runtime);
27
- }
28
- exports.isValidRuntime = isValidRuntime;
29
- const MESSAGE_FRIENDLY_RUNTIMES = {
30
- nodejs6: "Node.js 6 (Deprecated)",
31
- nodejs8: "Node.js 8 (Deprecated)",
32
- nodejs10: "Node.js 10",
33
- nodejs12: "Node.js 12",
34
- nodejs14: "Node.js 14",
35
- nodejs16: "Node.js 16",
36
- nodejs18: "Node.js 18",
37
- nodejs20: "Node.js 20",
38
- python310: "Python 3.10",
39
- python311: "Python 3.11",
40
- python312: "Python 3.12",
41
- };
42
- function getHumanFriendlyRuntimeName(runtime) {
43
- return MESSAGE_FRIENDLY_RUNTIMES[runtime] || runtime;
44
- }
45
- exports.getHumanFriendlyRuntimeName = getHumanFriendlyRuntimeName;
8
+ const supported = require("./supported");
46
9
  const factories = [node.tryCreateDelegate, python.tryCreateDelegate];
47
10
  async function getRuntimeDelegate(context) {
48
11
  const { projectDir, sourceDir, runtime } = context;
49
- validate.functionsDirectoryExists(sourceDir, projectDir);
50
- if (runtime && !isValidRuntime(runtime)) {
51
- throw new error_1.FirebaseError(`Cannot deploy function with runtime ${runtime}`);
12
+ if (runtime && !supported.isRuntime(runtime)) {
13
+ throw new error_1.FirebaseError(`firebase.json specifies invalid runtime ${runtime} for directory ${sourceDir}`);
52
14
  }
15
+ validate.functionsDirectoryExists(sourceDir, projectDir);
53
16
  for (const factory of factories) {
54
17
  const delegate = await factory(context);
55
18
  if (delegate) {
56
19
  return delegate;
57
20
  }
58
21
  }
59
- throw new error_1.FirebaseError(`Could not detect language for functions at ${sourceDir}`);
22
+ throw new error_1.FirebaseError(`Could not detect runtime for functions at ${sourceDir}`);
60
23
  }
61
24
  exports.getRuntimeDelegate = getRuntimeDelegate;
@@ -13,6 +13,7 @@ const parseRuntimeAndValidateSDK_1 = require("./parseRuntimeAndValidateSDK");
13
13
  const logger_1 = require("../../../../logger");
14
14
  const utils_1 = require("../../../../utils");
15
15
  const discovery = require("../discovery");
16
+ const supported = require("../supported");
16
17
  const validate = require("./validate");
17
18
  const versioning = require("./versioning");
18
19
  const parseTriggers = require("./parseTriggers");
@@ -25,7 +26,7 @@ async function tryCreateDelegate(context) {
25
26
  return undefined;
26
27
  }
27
28
  const runtime = (0, parseRuntimeAndValidateSDK_1.getRuntimeChoice)(context.sourceDir, context.runtime);
28
- if (!runtime.startsWith("nodejs")) {
29
+ if (!supported.runtimeIsLanguage(runtime, "nodejs")) {
29
30
  logger_1.logger.debug("Customer has a package.json but did not get a nodejs runtime. This should not happen");
30
31
  throw new error_1.FirebaseError(`Unexpected runtime ${runtime}`);
31
32
  }
@@ -38,7 +39,7 @@ class Delegate {
38
39
  this.projectDir = projectDir;
39
40
  this.sourceDir = sourceDir;
40
41
  this.runtime = runtime;
41
- this.name = "nodejs";
42
+ this.language = "nodejs";
42
43
  this._sdkVersion = undefined;
43
44
  this._bin = "";
44
45
  }
@@ -1,31 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRuntimeChoice = exports.DEPRECATED_NODE_VERSION_INFO = exports.UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG = exports.UNSUPPORTED_NODE_VERSION_FIREBASE_JSON_MSG = exports.RUNTIME_NOT_SET = void 0;
3
+ exports.getRuntimeChoice = exports.RUNTIME_NOT_SET = void 0;
4
4
  const path = require("path");
5
- const clc = require("colorette");
6
5
  const error_1 = require("../../../../error");
7
- const runtimes = require("../../runtimes");
6
+ const supported = require("../supported");
8
7
  const cjson = require("cjson");
9
- const ENGINE_RUNTIMES = {
10
- 6: "nodejs6",
11
- 8: "nodejs8",
12
- 10: "nodejs10",
13
- 12: "nodejs12",
14
- 14: "nodejs14",
15
- 16: "nodejs16",
16
- 18: "nodejs18",
17
- 20: "nodejs20",
18
- };
19
- const ENGINE_RUNTIMES_NAMES = Object.values(ENGINE_RUNTIMES);
20
- exports.RUNTIME_NOT_SET = "`runtime` field is required but was not found in firebase.json.\n" +
8
+ const supportedNodeVersions = Object.keys(supported.RUNTIMES)
9
+ .filter((s) => supported.runtimeIsLanguage(s, "nodejs"))
10
+ .filter((s) => !supported.isDecommissioned(s))
11
+ .map((s) => s.substring("nodejs".length));
12
+ exports.RUNTIME_NOT_SET = "`runtime` field is required but was not found in firebase.json or package.json.\n" +
21
13
  "To fix this, add the following lines to the `functions` section of your firebase.json:\n" +
22
- '"runtime": "nodejs18"\n';
23
- exports.UNSUPPORTED_NODE_VERSION_FIREBASE_JSON_MSG = clc.bold(`functions.runtime value is unsupported. ` +
24
- `Valid choices are: ${clc.bold("nodejs{10|12|14|16|18|20}")}.`);
25
- exports.UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG = clc.bold(`package.json in functions directory has an engines field which is unsupported. ` +
26
- `Valid choices are: ${clc.bold('{"node": 10|12|14|16|18|20}')}`);
27
- exports.DEPRECATED_NODE_VERSION_INFO = `\n\nDeploys to runtimes below Node.js 10 are now disabled in the Firebase CLI. ` +
28
- `${clc.bold(`Existing Node.js 8 functions ${clc.underline("will stop executing at a future date")}`)}. Update existing functions to Node.js 10 or greater as soon as possible.`;
14
+ `"runtime": "${supported.latest("nodejs")}" or set the "engine" field in package.json\n`;
29
15
  function getRuntimeChoiceFromPackageJson(sourceDir) {
30
16
  const packageJsonPath = path.join(sourceDir, "package.json");
31
17
  let loaded;
@@ -39,19 +25,14 @@ function getRuntimeChoiceFromPackageJson(sourceDir) {
39
25
  if (!engines || !engines.node) {
40
26
  throw new error_1.FirebaseError(exports.RUNTIME_NOT_SET);
41
27
  }
42
- return ENGINE_RUNTIMES[engines.node];
43
- }
44
- function getRuntimeChoice(sourceDir, runtimeFromConfig) {
45
- const runtime = runtimeFromConfig || getRuntimeChoiceFromPackageJson(sourceDir);
46
- const errorMessage = (runtimeFromConfig
47
- ? exports.UNSUPPORTED_NODE_VERSION_FIREBASE_JSON_MSG
48
- : exports.UNSUPPORTED_NODE_VERSION_PACKAGE_JSON_MSG) + exports.DEPRECATED_NODE_VERSION_INFO;
49
- if (!runtime || !ENGINE_RUNTIMES_NAMES.includes(runtime)) {
50
- throw new error_1.FirebaseError(errorMessage, { exit: 1 });
51
- }
52
- if (runtimes.isDeprecatedRuntime(runtime) || !runtimes.isValidRuntime(runtime)) {
53
- throw new error_1.FirebaseError(errorMessage, { exit: 1 });
28
+ const runtime = `nodejs${engines.node}`;
29
+ if (!supported.isRuntime(runtime)) {
30
+ throw new error_1.FirebaseError(`Detected node engine ${engines.node} in package.json, which is not a ` +
31
+ `supported version. Valid versions are ${supportedNodeVersions.join(", ")}`);
54
32
  }
55
33
  return runtime;
56
34
  }
35
+ function getRuntimeChoice(sourceDir, runtimeFromConfig) {
36
+ return runtimeFromConfig || getRuntimeChoiceFromPackageJson(sourceDir);
37
+ }
57
38
  exports.getRuntimeChoice = getRuntimeChoice;
@@ -1,27 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Delegate = exports.getPythonBinary = exports.tryCreateDelegate = exports.LATEST_VERSION = void 0;
3
+ exports.Delegate = exports.getPythonBinary = exports.tryCreateDelegate = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
6
  const node_fetch_1 = require("node-fetch");
7
7
  const util_1 = require("util");
8
8
  const portfinder = require("portfinder");
9
- const runtimes = require("..");
10
9
  const discovery = require("../discovery");
10
+ const supported = require("../supported");
11
11
  const logger_1 = require("../../../../logger");
12
12
  const python_1 = require("../../../../functions/python");
13
13
  const error_1 = require("../../../../error");
14
- exports.LATEST_VERSION = "python312";
14
+ const functional_1 = require("../../../../functional");
15
15
  async function tryCreateDelegate(context) {
16
+ var _a;
16
17
  const requirementsTextPath = path.join(context.sourceDir, "requirements.txt");
17
18
  if (!(await (0, util_1.promisify)(fs.exists)(requirementsTextPath))) {
18
19
  logger_1.logger.debug("Customer code is not Python code.");
19
20
  return;
20
21
  }
21
- const runtime = context.runtime ? context.runtime : exports.LATEST_VERSION;
22
- if (!runtimes.isValidRuntime(runtime)) {
22
+ const runtime = (_a = context.runtime) !== null && _a !== void 0 ? _a : supported.latest("python");
23
+ if (!supported.isRuntime(runtime)) {
23
24
  throw new error_1.FirebaseError(`Runtime ${runtime} is not a valid Python runtime`);
24
25
  }
26
+ if (!supported.runtimeIsLanguage(runtime, "python")) {
27
+ throw new error_1.FirebaseError(`Internal error. Trying to construct a python runtime delegate for runtime ${runtime}`, { exit: 1 });
28
+ }
25
29
  return Promise.resolve(new Delegate(context.projectId, context.sourceDir, runtime));
26
30
  }
27
31
  exports.tryCreateDelegate = tryCreateDelegate;
@@ -38,7 +42,7 @@ function getPythonBinary(runtime) {
38
42
  else if (runtime === "python312") {
39
43
  return "python3.12";
40
44
  }
41
- return "python";
45
+ (0, functional_1.assertExhaustive)(runtime, `Unhandled python runtime ${runtime}`);
42
46
  }
43
47
  exports.getPythonBinary = getPythonBinary;
44
48
  class Delegate {
@@ -46,7 +50,7 @@ class Delegate {
46
50
  this.projectId = projectId;
47
51
  this.sourceDir = sourceDir;
48
52
  this.runtime = runtime;
49
- this.name = "python";
53
+ this.language = "python";
50
54
  this._bin = "";
51
55
  this._modulesDir = "";
52
56
  }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.guardVersionSupport = exports.isDecommissioned = exports.latest = exports.runtimeIsLanguage = exports.isRuntime = exports.RUNTIMES = void 0;
4
+ const error_1 = require("../../../error");
5
+ const utils = require("../../../utils");
6
+ function runtimes(r) {
7
+ return r;
8
+ }
9
+ exports.RUNTIMES = runtimes({
10
+ nodejs6: {
11
+ friendly: "Node.js 6",
12
+ status: "decommissioned",
13
+ deprecationDate: "2019-04-17",
14
+ decommissionDate: "2020-08-01",
15
+ },
16
+ nodejs8: {
17
+ friendly: "Node.js 8",
18
+ status: "decommissioned",
19
+ deprecationDate: "2020-06-05",
20
+ decommissionDate: "2021-02-01",
21
+ },
22
+ nodejs10: {
23
+ friendly: "Node.js 10",
24
+ status: "GA",
25
+ deprecationDate: "2024-01-30",
26
+ decommissionDate: "2025-01-30",
27
+ },
28
+ nodejs12: {
29
+ friendly: "Node.js 12",
30
+ status: "GA",
31
+ deprecationDate: "2024-01-30",
32
+ decommissionDate: "2025-01-30",
33
+ },
34
+ nodejs14: {
35
+ friendly: "Node.js 14",
36
+ status: "GA",
37
+ deprecationDate: "2024-01-30",
38
+ decommissionDate: "2025-01-30",
39
+ },
40
+ nodejs16: {
41
+ friendly: "Node.js 16",
42
+ status: "GA",
43
+ deprecationDate: "2024-01-30",
44
+ decommissionDate: "2025-01-30",
45
+ },
46
+ nodejs18: {
47
+ friendly: "Node.js 18",
48
+ status: "GA",
49
+ deprecationDate: "2025-04-30",
50
+ decommissionDate: "2025-10-31",
51
+ },
52
+ nodejs20: {
53
+ friendly: "Node.js 20",
54
+ status: "GA",
55
+ deprecationDate: "2026-04-30",
56
+ decommissionDate: "2026-10-31",
57
+ },
58
+ python310: {
59
+ friendly: "Python 3.10",
60
+ status: "GA",
61
+ deprecationDate: "2026-10-04",
62
+ decommissionDate: "2027-04-30",
63
+ },
64
+ python311: {
65
+ friendly: "Python 3.11",
66
+ status: "GA",
67
+ deprecationDate: "2027-10-24",
68
+ decommissionDate: "2028-04-30",
69
+ },
70
+ python312: {
71
+ friendly: "Python 3.12",
72
+ status: "GA",
73
+ deprecationDate: "2028-10-02",
74
+ decommissionDate: "2029-04-30",
75
+ },
76
+ });
77
+ function isRuntime(maybe) {
78
+ return maybe in exports.RUNTIMES;
79
+ }
80
+ exports.isRuntime = isRuntime;
81
+ function runtimeIsLanguage(runtime, language) {
82
+ return runtime.startsWith(language);
83
+ }
84
+ exports.runtimeIsLanguage = runtimeIsLanguage;
85
+ function latest(language, runtimes = Object.keys(exports.RUNTIMES)) {
86
+ const sorted = runtimes
87
+ .filter((s) => runtimeIsLanguage(s, language))
88
+ .sort((left, right) => {
89
+ const leftVersion = +left.substring(language.length);
90
+ const rightVersion = +right.substring(language.length);
91
+ if (isNaN(leftVersion) || isNaN(rightVersion)) {
92
+ throw new error_1.FirebaseError("Internal error. Runtime or language names are malformed", {
93
+ exit: 1,
94
+ });
95
+ }
96
+ return leftVersion - rightVersion;
97
+ });
98
+ const latest = utils.last(sorted);
99
+ if (!latest) {
100
+ throw new error_1.FirebaseError(`Internal error trying to find the latest supported runtime for ${language}`, { exit: 1 });
101
+ }
102
+ return latest;
103
+ }
104
+ exports.latest = latest;
105
+ function isDecommissioned(runtime, now = new Date()) {
106
+ const cutoff = new Date(exports.RUNTIMES[runtime].decommissionDate);
107
+ return cutoff < now;
108
+ }
109
+ exports.isDecommissioned = isDecommissioned;
110
+ function guardVersionSupport(runtime, now = new Date()) {
111
+ const { deprecationDate, decommissionDate } = exports.RUNTIMES[runtime];
112
+ const decommission = new Date(decommissionDate);
113
+ if (now >= decommission) {
114
+ throw new error_1.FirebaseError(`Runtime ${exports.RUNTIMES[runtime].friendly} was decommissioned on ${decommissionDate}. To deploy ` +
115
+ "you must first upgrade your runtime version.", { exit: 1 });
116
+ }
117
+ const deprecation = new Date(deprecationDate);
118
+ if (now >= deprecation) {
119
+ utils.logLabeledWarning("functions", `Runtime ${exports.RUNTIMES[runtime].friendly} was deprecated on ${deprecationDate} and will be ` +
120
+ `decommissioned on ${decommissionDate}, after which you will not be able ` +
121
+ "to deploy without upgrading. Consider upgrading now to avoid disruption. See " +
122
+ "https://cloud.google.com/functions/docs/runtime-support for full " +
123
+ "details on the lifecycle policy");
124
+ return;
125
+ }
126
+ const warning = new Date(deprecation.getTime() - 90 * 24 * 60 * 60 * 1000);
127
+ if (now >= warning) {
128
+ utils.logLabeledWarning("functions", `Runtime ${exports.RUNTIMES[runtime].friendly} will be deprecated on ${deprecationDate} and will be ` +
129
+ `decommissioned on ${decommissionDate}, after which you will not be able ` +
130
+ "to deploy without upgrading. Consider upgrading now to avoid disruption. See " +
131
+ "https://cloud.google.com/functions/docs/runtime-support for full " +
132
+ "details on the lifecycle policy");
133
+ }
134
+ }
135
+ exports.guardVersionSupport = guardVersionSupport;
@@ -104,6 +104,10 @@ const EVENT_SERVICE_MAPPING = {
104
104
  "google.cloud.firestore.document.v1.created": firestoreService,
105
105
  "google.cloud.firestore.document.v1.updated": firestoreService,
106
106
  "google.cloud.firestore.document.v1.deleted": firestoreService,
107
+ "google.cloud.firestore.document.v1.written.withAuthContext": firestoreService,
108
+ "google.cloud.firestore.document.v1.created.withAuthContext": firestoreService,
109
+ "google.cloud.firestore.document.v1.updated.withAuthContext": firestoreService,
110
+ "google.cloud.firestore.document.v1.deleted.withAuthContext": firestoreService,
107
111
  };
108
112
  function serviceForEndpoint(endpoint) {
109
113
  if (backend.isEventTriggered(endpoint)) {
@@ -41,6 +41,7 @@ const downloadableEmulators_1 = require("./downloadableEmulators");
41
41
  const frameworks_1 = require("../frameworks");
42
42
  const experiments = require("../experiments");
43
43
  const portUtils_1 = require("./portUtils");
44
+ const supported_1 = require("../deploy/functions/runtimes/supported");
44
45
  const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
45
46
  async function exportOnExit(options) {
46
47
  const exportOnExitDir = options.exportOnExit;
@@ -308,7 +309,13 @@ async function startAll(options, showUI = true, runningTestScript = false) {
308
309
  utils.assertIsStringOrUndefined(options.extDevDir);
309
310
  for (const cfg of functionsCfg) {
310
311
  const functionsDir = path.join(projectDir, cfg.source);
311
- const runtime = (_e = options.extDevRuntime) !== null && _e !== void 0 ? _e : cfg.runtime;
312
+ let runtime = ((_e = options.extDevRuntime) !== null && _e !== void 0 ? _e : cfg.runtime);
313
+ if (!runtime) {
314
+ runtime = (0, supported_1.latest)("nodejs");
315
+ }
316
+ if (!(0, supported_1.isRuntime)(runtime)) {
317
+ throw new error_1.FirebaseError(`Cannot load functions from ${functionsDir} because it has invalid runtime ${runtime}`);
318
+ }
312
319
  emulatableBackends.push({
313
320
  functionsDir,
314
321
  runtime,
@@ -297,9 +297,9 @@ class FunctionsEmulator {
297
297
  runtime: emulatableBackend.runtime,
298
298
  };
299
299
  const runtimeDelegate = await runtimes.getRuntimeDelegate(runtimeDelegateContext);
300
- logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
300
+ logger_1.logger.debug(`Validating ${runtimeDelegate.language} source`);
301
301
  await runtimeDelegate.validate();
302
- logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
302
+ logger_1.logger.debug(`Building ${runtimeDelegate.language} source`);
303
303
  await runtimeDelegate.build();
304
304
  emulatableBackend.runtime = runtimeDelegate.runtime;
305
305
  emulatableBackend.bin = runtimeDelegate.bin;
@@ -53,6 +53,11 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
53
53
  res.json(body);
54
54
  });
55
55
  app.post(EmulatorHub.PATH_EXPORT, async (req, res) => {
56
+ if (req.headers.origin) {
57
+ res.status(403).json({
58
+ message: `Export cannot be triggered by external callers.`,
59
+ });
60
+ }
56
61
  const path = req.body.path;
57
62
  const initiatedBy = req.body.initiatedBy || "unknown";
58
63
  utils.logLabeledBullet("emulators", `Received export request. Exporting data to ${path}.`);
@@ -39,6 +39,18 @@ exports.ALL_EXPERIMENTS = experiments({
39
39
  "of how that image was created.",
40
40
  public: true,
41
41
  },
42
+ automaticallydeletegcfartifacts: {
43
+ shortDescription: "Control whether functions cleans up images after deploys",
44
+ fullDescription: "To control costs, Firebase defaults to automatically deleting containers " +
45
+ "created during the build process. This has the side-effect of preventing " +
46
+ "users from rolling back to previous revisions using the Run API. To change " +
47
+ `this behavior, call ${(0, colorette_1.bold)("experiments:disable deletegcfartifactsondeploy")} ` +
48
+ `consider also calling ${(0, colorette_1.bold)("experiments:enable deletegcfartifacts")} ` +
49
+ `to enable the new command ${(0, colorette_1.bold)("functions:deletegcfartifacts")} which` +
50
+ "lets you clean up images manually",
51
+ public: true,
52
+ default: true,
53
+ },
42
54
  emulatoruisnapshot: {
43
55
  shortDescription: "Load pre-release versions of the emulator UI",
44
56
  },