firebase-tools 13.35.1 → 14.0.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 (106) hide show
  1. package/lib/appdistribution/client.js +4 -2
  2. package/lib/apphosting/backend.js +65 -11
  3. package/lib/apphosting/config.js +130 -101
  4. package/lib/apphosting/rollout.js +3 -9
  5. package/lib/apphosting/secrets/dialogs.js +5 -2
  6. package/lib/apphosting/secrets/index.js +45 -3
  7. package/lib/apphosting/yaml.js +19 -8
  8. package/lib/commands/appdistribution-groups-create.js +1 -1
  9. package/lib/commands/appdistribution-groups-delete.js +1 -1
  10. package/lib/commands/appdistribution-groups-list.js +1 -1
  11. package/lib/commands/appdistribution-testers-add.js +1 -1
  12. package/lib/commands/appdistribution-testers-remove.js +1 -1
  13. package/lib/commands/apphosting-backends-create.js +1 -8
  14. package/lib/commands/apphosting-backends-delete.js +16 -26
  15. package/lib/commands/apphosting-backends-get.js +10 -16
  16. package/lib/commands/apphosting-backends-list.js +4 -10
  17. package/lib/commands/apphosting-rollouts-create.js +1 -8
  18. package/lib/commands/apphosting-secrets-access.js +1 -1
  19. package/lib/commands/apphosting-secrets-describe.js +1 -1
  20. package/lib/commands/apphosting-secrets-grantaccess.js +19 -9
  21. package/lib/commands/apphosting-secrets-set.js +31 -1
  22. package/lib/commands/apps-android-sha-create.js +1 -1
  23. package/lib/commands/apps-android-sha-delete.js +1 -1
  24. package/lib/commands/apps-android-sha-list.js +1 -1
  25. package/lib/commands/apps-create.js +1 -1
  26. package/lib/commands/apps-init.js +1 -1
  27. package/lib/commands/auth-export.js +1 -1
  28. package/lib/commands/auth-import.js +1 -1
  29. package/lib/commands/database-instances-create.js +1 -1
  30. package/lib/commands/database-profile.js +1 -2
  31. package/lib/commands/database-settings-set.js +1 -1
  32. package/lib/commands/database-update.js +1 -1
  33. package/lib/commands/dataconnect-sdk-generate.js +1 -1
  34. package/lib/commands/dataconnect-services-list.js +1 -1
  35. package/lib/commands/dataconnect-sql-diff.js +1 -1
  36. package/lib/commands/dataconnect-sql-grant.js +1 -1
  37. package/lib/commands/dataconnect-sql-migrate.js +2 -2
  38. package/lib/commands/dataconnect-sql-setup.js +1 -1
  39. package/lib/commands/dataconnect-sql-shell.js +1 -1
  40. package/lib/commands/deploy.js +3 -3
  41. package/lib/commands/emulators-exec.js +1 -1
  42. package/lib/commands/ext-dev-register.js +1 -1
  43. package/lib/commands/ext-dev-usage.js +2 -2
  44. package/lib/commands/ext-install.js +2 -3
  45. package/lib/commands/firestore-backups-delete.js +2 -2
  46. package/lib/commands/firestore-backups-get.js +1 -1
  47. package/lib/commands/firestore-backups-list.js +2 -2
  48. package/lib/commands/firestore-backups-schedules-create.js +4 -4
  49. package/lib/commands/firestore-backups-schedules-delete.js +2 -2
  50. package/lib/commands/firestore-backups-schedules-list.js +2 -2
  51. package/lib/commands/firestore-backups-schedules-update.js +1 -1
  52. package/lib/commands/firestore-databases-create.js +6 -6
  53. package/lib/commands/firestore-databases-delete.js +2 -2
  54. package/lib/commands/firestore-databases-get.js +1 -1
  55. package/lib/commands/firestore-databases-list.js +1 -1
  56. package/lib/commands/firestore-databases-restore.js +5 -5
  57. package/lib/commands/firestore-databases-update.js +4 -4
  58. package/lib/commands/firestore-delete.js +7 -8
  59. package/lib/commands/firestore-indexes-list.js +4 -4
  60. package/lib/commands/firestore-locations.js +1 -1
  61. package/lib/commands/functions-artifacts-setpolicy.js +7 -7
  62. package/lib/commands/functions-config-export.js +1 -1
  63. package/lib/commands/functions-deletegcfartifacts.js +1 -1
  64. package/lib/commands/functions-secrets-access.js +1 -1
  65. package/lib/commands/functions-secrets-describe.js +1 -1
  66. package/lib/commands/functions-secrets-destroy.js +2 -2
  67. package/lib/commands/functions-secrets-get.js +1 -1
  68. package/lib/commands/functions-secrets-prune.js +2 -2
  69. package/lib/commands/functions-secrets-set.js +3 -3
  70. package/lib/commands/index.js +0 -4
  71. package/lib/commands/init.js +3 -2
  72. package/lib/commands/internaltesting-frameworks-compose.js +1 -1
  73. package/lib/commands/remoteconfig-versions-list.js +1 -1
  74. package/lib/commands/setup-emulators-database.js +1 -1
  75. package/lib/commands/setup-emulators-dataconnect.js +1 -1
  76. package/lib/commands/setup-emulators-firestore.js +1 -1
  77. package/lib/commands/setup-emulators-pubsub.js +1 -1
  78. package/lib/commands/setup-emulators-storage.js +1 -1
  79. package/lib/commands/setup-emulators-ui.js +1 -1
  80. package/lib/config.js +16 -18
  81. package/lib/dataconnect/dataplaneClient.js +3 -1
  82. package/lib/deploy/functions/build.js +1 -0
  83. package/lib/deploy/functions/prompts.js +30 -1
  84. package/lib/deploy/functions/release/index.js +27 -9
  85. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +4 -4
  86. package/lib/emulator/apphosting/config.js +15 -14
  87. package/lib/emulator/auth/operations.js +2 -1
  88. package/lib/emulator/constants.js +1 -1
  89. package/lib/emulator/dataconnect/pgliteServer.js +2 -1
  90. package/lib/emulator/dataconnectEmulator.js +2 -0
  91. package/lib/emulator/downloadableEmulators.js +9 -9
  92. package/lib/emulator/env.js +2 -1
  93. package/lib/emulator/initEmulators.js +29 -4
  94. package/lib/experiments.js +1 -13
  95. package/lib/functions/artifacts.js +88 -1
  96. package/lib/gcp/cloudfunctions.js +1 -1
  97. package/lib/gcp/cloudfunctionsv2.js +3 -3
  98. package/lib/gcp/cloudsql/permissions.js +2 -1
  99. package/lib/gcp/cloudsql/permissions_setup.js +8 -5
  100. package/lib/gcp/proto.js +4 -3
  101. package/lib/init/features/dataconnect/sdk.js +4 -5
  102. package/package.json +2 -2
  103. package/standalone/package.json +1 -1
  104. package/templates/init/dataconnect/dataconnect.yaml +1 -1
  105. package/lib/commands/apphosting-config-export.js +0 -29
  106. package/lib/commands/experimental-functions-shell.js +0 -13
@@ -4,19 +4,19 @@ exports.printTriggerUrls = exports.release = void 0;
4
4
  const clc = require("colorette");
5
5
  const logger_1 = require("../../../logger");
6
6
  const functional_1 = require("../../../functional");
7
+ const utils = require("../../../utils");
7
8
  const backend = require("../backend");
8
- const containerCleaner = require("../containerCleaner");
9
9
  const planner = require("./planner");
10
10
  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");
15
14
  const functionsConfig_1 = require("../../../functionsConfig");
16
15
  const functionsDeployHelper_1 = require("../functionsDeployHelper");
17
16
  const error_1 = require("../../../error");
18
17
  const getProjectNumber_1 = require("../../../getProjectNumber");
19
18
  const extensions_1 = require("../../extensions");
19
+ const artifacts = require("../../../functions/artifacts");
20
20
  async function release(context, options, payload) {
21
21
  if (context.extensions && payload.extensions) {
22
22
  await (0, extensions_1.release)(context.extensions, options, payload.extensions);
@@ -78,13 +78,7 @@ async function release(context, options, payload) {
78
78
  reporter.printErrors(summary);
79
79
  const wantBackend = backend.merge(...Object.values(payload.functions).map((p) => p.wantBackend));
80
80
  printTriggerUrls(wantBackend);
81
- const haveEndpoints = backend.allEndpoints(wantBackend);
82
- const deletedEndpoints = Object.values(plan)
83
- .map((r) => r.endpointsToDelete)
84
- .reduce(functional_1.reduceFlat, []);
85
- if (experiments.isEnabled("automaticallydeletegcfartifacts")) {
86
- await containerCleaner.cleanupBuildImages(haveEndpoints, deletedEndpoints);
87
- }
81
+ await setupArtifactCleanupPolicies(options, options.projectId, Object.keys(wantBackend.endpoints));
88
82
  const allErrors = summary.results.filter((r) => r.error).map((r) => r.error);
89
83
  if (allErrors.length) {
90
84
  const opts = allErrors.length === 1 ? { original: allErrors[0] } : { children: allErrors };
@@ -110,3 +104,27 @@ function printTriggerUrls(results) {
110
104
  }
111
105
  }
112
106
  exports.printTriggerUrls = printTriggerUrls;
107
+ async function setupArtifactCleanupPolicies(options, projectId, locations) {
108
+ if (locations.length === 0) {
109
+ return;
110
+ }
111
+ const { locationsToSetup, locationsWithErrors: locationsWithCheckErrors } = await artifacts.checkCleanupPolicy(projectId, locations);
112
+ if (locationsToSetup.length === 0) {
113
+ return;
114
+ }
115
+ const daysToKeep = await prompts.promptForCleanupPolicyDays(options, locationsToSetup);
116
+ utils.logLabeledBullet("functions", `Configuring cleanup policy for ${locationsToSetup.length > 1 ? "repositories" : "repository"} in ${locationsToSetup.join(", ")}. ` +
117
+ `Images older than ${daysToKeep} days will be automatically deleted.`);
118
+ const { locationsWithPolicy, locationsWithErrors: locationsWithSetupErrors } = await artifacts.setCleanupPolicies(projectId, locationsToSetup, daysToKeep);
119
+ utils.logLabeledBullet("functions", `Configured cleanup policy for ${locationsWithPolicy.length > 1 ? "repositories" : "repository"} in ${locationsToSetup.join(", ")}.`);
120
+ const locationsWithErrors = [...locationsWithCheckErrors, ...locationsWithSetupErrors];
121
+ if (locationsWithErrors.length > 0) {
122
+ utils.logLabeledWarning("functions", `Failed to set up cleanup policy for repositories in ${locationsWithErrors.length > 1 ? "regions" : "region"} ` +
123
+ `${locationsWithErrors.join(", ")}.` +
124
+ "This could result in a small monthly bill as container images accumulate over time.");
125
+ throw new error_1.FirebaseError(`Functions successfully deployed but could not set up cleanup policy in ` +
126
+ `${locationsWithErrors.length > 1 ? "regions" : "region"} ${locationsWithErrors.join(", ")}.` +
127
+ `Pass the --force option to automatically set up a cleanup policy or` +
128
+ "run 'firebase functions:artifacts:setpolicy' to set up a cleanup policy to automatically delete old images.");
129
+ }
130
+ }
@@ -67,8 +67,8 @@ function assertBuildEndpoint(ep, id) {
67
67
  maxInstances: "Field<number>?",
68
68
  minInstances: "Field<number>?",
69
69
  concurrency: "Field<number>?",
70
- serviceAccount: "string?",
71
- serviceAccountEmail: "string?",
70
+ serviceAccount: "Field<string>?",
71
+ serviceAccountEmail: "Field<string>?",
72
72
  timeoutSeconds: "Field<number>?",
73
73
  vpc: "object?",
74
74
  labels: "object?",
@@ -123,8 +123,8 @@ function assertBuildEndpoint(ep, id) {
123
123
  eventType: "string",
124
124
  retry: "Field<boolean>",
125
125
  region: "Field<string>",
126
- serviceAccount: "string?",
127
- serviceAccountEmail: "string?",
126
+ serviceAccount: "Field<string>?",
127
+ serviceAccountEmail: "Field<string>?",
128
128
  channel: "string",
129
129
  });
130
130
  }
@@ -6,22 +6,23 @@ const config_1 = require("../../apphosting/config");
6
6
  const yaml_1 = require("../../apphosting/yaml");
7
7
  async function getLocalAppHostingConfiguration(backendDir) {
8
8
  const appHostingConfigPaths = (0, config_1.listAppHostingFilesInPath)(backendDir);
9
- const fileNameToPathMap = new Map();
10
- for (const path of appHostingConfigPaths) {
11
- const fileName = (0, path_1.basename)(path);
12
- fileNameToPathMap.set(fileName, path);
9
+ const fileNameToPathMap = Object.fromEntries(appHostingConfigPaths.map((path) => [(0, path_1.basename)(path), path]));
10
+ const output = yaml_1.AppHostingYamlConfig.empty();
11
+ const baseFilePath = fileNameToPathMap[config_1.APPHOSTING_BASE_YAML_FILE];
12
+ const emulatorsFilePath = fileNameToPathMap[config_1.APPHOSTING_EMULATORS_YAML_FILE];
13
+ const localFilePath = fileNameToPathMap[config_1.APPHOSTING_LOCAL_YAML_FILE];
14
+ if (baseFilePath) {
15
+ const baseFile = await yaml_1.AppHostingYamlConfig.loadFromFile(baseFilePath);
16
+ output.merge(baseFile, false);
13
17
  }
14
- const baseFilePath = fileNameToPathMap.get(config_1.APPHOSTING_BASE_YAML_FILE);
15
- const localFilePath = fileNameToPathMap.get(config_1.APPHOSTING_LOCAL_YAML_FILE);
16
- if (!baseFilePath && !localFilePath) {
17
- return yaml_1.AppHostingYamlConfig.empty();
18
+ if (emulatorsFilePath) {
19
+ const emulatorsConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(emulatorsFilePath);
20
+ output.merge(emulatorsConfig, false);
18
21
  }
19
- if (!baseFilePath || !localFilePath) {
20
- return await yaml_1.AppHostingYamlConfig.loadFromFile((baseFilePath || localFilePath));
22
+ if (localFilePath) {
23
+ const localYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localFilePath);
24
+ output.merge(localYamlConfig, true);
21
25
  }
22
- const localYamlConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(localFilePath);
23
- const baseConfig = await yaml_1.AppHostingYamlConfig.loadFromFile(baseFilePath);
24
- baseConfig.merge(localYamlConfig);
25
- return baseConfig;
26
+ return output;
26
27
  }
27
28
  exports.getLocalAppHostingConfiguration = getLocalAppHostingConfiguration;
@@ -216,7 +216,8 @@ function lookup(state, reqBody, ctx) {
216
216
  tryAddUser(state.getUserByLocalId(localId));
217
217
  }
218
218
  for (const email of (_c = reqBody.email) !== null && _c !== void 0 ? _c : []) {
219
- tryAddUser(state.getUserByEmail(email));
219
+ const canonicalizedEmail = (0, utils_1.canonicalizeEmailAddress)(email);
220
+ tryAddUser(state.getUserByEmail(canonicalizedEmail));
220
221
  }
221
222
  for (const phoneNumber of (_d = reqBody.phoneNumber) !== null && _d !== void 0 ? _d : []) {
222
223
  tryAddUser(state.getUserByPhoneNumber(phoneNumber));
@@ -103,7 +103,7 @@ Constants.FIREBASE_ENABLED_EXPERIMENTS = "FIREBASE_ENABLED_EXPERIMENTS";
103
103
  Constants.FIRESTORE_EMULATOR_HOST = "FIRESTORE_EMULATOR_HOST";
104
104
  Constants.FIRESTORE_EMULATOR_ENV_ALT = "FIREBASE_FIRESTORE_EMULATOR_ADDRESS";
105
105
  Constants.FIREBASE_DATABASE_EMULATOR_HOST = "FIREBASE_DATABASE_EMULATOR_HOST";
106
- Constants.FIREBASE_DATACONNECT_EMULATOR_HOST = "FIREBASE_DATACONNECT_EMULATOR_HOST";
106
+ Constants.FIREBASE_DATACONNECT_EMULATOR_HOST = "FIREBASE_DATA_CONNECT_EMULATOR_HOST";
107
107
  Constants.FIREBASE_AUTH_EMULATOR_HOST = "FIREBASE_AUTH_EMULATOR_HOST";
108
108
  Constants.FIREBASE_STORAGE_EMULATOR_HOST = "FIREBASE_STORAGE_EMULATOR_HOST";
109
109
  Constants.CLOUD_STORAGE_EMULATOR_HOST = "STORAGE_EMULATOR_HOST";
@@ -124,7 +124,8 @@ class PostgresServer {
124
124
  const db = await pglite_1.PGlite.create(pgliteArgs);
125
125
  return db;
126
126
  }
127
- throw err;
127
+ logger_1.logger.debug(`Error from pglite: ${err}`);
128
+ throw new error_1.FirebaseError("Unexpected error starting up Postgres.");
128
129
  }
129
130
  }
130
131
  async stop() {
@@ -21,6 +21,7 @@ const pgliteServer_1 = require("./dataconnect/pgliteServer");
21
21
  const controller_1 = require("./controller");
22
22
  const utils_1 = require("../utils");
23
23
  const env_1 = require("./env");
24
+ const ensureApiEnabled_1 = require("../ensureApiEnabled");
24
25
  exports.dataConnectEmulatorEvents = new events_1.EventEmitter();
25
26
  class DataConnectEmulator {
26
27
  constructor(args) {
@@ -43,6 +44,7 @@ class DataConnectEmulator {
43
44
  this.logger.logLabeled("WARN", "dataconnect", "Detected a 'demo-' project, but vector embeddings require a real project. Operations that use vector_embed will fail.");
44
45
  }
45
46
  else {
47
+ await (0, ensureApiEnabled_1.ensure)(this.args.projectId, (0, api_1.vertexAIOrigin)(), "dataconnect", true);
46
48
  this.logger.logLabeled("WARN", "dataconnect", "Operations that use vector_embed will make calls to production Vertex AI");
47
49
  }
48
50
  }
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
48
48
  },
49
49
  dataconnect: process.platform === "darwin"
50
50
  ? {
51
- version: "1.9.2",
52
- expectedSize: 26403584,
53
- expectedChecksum: "a0a957bb5d564059ed883fee9e9fd67a",
51
+ version: "2.0.0",
52
+ expectedSize: 26440448,
53
+ expectedChecksum: "64fd9ad182e59a46b9462757db6d5aac",
54
54
  }
55
55
  : process.platform === "win32"
56
56
  ? {
57
- version: "1.9.2",
58
- expectedSize: 26846208,
59
- expectedChecksum: "80f49b574aa69ef76fb49e2e96c8a699",
57
+ version: "2.0.0",
58
+ expectedSize: 26884096,
59
+ expectedChecksum: "92d654dfbb07fee4e1db2328ba6e00a7",
60
60
  }
61
61
  : {
62
- version: "1.9.2",
63
- expectedSize: 26316952,
64
- expectedChecksum: "cc3c0318e453d9ddf098b582ee0f2b77",
62
+ version: "2.0.0",
63
+ expectedSize: 26353816,
64
+ expectedChecksum: "29c7a57a00cb11f44f9b0ffb4710bbd2",
65
65
  },
66
66
  };
67
67
  exports.DownloadDetails = {
@@ -36,7 +36,8 @@ function setEnvVarsForEmulators(env, emulators) {
36
36
  env[constants_1.Constants.CLOUD_TASKS_EMULATOR_HOST] = host;
37
37
  break;
38
38
  case types_1.Emulators.DATACONNECT:
39
- env[constants_1.Constants.FIREBASE_DATACONNECT_EMULATOR_HOST] = host;
39
+ env[constants_1.Constants.FIREBASE_DATACONNECT_EMULATOR_HOST] = `http://${host}`;
40
+ env["FIREBASE_DATACONNECT_EMULATOR_HOST"] = host;
40
41
  }
41
42
  }
42
43
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AdditionalInitFns = void 0;
4
+ const clc = require("colorette");
4
5
  const path_1 = require("path");
5
6
  const prompt_1 = require("../prompt");
6
7
  const developmentServer_1 = require("./apphosting/developmentServer");
@@ -8,9 +9,11 @@ const emulatorLogger_1 = require("./emulatorLogger");
8
9
  const types_1 = require("./types");
9
10
  const config_1 = require("../apphosting/config");
10
11
  const detectProjectRoot_1 = require("../detectProjectRoot");
12
+ const projectUtils_1 = require("../projectUtils");
13
+ const secrets_1 = require("../apphosting/secrets");
11
14
  exports.AdditionalInitFns = {
12
- [types_1.Emulators.APPHOSTING]: async () => {
13
- var _a;
15
+ [types_1.Emulators.APPHOSTING]: async (config) => {
16
+ var _a, _b;
14
17
  const cwd = process.cwd();
15
18
  const additionalConfigs = new Map();
16
19
  const logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
@@ -30,13 +33,35 @@ exports.AdditionalInitFns = {
30
33
  catch (e) {
31
34
  logger.log("WARN", "Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommand`");
32
35
  }
36
+ const projectId = (0, projectUtils_1.getProjectId)(config.options);
37
+ let env = [];
33
38
  try {
34
- const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
35
- await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot);
39
+ const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({ cwd: config.options.cwd })) !== null && _a !== void 0 ? _a : backendRoot;
40
+ env = await (0, config_1.maybeGenerateEmulatorYaml)(projectId, projectRoot);
36
41
  }
37
42
  catch (e) {
38
43
  logger.log("WARN", "failed to export app hosting configs");
39
44
  }
45
+ const secretIds = (_b = env === null || env === void 0 ? void 0 : env.filter((e) => "secret" in e)) === null || _b === void 0 ? void 0 : _b.map((e) => e.secret);
46
+ if (secretIds === null || secretIds === void 0 ? void 0 : secretIds.length) {
47
+ if (!projectId) {
48
+ logger.log("WARN", "Cannot grant developers access to secrets for local development without knowing what project the secret is in. " +
49
+ `Run ${clc.bold(`firebase apphosting:secrets:grantaccess ${secretIds.join(",")} --project [project] --emails [email list]`)}`);
50
+ }
51
+ else {
52
+ const users = await (0, prompt_1.promptOnce)({
53
+ type: "input",
54
+ message: "Your config has secret values. Please provide a comma-separated list of users or groups who should have access to secrets for local development:",
55
+ });
56
+ if (users.length) {
57
+ await (0, secrets_1.grantEmailsSecretAccess)(projectId, secretIds, users.split(",").map((u) => u.trim()));
58
+ }
59
+ else {
60
+ logger.log("INFO", "Skipping granting developers access to secrets for local development. To grant access in the future, run " +
61
+ `Run ${clc.bold(`firebase apphosting:secrets:grantaccess ${secretIds.join(",")} --emails [email list]`)}`);
62
+ }
63
+ }
64
+ }
40
65
  return mapToObject(additionalConfigs);
41
66
  },
42
67
  [types_1.Emulators.DATACONNECT]: async (config) => {
@@ -41,18 +41,6 @@ exports.ALL_EXPERIMENTS = experiments({
41
41
  "of how that image was created.",
42
42
  public: true,
43
43
  },
44
- automaticallydeletegcfartifacts: {
45
- shortDescription: "Control whether functions cleans up images after deploys",
46
- fullDescription: "To control costs, Firebase defaults to automatically deleting containers " +
47
- "created during the build process. This has the side-effect of preventing " +
48
- "users from rolling back to previous revisions using the Run API. To change " +
49
- `this behavior, call ${(0, colorette_1.bold)("experiments:disable deletegcfartifactsondeploy")} ` +
50
- `consider also calling ${(0, colorette_1.bold)("experiments:enable deletegcfartifacts")} ` +
51
- `to enable the new command ${(0, colorette_1.bold)("functions:deletegcfartifacts")} which` +
52
- "lets you clean up images manually",
53
- public: true,
54
- default: true,
55
- },
56
44
  emulatoruisnapshot: {
57
45
  shortDescription: "Load pre-release versions of the emulator UI",
58
46
  },
@@ -115,7 +103,7 @@ exports.ALL_EXPERIMENTS = experiments({
115
103
  fdcconnectorevolution: {
116
104
  shortDescription: "Enable Data Connect connector evolution warnings.",
117
105
  fullDescription: "Enable Data Connect connector evolution warnings.",
118
- default: false,
106
+ default: true,
119
107
  public: false,
120
108
  },
121
109
  });
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasCleanupOptOut = exports.hasSameCleanupPolicy = exports.setCleanupPolicy = exports.optOutRepository = exports.updateRepository = exports.generateCleanupPolicy = exports.parseDaysFromPolicy = exports.daysToSeconds = exports.findExistingPolicy = exports.makeRepoPath = exports.DEFAULT_CLEANUP_DAYS = exports.OPT_OUT_LABEL_KEY = exports.CLEANUP_POLICY_ID = exports.GCF_REPO_ID = void 0;
3
+ exports.setCleanupPolicies = exports.checkCleanupPolicy = exports.hasCleanupOptOut = exports.hasSameCleanupPolicy = exports.setCleanupPolicy = exports.optOutRepository = exports.updateRepository = exports.generateCleanupPolicy = exports.parseDaysFromPolicy = exports.daysToSeconds = exports.findExistingPolicy = exports.getRepo = exports.getRepoCache = exports.makeRepoPath = exports.DEFAULT_CLEANUP_DAYS = exports.OPT_OUT_LABEL_KEY = exports.CLEANUP_POLICY_ID = exports.GCF_REPO_ID = void 0;
4
4
  const artifactregistry = require("../gcp/artifactregistry");
5
+ const logger_1 = require("../logger");
5
6
  const error_1 = require("../error");
6
7
  exports.GCF_REPO_ID = "gcf-artifacts";
7
8
  exports.CLEANUP_POLICY_ID = "firebase-functions-cleanup";
@@ -12,6 +13,17 @@ function makeRepoPath(projectId, location, repoName = exports.GCF_REPO_ID) {
12
13
  return `projects/${projectId}/locations/${location}/repositories/${repoName}`;
13
14
  }
14
15
  exports.makeRepoPath = makeRepoPath;
16
+ exports.getRepoCache = new Map();
17
+ async function getRepo(projectId, location, forceRefresh = false, repoName = exports.GCF_REPO_ID) {
18
+ const repoPath = makeRepoPath(projectId, location, repoName);
19
+ if (!forceRefresh && exports.getRepoCache.has(repoPath)) {
20
+ return exports.getRepoCache.get(repoPath);
21
+ }
22
+ const repo = await artifactregistry.getRepository(repoPath);
23
+ exports.getRepoCache.set(repoPath, repo);
24
+ return repo;
25
+ }
26
+ exports.getRepo = getRepo;
15
27
  function findExistingPolicy(repository) {
16
28
  var _a;
17
29
  return (_a = repository === null || repository === void 0 ? void 0 : repository.cleanupPolicies) === null || _a === void 0 ? void 0 : _a[exports.CLEANUP_POLICY_ID];
@@ -103,3 +115,78 @@ function hasCleanupOptOut(repo) {
103
115
  return !!(repo.labels && repo.labels[exports.OPT_OUT_LABEL_KEY] === "true");
104
116
  }
105
117
  exports.hasCleanupOptOut = hasCleanupOptOut;
118
+ async function checkCleanupPolicy(projectId, locations) {
119
+ if (locations.length === 0) {
120
+ return { locationsToSetup: [], locationsWithErrors: [] };
121
+ }
122
+ const checkRepos = await Promise.allSettled(locations.map(async (location) => {
123
+ try {
124
+ const repository = await exports.getRepo(projectId, location);
125
+ const hasPolicy = !!findExistingPolicy(repository);
126
+ const hasOptOut = hasCleanupOptOut(repository);
127
+ const hasOtherPolicies = repository.cleanupPolicies &&
128
+ Object.keys(repository.cleanupPolicies).some((key) => key !== exports.CLEANUP_POLICY_ID);
129
+ return {
130
+ location,
131
+ repository,
132
+ hasPolicy,
133
+ hasOptOut,
134
+ hasOtherPolicies,
135
+ };
136
+ }
137
+ catch (err) {
138
+ logger_1.logger.debug(`Failed to check artifact cleanup policy for region ${location}:`, err);
139
+ throw err;
140
+ }
141
+ }));
142
+ const locationsToSetup = [];
143
+ const locationsWithErrors = [];
144
+ for (let i = 0; i < checkRepos.length; i++) {
145
+ const result = checkRepos[i];
146
+ if (result.status === "fulfilled") {
147
+ if (!(result.value.hasPolicy || result.value.hasOptOut || result.value.hasOtherPolicies)) {
148
+ locationsToSetup.push(result.value.location);
149
+ }
150
+ }
151
+ else {
152
+ locationsWithErrors.push(locations[i]);
153
+ }
154
+ }
155
+ return { locationsToSetup, locationsWithErrors };
156
+ }
157
+ exports.checkCleanupPolicy = checkCleanupPolicy;
158
+ async function setCleanupPolicies(projectId, locations, daysToKeep) {
159
+ if (locations.length === 0)
160
+ return { locationsWithPolicy: [], locationsWithErrors: [] };
161
+ const locationsWithPolicy = [];
162
+ const locationsWithErrors = [];
163
+ const setupRepos = await Promise.allSettled(locations.map(async (location) => {
164
+ try {
165
+ logger_1.logger.debug(`Setting up artifact cleanup policy for region ${location}`);
166
+ const repo = await exports.getRepo(projectId, location);
167
+ await exports.setCleanupPolicy(repo, daysToKeep);
168
+ return location;
169
+ }
170
+ catch (err) {
171
+ throw new error_1.FirebaseError("Failed to set up artifact cleanup policy", {
172
+ original: err,
173
+ });
174
+ }
175
+ }));
176
+ for (let i = 0; i < locations.length; i++) {
177
+ const location = locations[i];
178
+ const result = setupRepos[i];
179
+ if (result.status === "rejected") {
180
+ logger_1.logger.debug(`Failed to set up artifact cleanup policy for region ${location}:`, result.reason);
181
+ locationsWithErrors.push(location);
182
+ }
183
+ else {
184
+ locationsWithPolicy.push(location);
185
+ }
186
+ }
187
+ return {
188
+ locationsWithPolicy,
189
+ locationsWithErrors,
190
+ };
191
+ }
192
+ exports.setCleanupPolicies = setCleanupPolicies;
@@ -340,7 +340,7 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
340
340
  }
341
341
  }
342
342
  proto.copyIfPresent(gcfFunction, endpoint, "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
343
- proto.renameIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "serviceAccount");
343
+ proto.convertIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "serviceAccount", (from) => proto.formatServiceAccount(from, endpoint.project, true));
344
344
  proto.convertIfPresent(gcfFunction, endpoint, "availableMemoryMb", (mem) => mem);
345
345
  proto.convertIfPresent(gcfFunction, endpoint, "timeout", "timeoutSeconds", (sec) => sec ? proto.durationFromSeconds(sec) : null);
346
346
  if (endpoint.vpc) {
@@ -193,7 +193,7 @@ function functionFromEndpoint(endpoint) {
193
193
  };
194
194
  proto.copyIfPresent(gcfFunction, endpoint, "labels");
195
195
  proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "secretEnvironmentVariables", "ingressSettings", "timeoutSeconds");
196
- proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "serviceAccountEmail", "serviceAccount");
196
+ proto.convertIfPresent(gcfFunction.serviceConfig, endpoint, "serviceAccountEmail", "serviceAccount", (from) => proto.formatServiceAccount(from, endpoint.project, true));
197
197
  const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
198
198
  gcfFunction.serviceConfig.availableMemory = mem > 1024 ? `${mem / 1024}Gi` : `${mem}Mi`;
199
199
  proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances");
@@ -215,8 +215,8 @@ function functionFromEndpoint(endpoint) {
215
215
  eventType: endpoint.eventTrigger.eventType,
216
216
  retryPolicy: "RETRY_POLICY_UNSPECIFIED",
217
217
  };
218
- if (endpoint.serviceAccount) {
219
- gcfFunction.eventTrigger.serviceAccountEmail = endpoint.serviceAccount;
218
+ if (gcfFunction.serviceConfig.serviceAccountEmail) {
219
+ gcfFunction.eventTrigger.serviceAccountEmail = gcfFunction.serviceConfig.serviceAccountEmail;
220
220
  }
221
221
  if (gcfFunction.eventTrigger.eventType === v2_1.PUBSUB_PUBLISH_EVENT) {
222
222
  if (!((_b = endpoint.eventTrigger.eventFilters) === null || _b === void 0 ? void 0 : _b.topic)) {
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.defaultPermissions = exports.readerRolePermissions = exports.writerRolePermissions = exports.ownerRolePermissions = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = exports.FIREBASE_SUPER_USER = exports.DEFAULT_SCHEMA = void 0;
3
+ exports.defaultPermissions = exports.readerRolePermissions = exports.writerRolePermissions = exports.ownerRolePermissions = exports.firebasewriter = exports.firebasereader = exports.firebaseowner = exports.CLOUDSQL_SUPER_USER = exports.FIREBASE_SUPER_USER = exports.DEFAULT_SCHEMA = void 0;
4
4
  exports.DEFAULT_SCHEMA = "public";
5
5
  exports.FIREBASE_SUPER_USER = "firebasesuperuser";
6
+ exports.CLOUDSQL_SUPER_USER = "cloudsqlsuperuser";
6
7
  function firebaseowner(databaseId, schema = exports.DEFAULT_SCHEMA) {
7
8
  return `firebaseowner_${databaseId}_${schema}`;
8
9
  }
@@ -173,14 +173,17 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
173
173
  };
174
174
  }
175
175
  exports.getSchemaMetadata = getSchemaMetadata;
176
+ function filterTableOwners(schemaInfo, databaseId) {
177
+ return [...new Set(schemaInfo.tables.map((t) => t.owner))].filter((owner) => owner !== permissions_1.CLOUDSQL_SUPER_USER && owner !== (0, permissions_1.firebaseowner)(databaseId, schemaInfo.name));
178
+ }
176
179
  async function setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, options, silent = false) {
177
180
  const schema = schemaInfo.name;
178
181
  const firebaseOwnerRole = (0, permissions_1.firebaseowner)(databaseId, schema);
179
- const nonFirebasetablesOwners = [...new Set(schemaInfo.tables.map((t) => t.owner))].filter((owner) => owner !== firebaseOwnerRole);
180
- const grantOwnersToSuperuserCmds = nonFirebasetablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
181
- const revokeOwnersFromSuperuserCmds = nonFirebasetablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
182
+ const uniqueTablesOwners = filterTableOwners(schemaInfo, databaseId);
183
+ const grantOwnersToSuperuserCmds = uniqueTablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
184
+ const revokeOwnersFromSuperuserCmds = uniqueTablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
182
185
  const greenfieldSetupCmds = await greenFieldSchemaSetup(instanceId, databaseId, schema, options);
183
- const grantCmds = nonFirebasetablesOwners.map((owner) => `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${owner}"`);
186
+ const grantCmds = uniqueTablesOwners.map((owner) => `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${owner}"`);
184
187
  const alterTableCmds = schemaInfo.tables.map((table) => `ALTER TABLE "${schema}"."${table.name}" OWNER TO "${firebaseOwnerRole}";`);
185
188
  const setupCmds = [
186
189
  ...grantOwnersToSuperuserCmds,
@@ -194,7 +197,7 @@ async function setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, o
194
197
  exports.setupBrownfieldAsGreenfield = setupBrownfieldAsGreenfield;
195
198
  async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, silent = false) {
196
199
  const schema = schemaInfo.name;
197
- const uniqueTablesOwners = [...new Set(schemaInfo.tables.map((t) => t.owner))];
200
+ const uniqueTablesOwners = filterTableOwners(schemaInfo, databaseId);
198
201
  const grantOwnersToFirebasesuperuser = uniqueTablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
199
202
  const revokeOwnersFromFirebasesuperuser = uniqueTablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
200
203
  const iamUser = (await (0, connect_2.getIAMUser)(options)).user;
package/lib/gcp/proto.js CHANGED
@@ -91,18 +91,19 @@ function getInvokerMembers(invoker, projectId) {
91
91
  return invoker.map((inv) => formatServiceAccount(inv, projectId));
92
92
  }
93
93
  exports.getInvokerMembers = getInvokerMembers;
94
- function formatServiceAccount(serviceAccount, projectId) {
94
+ function formatServiceAccount(serviceAccount, projectId, removeTypePrefix = false) {
95
95
  if (serviceAccount.length === 0) {
96
96
  throw new error_1.FirebaseError("Service account cannot be an empty string");
97
97
  }
98
98
  if (!serviceAccount.includes("@")) {
99
99
  throw new error_1.FirebaseError("Service account must be of the form 'service-account@' or 'service-account@{project-id}.iam.gserviceaccount.com'");
100
100
  }
101
+ const prefix = removeTypePrefix ? "" : "serviceAccount:";
101
102
  if (serviceAccount.endsWith("@")) {
102
103
  const suffix = `${projectId}.iam.gserviceaccount.com`;
103
- return `serviceAccount:${serviceAccount}${suffix}`;
104
+ return `${prefix}${serviceAccount}${suffix}`;
104
105
  }
105
- return `serviceAccount:${serviceAccount}`;
106
+ return `${prefix}${serviceAccount}`;
106
107
  }
107
108
  exports.formatServiceAccount = formatServiceAccount;
108
109
  function pruneUndefiends(obj) {
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.actuate = exports.generateSdkYaml = exports.doSetup = exports.FDC_APP_FOLDER = void 0;
4
4
  const yaml = require("yaml");
5
- const fs = require("fs");
6
5
  const clc = require("colorette");
7
6
  const path = require("path");
8
7
  const fsutils_1 = require("../../../fsutils");
@@ -18,7 +17,7 @@ const auth_1 = require("../../../auth");
18
17
  exports.FDC_APP_FOLDER = "_FDC_APP_FOLDER";
19
18
  async function doSetup(setup, config) {
20
19
  const sdkInfo = await askQuestions(setup, config);
21
- await actuate(sdkInfo);
20
+ await actuate(sdkInfo, config);
22
21
  (0, utils_1.logSuccess)(`If you'd like to add more generated SDKs to your app your later, run ${clc.bold("firebase init dataconnect:sdk")} again`);
23
22
  }
24
23
  exports.doSetup = doSetup;
@@ -157,11 +156,11 @@ async function generateSdkYaml(targetPlatform, connectorYaml, connectorDir, appD
157
156
  return connectorYaml;
158
157
  }
159
158
  exports.generateSdkYaml = generateSdkYaml;
160
- async function actuate(sdkInfo) {
159
+ async function actuate(sdkInfo, config) {
161
160
  var _a, _b;
162
161
  const connectorYamlPath = `${sdkInfo.connectorInfo.directory}/connector.yaml`;
163
- fs.writeFileSync(connectorYamlPath, sdkInfo.connectorYamlContents, "utf8");
164
- (0, utils_1.logBullet)(`Wrote new config to ${connectorYamlPath}`);
162
+ (0, utils_1.logBullet)(`Writing your new SDK configuration to ${connectorYamlPath}`);
163
+ await config.askWriteProjectFile(path.relative(config.projectDir, connectorYamlPath), sdkInfo.connectorYamlContents);
165
164
  const account = (0, auth_1.getGlobalDefaultAccount)();
166
165
  await dataconnectEmulator_1.DataConnectEmulator.generate({
167
166
  configDir: sdkInfo.connectorInfo.directory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.35.1",
3
+ "version": "14.0.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "preferGlobal": true,
31
31
  "engines": {
32
- "node": ">=18.0.0 || >=20.0.0 || >=22.0.0"
32
+ "node": ">=20.0.0 || >=22.0.0"
33
33
  },
34
34
  "author": "Firebase (https://firebase.google.com/)",
35
35
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  ]
31
31
  },
32
32
  "devDependencies": {
33
- "pkg": "^5.7.0",
33
+ "@yao-pkg/pkg": "^6.3.0",
34
34
  "prettier": "^1.15.3"
35
35
  }
36
36
  }
@@ -1,4 +1,4 @@
1
- specVersion: "v1beta"
1
+ specVersion: "v1"
2
2
  serviceId: __serviceId__
3
3
  location: __location__
4
4
  schema:
@@ -1,29 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
4
- const command_1 = require("../command");
5
- const projectUtils_1 = require("../projectUtils");
6
- const requireAuth_1 = require("../requireAuth");
7
- const secretManager = require("../gcp/secretManager");
8
- const requirePermissions_1 = require("../requirePermissions");
9
- const config_1 = require("../apphosting/config");
10
- const error_1 = require("../error");
11
- const detectProjectRoot_1 = require("../detectProjectRoot");
12
- exports.command = new command_1.Command("apphosting:config:export")
13
- .description("Export App Hosting configurations such as secrets into an apphosting.local.yaml file")
14
- .option("-s, --secrets <apphosting.yaml or apphosting.<environment>.yaml file to export secrets from>", "This command combines the base apphosting.yaml with the specified environment-specific file (e.g., apphosting.staging.yaml). If keys conflict, the environment-specific file takes precedence.")
15
- .before(requireAuth_1.requireAuth)
16
- .before(secretManager.ensureApi)
17
- .before(requirePermissions_1.requirePermissions, ["secretmanager.versions.access"])
18
- .action(async (options) => {
19
- var _a;
20
- const projectId = (0, projectUtils_1.needProjectId)(options);
21
- const environmentConfigFile = options.secrets;
22
- const cwd = process.cwd();
23
- const backendRoot = (0, config_1.discoverBackendRoot)(cwd);
24
- if (!backendRoot) {
25
- throw new error_1.FirebaseError("Missing apphosting.yaml: This command requires an apphosting.yaml configuration file. Please run 'firebase init apphosting' and try again.");
26
- }
27
- const projectRoot = (_a = (0, detectProjectRoot_1.detectProjectRoot)({})) !== null && _a !== void 0 ? _a : backendRoot;
28
- await (0, config_1.exportConfig)(cwd, projectRoot, backendRoot, projectId, environmentConfigFile);
29
- });
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.command = void 0;
4
- const functionsShellCommandAction_1 = require("../functionsShellCommandAction");
5
- const command_1 = require("../command");
6
- const requireConfig_1 = require("../requireConfig");
7
- const requirePermissions_1 = require("../requirePermissions");
8
- exports.command = new command_1.Command("experimental:functions:shell")
9
- .description("launch full Node shell with emulated functions. (Alias for `firebase functions:shell.)")
10
- .option("-p, --port <port>", "the port on which to emulate functions (default: 5000)", 5000)
11
- .before(requireConfig_1.requireConfig)
12
- .before(requirePermissions_1.requirePermissions)
13
- .action(functionsShellCommandAction_1.actionFunction);