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.
- package/lib/apphosting/utils.js +14 -0
- package/lib/apptesting/parseTestFiles.js +14 -5
- package/lib/commands/apptesting.js +20 -10
- package/lib/commands/index.js +5 -5
- package/lib/commands/studio-export.js +2 -2
- package/lib/dataconnect/load.js +24 -1
- package/lib/deploy/apphosting/util.js +1 -1
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
- package/lib/emulator/apphosting/serve.js +13 -15
- package/lib/emulator/downloadableEmulatorInfo.json +7 -7
- package/lib/firebase_studio/migrate.js +313 -142
- package/lib/init/features/hosting/index.js +71 -101
- package/lib/mcp/index.js +18 -6
- package/lib/track.js +1 -1
- package/package.json +1 -1
- package/templates/firebase-studio-export/readme_template.md +11 -7
- package/templates/firebase-studio-export/system_instructions_template.md +14 -0
- package/templates/firebase-studio-export/workflows/cleanup.md +20 -0
- package/templates/init/apphosting/apphosting.yaml +1 -0
- package/templates/firebase-studio-export/workflows/startup_workflow.md +0 -16
package/lib/apphosting/utils.js
CHANGED
|
@@ -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
|
-
|
|
66
|
-
|
|
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>", "
|
|
29
|
-
.option("--test-devices-file <string>", "
|
|
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 =
|
|
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
|
-
|
|
55
|
-
|
|
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"}`;
|
package/lib/commands/index.js
CHANGED
|
@@ -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-
|
|
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
|
|
37
|
+
logger_1.logger.info(`⏳ Exporting Studio app from ${rootPath} to Antigravity...`);
|
|
38
38
|
await (0, migrate_1.migrate)(rootPath, options);
|
|
39
39
|
});
|
package/lib/dataconnect/load.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
48
|
-
"expectedSize":
|
|
49
|
-
"expectedChecksum": "
|
|
50
|
-
"expectedChecksumSHA256": "
|
|
51
|
-
"remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.
|
|
52
|
-
"downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.
|
|
53
|
-
"binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.
|
|
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": {
|