firebase-tools 13.29.1 → 13.29.3

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.
@@ -51,7 +51,7 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
51
51
  const testCases = (0, options_parser_util_1.parseIntoStringArray)(options.testCaseIds, options.testCaseIdsFile);
52
52
  const testDevices = (0, options_parser_util_1.parseTestDevices)(options.testDevices, options.testDevicesFile);
53
53
  if (testCases.length && (options.testUsernameResource || options.testPasswordResource)) {
54
- throw new error_1.FirebaseError("Password and username resource names are not supported for the AI testing agent.");
54
+ throw new error_1.FirebaseError("Password and username resource names are not supported for the testing agent.");
55
55
  }
56
56
  const loginCredential = (0, options_parser_util_1.getLoginCredential)({
57
57
  username: options.testUsername,
@@ -1,15 +1,74 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.build = void 0;
3
+ exports.handleBuildErrors = exports.build = void 0;
4
4
  const dataconnectEmulator_1 = require("../emulator/dataconnectEmulator");
5
5
  const error_1 = require("../error");
6
+ const experiments = require("../experiments");
7
+ const prompt_1 = require("../prompt");
8
+ const utils = require("../utils");
6
9
  const graphqlError_1 = require("./graphqlError");
7
- async function build(options, configDir) {
10
+ async function build(options, configDir, dryRun) {
8
11
  var _a, _b;
9
- const buildResult = await dataconnectEmulator_1.DataConnectEmulator.build({ configDir });
12
+ const args = { configDir };
13
+ if (experiments.isEnabled("fdcconnectorevolution") && options.projectId) {
14
+ const flags = process.env["DATA_CONNECT_PREVIEW"];
15
+ if (flags) {
16
+ process.env["DATA_CONNECT_PREVIEW"] = flags + ",conn_evolution";
17
+ }
18
+ else {
19
+ process.env["DATA_CONNECT_PREVIEW"] = "conn_evolution";
20
+ }
21
+ args.projectId = options.projectId;
22
+ }
23
+ const buildResult = await dataconnectEmulator_1.DataConnectEmulator.build(args);
10
24
  if ((_a = buildResult === null || buildResult === void 0 ? void 0 : buildResult.errors) === null || _a === void 0 ? void 0 : _a.length) {
11
- throw new error_1.FirebaseError(`There are errors in your schema and connector files:\n${buildResult.errors.map(graphqlError_1.prettify).join("\n")}`);
25
+ await handleBuildErrors(buildResult.errors, options.nonInteractive, options.force, dryRun);
12
26
  }
13
27
  return (_b = buildResult === null || buildResult === void 0 ? void 0 : buildResult.metadata) !== null && _b !== void 0 ? _b : {};
14
28
  }
15
29
  exports.build = build;
30
+ async function handleBuildErrors(errors, nonInteractive, force, dryRun) {
31
+ if (errors.filter((w) => { var _a; return !((_a = w.extensions) === null || _a === void 0 ? void 0 : _a.warningLevel); }).length) {
32
+ throw new error_1.FirebaseError(`There are errors in your schema and connector files:\n${errors.map(graphqlError_1.prettify).join("\n")}`);
33
+ }
34
+ const interactiveAcks = errors.filter((w) => { var _a; return ((_a = w.extensions) === null || _a === void 0 ? void 0 : _a.warningLevel) === "INTERACTIVE_ACK"; });
35
+ const requiredAcks = errors.filter((w) => { var _a; return ((_a = w.extensions) === null || _a === void 0 ? void 0 : _a.warningLevel) === "REQUIRE_ACK"; });
36
+ const choices = [
37
+ { name: "Acknowledge all changes and proceed", value: "proceed" },
38
+ { name: "Reject changes and abort", value: "abort" },
39
+ ];
40
+ if (requiredAcks.length) {
41
+ utils.logLabeledWarning("dataconnect", `There are changes in your schema or connectors that may break your existing applications. These changes require explicit acknowledgement to proceed. You may either reject the changes and update your sources with the suggested workaround(s), if any, or acknowledge these changes and proceed with the deployment:\n` +
42
+ (0, graphqlError_1.prettifyWithWorkaround)(requiredAcks));
43
+ if (nonInteractive && !force) {
44
+ throw new error_1.FirebaseError("Explicit acknowledgement required for breaking schema or connector changes. Rerun this command with --force to deploy these changes.");
45
+ }
46
+ else if (!nonInteractive && !force && !dryRun) {
47
+ const result = await (0, prompt_1.promptOnce)({
48
+ message: "Would you like to proceed with these breaking changes?",
49
+ type: "list",
50
+ choices,
51
+ default: "abort",
52
+ });
53
+ if (result === "abort") {
54
+ throw new error_1.FirebaseError(`Deployment aborted.`);
55
+ }
56
+ }
57
+ }
58
+ if (interactiveAcks.length) {
59
+ utils.logLabeledWarning("dataconnect", `There are changes in your schema or connectors that may cause unexpected behavior in your existing applications:\n` +
60
+ interactiveAcks.map(graphqlError_1.prettify).join("\n"));
61
+ if (!nonInteractive && !force && !dryRun) {
62
+ const result = await (0, prompt_1.promptOnce)({
63
+ message: "Would you like to proceed with these changes?",
64
+ type: "list",
65
+ choices,
66
+ default: "proceed",
67
+ });
68
+ if (result === "abort") {
69
+ throw new error_1.FirebaseError(`Deployment aborted.`);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ exports.handleBuildErrors = handleBuildErrors;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.upgradeInstructions = exports.printFreeTrialUnavailable = exports.getFreeTrialInstanceId = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
3
+ exports.upgradeInstructions = exports.printFreeTrialUnavailable = exports.isFreeTrialError = exports.getFreeTrialInstanceId = exports.checkFreeTrialInstanceUsed = exports.freeTrialTermsLink = void 0;
4
4
  const cloudmonitoring_1 = require("../gcp/cloudmonitoring");
5
5
  const cloudsqladmin_1 = require("../gcp/cloudsql/cloudsqladmin");
6
6
  const utils = require("../utils");
@@ -36,17 +36,23 @@ async function getFreeTrialInstanceId(projectId) {
36
36
  return (_a = instances.find((i) => { var _a; return ((_a = i.settings.userLabels) === null || _a === void 0 ? void 0 : _a["firebase-data-connect"]) === "ft"; })) === null || _a === void 0 ? void 0 : _a.name;
37
37
  }
38
38
  exports.getFreeTrialInstanceId = getFreeTrialInstanceId;
39
+ async function isFreeTrialError(err, projectId) {
40
+ return err.message.includes("Quota Exhausted") && (await checkFreeTrialInstanceUsed(projectId))
41
+ ? true
42
+ : false;
43
+ }
44
+ exports.isFreeTrialError = isFreeTrialError;
39
45
  function printFreeTrialUnavailable(projectId, configYamlPath, instanceId) {
40
46
  if (!instanceId) {
41
- utils.logLabeledError("data connect", "The CloudSQL free trial has already been used on this project.");
42
- utils.logLabeledError("data connect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
47
+ utils.logLabeledError("dataconnect", "The CloudSQL free trial has already been used on this project.");
48
+ utils.logLabeledError("dataconnect", `You may create or use a paid CloudSQL instance by visiting https://console.cloud.google.com/sql/instances`);
43
49
  return;
44
50
  }
45
- utils.logLabeledError("data connect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
51
+ utils.logLabeledError("dataconnect", `Project '${projectId} already has a CloudSQL instance '${instanceId}' on the Firebase Data Connect no-cost trial.`);
46
52
  const reuseHint = `To use a different database in the same instance, ${clc.bold(`change the ${clc.blue("instanceId")} to "${instanceId}"`)} and update ${clc.blue("location")} in ` +
47
53
  `${clc.green(configYamlPath)}.`;
48
- utils.logLabeledError("data connect", reuseHint);
49
- utils.logLabeledError("data connect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
54
+ utils.logLabeledError("dataconnect", reuseHint);
55
+ utils.logLabeledError("dataconnect", `Alternatively, you may create a new (paid) CloudSQL instance at https://console.cloud.google.com/sql/instances`);
50
56
  }
51
57
  exports.printFreeTrialUnavailable = printFreeTrialUnavailable;
52
58
  function upgradeInstructions(projectId) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.prettify = void 0;
3
+ exports.prettifyWithWorkaround = exports.prettify = void 0;
4
+ const Table = require("cli-table");
4
5
  function prettify(err) {
5
6
  var _a, _b, _c, _d;
6
7
  const message = err.message;
@@ -14,3 +15,28 @@ function prettify(err) {
14
15
  return header.length ? `${header}: ${message}` : message;
15
16
  }
16
17
  exports.prettify = prettify;
18
+ function prettifyWithWorkaround(errs) {
19
+ var _a, _b;
20
+ const table = new Table({
21
+ head: ["Issue", "Workaround", "Reason"],
22
+ style: { head: ["yellow"] },
23
+ });
24
+ for (const e of errs) {
25
+ if (!((_b = (_a = e.extensions) === null || _a === void 0 ? void 0 : _a.workarounds) === null || _b === void 0 ? void 0 : _b.length)) {
26
+ table.push([prettify(e), "", ""]);
27
+ }
28
+ else {
29
+ const workarounds = e.extensions.workarounds;
30
+ for (let i = 0; i < workarounds.length; i++) {
31
+ if (i === 0) {
32
+ table.push([prettify(e), workarounds[i].Description, workarounds[i].Reason]);
33
+ }
34
+ else {
35
+ table.push(["", workarounds[i].Description, workarounds[i].Reason]);
36
+ }
37
+ }
38
+ }
39
+ }
40
+ return table.toString();
41
+ }
42
+ exports.prettifyWithWorkaround = prettifyWithWorkaround;
@@ -33,11 +33,7 @@ async function provisionCloudSql(args) {
33
33
  if (err.status !== 404) {
34
34
  throw err;
35
35
  }
36
- const freeTrialInstanceId = await (0, freeTrial_1.getFreeTrialInstanceId)(projectId);
37
- if (await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId)) {
38
- (0, freeTrial_1.printFreeTrialUnavailable)(projectId, configYamlPath, freeTrialInstanceId);
39
- throw new error_1.FirebaseError("No-cost Cloud SQL trial has already been used on this project.");
40
- }
36
+ cmekWarning();
41
37
  const cta = dryRun ? "It will be created on your next deploy" : "Creating it now.";
42
38
  silent ||
43
39
  utils.logLabeledBullet("dataconnect", `CloudSQL instance '${instanceId}' not found.` +
@@ -45,15 +41,25 @@ async function provisionCloudSql(args) {
45
41
  `\nThis instance is provided under the terms of the Data Connect no-cost trial ${(0, freeTrial_1.freeTrialTermsLink)()}` +
46
42
  `\nMonitor the progress at ${cloudSqlAdminClient.instanceConsoleLink(projectId, instanceId)}`);
47
43
  if (!dryRun) {
48
- const newInstance = await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.createInstance(projectId, locationId, instanceId, enableGoogleMlIntegration, waitForCreation), "Creating your instance...");
49
- if (newInstance) {
50
- silent || utils.logLabeledBullet("dataconnect", "Instance created");
51
- connectionName = (newInstance === null || newInstance === void 0 ? void 0 : newInstance.connectionName) || "";
44
+ try {
45
+ const newInstance = await (0, utils_1.promiseWithSpinner)(() => cloudSqlAdminClient.createInstance(projectId, locationId, instanceId, enableGoogleMlIntegration, waitForCreation), "Creating your instance...");
46
+ if (newInstance) {
47
+ silent || utils.logLabeledBullet("dataconnect", "Instance created");
48
+ connectionName = (newInstance === null || newInstance === void 0 ? void 0 : newInstance.connectionName) || "";
49
+ }
50
+ else {
51
+ silent ||
52
+ utils.logLabeledBullet("dataconnect", "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.");
53
+ return connectionName;
54
+ }
52
55
  }
53
- else {
54
- silent ||
55
- utils.logLabeledBullet("dataconnect", "Cloud SQL instance creation started - it should be ready shortly. Database and users will be created on your next deploy.");
56
- return connectionName;
56
+ catch (err) {
57
+ if (await (0, freeTrial_1.isFreeTrialError)(err, projectId)) {
58
+ const freeTrialInstanceId = await (0, freeTrial_1.getFreeTrialInstanceId)(projectId);
59
+ (0, freeTrial_1.printFreeTrialUnavailable)(projectId, configYamlPath, freeTrialInstanceId);
60
+ throw new error_1.FirebaseError("No-cost Cloud SQL trial has already been used on this project.");
61
+ }
62
+ throw err;
57
63
  }
58
64
  }
59
65
  }
@@ -107,3 +113,9 @@ function getUpdateReason(instance, requireGoogleMlIntegration) {
107
113
  return reason;
108
114
  }
109
115
  exports.getUpdateReason = getUpdateReason;
116
+ function cmekWarning() {
117
+ const message = "The no-cost Cloud SQL trial instance does not support customer managed encryption keys.\n" +
118
+ "If you'd like to use a CMEK to encrypt your data, first create a CMEK encrypted instance (https://cloud.google.com/sql/docs/postgres/configure-cmek#createcmekinstance).\n" +
119
+ "Then, edit your `dataconnect.yaml` file to use the encrypted instance and redeploy.";
120
+ utils.logLabeledWarning("dataconnect", message);
121
+ }
@@ -32,7 +32,7 @@ async function default_1(context, options) {
32
32
  const filters = (0, filters_1.getResourceFilters)(options);
33
33
  const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, options.config, c.source)));
34
34
  for (const si of serviceInfos) {
35
- si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory);
35
+ si.deploymentMetadata = await (0, build_1.build)(options, si.sourceDirectory, options.dryRun);
36
36
  }
37
37
  const unmatchedFilters = filters === null || filters === void 0 ? void 0 : filters.filter((f) => {
38
38
  const serviceMatched = serviceInfos.some((s) => s.dataConnectYaml.serviceId === f.serviceId);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
3
+ exports.warnIfNewGenkitFunctionIsMissingSecrets = exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  const clc = require("colorette");
5
5
  const backend = require("./backend");
6
6
  const build = require("./build");
@@ -28,6 +28,7 @@ const applyHash_1 = require("./cache/applyHash");
28
28
  const backend_1 = require("./backend");
29
29
  const functional_1 = require("../../functional");
30
30
  const prepare_1 = require("../extensions/prepare");
31
+ const prompt = require("../../prompt");
31
32
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
32
33
  async function prepare(context, options, payload) {
33
34
  var _a, _b;
@@ -170,6 +171,7 @@ async function prepare(context, options, payload) {
170
171
  }
171
172
  const wantBackend = backend.merge(...Object.values(wantBackends));
172
173
  const haveBackend = backend.merge(...Object.values(haveBackends));
174
+ await warnIfNewGenkitFunctionIsMissingSecrets(wantBackend, haveBackend, options);
173
175
  await Promise.all(Object.values(wantBackend.requiredAPIs).map(({ api }) => {
174
176
  return ensureApiEnabled.ensure(projectId, api, "functions", false);
175
177
  }));
@@ -324,3 +326,27 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
324
326
  return wantBuilds;
325
327
  }
326
328
  exports.loadCodebases = loadCodebases;
329
+ async function warnIfNewGenkitFunctionIsMissingSecrets(have, want, options) {
330
+ if (options.force) {
331
+ return;
332
+ }
333
+ const newAndMissingSecrets = backend.allEndpoints(backend.matchingBackend(want, (e) => {
334
+ var _a;
335
+ if (!backend.isCallableTriggered(e) || !e.callableTrigger.genkitAction) {
336
+ return false;
337
+ }
338
+ if ((_a = e.secretEnvironmentVariables) === null || _a === void 0 ? void 0 : _a.length) {
339
+ return false;
340
+ }
341
+ return !backend.hasEndpoint(have)(e);
342
+ }));
343
+ if (newAndMissingSecrets.length) {
344
+ const message = `The function(s) ${newAndMissingSecrets.map((e) => e.id).join(", ")} use Genkit but do not have access to a secret. ` +
345
+ "This may cause the function to fail if it depends on an API key. To learn more about granting a function access to " +
346
+ "secrets, see https://firebase.google.com/docs/functions/config-env?gen=2nd#secret_parameters. Continue?";
347
+ if (!(await prompt.confirm({ message, nonInteractive: options.nonInteractive }))) {
348
+ throw new error_1.FirebaseError("Aborted");
349
+ }
350
+ }
351
+ }
352
+ exports.warnIfNewGenkitFunctionIsMissingSecrets = warnIfNewGenkitFunctionIsMissingSecrets;
@@ -10,6 +10,8 @@ const developmentServer_2 = require("./developmentServer");
10
10
  const types_1 = require("../types");
11
11
  const config_1 = require("./config");
12
12
  const projectPath_1 = require("../../projectPath");
13
+ const registry_1 = require("../registry");
14
+ const env_1 = require("../env");
13
15
  async function start(options) {
14
16
  var _a;
15
17
  const hostname = constants_1.DEFAULT_HOST;
@@ -29,7 +31,7 @@ async function serve(port, startCommand, backendRelativeDir) {
29
31
  for (const env of apphostingLocalConfig.environmentVariables) {
30
32
  environmentVariablesAsRecord[env.variable] = env.value;
31
33
  }
32
- const environmentVariablesToInject = Object.assign(Object.assign({}, environmentVariablesAsRecord), { PORT: port.toString() });
34
+ const environmentVariablesToInject = Object.assign(Object.assign(Object.assign({}, getEmulatorEnvs()), environmentVariablesAsRecord), { PORT: port.toString() });
33
35
  if (startCommand) {
34
36
  developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `running custom start command: '${startCommand}'`);
35
37
  await (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject);
@@ -46,3 +48,9 @@ function availablePort(host, port) {
46
48
  family: (0, net_1.isIPv4)(host) ? "IPv4" : "IPv6",
47
49
  });
48
50
  }
51
+ function getEmulatorEnvs() {
52
+ const envs = {};
53
+ const emulatorInfos = registry_1.EmulatorRegistry.listRunningWithInfo();
54
+ (0, env_1.setEnvVarsForEmulators)(envs, emulatorInfos);
55
+ return envs;
56
+ }
@@ -589,7 +589,7 @@ async function startAll(options, showUI = true, runningTestScript = false) {
589
589
  default: false,
590
590
  }))) {
591
591
  await cleanShutdown();
592
- return { deprecationNotices: [] };
592
+ throw new error_1.FirebaseError("Command aborted");
593
593
  }
594
594
  }
595
595
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("BULLET", "dataconnect", `Importing data from ${exportMetadataFilePath}`);
@@ -28,6 +28,7 @@ const index_1 = require("./pg-gateway/index");
28
28
  const node_1 = require("./pg-gateway/platforms/node");
29
29
  const logger_1 = require("../../logger");
30
30
  const error_1 = require("../../error");
31
+ const node_string_decoder_1 = require("node:string_decoder");
31
32
  exports.TRUNCATE_TABLES_SQL = `
32
33
  DO $do$
33
34
  DECLARE _clear text;
@@ -52,6 +53,9 @@ class PostgresServer {
52
53
  return;
53
54
  }
54
55
  const db = await getDb();
56
+ if (data[0] === index_1.FrontendMessageCode.Terminate) {
57
+ await db.query("DEALLOCATE ALL");
58
+ }
55
59
  const result = await db.execProtocolRaw(data);
56
60
  return extendedQueryPatch.filterResponse(data, result);
57
61
  },
@@ -151,6 +155,9 @@ class PGliteExtendedQueryPatch {
151
155
  index_1.FrontendMessageCode.Bind,
152
156
  index_1.FrontendMessageCode.Close,
153
157
  ];
158
+ const decoder = new node_string_decoder_1.StringDecoder();
159
+ const decoded = decoder.write(message);
160
+ logger_1.logger.debug(decoded);
154
161
  if (pipelineStartMessages.includes(message[0])) {
155
162
  this.isExtendedQuery = true;
156
163
  }
@@ -65,9 +65,12 @@ class DataConnectEmulator {
65
65
  this.logger.logLabeled("INFO", "dataconnect", `FIREBASE_DATACONNECT_POSTGRESQL_STRING is set to ${clc.bold(connStr)} - using that instead of starting a new database`);
66
66
  }
67
67
  else if (pgHost && pgPort) {
68
- const dataDirectory = this.args.config.get("emulators.dataconnect.dataDir");
68
+ let dataDirectory = this.args.config.get("emulators.dataconnect.dataDir");
69
+ if (dataDirectory) {
70
+ dataDirectory = this.args.config.path(dataDirectory);
71
+ }
69
72
  const postgresDumpPath = this.args.importPath
70
- ? path.join(this.args.importPath, "postgres.tar.gz")
73
+ ? path.join(this.args.config.path(this.args.importPath), "postgres.tar.gz")
71
74
  : undefined;
72
75
  this.postgresServer = new pgliteServer_1.PostgresServer({
73
76
  dataDirectory,
@@ -135,7 +138,7 @@ class DataConnectEmulator {
135
138
  }
136
139
  async exportData(exportPath) {
137
140
  if (this.postgresServer) {
138
- await this.postgresServer.exportData(path.join(exportPath, "postgres.tar.gz"));
141
+ await this.postgresServer.exportData(path.join(this.args.config.path(exportPath), "postgres.tar.gz"));
139
142
  }
140
143
  else {
141
144
  throw new error_1.FirebaseError("The Data Connect emulator is currently connected to a separate Postgres instance. Export is not supported.");
@@ -174,6 +177,9 @@ class DataConnectEmulator {
174
177
  var _a;
175
178
  const commandInfo = await (0, downloadableEmulators_1.downloadIfNecessary)(types_1.Emulators.DATACONNECT);
176
179
  const cmd = ["--logtostderr", "-v=2", "build", `--config_dir=${args.configDir}`];
180
+ if (args.projectId) {
181
+ cmd.push(`--project_id=${args.projectId}`);
182
+ }
177
183
  const res = childProcess.spawnSync(commandInfo.binary, cmd, { encoding: "utf-8" });
178
184
  if ((0, downloadableEmulators_1.isIncomaptibleArchError)(res.error)) {
179
185
  throw new error_1.FirebaseError(`Unkown system error when running the Data Connect toolkit. ` +
@@ -48,20 +48,20 @@ const EMULATOR_UPDATE_DETAILS = {
48
48
  },
49
49
  dataconnect: process.platform === "darwin"
50
50
  ? {
51
- version: "1.7.5",
52
- expectedSize: 25281280,
53
- expectedChecksum: "85d0de96b5c08b553fd8506a2bc381bb",
51
+ version: "1.7.7",
52
+ expectedSize: 25359104,
53
+ expectedChecksum: "c5481addc04e14d10538add7aabda183",
54
54
  }
55
55
  : process.platform === "win32"
56
56
  ? {
57
- version: "1.7.5",
58
- expectedSize: 25711616,
59
- expectedChecksum: "c99d67fa8e74d41760b96122b055b8e2",
57
+ version: "1.7.7",
58
+ expectedSize: 25788416,
59
+ expectedChecksum: "9f7e5b9bcbca47de509fbc26cc1e0fa8",
60
60
  }
61
61
  : {
62
- version: "1.7.5",
63
- expectedSize: 25190552,
64
- expectedChecksum: "61d966b781e6f2887f8b38ec271b54e2",
62
+ version: "1.7.7",
63
+ expectedSize: 25268376,
64
+ expectedChecksum: "fb239ecf5dcbf87b762d12a3e9dee012",
65
65
  },
66
66
  };
67
67
  exports.DownloadDetails = {
@@ -118,7 +118,7 @@ class EmulatorHub extends ExpressBasedEmulator_1.ExpressBasedEmulator {
118
118
  return;
119
119
  }
120
120
  await instance.clearData();
121
- res.status(200).send("Data cleared");
121
+ res.status(200).json({ success: true });
122
122
  });
123
123
  return app;
124
124
  }
@@ -38,6 +38,11 @@ class EmulatorHubClient {
38
38
  const res = await this.tryOrigins((client) => client.get(hub_1.EmulatorHub.PATH_EMULATORS));
39
39
  return res.body;
40
40
  }
41
+ async clearDataConnectData() {
42
+ const origin = await this.getStatus();
43
+ const apiClient = new apiv2_1.Client({ urlPrefix: origin, auth: false });
44
+ await apiClient.post(hub_1.EmulatorHub.PATH_CLEAR_DATA_CONNECT);
45
+ }
41
46
  async postExport(options) {
42
47
  const origin = await this.getStatus();
43
48
  const apiClient = new apiv2_1.Client({ urlPrefix: origin, auth: false });
@@ -106,6 +106,12 @@ exports.ALL_EXPERIMENTS = experiments({
106
106
  default: true,
107
107
  public: false,
108
108
  },
109
+ fdcconnectorevolution: {
110
+ shortDescription: "Enable Data Connect connector evolution warnings.",
111
+ fullDescription: "Enable Data Connect connector evolution warnings.",
112
+ default: false,
113
+ public: false,
114
+ },
109
115
  });
110
116
  function isValidExperiment(name) {
111
117
  return Object.keys(exports.ALL_EXPERIMENTS).includes(name);
@@ -3,8 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getErrorMessage = exports.getValidator = void 0;
4
4
  const fs = require("fs");
5
5
  const path = require("path");
6
- const Ajv = require("ajv");
7
- const ajv = new Ajv();
6
+ const ajv_1 = require("ajv");
7
+ const ajv_formats_1 = require("ajv-formats");
8
+ const ajv = new ajv_1.Ajv({ allowUnionTypes: true });
9
+ (0, ajv_formats_1.default)(ajv);
8
10
  let _VALIDATOR = undefined;
9
11
  function getValidator() {
10
12
  if (!_VALIDATOR) {
@@ -17,13 +19,13 @@ function getValidator() {
17
19
  exports.getValidator = getValidator;
18
20
  function getErrorMessage(e) {
19
21
  if (e.keyword === "additionalProperties") {
20
- return `Object "${e.dataPath}" in "firebase.json" has unknown property: ${JSON.stringify(e.params)}`;
22
+ return `Object "${e.instancePath}" in "firebase.json" has unknown property: ${JSON.stringify(e.params)}`;
21
23
  }
22
24
  else if (e.keyword === "required") {
23
- return `Object "${e.dataPath}" in "firebase.json" is missing required property: ${JSON.stringify(e.params)}`;
25
+ return `Object "${e.instancePath}" in "firebase.json" is missing required property: ${JSON.stringify(e.params)}`;
24
26
  }
25
27
  else {
26
- return `Field "${e.dataPath}" in "firebase.json" is possibly invalid: ${e.message}`;
28
+ return `Field "${e.instancePath}" in "firebase.json" is possibly invalid: ${e.message}`;
27
29
  }
28
30
  }
29
31
  exports.getErrorMessage = getErrorMessage;
@@ -366,7 +366,7 @@ async function prepareFrameworks(purpose, targetNames, context, options, emulato
366
366
  await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, ".env"), `${dotEnvContents}
367
367
  __FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry}
368
368
  ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}`.trimStart());
369
- const envs = await (0, glob_1.glob)(getProjectPath(".env.*"));
369
+ const envs = await (0, glob_1.glob)(getProjectPath(".env.*"), { windowsPathsNoEscape: utils_2.IS_WINDOWS });
370
370
  await Promise.all(envs.map((path) => (0, promises_1.copyFile)(path, (0, path_1.join)(functionsDist, (0, path_1.basename)(path)))));
371
371
  (0, child_process_1.execSync)(`npm i --omit dev --no-audit`, {
372
372
  cwd: functionsDist,
@@ -259,9 +259,6 @@ function functionFromEndpoint(endpoint) {
259
259
  }
260
260
  else if (backend.isCallableTriggered(endpoint)) {
261
261
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { "deployment-callable": "true" });
262
- if (endpoint.callableTrigger.genkitAction) {
263
- gcfFunction.labels["genkit-action"] = endpoint.callableTrigger.genkitAction;
264
- }
265
262
  }
266
263
  else if (backend.isBlockingTriggered(endpoint)) {
267
264
  gcfFunction.labels = Object.assign(Object.assign({}, gcfFunction.labels), { [constants_1.BLOCKING_LABEL]: constants_1.BLOCKING_EVENT_TO_LABEL_KEY[endpoint.blockingTrigger.eventType] });
@@ -297,9 +294,6 @@ function endpointFromFunction(gcfFunction) {
297
294
  trigger = {
298
295
  callableTrigger: {},
299
296
  };
300
- if (gcfFunction.labels["genkit-action"]) {
301
- trigger.callableTrigger.genkitAction = gcfFunction.labels["genkit-action"];
302
- }
303
297
  }
304
298
  else if ((_d = gcfFunction.labels) === null || _d === void 0 ? void 0 : _d[constants_1.BLOCKING_LABEL]) {
305
299
  trigger = {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.actuate = exports.doSetup = void 0;
3
+ exports.toDNSCompatibleId = exports.actuate = exports.doSetup = void 0;
4
4
  const path_1 = require("path");
5
5
  const clc = require("colorette");
6
6
  const fs = require("fs-extra");
@@ -100,8 +100,10 @@ async function askQuestions(setup, isBillingEnabled) {
100
100
  })));
101
101
  }
102
102
  else {
103
- info.serviceId = info.serviceId || (0, path_1.basename)(process.cwd());
104
- info.cloudSqlInstanceId = info.cloudSqlInstanceId || `${info.serviceId || "app"}-fdc`;
103
+ const defaultServiceId = toDNSCompatibleId((0, path_1.basename)(process.cwd()));
104
+ info.serviceId = info.serviceId || defaultServiceId;
105
+ info.cloudSqlInstanceId =
106
+ info.cloudSqlInstanceId || `${info.serviceId.toLowerCase() || "app"}-fdc`;
105
107
  info.locationId = info.locationId || `us-central1`;
106
108
  info.cloudSqlDatabase = info.cloudSqlDatabase || `fdcdb`;
107
109
  }
@@ -269,7 +271,7 @@ async function promptForCloudSQL(setup, info) {
269
271
  info.cloudSqlInstanceId = await (0, prompt_1.promptOnce)({
270
272
  message: `What ID would you like to use for your new CloudSQL instance?`,
271
273
  type: "input",
272
- default: `${info.serviceId || "app"}-fdc`,
274
+ default: `${info.serviceId.toLowerCase() || "app"}-fdc`,
273
275
  });
274
276
  }
275
277
  if (info.locationId === "") {
@@ -339,3 +341,17 @@ async function locationChoices(setup) {
339
341
  ];
340
342
  }
341
343
  }
344
+ function toDNSCompatibleId(id) {
345
+ let defaultServiceId = (0, path_1.basename)(id)
346
+ .toLowerCase()
347
+ .replaceAll(/[^a-z0-9-]/g, "")
348
+ .slice(0, 63);
349
+ while (defaultServiceId.endsWith("-") && defaultServiceId.length) {
350
+ defaultServiceId = defaultServiceId.slice(0, defaultServiceId.length - 1);
351
+ }
352
+ while (defaultServiceId.startsWith("-") && defaultServiceId.length) {
353
+ defaultServiceId = defaultServiceId.slice(1, defaultServiceId.length);
354
+ }
355
+ return defaultServiceId || "app";
356
+ }
357
+ exports.toDNSCompatibleId = toDNSCompatibleId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "13.29.1",
3
+ "version": "13.29.3",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -60,11 +60,12 @@
60
60
  ]
61
61
  },
62
62
  "dependencies": {
63
- "@electric-sql/pglite": "^0.2.0",
63
+ "@electric-sql/pglite": "^0.2.16",
64
64
  "@google-cloud/cloud-sql-connector": "^1.3.3",
65
65
  "@google-cloud/pubsub": "^4.5.0",
66
66
  "abort-controller": "^3.0.0",
67
- "ajv": "^6.12.6",
67
+ "ajv": "^8.17.1",
68
+ "ajv-formats": "3.0.1",
68
69
  "archiver": "^7.0.0",
69
70
  "async-lock": "1.4.1",
70
71
  "body-parser": "^1.19.0",
@@ -75,8 +76,8 @@
75
76
  "commander": "^5.1.0",
76
77
  "configstore": "^5.0.1",
77
78
  "cors": "^2.8.5",
78
- "cross-env": "^5.1.3",
79
- "cross-spawn": "^7.0.3",
79
+ "cross-env": "^7.0.3",
80
+ "cross-spawn": "^7.0.5",
80
81
  "csv-parse": "^5.0.4",
81
82
  "deep-equal-in-any-order": "^2.0.6",
82
83
  "exegesis": "^4.2.0",
@@ -17,6 +17,11 @@
17
17
  "instanceId": {
18
18
  "type": "string",
19
19
  "description": "The ID of the CloudSQL instance for this database"
20
+ },
21
+ "schemaValidation": {
22
+ "type": "string",
23
+ "enum": ["COMPATIBLE", "STRICT"],
24
+ "description": "Schema validation mode for schema migrations"
20
25
  }
21
26
  }
22
27
  }