firebase-tools 15.9.2-ct-studioexport3.0 → 15.10.1

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getEnvironmentName = getEnvironmentName;
4
4
  exports.promptForAppHostingYaml = promptForAppHostingYaml;
5
+ exports.getAutoinitEnvVars = getAutoinitEnvVars;
5
6
  const error_1 = require("../error");
6
7
  const config_1 = require("./config");
7
8
  const prompt = require("../prompt");
@@ -36,3 +37,16 @@ async function promptForAppHostingYaml(apphostingFileNameToPathMap, promptMessag
36
37
  });
37
38
  return fileToExportPath;
38
39
  }
40
+ function getAutoinitEnvVars(webappConfig) {
41
+ if (!webappConfig) {
42
+ return {};
43
+ }
44
+ return {
45
+ FIREBASE_WEBAPP_CONFIG: JSON.stringify(webappConfig),
46
+ FIREBASE_CONFIG: JSON.stringify({
47
+ databaseURL: webappConfig.databaseURL,
48
+ storageBucket: webappConfig.storageBucket,
49
+ projectId: webappConfig.projectId,
50
+ }),
51
+ };
52
+ }
@@ -27,8 +27,8 @@ async function parseTestFiles(dir, targetUri, filePattern, namePattern) {
27
27
  }
28
28
  return accumulator;
29
29
  }, {});
30
- const fileFilterFn = createFilter(filePattern);
31
- const nameFilterFn = createFilter(namePattern);
30
+ const fileFilterFn = createFilter(filePattern, "file pattern");
31
+ const nameFilterFn = createFilter(namePattern, "test name pattern");
32
32
  const filteredInvocations = files
33
33
  .filter((file) => fileFilterFn(file.path))
34
34
  .flatMap((file) => file.invocations)
@@ -61,9 +61,18 @@ async function parseTestFiles(dir, targetUri, filePattern, namePattern) {
61
61
  };
62
62
  });
63
63
  }
64
- function createFilter(pattern) {
65
- const regex = pattern ? new RegExp(pattern) : undefined;
66
- return (s) => !regex || regex.test(s);
64
+ function createFilter(pattern, context) {
65
+ try {
66
+ const regex = pattern ? new RegExp(pattern) : undefined;
67
+ return (s) => !regex || regex.test(s);
68
+ }
69
+ catch (ex) {
70
+ if (ex instanceof SyntaxError) {
71
+ const errMsg = context ? `Invalid ${context} regex: ${pattern}` : `Invalid regex: ${pattern}`;
72
+ throw new error_1.FirebaseError(errMsg, { original: (0, error_1.getError)(ex) });
73
+ }
74
+ throw ex;
75
+ }
67
76
  }
68
77
  async function parseTestFilesRecursive(params) {
69
78
  const testDir = params.testDir;
@@ -3,14 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
4
  const requireAuth_1 = require("../requireAuth");
5
5
  const command_1 = require("../command");
6
- const logger_1 = require("../logger");
7
- const clc = require("colorette");
8
6
  const parseTestFiles_1 = require("../apptesting/parseTestFiles");
9
7
  const ora = require("ora");
10
8
  const error_1 = require("../error");
11
9
  const client_1 = require("../appdistribution/client");
12
10
  const distribution_1 = require("../appdistribution/distribution");
13
11
  const options_parser_util_1 = require("../appdistribution/options-parser-util");
12
+ const utils = require("../utils");
13
+ const fsutils_1 = require("../fsutils");
14
+ const path = require("path");
14
15
  const defaultDevices = [
15
16
  {
16
17
  model: "MediumPhone.arm",
@@ -24,35 +25,44 @@ exports.command = new command_1.Command("apptesting:execute <release-binary-file
24
25
  .option("--app <app_id>", "The app id of your Firebase web app. Optional if the project contains exactly one web app.")
25
26
  .option("--test-file-pattern <pattern>", "Test file pattern. Only tests contained in files that match this pattern will be executed.")
26
27
  .option("--test-name-pattern <pattern>", "Test name pattern. Only tests with names that match this pattern will be executed.")
27
- .option("--test-dir <test_dir>", "Directory where tests can be found.")
28
- .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.")
29
- .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.")
28
+ .option("--test-dir <test_dir>", "Directory where tests can be found. Defaults to './tests'.")
29
+ .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.")
30
+ .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.")
31
+ .option("--test-non-blocking", "Run automated tests without waiting for them to complete. Visit the Firebase console for the test results.")
30
32
  .before(requireAuth_1.requireAuth)
31
33
  .action(async (target, options) => {
32
34
  const appName = (0, options_parser_util_1.getAppName)(options);
33
- const testDir = options.testDir || "tests";
35
+ const testDir = path.resolve(options.testDir || "tests");
36
+ if (!(0, fsutils_1.dirExistsSync)(testDir)) {
37
+ throw new error_1.FirebaseError(`Tests directory not found: ${testDir}. Use the --test-dir flag to choose a different directory.`);
38
+ }
34
39
  const tests = await (0, parseTestFiles_1.parseTestFiles)(testDir, undefined, options.testFilePattern, options.testNamePattern);
35
40
  const testDevices = (0, options_parser_util_1.parseTestDevices)(options.testDevices, options.testDevicesFile);
36
41
  if (!tests.length) {
37
42
  throw new error_1.FirebaseError("No tests found");
38
43
  }
39
44
  const invokeSpinner = ora("Requesting test execution");
45
+ const client = new client_1.AppDistributionClient();
40
46
  let releaseTests;
41
47
  let release;
42
48
  try {
43
- const client = new client_1.AppDistributionClient();
44
49
  release = await (0, distribution_1.upload)(client, appName, new distribution_1.Distribution(target));
45
50
  invokeSpinner.start();
46
51
  releaseTests = await invokeTests(client, release.name, tests, !testDevices.length ? defaultDevices : testDevices);
47
- invokeSpinner.text = "Test execution requested";
52
+ invokeSpinner.text = `${pluralizeTests(releaseTests.length)} started successfully!`;
48
53
  invokeSpinner.succeed();
49
54
  }
50
55
  catch (ex) {
51
56
  invokeSpinner.fail("Failed to request test execution");
52
57
  throw ex;
53
58
  }
54
- logger_1.logger.info(clc.bold(`\n${clc.white("===")} Running ${pluralizeTests(releaseTests.length)}`));
55
- logger_1.logger.info(`View progress and results in the Firebase Console:\n${release.firebaseConsoleUri}`);
59
+ if (options.testNonBlocking) {
60
+ utils.logBullet(`View progress and results in the Firebase Console:\n${release.firebaseConsoleUri}`);
61
+ }
62
+ else {
63
+ await (0, distribution_1.awaitTestResults)(releaseTests, client);
64
+ utils.logBullet(`View detailed results in the Firebase Console:\n${release.firebaseConsoleUri}`);
65
+ }
56
66
  });
57
67
  function pluralizeTests(numTests) {
58
68
  return `${numTests} test${numTests === 1 ? "" : "s"}`;
@@ -33,6 +33,11 @@ function load(client) {
33
33
  client.appdistribution.testCases = {};
34
34
  client.appdistribution.testCases.export = loadCommand("appdistribution-testcases-export");
35
35
  client.appdistribution.testCases.import = loadCommand("appdistribution-testcases-import");
36
+ client.apptesting = {};
37
+ client.apptesting.execute = loadCommand("apptesting");
38
+ if (experiments.isEnabled("apptesting")) {
39
+ client.apptesting.wata = loadCommand("apptesting-wata");
40
+ }
36
41
  client.apps = {};
37
42
  client.apps.create = loadCommand("apps-create");
38
43
  client.apps.list = loadCommand("apps-list");
@@ -254,11 +259,6 @@ function load(client) {
254
259
  client.target.clear = loadCommand("target-clear");
255
260
  client.target.remove = loadCommand("target-remove");
256
261
  client.use = loadCommand("use");
257
- client.apptesting = {};
258
- client.apptesting.execute = loadCommand("apptesting");
259
- if (experiments.isEnabled("apptesting")) {
260
- client.apptesting.wata = loadCommand("apptesting-wata");
261
- }
262
262
  const t1 = process.hrtime.bigint();
263
263
  const diffMS = (t1 - t0) / BigInt(1e6);
264
264
  if (diffMS > 100) {
@@ -10,7 +10,7 @@ const unzip_1 = require("../unzip");
10
10
  const fs = require("fs");
11
11
  exports.command = new command_1.Command("studio:export <path>")
12
12
  .description("Bootstrap Firebase Studio apps for migration to Antigravity. Run on the unzipped folder from the Firebase Studio download, or directly on the downloaded zip file.")
13
- .option("--no-start-agy", "skip starting the Antigravity IDE after migration")
13
+ .option("--no-start-antigravity", "skip starting the Antigravity IDE after migration")
14
14
  .action(async (exportPath, options) => {
15
15
  if (!exportPath) {
16
16
  throw new error_1.FirebaseError("Must specify a path for migration.", { exit: 1 });
@@ -34,6 +34,6 @@ exports.command = new command_1.Command("studio:export <path>")
34
34
  rootPath = extractPath;
35
35
  }
36
36
  }
37
- logger_1.logger.info(`⏳ Exporting Studio apps from ${rootPath} to Antigravity...`);
37
+ logger_1.logger.info(`⏳ Exporting Studio app from ${rootPath} to Antigravity...`);
38
38
  await (0, migrate_1.migrate)(rootPath, options);
39
39
  });
@@ -6,6 +6,7 @@ exports.loadAll = loadAll;
6
6
  exports.load = load;
7
7
  exports.readFirebaseJson = readFirebaseJson;
8
8
  exports.readDataConnectYaml = readDataConnectYaml;
9
+ exports.inferClientCache = inferClientCache;
9
10
  exports.readConnectorYaml = readConnectorYaml;
10
11
  exports.readGQLFiles = readGQLFiles;
11
12
  exports.squashGraphQL = squashGraphQL;
@@ -28,7 +29,7 @@ async function pickOneService(projectId, config, service, location) {
28
29
  async function pickServices(projectId, config, serviceId, location) {
29
30
  const serviceInfos = await loadAll(projectId, config);
30
31
  if (serviceInfos.length === 0) {
31
- throw new error_1.FirebaseError("No Data Connect services found in firebase.json." +
32
+ throw new error_1.FirebaseError("No Data Connect services found in firebase.json. " +
32
33
  `\nYou can run ${clc.bold("firebase init dataconnect")} to add a Data Connect service.`);
33
34
  }
34
35
  const matchingServices = serviceInfos.filter((i) => (!serviceId || i.dataConnectYaml.serviceId === serviceId) &&
@@ -63,6 +64,7 @@ async function load(projectId, config, sourceDirectory) {
63
64
  const connectorDir = path.join(resolvedDir, dir);
64
65
  const connectorYaml = await readConnectorYaml(connectorDir);
65
66
  const connectorGqls = await readGQLFiles(connectorDir);
67
+ const clientCache = inferClientCache(connectorYaml);
66
68
  return {
67
69
  directory: connectorDir,
68
70
  connectorYaml,
@@ -71,6 +73,7 @@ async function load(projectId, config, sourceDirectory) {
71
73
  source: {
72
74
  files: connectorGqls,
73
75
  },
76
+ client_cache: clientCache,
74
77
  },
75
78
  };
76
79
  }));
@@ -122,6 +125,26 @@ function validateDataConnectYaml(unvalidated) {
122
125
  }
123
126
  return unvalidated;
124
127
  }
128
+ function inferClientCache(connectorYaml) {
129
+ const platforms = [
130
+ connectorYaml.generate?.javascriptSdk,
131
+ connectorYaml.generate?.swiftSdk,
132
+ connectorYaml.generate?.kotlinSdk,
133
+ connectorYaml.generate?.dartSdk,
134
+ ];
135
+ for (const sdk of platforms) {
136
+ if (sdk) {
137
+ const sdkList = Array.isArray(sdk) ? sdk : [sdk];
138
+ if (sdkList.some((s) => s.clientCache)) {
139
+ return {
140
+ strict_validation_enabled: true,
141
+ entity_id_included: true,
142
+ };
143
+ }
144
+ }
145
+ }
146
+ return undefined;
147
+ }
125
148
  async function readConnectorYaml(sourceDirectory) {
126
149
  const file = await (0, utils_1.readFileFromDirectory)(sourceDirectory, "connector.yaml");
127
150
  const connectorYaml = await (0, utils_1.wrappedSafeLoad)(file.source);
@@ -35,7 +35,7 @@ async function createArchive(config, rootDir, targetSubDir) {
35
35
  await pipeAsync(archive, fileStream);
36
36
  }
37
37
  catch (err) {
38
- throw new error_1.FirebaseError("Could not read source directory. Remove links and shortcuts and try again.", { original: err, exit: 1 });
38
+ throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${err}`, { original: err, exit: 1 });
39
39
  }
40
40
  return tmpFile;
41
41
  }
@@ -98,7 +98,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
98
98
  if (err instanceof error_1.FirebaseError) {
99
99
  throw err;
100
100
  }
101
- throw new error_1.FirebaseError("Could not read source directory. Remove links and shortcuts and try again.", {
101
+ throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${err}`, {
102
102
  original: err,
103
103
  exit: 1,
104
104
  });
@@ -20,9 +20,10 @@ const utils_1 = require("../../utils");
20
20
  const apphosting = require("../../gcp/apphosting");
21
21
  const constants_2 = require("../constants");
22
22
  const fetchWebSetup_1 = require("../../fetchWebSetup");
23
- const apps_1 = require("../../management/apps");
24
23
  const child_process_1 = require("child_process");
25
24
  const semver_1 = require("semver");
25
+ const utils_2 = require("../../apphosting/utils");
26
+ const apps_1 = require("../../management/apps");
26
27
  const secretResourceRegex = /^projects\/([^/]+)\/secrets\/([^/]+)(?:\/versions\/((?:latest)|\d+))?$/;
27
28
  const secretShorthandRegex = /^([^/@]+)(?:@((?:latest)|\d+))?$/;
28
29
  async function loadSecret(project, name) {
@@ -81,6 +82,15 @@ async function start(options) {
81
82
  startCommand = await (0, developmentServer_1.detectPackageManagerStartCommand)(backendRoot);
82
83
  developmentServer_2.logger.logLabeled("BULLET", types_1.Emulators.APPHOSTING, `starting app with: '${startCommand}'`);
83
84
  }
85
+ const packageManager = await (0, developmentServer_1.detectPackageManager)(backendRoot).catch(() => undefined);
86
+ let autoinitEnvVars = {};
87
+ if (packageManager === "pnpm") {
88
+ (0, utils_1.logLabeledWarning)("apphosting", "Firebase JS SDK autoinit does not currently support PNPM.");
89
+ }
90
+ else {
91
+ const webappConfig = await getBackendAppConfig(options?.projectId, options?.backendId);
92
+ autoinitEnvVars = (0, utils_2.getAutoinitEnvVars)(webappConfig);
93
+ }
84
94
  const apphostingLocalConfig = await (0, config_1.getLocalAppHostingConfiguration)(backendRoot);
85
95
  const resolveEnv = Object.entries(apphostingLocalConfig.env).map(async ([key, value]) => [
86
96
  key,
@@ -88,6 +98,7 @@ async function start(options) {
88
98
  ]);
89
99
  const environmentVariablesToInject = {
90
100
  NODE_ENV: process.env.NODE_ENV,
101
+ ...autoinitEnvVars,
91
102
  ...getEmulatorEnvs(),
92
103
  ...Object.fromEntries(await Promise.all(resolveEnv)),
93
104
  FIREBASE_APP_HOSTING: "1",
@@ -96,20 +107,7 @@ async function start(options) {
96
107
  PROJECT_ID: options?.projectId,
97
108
  PORT: port.toString(),
98
109
  };
99
- const packageManager = await (0, developmentServer_1.detectPackageManager)(backendRoot).catch(() => undefined);
100
- if (packageManager === "pnpm") {
101
- (0, utils_1.logLabeledWarning)("apphosting", `Firebase JS SDK autoinit does not currently support PNPM.`);
102
- }
103
- else {
104
- const webappConfig = await getBackendAppConfig(options?.projectId, options?.backendId);
105
- if (webappConfig) {
106
- environmentVariablesToInject["FIREBASE_WEBAPP_CONFIG"] || (environmentVariablesToInject["FIREBASE_WEBAPP_CONFIG"] = JSON.stringify(webappConfig));
107
- environmentVariablesToInject["FIREBASE_CONFIG"] || (environmentVariablesToInject["FIREBASE_CONFIG"] = JSON.stringify({
108
- databaseURL: webappConfig.databaseURL,
109
- storageBucket: webappConfig.storageBucket,
110
- projectId: webappConfig.projectId,
111
- }));
112
- }
110
+ if (packageManager !== "pnpm") {
113
111
  await tripFirebasePostinstall(backendRoot, environmentVariablesToInject);
114
112
  }
115
113
  (0, spawn_1.spawnWithCommandString)(startCommand, backendRoot, environmentVariablesToInject)
@@ -44,13 +44,13 @@
44
44
  }
45
45
  },
46
46
  "pubsub": {
47
- "version": "0.8.27",
48
- "expectedSize": 52924291,
49
- "expectedChecksum": "cb0d35db6aa1bb5e3f7e2a5a690c631d",
50
- "expectedChecksumSHA256": "0b793b420b608b68c200a0d15123c63967ac2863bbd9545ecb087d5b28871339",
51
- "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.27.zip",
52
- "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.27.zip",
53
- "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.27/pubsub-emulator/bin/cloud-pubsub-emulator"
47
+ "version": "0.8.29",
48
+ "expectedSize": 52906482,
49
+ "expectedChecksum": "8345b0a923dda5634dd56a25f913cf37",
50
+ "expectedChecksumSHA256": "31228112fb95a6818d13a88e801f4be6fb5ea5b601175b74528cc61823ea5507",
51
+ "remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.29.zip",
52
+ "downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.29.zip",
53
+ "binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.29/pubsub-emulator/bin/cloud-pubsub-emulator"
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {