firebase-tools 13.27.0 → 13.29.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 (47) hide show
  1. package/lib/appdistribution/client.js +2 -1
  2. package/lib/appdistribution/options-parser-util.js +5 -5
  3. package/lib/auth.js +13 -3
  4. package/lib/bin/firebase.js +4 -3
  5. package/lib/commands/appdistribution-distribute.js +55 -31
  6. package/lib/commands/firestore-indexes-list.js +4 -2
  7. package/lib/commands/index.js +2 -2
  8. package/lib/dataconnect/fileUtils.js +3 -0
  9. package/lib/dataconnect/schemaMigration.js +8 -15
  10. package/lib/dataconnect/types.js +1 -1
  11. package/lib/deploy/extensions/tasks.js +2 -2
  12. package/lib/deploy/functions/backend.js +1 -3
  13. package/lib/deploy/functions/build.js +3 -1
  14. package/lib/deploy/functions/release/planner.js +4 -4
  15. package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +4 -0
  16. package/lib/deploy/functions/runtimes/supported/types.js +1 -1
  17. package/lib/emulator/apphosting/developmentServer.js +1 -1
  18. package/lib/emulator/apphosting/index.js +1 -1
  19. package/lib/emulator/auth/operations.js +8 -5
  20. package/lib/emulator/controller.js +19 -1
  21. package/lib/emulator/dataconnect/pgliteServer.js +34 -7
  22. package/lib/emulator/dataconnectEmulator.js +20 -1
  23. package/lib/emulator/downloadableEmulators.js +22 -10
  24. package/lib/emulator/eventarcEmulator.js +1 -0
  25. package/lib/emulator/initEmulators.js +17 -2
  26. package/lib/error.js +11 -1
  27. package/lib/experiments.js +0 -6
  28. package/lib/extensions/localHelper.js +2 -2
  29. package/lib/extensions/runtimes/node.js +16 -15
  30. package/lib/extensions/types.js +6 -9
  31. package/lib/functions/events/v2.js +7 -3
  32. package/lib/gcp/cloudfunctionsv2.js +6 -0
  33. package/lib/init/features/dataconnect/index.js +8 -5
  34. package/lib/init/features/emulators.js +1 -1
  35. package/lib/init/features/genkit/index.js +13 -5
  36. package/lib/init/spawn.js +38 -7
  37. package/lib/management/projects.js +23 -1
  38. package/lib/requireAuth.js +2 -1
  39. package/package.json +3 -2
  40. package/schema/firebase-config.json +3 -0
  41. package/templates/init/dataconnect/dataconnect.yaml +1 -0
  42. package/templates/init/functions/javascript/package.lint.json +1 -1
  43. package/templates/init/functions/javascript/package.nolint.json +1 -1
  44. package/templates/init/functions/typescript/package.lint.json +1 -1
  45. package/templates/init/functions/typescript/package.nolint.json +1 -1
  46. package/templates/init/functions/typescript/tsconfig.json +3 -1
  47. package/templates/init/dataconnect/dataconnect-fdccompatiblemode.yaml +0 -12
@@ -231,7 +231,7 @@ class AppDistributionClient {
231
231
  }
232
232
  utils.logSuccess(`Testers removed from group successfully`);
233
233
  }
234
- async createReleaseTest(releaseName, devices, loginCredential) {
234
+ async createReleaseTest(releaseName, devices, loginCredential, testCaseName) {
235
235
  try {
236
236
  const response = await this.appDistroV1AlphaClient.request({
237
237
  method: "POST",
@@ -239,6 +239,7 @@ class AppDistributionClient {
239
239
  body: {
240
240
  deviceExecutions: devices.map(types_1.mapDeviceToExecution),
241
241
  loginCredential,
242
+ testCase: testCaseName,
242
243
  },
243
244
  });
244
245
  return response.body;
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getLoginCredential = exports.getTestDevices = exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.getTestersOrGroups = void 0;
3
+ exports.getLoginCredential = exports.parseTestDevices = exports.getAppName = exports.getProjectName = exports.ensureFileExists = exports.getEmails = exports.parseIntoStringArray = void 0;
4
4
  const fs = require("fs-extra");
5
5
  const error_1 = require("../error");
6
6
  const projectUtils_1 = require("../projectUtils");
7
- function getTestersOrGroups(value, file) {
7
+ function parseIntoStringArray(value, file) {
8
8
  if (!value && file) {
9
9
  ensureFileExists(file);
10
10
  value = fs.readFileSync(file, "utf8");
@@ -14,7 +14,7 @@ function getTestersOrGroups(value, file) {
14
14
  }
15
15
  return [];
16
16
  }
17
- exports.getTestersOrGroups = getTestersOrGroups;
17
+ exports.parseIntoStringArray = parseIntoStringArray;
18
18
  function getEmails(emails, file) {
19
19
  if (emails.length === 0) {
20
20
  ensureFileExists(file);
@@ -49,7 +49,7 @@ function getAppName(options) {
49
49
  return `projects/${appId.split(":")[1]}/apps/${appId}`;
50
50
  }
51
51
  exports.getAppName = getAppName;
52
- function getTestDevices(value, file) {
52
+ function parseTestDevices(value, file) {
53
53
  if (!value && file) {
54
54
  ensureFileExists(file);
55
55
  value = fs.readFileSync(file, "utf8");
@@ -63,7 +63,7 @@ function getTestDevices(value, file) {
63
63
  .filter((entry) => !!entry)
64
64
  .map((str) => parseTestDevice(str));
65
65
  }
66
- exports.getTestDevices = getTestDevices;
66
+ exports.parseTestDevices = parseTestDevices;
67
67
  function parseTestDevice(testDeviceString) {
68
68
  const entries = testDeviceString.split(",");
69
69
  const allowedKeys = new Set(["model", "version", "orientation", "locale"]);
package/lib/auth.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
3
+ exports.addAdditionalAccount = exports.logout = exports.getAccessToken = exports.haveValidTokens = exports.isExpired = exports.loggedIn = exports.findAccountByEmail = exports.loginGithub = exports.loginGoogle = exports.setGlobalDefaultAccount = exports.setProjectAccount = exports.loginAdditionalAccount = exports.selectAccount = exports.setRefreshToken = exports.setActiveAccount = exports.getAllAccounts = exports.getAdditionalAccounts = exports.getProjectDefaultAccount = exports.getGlobalDefaultAccount = void 0;
4
4
  const clc = require("colorette");
5
5
  const FormData = require("form-data");
6
6
  const http = require("http");
@@ -401,6 +401,16 @@ function loggedIn() {
401
401
  return !!lastAccessToken;
402
402
  }
403
403
  exports.loggedIn = loggedIn;
404
+ function isExpired(tokens) {
405
+ const hasExpiration = (p) => !!p.expires_at;
406
+ if (hasExpiration(tokens)) {
407
+ return !(tokens && tokens.expires_at && tokens.expires_at > Date.now());
408
+ }
409
+ else {
410
+ return !tokens;
411
+ }
412
+ }
413
+ exports.isExpired = isExpired;
404
414
  function haveValidTokens(refreshToken, authScopes) {
405
415
  var _a;
406
416
  if (!(lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.access_token)) {
@@ -413,8 +423,8 @@ function haveValidTokens(refreshToken, authScopes) {
413
423
  const oldScopesJSON = JSON.stringify(((_a = lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.scopes) === null || _a === void 0 ? void 0 : _a.sort()) || []);
414
424
  const newScopesJSON = JSON.stringify(authScopes.sort());
415
425
  const hasSameScopes = oldScopesJSON === newScopesJSON;
416
- const isExpired = ((lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at) || 0) < Date.now() + FIFTEEN_MINUTES_IN_MS;
417
- const valid = hasTokens && hasSameScopes && !isExpired;
426
+ const expired = ((lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at) || 0) < Date.now() + FIFTEEN_MINUTES_IN_MS;
427
+ const valid = hasTokens && hasSameScopes && !expired;
418
428
  if (hasTokens) {
419
429
  logger_1.logger.debug(`Checked if tokens are valid: ${valid}, expires at: ${lastAccessToken === null || lastAccessToken === void 0 ? void 0 : lastAccessToken.expires_at}`);
420
430
  }
@@ -26,7 +26,7 @@ const client = require("..");
26
26
  const fsutils = require("../fsutils");
27
27
  const utils = require("../utils");
28
28
  const winston = require("winston");
29
- let args = process.argv.slice(2);
29
+ const args = process.argv.slice(2);
30
30
  let cmd;
31
31
  function findAvailableLogFile() {
32
32
  const candidates = ["firebase-debug.log"];
@@ -126,9 +126,10 @@ process.on("uncaughtException", (err) => {
126
126
  (0, errorOut_1.errorOut)(err);
127
127
  });
128
128
  if (!(0, handlePreviewToggles_1.handlePreviewToggles)(args)) {
129
- cmd = client.cli.parse(process.argv);
130
- args = args.filter((arg) => !arg.includes("-"));
131
129
  if (!args.length) {
132
130
  client.cli.help();
133
131
  }
132
+ else {
133
+ cmd = client.cli.parse(process.argv);
134
+ }
134
135
  }
@@ -23,14 +23,14 @@ function getReleaseNotes(releaseNotes, releaseNotesFile) {
23
23
  return "";
24
24
  }
25
25
  exports.command = new command_1.Command("appdistribution:distribute <release-binary-file>")
26
- .description("upload a release binary")
26
+ .description("upload a release binary and optionally distribute it to testers and run automated tests")
27
27
  .option("--app <app_id>", "the app id of your Firebase app")
28
28
  .option("--release-notes <string>", "release notes to include")
29
29
  .option("--release-notes-file <file>", "path to file with release notes")
30
- .option("--testers <string>", "a comma separated list of tester emails to distribute to")
31
- .option("--testers-file <file>", "path to file with a comma separated list of tester emails to distribute to")
32
- .option("--groups <string>", "a comma separated list of group aliases to distribute to")
33
- .option("--groups-file <file>", "path to file with a comma separated list of group aliases to distribute to")
30
+ .option("--testers <string>", "a comma-separated list of tester emails to distribute to")
31
+ .option("--testers-file <file>", "path to file with a comma- or newline-separated list of tester emails to distribute to")
32
+ .option("--groups <string>", "a comma-separated list of group aliases to distribute to")
33
+ .option("--groups-file <file>", "path to file with a comma- or newline-separated list of group aliases to distribute to")
34
34
  .option("--test-devices <string>", "semicolon-separated list of devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
35
35
  .option("--test-devices-file <string>", "path to file containing a list of semicolon- or newline-separated devices to run automated tests on, in the format 'model=<model-id>,version=<os-version-id>,locale=<locale>,orientation=<orientation>'. Run 'gcloud firebase test android|ios models list' to see available devices. Note: This feature is in beta.")
36
36
  .option("--test-username <string>", "username for automatic login")
@@ -39,14 +39,20 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
39
39
  .option("--test-username-resource <string>", "resource name for the username field for automatic login")
40
40
  .option("--test-password-resource <string>", "resource name for the password field for automatic login")
41
41
  .option("--test-non-blocking", "run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
42
+ .option("--test-case-ids <string>", "a comma-separated list of test case IDs.")
43
+ .option("--test-case-ids-file <file>", "path to file with a comma- or newline-separated list of test case IDs.")
42
44
  .before(requireAuth_1.requireAuth)
43
45
  .action(async (file, options) => {
44
46
  const appName = (0, options_parser_util_1.getAppName)(options);
45
47
  const distribution = new distribution_1.Distribution(file);
46
48
  const releaseNotes = getReleaseNotes(options.releaseNotes, options.releaseNotesFile);
47
- const testers = (0, options_parser_util_1.getTestersOrGroups)(options.testers, options.testersFile);
48
- const groups = (0, options_parser_util_1.getTestersOrGroups)(options.groups, options.groupsFile);
49
- const testDevices = (0, options_parser_util_1.getTestDevices)(options.testDevices, options.testDevicesFile);
49
+ const testers = (0, options_parser_util_1.parseIntoStringArray)(options.testers, options.testersFile);
50
+ const groups = (0, options_parser_util_1.parseIntoStringArray)(options.groups, options.groupsFile);
51
+ const testCases = (0, options_parser_util_1.parseIntoStringArray)(options.testCaseIds, options.testCaseIdsFile);
52
+ const testDevices = (0, options_parser_util_1.parseTestDevices)(options.testDevices, options.testDevicesFile);
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.");
55
+ }
50
56
  const loginCredential = (0, options_parser_util_1.getLoginCredential)({
51
57
  username: options.testUsername,
52
58
  password: options.testPassword,
@@ -137,39 +143,57 @@ exports.command = new command_1.Command("appdistribution:distribute <release-bin
137
143
  }
138
144
  await requests.updateReleaseNotes(releaseName, releaseNotes);
139
145
  await requests.distribute(releaseName, testers, groups);
140
- if (testDevices === null || testDevices === void 0 ? void 0 : testDevices.length) {
141
- utils.logBullet("starting automated tests (note: this feature is in beta)");
142
- const releaseTest = await requests.createReleaseTest(releaseName, testDevices, loginCredential);
143
- utils.logSuccess(`Release test created successfully`);
146
+ if (testDevices.length) {
147
+ utils.logBullet("starting automated test (note: this feature is in beta)");
148
+ const releaseTestPromises = [];
149
+ if (!testCases.length) {
150
+ releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential));
151
+ }
152
+ else {
153
+ for (const testCaseId of testCases) {
154
+ releaseTestPromises.push(requests.createReleaseTest(releaseName, testDevices, loginCredential, `${appName}/testCases/${testCaseId}`));
155
+ }
156
+ }
157
+ const releaseTests = await Promise.all(releaseTestPromises);
158
+ utils.logSuccess(`${releaseTests.length} release test(s) started successfully`);
144
159
  if (!options.testNonBlocking) {
145
- await awaitTestResults(releaseTest.name, requests);
160
+ await awaitTestResults(releaseTests, requests);
146
161
  }
147
162
  }
148
163
  });
149
- async function awaitTestResults(releaseTestName, requests) {
164
+ async function awaitTestResults(releaseTests, requests) {
165
+ const releaseTestNames = new Set(releaseTests.map((rt) => rt.name));
150
166
  for (let i = 0; i < TEST_MAX_POLLING_RETRIES; i++) {
151
- utils.logBullet("the automated tests results are pending");
167
+ utils.logBullet(`${releaseTestNames.size} automated test results are pending...`);
152
168
  await delay(TEST_POLLING_INTERVAL_MILLIS);
153
- const releaseTest = await requests.getReleaseTest(releaseTestName);
154
- if (releaseTest.deviceExecutions.every((e) => e.state === "PASSED")) {
155
- utils.logSuccess("automated test(s) passed!");
156
- return;
157
- }
158
- for (const execution of releaseTest.deviceExecutions) {
159
- switch (execution.state) {
160
- case "PASSED":
161
- case "IN_PROGRESS":
169
+ for (const releaseTestName of releaseTestNames) {
170
+ const releaseTest = await requests.getReleaseTest(releaseTestName);
171
+ if (releaseTest.deviceExecutions.every((e) => e.state === "PASSED")) {
172
+ releaseTestNames.delete(releaseTestName);
173
+ if (releaseTestNames.size === 0) {
174
+ utils.logSuccess("Automated test(s) passed!");
175
+ return;
176
+ }
177
+ else {
162
178
  continue;
163
- case "FAILED":
164
- throw new error_1.FirebaseError(`Automated test failed for ${deviceToString(execution.device)}: ${execution.failedReason}`, { exit: 1 });
165
- case "INCONCLUSIVE":
166
- throw new error_1.FirebaseError(`Automated test inconclusive for ${deviceToString(execution.device)}: ${execution.inconclusiveReason}`, { exit: 1 });
167
- default:
168
- throw new error_1.FirebaseError(`Unsupported automated test state for ${deviceToString(execution.device)}: ${execution.state}`, { exit: 1 });
179
+ }
180
+ }
181
+ for (const execution of releaseTest.deviceExecutions) {
182
+ switch (execution.state) {
183
+ case "PASSED":
184
+ case "IN_PROGRESS":
185
+ continue;
186
+ case "FAILED":
187
+ throw new error_1.FirebaseError(`Automated test failed for ${deviceToString(execution.device)}: ${execution.failedReason}`, { exit: 1 });
188
+ case "INCONCLUSIVE":
189
+ throw new error_1.FirebaseError(`Automated test inconclusive for ${deviceToString(execution.device)}: ${execution.inconclusiveReason}`, { exit: 1 });
190
+ default:
191
+ throw new error_1.FirebaseError(`Unsupported automated test state for ${deviceToString(execution.device)}: ${execution.state}`, { exit: 1 });
192
+ }
169
193
  }
170
194
  }
171
195
  }
172
- throw new error_1.FirebaseError("It took longer than expected to process your test, please try again.", {
196
+ throw new error_1.FirebaseError("It took longer than expected to run your test(s), please try again.", {
173
197
  exit: 1,
174
198
  });
175
199
  }
@@ -9,6 +9,7 @@ const requirePermissions_1 = require("../requirePermissions");
9
9
  const types_1 = require("../emulator/types");
10
10
  const commandUtils_1 = require("../emulator/commandUtils");
11
11
  const pretty_print_1 = require("../firestore/pretty-print");
12
+ const projectUtils_1 = require("../projectUtils");
12
13
  exports.command = new command_1.Command("firestore:indexes")
13
14
  .description("List indexes in your project's Cloud Firestore database.")
14
15
  .option("--pretty", "Pretty print. When not specified the indexes are printed in the " +
@@ -21,8 +22,9 @@ exports.command = new command_1.Command("firestore:indexes")
21
22
  const indexApi = new fsi.FirestoreApi();
22
23
  const printer = new pretty_print_1.PrettyPrint();
23
24
  const databaseId = (_a = options.database) !== null && _a !== void 0 ? _a : "(default)";
24
- const indexes = await indexApi.listIndexes(options.project, databaseId);
25
- const fieldOverrides = await indexApi.listFieldOverrides(options.project, databaseId);
25
+ const projectId = (0, projectUtils_1.needProjectId)(options);
26
+ const indexes = await indexApi.listIndexes(projectId, databaseId);
27
+ const fieldOverrides = await indexApi.listFieldOverrides(projectId, databaseId);
26
28
  const indexSpec = indexApi.makeIndexSpec(indexes, fieldOverrides);
27
29
  if (options.pretty) {
28
30
  logger_1.logger.info(clc.bold(clc.white("Compound Indexes")));
@@ -172,6 +172,8 @@ function load(client) {
172
172
  client.apphosting.secrets.grantaccess = loadCommand("apphosting-secrets-grantaccess");
173
173
  client.apphosting.secrets.describe = loadCommand("apphosting-secrets-describe");
174
174
  client.apphosting.secrets.access = loadCommand("apphosting-secrets-access");
175
+ client.apphosting.rollouts = {};
176
+ client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
175
177
  client.apphosting.config = {};
176
178
  client.apphosting.config.export = loadCommand("apphosting-config-export");
177
179
  if (experiments.isEnabled("internaltesting")) {
@@ -180,8 +182,6 @@ function load(client) {
180
182
  client.apphosting.builds.create = loadCommand("apphosting-builds-create");
181
183
  client.apphosting.repos = {};
182
184
  client.apphosting.repos.create = loadCommand("apphosting-repos-create");
183
- client.apphosting.rollouts = {};
184
- client.apphosting.rollouts.create = loadCommand("apphosting-rollouts-create");
185
185
  client.apphosting.rollouts.list = loadCommand("apphosting-rollouts-list");
186
186
  }
187
187
  }
@@ -53,6 +53,9 @@ function validateConnectorYaml(unvalidated) {
53
53
  return unvalidated;
54
54
  }
55
55
  async function readGQLFiles(sourceDir) {
56
+ if (!fs.existsSync(sourceDir)) {
57
+ return [];
58
+ }
56
59
  const files = await fs.readdir(sourceDir);
57
60
  return files
58
61
  .filter((f) => f.endsWith(".gql") || f.endsWith(".graphql"))
@@ -13,18 +13,15 @@ const prompt_1 = require("../prompt");
13
13
  const logger_1 = require("../logger");
14
14
  const error_1 = require("../error");
15
15
  const utils_1 = require("../utils");
16
- const experiments = require("../experiments");
17
16
  const errors = require("./errors");
18
17
  async function diffSchema(schema, schemaValidation) {
19
18
  const { serviceName, instanceName, databaseId } = getIdentifiers(schema);
20
19
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, false);
21
20
  let diffs = [];
22
- let validationMode = experiments.isEnabled("fdccompatiblemode")
23
- ? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
24
- : "STRICT";
21
+ let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
25
22
  setSchemaValidationMode(schema, validationMode);
26
23
  try {
27
- if (!schemaValidation && experiments.isEnabled("fdccompatiblemode")) {
24
+ if (!schemaValidation) {
28
25
  (0, utils_1.logLabeledBullet)("dataconnect", `generating required schema changes...`);
29
26
  }
30
27
  await (0, client_1.upsertSchema)(schema, true);
@@ -52,7 +49,7 @@ async function diffSchema(schema, schemaValidation) {
52
49
  diffs = incompatible.diffs;
53
50
  }
54
51
  }
55
- if (experiments.isEnabled("fdccompatiblemode") && !schemaValidation) {
52
+ if (!schemaValidation) {
56
53
  validationMode = "STRICT";
57
54
  setSchemaValidationMode(schema, validationMode);
58
55
  try {
@@ -89,9 +86,7 @@ async function migrateSchema(args) {
89
86
  const { serviceName, instanceId, instanceName, databaseId } = getIdentifiers(schema);
90
87
  await ensureServiceIsConnectedToCloudSql(serviceName, instanceName, databaseId, true);
91
88
  let diffs = [];
92
- let validationMode = experiments.isEnabled("fdccompatiblemode")
93
- ? schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE"
94
- : "STRICT";
89
+ let validationMode = schemaValidation !== null && schemaValidation !== void 0 ? schemaValidation : "COMPATIBLE";
95
90
  setSchemaValidationMode(schema, validationMode);
96
91
  try {
97
92
  await (0, client_1.upsertSchema)(schema, validateOnly);
@@ -124,7 +119,7 @@ async function migrateSchema(args) {
124
119
  await (0, client_1.upsertSchema)(schema, validateOnly);
125
120
  }
126
121
  }
127
- if (experiments.isEnabled("fdccompatiblemode") && !schemaValidation) {
122
+ if (!schemaValidation) {
128
123
  validationMode = "STRICT";
129
124
  setSchemaValidationMode(schema, validationMode);
130
125
  try {
@@ -185,11 +180,9 @@ function diffsEqual(x, y) {
185
180
  return true;
186
181
  }
187
182
  function setSchemaValidationMode(schema, schemaValidation) {
188
- if (experiments.isEnabled("fdccompatiblemode")) {
189
- const postgresDatasource = schema.datasources.find((d) => d.postgresql);
190
- if (postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) {
191
- postgresDatasource.postgresql.schemaValidation = schemaValidation;
192
- }
183
+ const postgresDatasource = schema.datasources.find((d) => d.postgresql);
184
+ if (postgresDatasource === null || postgresDatasource === void 0 ? void 0 : postgresDatasource.postgresql) {
185
+ postgresDatasource.postgresql.schemaValidation = schemaValidation;
193
186
  }
194
187
  }
195
188
  function getIdentifiers(schema) {
@@ -17,7 +17,7 @@ var Platform;
17
17
  Platform["MULTIPLE"] = "MULTIPLE";
18
18
  })(Platform = exports.Platform || (exports.Platform = {}));
19
19
  function toDatasource(projectId, locationId, ds) {
20
- if (ds.postgresql) {
20
+ if (ds === null || ds === void 0 ? void 0 : ds.postgresql) {
21
21
  return {
22
22
  postgresql: {
23
23
  database: ds.postgresql.database,
@@ -7,7 +7,7 @@ const extensionsApi = require("../../extensions/extensionsApi");
7
7
  const extensionsHelper_1 = require("../../extensions/extensionsHelper");
8
8
  const refs = require("../../extensions/refs");
9
9
  const utils = require("../../utils");
10
- const types_1 = require("../../extensions/types");
10
+ const error_2 = require("../../error");
11
11
  const isRetryable = (err) => err.status === 429 || err.status === 409;
12
12
  function extensionsDeploymentHandler(errorHandler) {
13
13
  return async (task) => {
@@ -54,7 +54,7 @@ function createExtensionInstanceTask(projectId, instanceSpec, validateOnly = fal
54
54
  await extensionsApi.createInstance(createArgs);
55
55
  }
56
56
  catch (err) {
57
- if ((0, types_1.isObject)(err) && err.status === 409) {
57
+ if ((0, error_2.isObject)(err) && err.status === 409) {
58
58
  throw new error_1.FirebaseError(`Failed to create extension instance. Extension instance ${clc.bold(instanceSpec.instanceId)} already exists.`);
59
59
  }
60
60
  throw err;
@@ -25,9 +25,7 @@ function endpointTriggerType(endpoint) {
25
25
  else if (isBlockingTriggered(endpoint)) {
26
26
  return endpoint.blockingTrigger.eventType;
27
27
  }
28
- else {
29
- throw new Error("Unexpected trigger type for endpoint " + JSON.stringify(endpoint));
30
- }
28
+ (0, functional_1.assertExhaustive)(endpoint);
31
29
  }
32
30
  exports.endpointTriggerType = endpointTriggerType;
33
31
  exports.AllVpcEgressSettings = ["PRIVATE_RANGES_ONLY", "ALL_TRAFFIC"];
@@ -259,7 +259,9 @@ function discoverTrigger(endpoint, region, r) {
259
259
  return { httpsTrigger };
260
260
  }
261
261
  else if (isCallableTriggered(endpoint)) {
262
- return { callableTrigger: {} };
262
+ const trigger = { callableTrigger: {} };
263
+ proto.copyIfPresent(trigger.callableTrigger, endpoint.callableTrigger, "genkitAction");
264
+ return trigger;
263
265
  }
264
266
  else if (isBlockingTriggered(endpoint)) {
265
267
  return { blockingTrigger: endpoint.blockingTrigger };
@@ -7,7 +7,6 @@ const error_1 = require("../../../error");
7
7
  const utils = require("../../../utils");
8
8
  const backend = require("../backend");
9
9
  const v2events = require("../../../functions/events/v2");
10
- const v2_1 = require("../../../functions/events/v2");
11
10
  function calculateChangesets(want, have, keyFn, deleteAll) {
12
11
  const toCreate = utils.groupBy(Object.keys(want)
13
12
  .filter((id) => !have[id])
@@ -167,9 +166,9 @@ function upgradedScheduleFromV1ToV2(want, have) {
167
166
  exports.upgradedScheduleFromV1ToV2 = upgradedScheduleFromV1ToV2;
168
167
  function checkForUnsafeUpdate(want, have) {
169
168
  return (backend.isEventTriggered(want) &&
170
- v2_1.FIRESTORE_EVENT_WITH_AUTH_CONTEXT_REGEX.test(want.eventTrigger.eventType) &&
171
169
  backend.isEventTriggered(have) &&
172
- v2_1.FIRESTORE_EVENT_REGEX.test(have.eventTrigger.eventType));
170
+ want.eventTrigger.eventType ===
171
+ v2events.CONVERTABLE_EVENTS[have.eventTrigger.eventType]);
173
172
  }
174
173
  exports.checkForUnsafeUpdate = checkForUnsafeUpdate;
175
174
  function checkForIllegalUpdate(want, have) {
@@ -196,7 +195,8 @@ function checkForIllegalUpdate(want, have) {
196
195
  };
197
196
  const wantType = triggerType(want);
198
197
  const haveType = triggerType(have);
199
- if (wantType !== haveType) {
198
+ const upgradingHttpsFunction = backend.isHttpsTriggered(have) && backend.isCallableTriggered(want);
199
+ if (wantType !== haveType && !upgradingHttpsFunction) {
200
200
  throw new error_1.FirebaseError(`[${(0, functionsDeployHelper_1.getFunctionLabel)(want)}] Changing from ${haveType} function to ${wantType} function is not allowed. Please delete your function and create a new one instead.`);
201
201
  }
202
202
  if (want.platform === "gcfv1" && have.platform === "gcfv2") {
@@ -134,6 +134,9 @@ function assertBuildEndpoint(ep, id) {
134
134
  });
135
135
  }
136
136
  else if (build.isCallableTriggered(ep)) {
137
+ (0, parsing_1.assertKeyTypes)(prefix + ".callableTrigger", ep.callableTrigger, {
138
+ genkitAction: "string?",
139
+ });
137
140
  }
138
141
  else if (build.isScheduleTriggered(ep)) {
139
142
  (0, parsing_1.assertKeyTypes)(prefix + ".scheduleTrigger", ep.scheduleTrigger, {
@@ -216,6 +219,7 @@ function parseEndpointForBuild(id, ep, project, defaultRegion, runtime) {
216
219
  }
217
220
  else if (build.isCallableTriggered(ep)) {
218
221
  triggered = { callableTrigger: {} };
222
+ (0, proto_1.copyIfPresent)(triggered.callableTrigger, ep.callableTrigger, "genkitAction");
219
223
  }
220
224
  else if (build.isScheduleTriggered(ep)) {
221
225
  const st = {
@@ -55,7 +55,7 @@ exports.RUNTIMES = runtimes({
55
55
  },
56
56
  nodejs22: {
57
57
  friendly: "Node.js 22",
58
- status: "beta",
58
+ status: "GA",
59
59
  deprecationDate: "2027-04-30",
60
60
  decommissionDate: "2027-10-31",
61
61
  },
@@ -26,7 +26,7 @@ async function detectStartCommand(rootDir) {
26
26
  return `${packageManager} run dev`;
27
27
  }
28
28
  catch (e) {
29
- throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommandOverride`");
29
+ throw new error_1.FirebaseError("Failed to auto-detect your project's start command. Consider manually setting the start command by setting `firebase.json#emulators.apphosting.startCommand`");
30
30
  }
31
31
  }
32
32
  exports.detectStartCommand = detectStartCommand;
@@ -11,7 +11,7 @@ class AppHostingEmulator {
11
11
  async start() {
12
12
  const { hostname, port } = await (0, serve_1.start)({
13
13
  port: this.args.port,
14
- startCommand: this.args.startCommandOverride,
14
+ startCommand: this.args.startCommand,
15
15
  rootDirectory: this.args.rootDirectory,
16
16
  });
17
17
  this.args.options.host = hostname;
@@ -738,11 +738,7 @@ function setAccountInfo(state, reqBody, ctx) {
738
738
  }
739
739
  function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl = undefined } = {}) {
740
740
  var _a, _b;
741
- const unimplementedFields = [
742
- "provider",
743
- "upgradeToFederatedLogin",
744
- "linkProviderUserInfo",
745
- ];
741
+ const unimplementedFields = ["provider", "upgradeToFederatedLogin"];
746
742
  for (const field of unimplementedFields) {
747
743
  if (field in reqBody) {
748
744
  throw new errors_1.NotImplementedError(`${field} is not implemented yet.`);
@@ -908,8 +904,15 @@ function setAccountInfoImpl(state, reqBody, { privileged = false, emulatorUrl =
908
904
  updates.phoneNumber = undefined;
909
905
  }
910
906
  }
907
+ if (reqBody.linkProviderUserInfo) {
908
+ (0, errors_1.assert)(reqBody.linkProviderUserInfo.providerId, "MISSING_PROVIDER_ID");
909
+ (0, errors_1.assert)(reqBody.linkProviderUserInfo.rawId, "MISSING_RAW_ID");
910
+ }
911
911
  user = state.updateUserByLocalId(user.localId, updates, {
912
912
  deleteProviders: reqBody.deleteProvider,
913
+ upsertProviders: reqBody.linkProviderUserInfo
914
+ ? [reqBody.linkProviderUserInfo]
915
+ : undefined,
913
916
  });
914
917
  if (signInProvider !== state_1.PROVIDER_ANONYMOUS && user.initialEmail && isEmailUpdate) {
915
918
  if (!emulatorUrl) {
@@ -577,6 +577,20 @@ async function startAll(options, showUI = true, runningTestScript = false) {
577
577
  utils.assertIsString(options.import);
578
578
  const importDirAbsPath = path.resolve(options.import);
579
579
  const exportMetadataFilePath = path.resolve(importDirAbsPath, exportMetadata.dataconnect.path);
580
+ const dataDirectory = options.config.get("emulators.dataconnect.dataDir");
581
+ if (exportMetadataFilePath && dataDirectory) {
582
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("WARN", "dataconnect", "'firebase.json#emulators.dataconnect.dataDir' is set and `--import` flag was passed. " +
583
+ "This will overwrite any data saved from previous runs.");
584
+ if (!options.nonInteractive &&
585
+ !(await (0, prompt_1.promptOnce)({
586
+ type: "confirm",
587
+ message: `Do you wish to continue and overwrite data in ${dataDirectory}?`,
588
+ default: false,
589
+ }))) {
590
+ await cleanShutdown();
591
+ return { deprecationNotices: [] };
592
+ }
593
+ }
580
594
  emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.DATACONNECT).logLabeled("BULLET", "dataconnect", `Importing data from ${exportMetadataFilePath}`);
581
595
  args.importPath = exportMetadataFilePath;
582
596
  void (0, track_1.trackEmulator)("emulator_import", {
@@ -615,10 +629,14 @@ async function startAll(options, showUI = true, runningTestScript = false) {
615
629
  const apphostingConfig = (_l = options.config.src.emulators) === null || _l === void 0 ? void 0 : _l[types_1.Emulators.APPHOSTING];
616
630
  if (listenForEmulator.apphosting) {
617
631
  const apphostingAddr = legacyGetFirstAddr(types_1.Emulators.APPHOSTING);
632
+ if (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride) {
633
+ const apphostingLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.APPHOSTING);
634
+ apphostingLogger.logLabeled("WARN", types_1.Emulators.APPHOSTING, "The `firebase.json#emulators.apphosting.startCommandOverride` config is deprecated, please use `firebase.json#emulators.apphosting.startCommand` to set a custom start command instead");
635
+ }
618
636
  const apphostingEmulator = new apphosting_1.AppHostingEmulator({
619
637
  host: apphostingAddr.host,
620
638
  port: apphostingAddr.port,
621
- startCommandOverride: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride,
639
+ startCommand: (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommand) || (apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.startCommandOverride),
622
640
  rootDirectory: apphostingConfig === null || apphostingConfig === void 0 ? void 0 : apphostingConfig.rootDirectory,
623
641
  options,
624
642
  });
@@ -27,6 +27,7 @@ const fs = require("fs");
27
27
  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
+ const error_1 = require("../../error");
30
31
  exports.TRUNCATE_TABLES_SQL = `
31
32
  DO $do$
32
33
  BEGIN
@@ -62,6 +63,7 @@ class PostgresServer {
62
63
  server.emit("error", err);
63
64
  });
64
65
  });
66
+ this.server = server;
65
67
  const listeningPromise = new Promise((resolve) => {
66
68
  server.listen(port, host, () => {
67
69
  resolve();
@@ -77,7 +79,7 @@ class PostgresServer {
77
79
  const pgliteArgs = {
78
80
  username: this.username,
79
81
  database: this.database,
80
- debug: 0,
82
+ debug: this.debug,
81
83
  extensions: {
82
84
  vector,
83
85
  uuidOssp,
@@ -90,7 +92,7 @@ class PostgresServer {
90
92
  const file = new File([rf], this.importPath);
91
93
  pgliteArgs.loadDataDir = file;
92
94
  }
93
- this.db = await pglite_1.PGlite.create(pgliteArgs);
95
+ this.db = await this.forceCreateDB(pgliteArgs);
94
96
  await this.db.waitReady;
95
97
  }
96
98
  return this.db;
@@ -105,12 +107,37 @@ class PostgresServer {
105
107
  const arrayBuff = await dump.arrayBuffer();
106
108
  fs.writeFileSync(exportPath, new Uint8Array(arrayBuff));
107
109
  }
108
- constructor(database, username, dataDirectory, importPath) {
110
+ async forceCreateDB(pgliteArgs) {
111
+ try {
112
+ const db = await pglite_1.PGlite.create(pgliteArgs);
113
+ return db;
114
+ }
115
+ catch (err) {
116
+ if (pgliteArgs.dataDir && (0, error_1.hasMessage)(err) && /Database already exists/.test(err.message)) {
117
+ fs.rmSync(pgliteArgs.dataDir, { force: true, recursive: true });
118
+ const db = await pglite_1.PGlite.create(pgliteArgs);
119
+ return db;
120
+ }
121
+ throw err;
122
+ }
123
+ }
124
+ async stop() {
125
+ if (this.db) {
126
+ await this.db.close();
127
+ }
128
+ if (this.server) {
129
+ this.server.close();
130
+ }
131
+ return;
132
+ }
133
+ constructor(args) {
109
134
  this.db = undefined;
110
- this.username = username;
111
- this.database = database;
112
- this.dataDirectory = dataDirectory;
113
- this.importPath = importPath;
135
+ this.server = undefined;
136
+ this.username = args.username;
137
+ this.database = args.database;
138
+ this.dataDirectory = args.dataDirectory;
139
+ this.importPath = args.importPath;
140
+ this.debug = args.debug ? 5 : 0;
114
141
  }
115
142
  }
116
143
  exports.PostgresServer = PostgresServer;