firebase-tools 14.12.1 → 14.13.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 (54) hide show
  1. package/README.md +1 -1
  2. package/lib/commands/dataconnect-services-list.js +5 -5
  3. package/lib/commands/dataconnect-sql-grant.js +5 -0
  4. package/lib/commands/dataconnect-sql-setup.js +1 -3
  5. package/lib/crashlytics/getIssueDetails.js +41 -0
  6. package/lib/crashlytics/getSampleCrash.js +48 -0
  7. package/lib/dataconnect/client.js +23 -15
  8. package/lib/dataconnect/ensureApis.js +5 -9
  9. package/lib/dataconnect/fileUtils.js +5 -6
  10. package/lib/dataconnect/freeTrial.js +16 -39
  11. package/lib/dataconnect/provisionCloudSql.js +67 -70
  12. package/lib/dataconnect/schemaMigration.js +75 -47
  13. package/lib/deploy/dataconnect/deploy.js +9 -11
  14. package/lib/deploy/dataconnect/prepare.js +7 -10
  15. package/lib/deploy/dataconnect/release.js +11 -5
  16. package/lib/deploy/functions/backend.js +8 -2
  17. package/lib/deploy/functions/build.js +23 -1
  18. package/lib/deploy/functions/ensure.js +1 -1
  19. package/lib/deploy/functions/functionsDeployHelper.js +8 -1
  20. package/lib/deploy/functions/prepare.js +6 -4
  21. package/lib/deploy/functions/pricing.js +12 -5
  22. package/lib/deploy/functions/release/fabricator.js +25 -3
  23. package/lib/emulator/controller.js +2 -1
  24. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  25. package/lib/emulator/functionsEmulator.js +9 -1
  26. package/lib/experiments.js +4 -0
  27. package/lib/extensions/extensionsHelper.js +4 -15
  28. package/lib/extensions/utils.js +1 -12
  29. package/lib/firestore/api.js +25 -11
  30. package/lib/firestore/pretty-print.js +7 -0
  31. package/lib/functional.js +7 -1
  32. package/lib/functions/projectConfig.js +25 -2
  33. package/lib/functions/secrets.js +3 -0
  34. package/lib/gcp/cloudfunctionsv2.js +3 -31
  35. package/lib/gcp/cloudscheduler.js +1 -1
  36. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
  37. package/lib/gcp/cloudsql/connect.js +1 -1
  38. package/lib/gcp/cloudsql/permissionsSetup.js +13 -15
  39. package/lib/gcp/k8s.js +32 -0
  40. package/lib/gcp/runv2.js +178 -0
  41. package/lib/gemini/fdcExperience.js +5 -3
  42. package/lib/init/features/dataconnect/index.js +266 -162
  43. package/lib/init/features/dataconnect/sdk.js +32 -16
  44. package/lib/init/features/project.js +4 -0
  45. package/lib/management/studio.js +1 -1
  46. package/lib/mcp/tools/core/init.js +7 -6
  47. package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
  48. package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
  49. package/lib/mcp/tools/crashlytics/index.js +7 -1
  50. package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
  51. package/lib/rtdb.js +1 -1
  52. package/package.json +1 -1
  53. package/schema/firebase-config.json +3 -0
  54. package/lib/extensions/resolveSource.js +0 -24
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.11.1",
58
- "expectedSize": 29352800,
59
- "expectedChecksum": "52d86a5546bbb9e2fcd67faa90b9f07e",
60
- "expectedChecksumSHA256": "217b66589c32d4378201100fa968e69f7c94537044b892be1aaa14d7f6ce6b12",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.1",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
57
+ "version": "2.11.2",
58
+ "expectedSize": 29447008,
59
+ "expectedChecksum": "13bc7d3bb0a0bbfe601991361e4413c2",
60
+ "expectedChecksumSHA256": "e3b029eb461f0fe6f0c825c7a71d42c7a09c2b8ee4fac10c3e187d78fe5083f6",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.2",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
63
63
  },
64
64
  "win32": {
65
- "version": "2.11.1",
66
- "expectedSize": 29841920,
67
- "expectedChecksum": "704cee75ad2d384cf28ac1683c8d1179",
68
- "expectedChecksumSHA256": "fadb5a1d0f03160c133389df9d452cc620cfdface041971a5356ac5a69e937ff",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.1",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1.exe"
65
+ "version": "2.11.2",
66
+ "expectedSize": 29934592,
67
+ "expectedChecksum": "032a0749781fc338b446d753dd543bf5",
68
+ "expectedChecksumSHA256": "2d0498b3ef94b4e777d6fc1d526279376caf3842549f39f13a8ca327393bf810",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.2",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.11.1",
74
- "expectedSize": 29282488,
75
- "expectedChecksum": "435656eab760033228bf7b7e77d56dde",
76
- "expectedChecksumSHA256": "0abb33c0bba0ed8ef1394cc1cab5a333ec81b3dddf48aa625abe8ca28f38840e",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.1",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
73
+ "version": "2.11.2",
74
+ "expectedSize": 29368504,
75
+ "expectedChecksum": "065a7523f881952040ac678a9a1e9323",
76
+ "expectedChecksumSHA256": "41ca6561cf77107b5d1d829233844d21c46a5e4bb823c150b1b32591dc2463e0",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.2",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
79
79
  }
80
80
  }
81
81
  }
@@ -28,8 +28,9 @@ const utils_1 = require("../utils");
28
28
  const adminSdkConfig_1 = require("./adminSdkConfig");
29
29
  const validate_1 = require("../deploy/functions/validate");
30
30
  const secretManager_1 = require("../gcp/secretManager");
31
- const runtimes = require("../deploy/functions/runtimes");
32
31
  const backend = require("../deploy/functions/backend");
32
+ const build = require("../deploy/functions/build");
33
+ const runtimes = require("../deploy/functions/runtimes");
33
34
  const functionsEnv = require("../functions/env");
34
35
  const v1_1 = require("../functions/events/v1");
35
36
  const build_1 = require("../deploy/functions/build");
@@ -82,6 +83,7 @@ class FunctionsEmulator {
82
83
  this.blockingFunctionsConfig = {};
83
84
  this.staticBackends = [];
84
85
  this.dynamicBackends = [];
86
+ this.watchers = [];
85
87
  this.debugMode = false;
86
88
  this.staticBackends = args.emulatableBackends;
87
89
  emulatorLogger_1.EmulatorLogger.setVerbosity(this.args.verbosity ? emulatorLogger_1.Verbosity[this.args.verbosity] : emulatorLogger_1.Verbosity["DEBUG"]);
@@ -271,6 +273,7 @@ class FunctionsEmulator {
271
273
  ],
272
274
  persistent: true,
273
275
  });
276
+ this.watchers.push(watcher);
274
277
  const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
275
278
  watcher.on("change", (filePath) => {
276
279
  this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
@@ -292,6 +295,10 @@ class FunctionsEmulator {
292
295
  for (const pool of Object.values(this.workerPools)) {
293
296
  pool.exit();
294
297
  }
298
+ for (const watcher of this.watchers) {
299
+ await watcher.close();
300
+ }
301
+ this.watchers = [];
295
302
  if (this.destroyServer) {
296
303
  await this.destroyServer();
297
304
  }
@@ -329,6 +336,7 @@ class FunctionsEmulator {
329
336
  await this.args.extensionsEmulator.addDynamicExtensions(emulatableBackend.codebase, discoveredBuild);
330
337
  await this.loadDynamicExtensionBackends();
331
338
  }
339
+ build.applyPrefix(discoveredBuild, emulatableBackend.prefix || "");
332
340
  const resolution = await (0, build_1.resolveBackend)({
333
341
  build: discoveredBuild,
334
342
  firebaseConfig: JSON.parse(firebaseConfig),
@@ -48,6 +48,10 @@ exports.ALL_EXPERIMENTS = experiments({
48
48
  default: true,
49
49
  public: true,
50
50
  },
51
+ runfunctions: {
52
+ shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
53
+ public: false,
54
+ },
51
55
  emulatoruisnapshot: {
52
56
  shortDescription: "Load pre-release versions of the emulator UI",
53
57
  },
@@ -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.AUTOPOPULATED_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.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");
@@ -21,10 +21,8 @@ const unzip_1 = require("./../unzip");
21
21
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
22
22
  const api_1 = require("../api");
23
23
  const archiveDirectory_1 = require("../archiveDirectory");
24
- const utils_1 = require("./utils");
25
24
  const functionsConfig_1 = require("../functionsConfig");
26
25
  const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
27
- const resolveSource_1 = require("./resolveSource");
28
26
  const error_1 = require("../error");
29
27
  const diagnose_1 = require("./diagnose");
30
28
  const askUserForParam_1 = require("./askUserForParam");
@@ -37,7 +35,7 @@ const prompt_1 = require("../prompt");
37
35
  const refs = require("./refs");
38
36
  const localHelper_1 = require("./localHelper");
39
37
  const logger_1 = require("../logger");
40
- const utils_2 = require("../utils");
38
+ const utils_1 = require("../utils");
41
39
  const change_log_1 = require("./change-log");
42
40
  const getProjectNumber_1 = require("../getProjectNumber");
43
41
  const constants_1 = require("../emulator/constants");
@@ -61,7 +59,7 @@ var SourceOrigin;
61
59
  exports.logPrefix = "extensions";
62
60
  const VALID_LICENSES = ["apache-2.0"];
63
61
  exports.URL_REGEX = /^https:/;
64
- exports.EXTENSIONS_BUCKET_NAME = (0, utils_2.envOverride)("FIREBASE_EXTENSIONS_UPLOAD_BUCKET", "firebase-ext-eap-uploads");
62
+ exports.EXTENSIONS_BUCKET_NAME = (0, utils_1.envOverride)("FIREBASE_EXTENSIONS_UPLOAD_BUCKET", "firebase-ext-eap-uploads");
65
63
  const AUTOPOPULATED_PARAM_NAMES = [
66
64
  "PROJECT_ID",
67
65
  "STORAGE_BUCKET",
@@ -104,7 +102,7 @@ async function getFirebaseProjectParams(projectId, emulatorMode = false) {
104
102
  projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
105
103
  }
106
104
  catch (err) {
107
- (0, utils_2.logLabeledError)("extensions", `Unable to look up project number for ${projectId}.\n` +
105
+ (0, utils_1.logLabeledError)("extensions", `Unable to look up project number for ${projectId}.\n` +
108
106
  " If this is a real project, ensure that you are logged in and have access to it.\n" +
109
107
  " If this is a fake project, please use a project ID starting with 'demo-' to skip production calls.\n" +
110
108
  " Continuing with a fake project number - secrets and other features that require production access may behave unexpectedly.");
@@ -810,15 +808,6 @@ function displayReleaseNotes(args) {
810
808
  logger_1.logger.info(message);
811
809
  }
812
810
  exports.displayReleaseNotes = displayReleaseNotes;
813
- async function promptForOfficialExtension(message) {
814
- const officialExts = await (0, resolveSource_1.getExtensionRegistry)(true);
815
- return await (0, prompt_1.select)({
816
- message,
817
- choices: (0, utils_1.convertOfficialExtensionsToList)(officialExts),
818
- pageSize: Object.keys(officialExts).length,
819
- });
820
- }
821
- exports.promptForOfficialExtension = promptForOfficialExtension;
822
811
  async function promptForRepeatInstance(projectName, extensionName) {
823
812
  const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'. What would you like to do?`;
824
813
  return await (0, prompt_1.select)({
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getResourceRuntime = exports.formatTimestamp = exports.getRandomString = exports.convertOfficialExtensionsToList = exports.convertExtensionOptionToLabeledList = void 0;
3
+ exports.getResourceRuntime = exports.formatTimestamp = exports.getRandomString = exports.convertExtensionOptionToLabeledList = void 0;
4
4
  const types_1 = require("./types");
5
5
  function convertExtensionOptionToLabeledList(options) {
6
6
  return options.map((option) => {
@@ -12,17 +12,6 @@ function convertExtensionOptionToLabeledList(options) {
12
12
  });
13
13
  }
14
14
  exports.convertExtensionOptionToLabeledList = convertExtensionOptionToLabeledList;
15
- function convertOfficialExtensionsToList(officialExts) {
16
- const l = Object.entries(officialExts).map(([key, entry]) => {
17
- return {
18
- checked: false,
19
- value: `${entry.publisher}/${key}`,
20
- };
21
- });
22
- l.sort((a, b) => a.value.localeCompare(b.value));
23
- return l;
24
- }
25
- exports.convertOfficialExtensionsToList = convertOfficialExtensionsToList;
26
15
  function getRandomString(length) {
27
16
  const SUFFIX_CHAR_SET = "abcdefghijklmnopqrstuvwxyz0123456789";
28
17
  let result = "";
@@ -6,6 +6,7 @@ const logger_1 = require("../logger");
6
6
  const utils = require("../utils");
7
7
  const validator = require("./validator");
8
8
  const types = require("./api-types");
9
+ const api_types_1 = require("./api-types");
9
10
  const sort = require("./api-sort");
10
11
  const util = require("./util");
11
12
  const prompt_1 = require("../prompt");
@@ -13,6 +14,7 @@ const api_1 = require("../api");
13
14
  const error_1 = require("../error");
14
15
  const apiv2_1 = require("../apiv2");
15
16
  const pretty_print_1 = require("./pretty-print");
17
+ const functional_1 = require("../functional");
16
18
  class FirestoreApi {
17
19
  constructor() {
18
20
  this.apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
@@ -33,6 +35,7 @@ class FirestoreApi {
33
35
  });
34
36
  }
35
37
  async deploy(options, indexes, fieldOverrides, databaseId = "(default)") {
38
+ var _a;
36
39
  const spec = this.upgradeOldSpec({
37
40
  indexes,
38
41
  fieldOverrides,
@@ -42,8 +45,10 @@ class FirestoreApi {
42
45
  const fieldOverridesToDeploy = spec.fieldOverrides;
43
46
  const existingIndexes = await this.listIndexes(options.project, databaseId);
44
47
  const existingFieldOverrides = await this.listFieldOverrides(options.project, databaseId);
48
+ const database = await this.getDatabase(options.project, databaseId);
49
+ const edition = (_a = database.databaseEdition) !== null && _a !== void 0 ? _a : api_types_1.DatabaseEdition.STANDARD;
45
50
  const indexesToDelete = existingIndexes.filter((index) => {
46
- return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec));
51
+ return !indexesToDeploy.some((spec) => this.indexMatchesSpec(index, spec, edition));
47
52
  });
48
53
  const fieldOverridesToDelete = existingFieldOverrides.filter((field) => {
49
54
  return !fieldOverridesToDeploy.some((spec) => {
@@ -79,7 +84,7 @@ class FirestoreApi {
79
84
  }
80
85
  }
81
86
  for (const index of indexesToDeploy) {
82
- const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index));
87
+ const exists = existingIndexes.some((x) => this.indexMatchesSpec(x, index, edition));
83
88
  if (exists) {
84
89
  logger_1.logger.debug(`Skipping existing index: ${JSON.stringify(index)}`);
85
90
  }
@@ -222,8 +227,9 @@ class FirestoreApi {
222
227
  if (index.multikey) {
223
228
  validator.assertType("multikey", index.multikey, "boolean");
224
229
  }
225
- if (index.unique) {
230
+ if (index.unique !== undefined) {
226
231
  validator.assertType("unique", index.unique, "boolean");
232
+ throw new error_1.FirebaseError("The `unique` index configuration is not supported yet.");
227
233
  }
228
234
  validator.assertHas(index, "fields");
229
235
  index.fields.forEach((field) => {
@@ -328,7 +334,18 @@ class FirestoreApi {
328
334
  const url = index.name;
329
335
  return this.apiClient.delete(`/${url}`);
330
336
  }
331
- indexMatchesSpec(index, spec) {
337
+ optionalApiScopeMatches(lhs, rhs) {
338
+ return (0, functional_1.optionalValueMatches)(lhs, rhs, types.ApiScope.ANY_API);
339
+ }
340
+ optionalDensityMatches(lhs, rhs, edition) {
341
+ const defaultValue = edition === api_types_1.DatabaseEdition.STANDARD ? types.Density.SPARSE_ALL : types.Density.DENSE;
342
+ return (0, functional_1.optionalValueMatches)(lhs, rhs, defaultValue);
343
+ }
344
+ optionalMultikeyMatches(lhs, rhs) {
345
+ const defaultValue = false;
346
+ return (0, functional_1.optionalValueMatches)(lhs, rhs, defaultValue);
347
+ }
348
+ indexMatchesSpec(index, spec, edition) {
332
349
  const collection = util.parseIndexName(index.name).collectionGroupId;
333
350
  if (collection !== spec.collectionGroup) {
334
351
  return false;
@@ -336,16 +353,13 @@ class FirestoreApi {
336
353
  if (index.queryScope !== spec.queryScope) {
337
354
  return false;
338
355
  }
339
- if (index.apiScope !== spec.apiScope) {
340
- return false;
341
- }
342
- if (index.density !== spec.density) {
356
+ if (!this.optionalApiScopeMatches(index.apiScope, spec.apiScope)) {
343
357
  return false;
344
358
  }
345
- if (index.multikey !== spec.multikey) {
359
+ if (!this.optionalDensityMatches(index.density, spec.density, edition)) {
346
360
  return false;
347
361
  }
348
- if (index.unique !== spec.unique) {
362
+ if (!this.optionalMultikeyMatches(index.multikey, spec.multikey)) {
349
363
  return false;
350
364
  }
351
365
  if (index.fields.length !== spec.fields.length) {
@@ -364,7 +378,7 @@ class FirestoreApi {
364
378
  if (iField.arrayConfig !== sField.arrayConfig) {
365
379
  return false;
366
380
  }
367
- if (iField.vectorConfig !== sField.vectorConfig) {
381
+ if (!utils.deepEqual(iField.vectorConfig, sField.vectorConfig)) {
368
382
  return false;
369
383
  }
370
384
  i++;
@@ -177,6 +177,13 @@ class PrettyPrint {
177
177
  }
178
178
  result += `(${field.fieldPath},${configString}) `;
179
179
  });
180
+ result += " -- ";
181
+ if (index.density !== undefined) {
182
+ result += clc.cyan(`Density:${index.density} `);
183
+ }
184
+ if (index.multikey !== undefined) {
185
+ result += clc.cyan(`Multikey:${index.multikey ? "YES" : "NO"}`);
186
+ }
180
187
  return result;
181
188
  }
182
189
  prettyBackupString(backup) {
package/lib/functional.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.nullsafeVisitor = exports.mapObject = exports.partitionRecord = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
3
+ exports.optionalValueMatches = exports.nullsafeVisitor = exports.mapObject = exports.partitionRecord = exports.partition = exports.assertExhaustive = exports.zipIn = exports.zip = exports.reduceFlat = exports.flatten = exports.flattenArray = exports.flattenObject = void 0;
4
4
  function* flattenObject(obj) {
5
5
  function* helper(path, obj) {
6
6
  for (const [k, v] of Object.entries(obj)) {
@@ -85,3 +85,9 @@ const nullsafeVisitor = (func, ...rest) => (first) => {
85
85
  return func(first, ...rest);
86
86
  };
87
87
  exports.nullsafeVisitor = nullsafeVisitor;
88
+ function optionalValueMatches(lhs, rhs, defaultValue) {
89
+ lhs = lhs === undefined ? defaultValue : lhs;
90
+ rhs = rhs === undefined ? defaultValue : rhs;
91
+ return lhs === rhs;
92
+ }
93
+ exports.optionalValueMatches = optionalValueMatches;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
3
+ exports.configForCodebase = exports.normalizeAndValidate = exports.validate = exports.assertUnique = exports.validatePrefix = exports.validateCodebase = exports.normalize = exports.DEFAULT_CODEBASE = void 0;
4
4
  const error_1 = require("../error");
5
5
  exports.DEFAULT_CODEBASE = "default";
6
6
  function normalize(config) {
@@ -23,6 +23,15 @@ function validateCodebase(codebase) {
23
23
  }
24
24
  }
25
25
  exports.validateCodebase = validateCodebase;
26
+ function validatePrefix(prefix) {
27
+ if (prefix.length > 30) {
28
+ throw new error_1.FirebaseError("Invalid prefix. Prefix must be 30 characters or less.");
29
+ }
30
+ if (!/^[a-z](?:[a-z0-9-]*[a-z0-9])?$/.test(prefix)) {
31
+ throw new error_1.FirebaseError("Invalid prefix. Prefix must start with a lowercase letter, can contain only lowercase letters, numeric characters, and dashes, and cannot start or end with a dash.");
32
+ }
33
+ }
34
+ exports.validatePrefix = validatePrefix;
26
35
  function validateSingle(config) {
27
36
  if (!config.source) {
28
37
  throw new error_1.FirebaseError("codebase source must be specified");
@@ -31,6 +40,9 @@ function validateSingle(config) {
31
40
  config.codebase = exports.DEFAULT_CODEBASE;
32
41
  }
33
42
  validateCodebase(config.codebase);
43
+ if (config.prefix) {
44
+ validatePrefix(config.prefix);
45
+ }
34
46
  return Object.assign(Object.assign({}, config), { source: config.source, codebase: config.codebase });
35
47
  }
36
48
  function assertUnique(config, property, propval) {
@@ -47,10 +59,21 @@ function assertUnique(config, property, propval) {
47
59
  }
48
60
  }
49
61
  exports.assertUnique = assertUnique;
62
+ function assertUniqueSourcePrefixPair(config) {
63
+ var _a;
64
+ const sourcePrefixPairs = new Set();
65
+ for (const c of config) {
66
+ const key = JSON.stringify({ source: c.source, prefix: c.prefix || "" });
67
+ if (sourcePrefixPairs.has(key)) {
68
+ throw new error_1.FirebaseError(`More than one functions config specifies the same source directory ('${c.source}') and prefix ('${(_a = c.prefix) !== null && _a !== void 0 ? _a : ""}'). Please add a unique 'prefix' to each function configuration that shares this source to resolve the conflict.`);
69
+ }
70
+ sourcePrefixPairs.add(key);
71
+ }
72
+ }
50
73
  function validate(config) {
51
74
  const validated = config.map((cfg) => validateSingle(cfg));
52
- assertUnique(validated, "source");
53
75
  assertUnique(validated, "codebase");
76
+ assertUniqueSourcePrefixPair(validated);
54
77
  return validated;
55
78
  }
56
79
  exports.validate = validate;
@@ -235,6 +235,9 @@ async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
235
235
  const cfn = await poller.pollOperation(Object.assign(Object.assign({}, gcfV2PollerOptions), { operationResourceName: op.name }));
236
236
  return gcfV2.endpointFromFunction(cfn);
237
237
  }
238
+ else if (endpoint.platform === "run") {
239
+ throw new error_1.FirebaseError("Updating Cloud Run functions is not yet implemented.");
240
+ }
238
241
  else {
239
242
  (0, functional_1.assertExhaustive)(endpoint.platform);
240
243
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.mebibytes = exports.API_VERSION = void 0;
3
+ exports.endpointFromFunction = exports.functionFromEndpoint = exports.deleteFunction = exports.updateFunction = exports.listAllFunctions = exports.listFunctions = exports.getFunction = exports.createFunction = exports.generateUploadUrl = exports.API_VERSION = void 0;
4
4
  const apiv2_1 = require("../apiv2");
5
5
  const error_1 = require("../error");
6
6
  const api_1 = require("../api");
@@ -13,6 +13,7 @@ const utils = require("../utils");
13
13
  const projectConfig = require("../functions/projectConfig");
14
14
  const constants_1 = require("../functions/constants");
15
15
  const cloudfunctions_1 = require("./cloudfunctions");
16
+ const k8s_1 = require("./k8s");
16
17
  exports.API_VERSION = "v2";
17
18
  const DEFAULT_MAX_INSTANCE_COUNT = 100;
18
19
  const client = new apiv2_1.Client({
@@ -20,35 +21,6 @@ const client = new apiv2_1.Client({
20
21
  auth: true,
21
22
  apiVersion: exports.API_VERSION,
22
23
  });
23
- const BYTES_PER_UNIT = {
24
- "": 1,
25
- k: 1e3,
26
- M: 1e6,
27
- G: 1e9,
28
- T: 1e12,
29
- Ki: 1 << 10,
30
- Mi: 1 << 20,
31
- Gi: 1 << 30,
32
- Ti: 1 << 40,
33
- };
34
- function mebibytes(memory) {
35
- const re = /^([0-9]+(\.[0-9]*)?)(Ki|Mi|Gi|Ti|k|M|G|T|([eE]([0-9]+)))?$/;
36
- const matches = re.exec(memory);
37
- if (!matches) {
38
- throw new Error(`Invalid memory quantity "${memory}""`);
39
- }
40
- const quantity = Number.parseFloat(matches[1]);
41
- let bytes;
42
- if (matches[5]) {
43
- bytes = quantity * Math.pow(10, Number.parseFloat(matches[5]));
44
- }
45
- else {
46
- const suffix = matches[3] || "";
47
- bytes = quantity * BYTES_PER_UNIT[suffix];
48
- }
49
- return bytes / (1 << 20);
50
- }
51
- exports.mebibytes = mebibytes;
52
24
  function functionsOpLogReject(func, type, err) {
53
25
  var _a, _b, _c, _d, _e, _f, _g;
54
26
  if ((_a = err === null || err === void 0 ? void 0 : err.message) === null || _a === void 0 ? void 0 : _a.includes("Runtime validation errors")) {
@@ -360,7 +332,7 @@ function endpointFromFunction(gcfFunction) {
360
332
  logger_1.logger.debug("Prod should always return a valid memory amount");
361
333
  return prod;
362
334
  }
363
- const mem = mebibytes(prod);
335
+ const mem = (0, k8s_1.mebibytes)(prod);
364
336
  if (!backend.isValidMemoryOption(mem)) {
365
337
  logger_1.logger.debug("Converting a function to an endpoint with an invalid memory option", mem);
366
338
  }
@@ -124,7 +124,7 @@ async function jobFromEndpoint(endpoint, location, projectNumber) {
124
124
  },
125
125
  };
126
126
  }
127
- else if (endpoint.platform === "gcfv2") {
127
+ else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
128
128
  job.timeZone = endpoint.scheduleTrigger.timeZone || DEFAULT_TIME_ZONE_V2;
129
129
  job.httpTarget = {
130
130
  uri: endpoint.uri,
@@ -55,9 +55,8 @@ async function createInstance(args) {
55
55
  if (args.enableGoogleMlIntegration) {
56
56
  databaseFlags.push({ name: "cloudsql.enable_google_ml_integration", value: "on" });
57
57
  }
58
- let op;
59
58
  try {
60
- op = await client.post(`projects/${args.projectId}/instances`, {
59
+ await client.post(`projects/${args.projectId}/instances`, {
61
60
  name: args.instanceId,
62
61
  region: args.location,
63
62
  databaseVersion: "POSTGRES_15",
@@ -78,23 +77,12 @@ async function createInstance(args) {
78
77
  },
79
78
  },
80
79
  });
80
+ return;
81
81
  }
82
82
  catch (err) {
83
83
  handleAllowlistError(err, args.location);
84
84
  throw err;
85
85
  }
86
- if (!args.waitForCreation) {
87
- return;
88
- }
89
- const opName = `projects/${args.projectId}/operations/${op.body.name}`;
90
- const pollRes = await operationPoller.pollOperation({
91
- apiOrigin: (0, api_1.cloudSQLAdminOrigin)(),
92
- apiVersion: API_VERSION,
93
- operationResourceName: opName,
94
- doneFn: (op) => op.status === "DONE",
95
- masterTimeout: 1200000,
96
- });
97
- return pollRes;
98
86
  }
99
87
  exports.createInstance = createInstance;
100
88
  async function updateInstanceForDataConnect(instance, enableGoogleMlIntegration) {
@@ -128,7 +128,7 @@ async function getIAMUser(options) {
128
128
  return toDatabaseUser(account);
129
129
  }
130
130
  exports.getIAMUser = getIAMUser;
131
- async function setupIAMUsers(instanceId, databaseId, options) {
131
+ async function setupIAMUsers(instanceId, options) {
132
132
  const projectId = (0, projectUtils_1.needProjectId)(options);
133
133
  const { user, mode } = await getIAMUser(options);
134
134
  await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
@@ -4,14 +4,13 @@ exports.brownfieldSqlSetup = exports.setupBrownfieldAsGreenfield = exports.getSc
4
4
  const clc = require("colorette");
5
5
  const permissions_1 = require("./permissions");
6
6
  const cloudsqladmin_1 = require("./cloudsqladmin");
7
- const connect_1 = require("./connect");
8
7
  const logger_1 = require("../../logger");
9
8
  const prompt_1 = require("../../prompt");
10
9
  const error_1 = require("../../error");
11
10
  const projectUtils_1 = require("../../projectUtils");
12
- const connect_2 = require("./connect");
11
+ const connect_1 = require("./connect");
13
12
  const lodash_1 = require("lodash");
14
- const connect_3 = require("./connect");
13
+ const connect_2 = require("./connect");
15
14
  const utils = require("../../utils");
16
15
  var SchemaSetupStatus;
17
16
  (function (SchemaSetupStatus) {
@@ -53,7 +52,7 @@ async function checkSQLRoleIsGranted(options, instanceId, databaseId, grantedRol
53
52
  END $$;
54
53
  `;
55
54
  try {
56
- await (0, connect_2.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [checkCmd], true);
55
+ await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [checkCmd], true);
57
56
  return true;
58
57
  }
59
58
  catch (e) {
@@ -77,7 +76,6 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
77
76
  if (!userIsCSQLAdmin) {
78
77
  throw new error_1.FirebaseError(`Missing required IAM permission to setup SQL schemas. SQL schema setup requires 'roles/cloudsql.admin' or an equivalent role.`);
79
78
  }
80
- await (0, connect_1.setupIAMUsers)(instanceId, databaseId, options);
81
79
  let runGreenfieldSetup = false;
82
80
  if (schemaInfo.setupStatus === SchemaSetupStatus.GreenField) {
83
81
  runGreenfieldSetup = true;
@@ -89,7 +87,7 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
89
87
  }
90
88
  if (runGreenfieldSetup) {
91
89
  const greenfieldSetupCmds = await greenFieldSchemaSetup(instanceId, databaseId, schema, options);
92
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, greenfieldSetupCmds, silent, true);
90
+ await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, greenfieldSetupCmds, silent, true);
93
91
  logFn(clc.green("Database setup complete."));
94
92
  return SchemaSetupStatus.GreenField;
95
93
  }
@@ -125,15 +123,15 @@ async function greenFieldSchemaSetup(instanceId, databaseId, schema, options) {
125
123
  logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
126
124
  revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${(0, permissions_1.firebaseowner)(databaseId)}"`);
127
125
  }
128
- const user = (await (0, connect_2.getIAMUser)(options)).user;
126
+ const user = (await (0, connect_1.getIAMUser)(options)).user;
129
127
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
130
- const { user: fdcP4SAUser } = (0, connect_3.toDatabaseUser)((0, connect_3.getDataConnectP4SA)(projectNumber));
128
+ const { user: fdcP4SAUser } = (0, connect_2.toDatabaseUser)((0, connect_2.getDataConnectP4SA)(projectNumber));
131
129
  const sqlRoleSetupCmds = (0, lodash_1.concat)(revokes, [`CREATE SCHEMA IF NOT EXISTS "${schema}"`], (0, permissions_1.ownerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.writerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), (0, permissions_1.readerRolePermissions)(databaseId, permissions_1.FIREBASE_SUPER_USER, schema), `GRANT "${(0, permissions_1.firebaseowner)(databaseId, schema)}" TO "${user}"`, `GRANT "${(0, permissions_1.firebasewriter)(databaseId, schema)}" TO "${fdcP4SAUser}"`, (0, permissions_1.defaultPermissions)(databaseId, schema, (0, permissions_1.firebaseowner)(databaseId, schema)));
132
130
  return sqlRoleSetupCmds;
133
131
  }
134
132
  exports.greenFieldSchemaSetup = greenFieldSchemaSetup;
135
133
  async function getSchemaMetadata(instanceId, databaseId, schema, options) {
136
- const checkSchemaExists = await (0, connect_2.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [
134
+ const checkSchemaExists = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [
137
135
  `SELECT pg_get_userbyid(nspowner)
138
136
  FROM pg_namespace
139
137
  WHERE nspname = '${schema}';`,
@@ -148,7 +146,7 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
148
146
  }
149
147
  const schemaOwner = checkSchemaExists[0].rows[0].pg_get_userbyid;
150
148
  const cmd = `SELECT tablename, tableowner FROM pg_tables WHERE schemaname='${schema}'`;
151
- const res = await (0, connect_2.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [cmd], true);
149
+ const res = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, [cmd], true);
152
150
  const tables = res[0].rows.map((row) => {
153
151
  return {
154
152
  name: row.tablename,
@@ -157,7 +155,7 @@ async function getSchemaMetadata(instanceId, databaseId, schema, options) {
157
155
  });
158
156
  const checkRoleExists = async (role) => {
159
157
  const cmd = [`SELECT to_regrole('"${role}"') IS NOT NULL AS exists;`];
160
- const result = await (0, connect_2.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, cmd, true);
158
+ const result = await (0, connect_1.executeSqlCmdsAsIamUser)(options, instanceId, databaseId, cmd, true);
161
159
  return result[0].rows[0].exists;
162
160
  };
163
161
  let setupStatus;
@@ -198,7 +196,7 @@ async function setupBrownfieldAsGreenfield(instanceId, databaseId, schemaInfo, o
198
196
  ...alterTableCmds,
199
197
  ...revokeOwnersFromSuperuserCmds,
200
198
  ];
201
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, setupCmds, silent, true);
199
+ await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, setupCmds, silent, true);
202
200
  }
203
201
  exports.setupBrownfieldAsGreenfield = setupBrownfieldAsGreenfield;
204
202
  async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, silent = false) {
@@ -206,9 +204,9 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
206
204
  const uniqueTablesOwners = filterTableOwners(schemaInfo, databaseId);
207
205
  const grantOwnersToFirebasesuperuser = uniqueTablesOwners.map((owner) => `GRANT "${owner}" TO "${permissions_1.FIREBASE_SUPER_USER}"`);
208
206
  const revokeOwnersFromFirebasesuperuser = uniqueTablesOwners.map((owner) => `REVOKE "${owner}" FROM "${permissions_1.FIREBASE_SUPER_USER}"`);
209
- const iamUser = (await (0, connect_2.getIAMUser)(options)).user;
207
+ const iamUser = (await (0, connect_1.getIAMUser)(options)).user;
210
208
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
211
- const { user: fdcP4SAUser } = (0, connect_3.toDatabaseUser)((0, connect_3.getDataConnectP4SA)(projectNumber));
209
+ const { user: fdcP4SAUser } = (0, connect_2.toDatabaseUser)((0, connect_2.getDataConnectP4SA)(projectNumber));
212
210
  const firebaseDefaultPermissions = uniqueTablesOwners.flatMap((owner) => (0, permissions_1.defaultPermissions)(databaseId, schema, owner));
213
211
  const brownfieldSetupCmds = [
214
212
  ...grantOwnersToFirebasesuperuser,
@@ -219,6 +217,6 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
219
217
  ...firebaseDefaultPermissions,
220
218
  ...revokeOwnersFromFirebasesuperuser,
221
219
  ];
222
- await (0, connect_2.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
220
+ await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
223
221
  }
224
222
  exports.brownfieldSqlSetup = brownfieldSqlSetup;