firebase-tools 13.18.0 → 13.20.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 (74) hide show
  1. package/README.md +10 -9
  2. package/lib/commands/dataconnect-sdk-generate.js +5 -2
  3. package/lib/commands/emulators-start.js +3 -0
  4. package/lib/commands/ext-info.js +3 -1
  5. package/lib/commands/ext-sdk-install.js +88 -0
  6. package/lib/commands/ext.js +1 -0
  7. package/lib/commands/index.js +2 -0
  8. package/lib/commands/init.js +20 -16
  9. package/lib/commands/setup-emulators-dataconnect.js +0 -14
  10. package/lib/dataconnect/ensureApis.js +0 -1
  11. package/lib/dataconnect/fileUtils.js +16 -4
  12. package/lib/dataconnect/freeTrial.js +8 -6
  13. package/lib/dataconnect/provisionCloudSql.js +4 -4
  14. package/lib/dataconnect/types.js +1 -0
  15. package/lib/dataconnect/webhook.js +31 -0
  16. package/lib/deploy/dataconnect/deploy.js +2 -0
  17. package/lib/deploy/dataconnect/prepare.js +2 -0
  18. package/lib/deploy/dataconnect/release.js +10 -5
  19. package/lib/deploy/extensions/deploymentSummary.js +3 -2
  20. package/lib/deploy/extensions/planner.js +32 -3
  21. package/lib/deploy/extensions/prepare.js +15 -56
  22. package/lib/deploy/extensions/release.js +11 -10
  23. package/lib/deploy/extensions/tasks.js +32 -21
  24. package/lib/deploy/functions/prepare.js +8 -0
  25. package/lib/deploy/functions/runtimes/node/index.js +5 -0
  26. package/lib/emulator/commandUtils.js +6 -1
  27. package/lib/emulator/constants.js +1 -1
  28. package/lib/emulator/controller.js +17 -3
  29. package/lib/emulator/dataconnect/pg-gateway/auth/base-auth-flow.js +11 -0
  30. package/lib/emulator/dataconnect/pg-gateway/auth/cert.js +69 -0
  31. package/lib/emulator/dataconnect/pg-gateway/auth/index.js +22 -0
  32. package/lib/emulator/dataconnect/pg-gateway/auth/md5.js +135 -0
  33. package/lib/emulator/dataconnect/pg-gateway/auth/password.js +65 -0
  34. package/lib/emulator/dataconnect/pg-gateway/auth/sasl/sasl-mechanism.js +34 -0
  35. package/lib/emulator/dataconnect/pg-gateway/auth/sasl/scram-sha-256.js +298 -0
  36. package/lib/emulator/dataconnect/pg-gateway/auth/trust.js +2 -0
  37. package/lib/emulator/dataconnect/pg-gateway/backend-error.js +75 -0
  38. package/lib/emulator/dataconnect/pg-gateway/buffer-reader.js +55 -0
  39. package/lib/emulator/dataconnect/pg-gateway/buffer-writer.js +79 -0
  40. package/lib/emulator/dataconnect/pg-gateway/connection.js +419 -0
  41. package/lib/emulator/dataconnect/pg-gateway/connection.types.js +8 -0
  42. package/lib/emulator/dataconnect/pg-gateway/crypto.js +40 -0
  43. package/lib/emulator/dataconnect/pg-gateway/duplex.js +53 -0
  44. package/lib/emulator/dataconnect/pg-gateway/index.js +27 -0
  45. package/lib/emulator/dataconnect/pg-gateway/message-buffer.js +96 -0
  46. package/lib/emulator/dataconnect/pg-gateway/message-codes.js +54 -0
  47. package/lib/emulator/dataconnect/pg-gateway/platforms/node/index.js +13 -0
  48. package/lib/emulator/dataconnect/pg-gateway/polyfills/readable-stream-async-iterator.js +36 -0
  49. package/lib/emulator/dataconnect/pg-gateway/utils.js +40 -0
  50. package/lib/emulator/dataconnect/pgliteServer.js +134 -0
  51. package/lib/emulator/dataconnectEmulator.js +55 -73
  52. package/lib/emulator/dataconnectToolkitController.js +44 -0
  53. package/lib/emulator/downloadableEmulators.js +22 -11
  54. package/lib/emulator/hub.js +2 -1
  55. package/lib/emulator/portUtils.js +9 -11
  56. package/lib/emulator/storage/rules/runtime.js +1 -1
  57. package/lib/experiments.js +1 -0
  58. package/lib/extensions/extensionsApi.js +3 -2
  59. package/lib/extensions/extensionsHelper.js +3 -3
  60. package/lib/extensions/localHelper.js +31 -0
  61. package/lib/extensions/runtimes/common.js +186 -38
  62. package/lib/extensions/runtimes/node.js +399 -0
  63. package/lib/extensions/types.js +10 -14
  64. package/lib/extensions/warnings.js +5 -2
  65. package/lib/init/features/dataconnect/index.js +148 -111
  66. package/lib/init/features/dataconnect/sdk.js +40 -27
  67. package/lib/init/features/emulators.js +2 -14
  68. package/lib/prompt.js +1 -1
  69. package/lib/rc.js +1 -9
  70. package/package.json +3 -1
  71. package/schema/connector-yaml.json +14 -0
  72. package/schema/firebase-config.json +6 -0
  73. package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +1 -1
  74. package/templates/init/dataconnect/queries.gql +1 -2
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.start = exports.downloadIfNecessary = exports.stop = exports.getPID = exports.get = exports.getDownloadDetails = exports.requiresJava = exports.handleEmulatorProcessError = exports._getCommand = exports.getLogFileName = exports.DownloadDetails = void 0;
4
+ const lsofi = require("lsofi");
4
5
  const types_1 = require("./types");
5
6
  const constants_1 = require("./constants");
6
7
  const error_1 = require("../error");
@@ -14,6 +15,7 @@ const os = require("os");
14
15
  const registry_1 = require("./registry");
15
16
  const download_1 = require("../emulator/download");
16
17
  const experiments = require("../experiments");
18
+ const process = require("process");
17
19
  const EMULATOR_INSTANCE_KILL_TIMEOUT = 4000;
18
20
  const CACHE_DIR = process.env.FIREBASE_EMULATORS_PATH || path.join(os.homedir(), ".cache", "firebase", "emulators");
19
21
  const EMULATOR_UPDATE_DETAILS = {
@@ -46,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
46
48
  },
47
49
  dataconnect: process.platform === "darwin"
48
50
  ? {
49
- version: "1.3.7",
50
- expectedSize: 25019136,
51
- expectedChecksum: "648ca3e289db8209c2d555bb381e5e5e",
51
+ version: "1.4.2",
52
+ expectedSize: 25125632,
53
+ expectedChecksum: "25c7dfe5816f4bfba82f002bcab10340",
52
54
  }
53
55
  : process.platform === "win32"
54
56
  ? {
55
- version: "1.3.7",
56
- expectedSize: 25441792,
57
- expectedChecksum: "16081147aa94f1b8691329d5b9430b69",
57
+ version: "1.4.2",
58
+ expectedSize: 25548800,
59
+ expectedChecksum: "854ddf17fd9adeafd19531282ccc2a46",
58
60
  }
59
61
  : {
60
- version: "1.3.7",
61
- expectedSize: 24928408,
62
- expectedChecksum: "8749930f3a43f616e3ae231af8c787a8",
62
+ version: "1.4.2",
63
+ expectedSize: 25034904,
64
+ expectedChecksum: "ed21d946879647f24fade5cdd93c9eb3",
63
65
  },
64
66
  };
65
67
  exports.DownloadDetails = {
@@ -295,6 +297,7 @@ function _getCommand(emulator, args) {
295
297
  optionalArgs: baseCmd.optionalArgs,
296
298
  joinArgs: baseCmd.joinArgs,
297
299
  shell: baseCmd.shell,
300
+ port: args.port,
298
301
  };
299
302
  }
300
303
  exports._getCommand = _getCommand;
@@ -308,11 +311,15 @@ async function _fatal(emulator, errorMsg) {
308
311
  process.exit(1);
309
312
  }
310
313
  }
311
- async function handleEmulatorProcessError(emulator, err) {
314
+ async function handleEmulatorProcessError(emulator, err, port) {
312
315
  const description = constants_1.Constants.description(emulator);
313
316
  if (err.path === "java" && err.code === "ENOENT") {
314
317
  await _fatal(emulator, `${description} has exited because java is not installed, you can install it from https://openjdk.java.net/install/`);
315
318
  }
319
+ else if (err.code === "EADDRINUSE") {
320
+ const ps = port ? await lsofi(port) : false;
321
+ await _fatal(emulator, `${description} has exited because its configured port is already in use${ps ? ` by process number ${ps}` : ""}. Are you running another copy of the emulator suite?`);
322
+ }
316
323
  else {
317
324
  await _fatal(emulator, `${description} has exited: ${err}`);
318
325
  }
@@ -366,9 +373,13 @@ async function _runBinary(emulator, command, extraEnv) {
366
373
  if (data.toString().includes("java.lang.UnsupportedClassVersionError")) {
367
374
  logger.logLabeled("WARN", emulator.name, "Unsupported java version, make sure java --version reports 1.8 or higher.");
368
375
  }
376
+ if (data.toString().includes("address already in use")) {
377
+ const message = `${description} has exited because its configured port ${command.port} is already in use. Are you running another copy of the emulator suite?`;
378
+ logger.logLabeled("ERROR", emulator.name, message);
379
+ }
369
380
  });
370
381
  emulator.instance.on("error", (err) => {
371
- handleEmulatorProcessError(emulator.name, err);
382
+ void handleEmulatorProcessError(emulator.name, err, command.port);
372
383
  });
373
384
  emulator.instance.once("exit", async (code, signal) => {
374
385
  if (signal) {
@@ -10,6 +10,7 @@ const types_1 = require("./types");
10
10
  const hubExport_1 = require("./hubExport");
11
11
  const registry_1 = require("./registry");
12
12
  const ExpressBasedEmulator_1 = require("./ExpressBasedEmulator");
13
+ const utils_1 = require("../utils");
13
14
  const pkg = require("../../package.json");
14
15
  class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
15
16
  static readLocatorFile(projectId) {
@@ -19,7 +20,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
19
20
  }
20
21
  const data = fs.readFileSync(locatorPath, "utf8").toString();
21
22
  const locator = JSON.parse(data);
22
- if (locator.version !== this.CLI_VERSION) {
23
+ if (!utils_1.isVSCodeExtension && locator.version !== this.CLI_VERSION) {
23
24
  logger_1.logger.debug(`Found locator with mismatched version, ignoring: ${JSON.stringify(locator)}`);
24
25
  return undefined;
25
26
  }
@@ -11,7 +11,6 @@ const types_1 = require("./types");
11
11
  const constants_1 = require("./constants");
12
12
  const emulatorLogger_1 = require("./emulatorLogger");
13
13
  const node_child_process_1 = require("node:child_process");
14
- const dataconnectEmulator_1 = require("./dataconnectEmulator");
15
14
  const RESTRICTED_PORTS = new Set([
16
15
  1,
17
16
  7,
@@ -147,6 +146,7 @@ const EMULATOR_CAN_LISTEN_ON_PRIMARY_ONLY = {
147
146
  firestore: true,
148
147
  "firestore.websocket": true,
149
148
  pubsub: true,
149
+ "dataconnect.postgres": true,
150
150
  dataconnect: false,
151
151
  hub: false,
152
152
  ui: false,
@@ -185,7 +185,11 @@ async function resolveHostAndAssignPorts(listenConfig) {
185
185
  lookupForHost.set(host, lookup);
186
186
  }
187
187
  const findAddrs = lookup.then(async (addrs) => {
188
- const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket" ? types_1.Emulators.FIRESTORE : name);
188
+ const emuLogger = emulatorLogger_1.EmulatorLogger.forEmulator(name === "firestore.websocket"
189
+ ? types_1.Emulators.FIRESTORE
190
+ : name === "dataconnect.postgres"
191
+ ? types_1.Emulators.DATACONNECT
192
+ : name);
189
193
  if (addrs.some((addr) => addr.address === dns_1.IPV6_UNSPECIFIED.address)) {
190
194
  if (!addrs.some((addr) => addr.address === dns_1.IPV4_UNSPECIFIED.address)) {
191
195
  emuLogger.logLabeled("DEBUG", name, `testing listening on IPv4 wildcard in addition to IPv6. To listen on IPv6 only, use "::0" instead.`);
@@ -222,14 +226,6 @@ async function resolveHostAndAssignPorts(listenConfig) {
222
226
  available.push(listen);
223
227
  }
224
228
  else {
225
- if (/^dataconnect/i.exec(name)) {
226
- const alreadyRunning = await (0, dataconnectEmulator_1.checkIfDataConnectEmulatorRunningOnAddress)(listen);
227
- if (alreadyRunning) {
228
- emuLogger.logLabeled("DEBUG", "dataconnect", `Detected already running emulator on ${listen.address}:${listen.port}. Will attempt to reuse it.`);
229
- }
230
- available.push(listen);
231
- continue;
232
- }
233
229
  if (!portFixed) {
234
230
  if (i > 0) {
235
231
  emuLogger.logLabeled("DEBUG", name, `Port ${p} taken on secondary address ${addr.address}, will keep searching to find a better port.`);
@@ -281,7 +277,9 @@ exports.resolveHostAndAssignPorts = resolveHostAndAssignPorts;
281
277
  function portDescription(name) {
282
278
  return name === "firestore.websocket"
283
279
  ? `websocket server for ${types_1.Emulators.FIRESTORE}`
284
- : constants_1.Constants.description(name);
280
+ : name === "dataconnect.postgres"
281
+ ? `postgres server for ${types_1.Emulators.DATACONNECT}`
282
+ : constants_1.Constants.description(name);
285
283
  }
286
284
  function warnPartiallyAvailablePort(emuLogger, port, available, unavailable) {
287
285
  emuLogger.logLabeled("WARN", `Port ${port} is available on ` +
@@ -109,7 +109,7 @@ class StorageRulesRuntime {
109
109
  };
110
110
  });
111
111
  this._childprocess.on("error", (err) => {
112
- (0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
112
+ void (0, downloadableEmulators_1.handleEmulatorProcessError)(types_2.Emulators.STORAGE, err);
113
113
  });
114
114
  (_a = this._childprocess.stderr) === null || _a === void 0 ? void 0 : _a.on("data", (buf) => {
115
115
  const error = buf.toString();
@@ -109,6 +109,7 @@ exports.ALL_EXPERIMENTS = experiments({
109
109
  fdccompatiblemode: {
110
110
  shortDescription: "Enable Data Connect schema migrations in Compatible Mode",
111
111
  fullDescription: "Enable Data Connect schema migrations in Compatible Mode",
112
+ default: true,
112
113
  public: false,
113
114
  },
114
115
  });
@@ -15,10 +15,11 @@ const extensionsApiClient = new apiv2_1.Client({
15
15
  urlPrefix: (0, api_1.extensionsOrigin)(),
16
16
  apiVersion: EXTENSIONS_API_VERSION,
17
17
  });
18
- async function createInstanceHelper(projectId, instanceId, config, validateOnly = false) {
18
+ async function createInstanceHelper(projectId, instanceId, config, labels, validateOnly = false) {
19
19
  const createRes = await extensionsApiClient.post(`/projects/${projectId}/instances/`, {
20
20
  name: `projects/${projectId}/instances/${instanceId}`,
21
21
  config,
22
+ labels,
22
23
  }, {
23
24
  queryParams: {
24
25
  validateOnly: validateOnly ? "true" : "false",
@@ -63,7 +64,7 @@ async function createInstance(args) {
63
64
  if (args.eventarcChannel) {
64
65
  config.eventarcChannel = args.eventarcChannel;
65
66
  }
66
- return createInstanceHelper(args.projectId, args.instanceId, config, args.validateOnly);
67
+ return await createInstanceHelper(args.projectId, args.instanceId, config, args.labels, args.validateOnly);
67
68
  }
68
69
  exports.createInstance = createInstance;
69
70
  async function deleteInstance(projectId, instanceId) {
@@ -7,7 +7,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
7
7
  function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
10
+ exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
11
11
  const clc = require("colorette");
12
12
  const ora = require("ora");
13
13
  const semver = require("semver");
@@ -69,7 +69,7 @@ const AUTOPOPULATED_PARAM_NAMES = [
69
69
  "DATABASE_INSTANCE",
70
70
  "DATABASE_URL",
71
71
  ];
72
- exports.AUTOPOULATED_PARAM_PLACEHOLDERS = {
72
+ exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = {
73
73
  PROJECT_ID: "project-id",
74
74
  STORAGE_BUCKET: "project-id.appspot.com",
75
75
  EXT_INSTANCE_ID: "extension-id",
@@ -449,7 +449,7 @@ async function validateExtensionSpec(rootDirectory, extensionId) {
449
449
  throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
450
450
  }
451
451
  const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
452
- subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
452
+ subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOPULATED_PARAM_PLACEHOLDERS);
453
453
  validateSpec(subbedSpec);
454
454
  return extensionSpec;
455
455
  }
@@ -6,11 +6,20 @@ const path = require("path");
6
6
  const yaml = require("yaml");
7
7
  const fsutils_1 = require("../fsutils");
8
8
  const error_1 = require("../error");
9
+ const types_1 = require("./types");
9
10
  const logger_1 = require("../logger");
11
+ const extensionsHelper_1 = require("./extensionsHelper");
10
12
  exports.EXTENSIONS_SPEC_FILE = "extension.yaml";
11
13
  const EXTENSIONS_PREINSTALL_FILE = "PREINSTALL.md";
12
14
  async function getLocalExtensionSpec(directory) {
13
15
  const spec = await parseYAML(readFile(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE)));
16
+ if (spec.lifecycleEvents) {
17
+ spec.lifecycleEvents = fixLifecycleEvents(spec.lifecycleEvents);
18
+ }
19
+ if (!(0, types_1.isExtensionSpec)(spec)) {
20
+ (0, extensionsHelper_1.validateSpec)(spec);
21
+ throw new error_1.FirebaseError("Error: extension.yaml does not contain a valid extension specification.");
22
+ }
14
23
  try {
15
24
  const preinstall = readFile(path.resolve(directory, EXTENSIONS_PREINSTALL_FILE));
16
25
  spec.preinstallContent = preinstall;
@@ -21,6 +30,28 @@ async function getLocalExtensionSpec(directory) {
21
30
  return spec;
22
31
  }
23
32
  exports.getLocalExtensionSpec = getLocalExtensionSpec;
33
+ function fixLifecycleEvents(lifecycleEvents) {
34
+ const stages = {
35
+ onInstall: "ON_INSTALL",
36
+ onUpdate: "ON_UPDATE",
37
+ onConfigure: "ON_CONFIGURE",
38
+ stageUnspecified: "STAGE_UNSPECIFIED",
39
+ };
40
+ const arrayLifecycle = [];
41
+ if ((0, types_1.isObject)(lifecycleEvents)) {
42
+ for (const [key, val] of Object.entries(lifecycleEvents)) {
43
+ if ((0, types_1.isObject)(val) &&
44
+ typeof val.function === "string" &&
45
+ typeof val.processingMessage === "string") {
46
+ arrayLifecycle.push({
47
+ stage: stages[key] || stages["stageUnspecified"],
48
+ taskQueueTriggerFunction: val.function,
49
+ });
50
+ }
51
+ }
52
+ }
53
+ return arrayLifecycle;
54
+ }
24
55
  function findExtensionYaml(directory) {
25
56
  while (!(0, fsutils_1.fileExistsSync)(path.resolve(directory, exports.EXTENSIONS_SPEC_FILE))) {
26
57
  const parentDir = path.dirname(directory);
@@ -1,45 +1,41 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractExtensionsFromBuilds = exports.extractAllDynamicExtensions = void 0;
3
+ exports.longestCommonPrefix = exports.snakeToCamelCase = exports.lowercaseFirstLetter = exports.capitalizeFirstLetter = exports.toTitleCase = exports.getInstallPathPrefix = exports.getCodebaseDir = exports.writeSDK = exports.getCodebaseRuntime = exports.copyDirectory = exports.writeFile = exports.isTypescriptCodebase = exports.extensionMatchesAnyFilter = exports.extractExtensionsFromBuilds = exports.getErrorMessage = exports.fixDarkBlueText = void 0;
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const prompt_1 = require("../../prompt");
7
+ const fsutils = require("../../fsutils");
8
+ const utils_1 = require("../../utils");
9
+ const error_1 = require("../../error");
4
10
  const projectConfig_1 = require("../../functions/projectConfig");
5
- const prepare_1 = require("../../deploy/functions/prepare");
6
- const functionsConfig_1 = require("../../functionsConfig");
7
- const functionsDeployHelper_1 = require("../../deploy/functions/functionsDeployHelper");
8
- const logger_1 = require("../../logger");
9
- const savedLoggerSilent = logger_1.logger.silent;
10
- function silenceLogging() {
11
- logger_1.logger.silent = true;
12
- }
13
- function resumeLogging() {
14
- logger_1.logger.silent = savedLoggerSilent;
15
- }
16
- async function extractAllDynamicExtensions(options) {
17
- const firebaseConfig = await (0, functionsConfig_1.getFirebaseConfig)(options);
18
- const runtimeConfig = { firebase: firebaseConfig };
19
- const functionsConfig = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
20
- let functionsBuilds = {};
21
- const codebases = (0, functionsDeployHelper_1.targetCodebases)(functionsConfig);
22
- silenceLogging();
23
- for (const codebase of codebases) {
24
- try {
25
- const filters = [{ codebase: `${codebase}` }];
26
- const builds = await (0, prepare_1.loadCodebases)(functionsConfig, options, firebaseConfig, runtimeConfig, filters);
27
- functionsBuilds = Object.assign(Object.assign({}, functionsBuilds), builds);
28
- }
29
- catch (err) {
30
- }
11
+ const types_1 = require("../types");
12
+ const functionRuntimes = require("../../deploy/functions/runtimes");
13
+ const nodeRuntime = require("./node");
14
+ function fixDarkBlueText(txt) {
15
+ const DARK_BLUE = "\u001b[34m";
16
+ const BRIGHT_CYAN = "\u001b[36;1m";
17
+ return txt.replaceAll(DARK_BLUE, BRIGHT_CYAN);
18
+ }
19
+ exports.fixDarkBlueText = fixDarkBlueText;
20
+ function getErrorMessage(err, defaultMsg) {
21
+ if ((0, types_1.isObject)(err) && err.message && typeof err.message === "string") {
22
+ return err.message;
23
+ }
24
+ else {
25
+ return defaultMsg;
31
26
  }
32
- resumeLogging();
33
- return extractExtensionsFromBuilds(functionsBuilds);
34
27
  }
35
- exports.extractAllDynamicExtensions = extractAllDynamicExtensions;
28
+ exports.getErrorMessage = getErrorMessage;
36
29
  function extractExtensionsFromBuilds(builds, filters) {
37
30
  const extRecords = {};
38
31
  for (const [codebase, build] of Object.entries(builds)) {
39
32
  if (build.extensions) {
40
33
  for (const [id, ext] of Object.entries(build.extensions)) {
41
34
  if (extensionMatchesAnyFilter(codebase, id, filters)) {
42
- extRecords[id] = ext;
35
+ if (extRecords[id]) {
36
+ throw new error_1.FirebaseError(`Duplicate extension id found: ${id}`);
37
+ }
38
+ extRecords[id] = Object.assign(Object.assign({}, ext), { labels: { createdBy: "SDK", codebase } });
43
39
  }
44
40
  }
45
41
  }
@@ -53,6 +49,7 @@ function extensionMatchesAnyFilter(codebase, extensionId, filters) {
53
49
  }
54
50
  return filters.some((f) => extensionMatchesFilter(codebase, extensionId, f));
55
51
  }
52
+ exports.extensionMatchesAnyFilter = extensionMatchesAnyFilter;
56
53
  function extensionMatchesFilter(codebase, extensionId, filter) {
57
54
  if (codebase && filter.codebase) {
58
55
  if (codebase !== filter.codebase) {
@@ -62,14 +59,165 @@ function extensionMatchesFilter(codebase, extensionId, filter) {
62
59
  if (!filter.idChunks) {
63
60
  return true;
64
61
  }
65
- const idChunks = extensionId.split("-");
66
- if (idChunks.length < filter.idChunks.length) {
67
- return false;
62
+ const filterId = filter.idChunks.join("-");
63
+ return extensionId === filterId;
64
+ }
65
+ function isTypescriptCodebase(codebaseDir) {
66
+ return fsutils.fileExistsSync(path.join(codebaseDir, "tsconfig.json"));
67
+ }
68
+ exports.isTypescriptCodebase = isTypescriptCodebase;
69
+ async function writeFile(filePath, data, options) {
70
+ const shortFilePath = filePath.replace(process.cwd(), ".");
71
+ if (fsutils.fileExistsSync(filePath)) {
72
+ if (await (0, prompt_1.confirm)({
73
+ message: `${shortFilePath} already exists. Overwite it?`,
74
+ nonInteractive: options.nonInteractive,
75
+ force: options.force,
76
+ default: false,
77
+ })) {
78
+ try {
79
+ await fs.promises.writeFile(filePath, data, { flag: "w" });
80
+ (0, utils_1.logLabeledBullet)("extensions", `successfully wrote ${shortFilePath}`);
81
+ }
82
+ catch (err) {
83
+ throw new error_1.FirebaseError(`Failed to write ${shortFilePath}:\n ${err}`);
84
+ }
85
+ }
86
+ else {
87
+ return;
88
+ }
68
89
  }
69
- for (let i = 0; i < filter.idChunks.length; i++) {
70
- if (idChunks[i] !== filter.idChunks[i]) {
71
- return false;
90
+ else {
91
+ try {
92
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
93
+ try {
94
+ await fs.promises.writeFile(`${filePath}`, data, { flag: "w" });
95
+ (0, utils_1.logLabeledBullet)("extensions", `successfully created ${shortFilePath}`);
96
+ }
97
+ catch (err) {
98
+ throw new error_1.FirebaseError(`Failed to create ${shortFilePath}:\n ${err}`);
99
+ }
100
+ }
101
+ catch (err) {
102
+ throw new error_1.FirebaseError(`Error during SDK file creation:\n ${err}`);
103
+ }
104
+ }
105
+ }
106
+ exports.writeFile = writeFile;
107
+ async function copyDirectory(src, dest, options) {
108
+ const shortDestPath = dest.replace(process.cwd(), ",");
109
+ if (fsutils.dirExistsSync(dest)) {
110
+ if (await (0, prompt_1.confirm)({
111
+ message: `${shortDestPath} already exists. Copy anyway?`,
112
+ nonInteractive: options.nonInteractive,
113
+ force: options.force,
114
+ default: false,
115
+ })) {
116
+ const entries = await fs.promises.readdir(src, { withFileTypes: true });
117
+ for (const entry of entries) {
118
+ const srcPath = path.join(src, entry.name);
119
+ const destPath = path.join(dest, entry.name);
120
+ if (entry.isDirectory()) {
121
+ if (srcPath.includes("node_modules")) {
122
+ continue;
123
+ }
124
+ await copyDirectory(srcPath, destPath, { force: true });
125
+ }
126
+ else if (entry.isFile())
127
+ try {
128
+ await fs.promises.copyFile(srcPath, destPath);
129
+ }
130
+ catch (err) {
131
+ throw new error_1.FirebaseError(`Failed to copy ${destPath.replace(process.cwd(), ".")}:\n ${err}`);
132
+ }
133
+ }
134
+ }
135
+ else {
136
+ return;
137
+ }
138
+ }
139
+ else {
140
+ await fs.promises.mkdir(dest, { recursive: true }).then(async () => {
141
+ await copyDirectory(src, dest, { force: true });
142
+ });
143
+ }
144
+ }
145
+ exports.copyDirectory = copyDirectory;
146
+ async function getCodebaseRuntime(options) {
147
+ const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
148
+ const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase || projectConfig_1.DEFAULT_CODEBASE);
149
+ const sourceDirName = codebaseConfig.source;
150
+ const sourceDir = options.config.path(sourceDirName);
151
+ const delegateContext = {
152
+ projectId: "",
153
+ sourceDir,
154
+ projectDir: options.config.projectDir,
155
+ runtime: codebaseConfig.runtime,
156
+ };
157
+ let delegate;
158
+ try {
159
+ delegate = await functionRuntimes.getRuntimeDelegate(delegateContext);
160
+ }
161
+ catch (err) {
162
+ throw new error_1.FirebaseError(`Could not detect target language for SDK at ${sourceDir}`);
163
+ }
164
+ return delegate.runtime;
165
+ }
166
+ exports.getCodebaseRuntime = getCodebaseRuntime;
167
+ async function writeSDK(extensionRef, localPath, spec, options) {
168
+ const runtime = await getCodebaseRuntime(options);
169
+ if (runtime.startsWith("nodejs")) {
170
+ let sampleImport = await nodeRuntime.writeSDK(extensionRef, localPath, spec, options);
171
+ sampleImport = fixDarkBlueText(sampleImport);
172
+ return sampleImport;
173
+ }
174
+ else {
175
+ throw new error_1.FirebaseError(`Extension SDK generation is currently only supported for NodeJs. We detected the target source to be: ${runtime}`);
176
+ }
177
+ }
178
+ exports.writeSDK = writeSDK;
179
+ function getCodebaseDir(options) {
180
+ const config = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
181
+ const codebaseConfig = (0, projectConfig_1.configForCodebase)(config, options.codebase || projectConfig_1.DEFAULT_CODEBASE);
182
+ return `${options.projectRoot}/${codebaseConfig.source}/`;
183
+ }
184
+ exports.getCodebaseDir = getCodebaseDir;
185
+ function getInstallPathPrefix(options) {
186
+ return `${getCodebaseDir(options)}generated/extensions/`;
187
+ }
188
+ exports.getInstallPathPrefix = getInstallPathPrefix;
189
+ function toTitleCase(txt) {
190
+ return txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase();
191
+ }
192
+ exports.toTitleCase = toTitleCase;
193
+ function capitalizeFirstLetter(txt) {
194
+ return txt.charAt(0).toUpperCase() + txt.substring(1);
195
+ }
196
+ exports.capitalizeFirstLetter = capitalizeFirstLetter;
197
+ function lowercaseFirstLetter(txt) {
198
+ return txt.charAt(0).toLowerCase() + txt.substring(1);
199
+ }
200
+ exports.lowercaseFirstLetter = lowercaseFirstLetter;
201
+ function snakeToCamelCase(txt) {
202
+ let ret = txt.toLowerCase();
203
+ ret = ret.replace(/_/g, " ");
204
+ ret = ret.replace(/\w\S*/g, toTitleCase);
205
+ ret = ret.charAt(0).toLowerCase() + ret.substring(1);
206
+ return ret;
207
+ }
208
+ exports.snakeToCamelCase = snakeToCamelCase;
209
+ function longestCommonPrefix(arr) {
210
+ if (arr.length === 0) {
211
+ return "";
212
+ }
213
+ let prefix = "";
214
+ for (let pos = 0; pos < arr[0].length; pos++) {
215
+ if (arr.every((s) => s.charAt(pos) === arr[0][pos])) {
216
+ prefix += arr[0][pos];
72
217
  }
218
+ else
219
+ break;
73
220
  }
74
- return true;
221
+ return prefix;
75
222
  }
223
+ exports.longestCommonPrefix = longestCommonPrefix;