firebase-tools 15.9.2-ct-studioexport3.0 → 15.10.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.
- package/lib/commands/studio-export.js +2 -2
- package/lib/dataconnect/load.js +24 -1
- package/lib/firebase_studio/migrate.js +99 -63
- package/lib/init/features/hosting/index.js +71 -101
- package/lib/track.js +1 -1
- package/package.json +1 -1
- package/templates/firebase-studio-export/workflows/startup_workflow.md +3 -7
|
@@ -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);
|
|
@@ -15,6 +15,43 @@ const track = require("../track");
|
|
|
15
15
|
const secrets_1 = require("../apphosting/secrets");
|
|
16
16
|
const env = require("../functions/env");
|
|
17
17
|
const error_1 = require("../error");
|
|
18
|
+
const os = require("os");
|
|
19
|
+
async function setupAntigravityMcpServer(rootPath) {
|
|
20
|
+
const mcpConfigDir = path.join(os.homedir(), ".gemini", "antigravity");
|
|
21
|
+
const mcpConfigPath = path.join(mcpConfigDir, "mcp_config.json");
|
|
22
|
+
let mcpConfig = { mcpServers: {} };
|
|
23
|
+
try {
|
|
24
|
+
await fs.mkdir(mcpConfigDir, { recursive: true });
|
|
25
|
+
const content = await fs
|
|
26
|
+
.readFile(mcpConfigPath, "utf-8")
|
|
27
|
+
.catch((err) => {
|
|
28
|
+
if (err.code === "ENOENT") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
});
|
|
33
|
+
if (content) {
|
|
34
|
+
mcpConfig = JSON.parse(content);
|
|
35
|
+
if (!mcpConfig.mcpServers) {
|
|
36
|
+
mcpConfig.mcpServers = {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (mcpConfig.mcpServers["firebase"]) {
|
|
40
|
+
logger_1.logger.info("ℹ️ Firebase MCP server already configured in Antigravity, skipping.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
mcpConfig.mcpServers["firebase"] = {
|
|
44
|
+
command: "npx",
|
|
45
|
+
args: ["-y", "firebase-tools@latest", "mcp", "--dir", path.resolve(rootPath)],
|
|
46
|
+
};
|
|
47
|
+
await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
48
|
+
logger_1.logger.info(`✅ Configured Firebase MCP server in ${mcpConfigPath}`);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
utils.logWarning(`Could not configure Antigravity MCP server: ${message}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
18
55
|
async function detectAppType(rootPath) {
|
|
19
56
|
try {
|
|
20
57
|
await fs.access(path.join(rootPath, "pubspec.yaml"));
|
|
@@ -73,6 +110,10 @@ async function downloadGitHubDir(apiUrl, localPath) {
|
|
|
73
110
|
}
|
|
74
111
|
}
|
|
75
112
|
}
|
|
113
|
+
const isValidFirebaseProjectId = (projectId) => {
|
|
114
|
+
const projectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
|
|
115
|
+
return projectIdRegex.test(projectId);
|
|
116
|
+
};
|
|
76
117
|
async function extractMetadata(rootPath, overrideProjectId) {
|
|
77
118
|
const metadataPath = path.join(rootPath, "metadata.json");
|
|
78
119
|
let metadata = {};
|
|
@@ -97,7 +138,12 @@ async function extractMetadata(rootPath, overrideProjectId) {
|
|
|
97
138
|
}
|
|
98
139
|
}
|
|
99
140
|
if (projectId) {
|
|
100
|
-
|
|
141
|
+
if (!isValidFirebaseProjectId(projectId)) {
|
|
142
|
+
throw new error_1.FirebaseError(`Invalid project ID: ${projectId}.`, {
|
|
143
|
+
exit: 1,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
logger_1.logger.info(`✅ Using Firebase Project: ${projectId}`);
|
|
101
147
|
}
|
|
102
148
|
else {
|
|
103
149
|
logger_1.logger.info(`❌ Failed to determine the Firebase Project ID. You can set a project later with 'firebase use <project-id>' or by setting the '--project' flag.`);
|
|
@@ -130,15 +176,15 @@ async function updateReadme(rootPath, blueprintContent, appName) {
|
|
|
130
176
|
await fs.writeFile(readmePath, newReadme);
|
|
131
177
|
logger_1.logger.info("✅ Updated README.md with project details and origin info");
|
|
132
178
|
}
|
|
133
|
-
async function
|
|
134
|
-
const agentDir = path.join(rootPath, ".
|
|
179
|
+
async function injectAntigravityContext(rootPath, projectId, appName) {
|
|
180
|
+
const agentDir = path.join(rootPath, ".agents");
|
|
135
181
|
const rulesDir = path.join(agentDir, "rules");
|
|
136
182
|
const workflowsDir = path.join(agentDir, "workflows");
|
|
137
183
|
const skillsDir = path.join(agentDir, "skills");
|
|
138
184
|
await fs.mkdir(rulesDir, { recursive: true });
|
|
139
185
|
await fs.mkdir(workflowsDir, { recursive: true });
|
|
140
186
|
await fs.mkdir(skillsDir, { recursive: true });
|
|
141
|
-
logger_1.logger.info("⏳ Fetching
|
|
187
|
+
logger_1.logger.info("⏳ Fetching Antigravity skills from firebase/agent-skills...");
|
|
142
188
|
try {
|
|
143
189
|
const skillsResponse = await fetch("https://api.github.com/repos/firebase/agent-skills/contents/skills");
|
|
144
190
|
if (!skillsResponse.ok) {
|
|
@@ -160,30 +206,22 @@ async function injectAgyContext(rootPath, projectId, appName) {
|
|
|
160
206
|
logger_1.logger.info(`✅ Downloaded Firebase skills`);
|
|
161
207
|
}
|
|
162
208
|
catch (err) {
|
|
163
|
-
utils.logWarning(`Could not download
|
|
164
|
-
}
|
|
165
|
-
logger_1.logger.info("⏳ Fetching Genkit skill...");
|
|
166
|
-
try {
|
|
167
|
-
const genkitSkillDir = path.join(skillsDir, "developing-genkit-js");
|
|
168
|
-
await downloadGitHubDir("https://api.github.com/repos/genkit-ai/skills/contents/skills/developing-genkit-js?ref=main", genkitSkillDir);
|
|
169
|
-
logger_1.logger.info(`✅ Downloaded Genkit skill`);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
utils.logWarning(`Could not download Genkit skill, skipping. ${err}`);
|
|
209
|
+
utils.logWarning(`Could not download Antigravity skills, skipping. ${err}`);
|
|
173
210
|
}
|
|
174
211
|
const systemInstructionsTemplate = await (0, templates_1.readTemplate)("firebase-studio-export/system_instructions_template.md");
|
|
175
212
|
const systemInstructions = systemInstructionsTemplate
|
|
176
213
|
.replace("${projectId}", projectId || "None")
|
|
177
214
|
.replace("${appName}", appName);
|
|
178
215
|
await fs.writeFile(path.join(rulesDir, "migration-context.md"), systemInstructions);
|
|
179
|
-
logger_1.logger.info("✅ Injected
|
|
216
|
+
logger_1.logger.info("✅ Injected Antigravity rules");
|
|
180
217
|
try {
|
|
181
218
|
const startupWorkflow = await (0, templates_1.readTemplate)("firebase-studio-export/workflows/startup_workflow.md");
|
|
182
219
|
await fs.writeFile(path.join(workflowsDir, "startup.md"), startupWorkflow);
|
|
183
|
-
logger_1.logger.info("✅ Created
|
|
220
|
+
logger_1.logger.info("✅ Created Antigravity startup workflow");
|
|
184
221
|
}
|
|
185
222
|
catch (err) {
|
|
186
|
-
|
|
223
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
224
|
+
logger_1.logger.debug(`Could not read or write startup workflow: ${message}`);
|
|
187
225
|
}
|
|
188
226
|
}
|
|
189
227
|
async function getAgyCommand(startAgy) {
|
|
@@ -193,7 +231,7 @@ async function getAgyCommand(startAgy) {
|
|
|
193
231
|
const commands = ["agy", "antigravity"];
|
|
194
232
|
for (const cmd of commands) {
|
|
195
233
|
if (utils.commandExistsSync(cmd)) {
|
|
196
|
-
logger_1.logger.info(`✅ Antigravity IDE
|
|
234
|
+
logger_1.logger.info(`✅ Antigravity IDE detected`);
|
|
197
235
|
return cmd;
|
|
198
236
|
}
|
|
199
237
|
}
|
|
@@ -201,14 +239,24 @@ async function getAgyCommand(startAgy) {
|
|
|
201
239
|
const macPath = "/Applications/Antigravity.app/Contents/Resources/app/bin/agy";
|
|
202
240
|
try {
|
|
203
241
|
await fs.access(macPath);
|
|
204
|
-
logger_1.logger.info(`✅ Antigravity IDE
|
|
242
|
+
logger_1.logger.info(`✅ Antigravity IDE detected at ${macPath}`);
|
|
205
243
|
return macPath;
|
|
206
244
|
}
|
|
207
245
|
catch {
|
|
208
246
|
}
|
|
209
247
|
}
|
|
248
|
+
if (process.platform === "win32") {
|
|
249
|
+
const winPath = path.join(process.env.LOCALAPPDATA || "", "Programs", "Antigravity", "bin", "agy.exe");
|
|
250
|
+
try {
|
|
251
|
+
await fs.access(winPath);
|
|
252
|
+
logger_1.logger.info(`✅ Antigravity IDE CLI detected at ${winPath}`);
|
|
253
|
+
return winPath;
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
210
258
|
const downloadLink = "https://antigravity.google/download";
|
|
211
|
-
logger_1.logger.info(`⚠️ Antigravity IDE
|
|
259
|
+
logger_1.logger.info(`⚠️ Antigravity IDE not found in your PATH. To ensure a seamless migration, please download and install Antigravity: ${downloadLink}`);
|
|
212
260
|
return undefined;
|
|
213
261
|
}
|
|
214
262
|
async function createFirebaseConfigs(rootPath, projectId) {
|
|
@@ -248,7 +296,8 @@ async function createFirebaseConfigs(rootPath, projectId) {
|
|
|
248
296
|
}
|
|
249
297
|
}
|
|
250
298
|
catch (err) {
|
|
251
|
-
|
|
299
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
300
|
+
utils.logWarning(`Could not fetch backends from Firebase CLI, using default "studio". ${message}`);
|
|
252
301
|
}
|
|
253
302
|
const firebaseJson = {
|
|
254
303
|
apphosting: {
|
|
@@ -256,7 +305,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
|
|
|
256
305
|
ignore: [
|
|
257
306
|
"node_modules",
|
|
258
307
|
".git",
|
|
259
|
-
".
|
|
308
|
+
".agents",
|
|
260
309
|
".idx",
|
|
261
310
|
"firebase-debug.log",
|
|
262
311
|
"firebase-debug.*.log",
|
|
@@ -268,7 +317,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
|
|
|
268
317
|
logger_1.logger.info(`✅ Created firebase.json with backendId: ${backendId}`);
|
|
269
318
|
}
|
|
270
319
|
}
|
|
271
|
-
async function
|
|
320
|
+
async function writeAntigravityConfigs(rootPath) {
|
|
272
321
|
const vscodeDir = path.join(rootPath, ".vscode");
|
|
273
322
|
await fs.mkdir(vscodeDir, { recursive: true });
|
|
274
323
|
const tasksJson = {
|
|
@@ -291,7 +340,8 @@ async function writeAgyConfigs(rootPath) {
|
|
|
291
340
|
settings = JSON.parse(settingsContent);
|
|
292
341
|
}
|
|
293
342
|
catch (err) {
|
|
294
|
-
|
|
343
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
344
|
+
logger_1.logger.debug(`Could not read ${settingsPath}: ${message}`);
|
|
295
345
|
}
|
|
296
346
|
const cleanSettings = {};
|
|
297
347
|
for (const [key, value] of Object.entries(settings)) {
|
|
@@ -322,14 +372,6 @@ async function writeAgyConfigs(rootPath) {
|
|
|
322
372
|
}
|
|
323
373
|
async function cleanupUnusedFiles(rootPath) {
|
|
324
374
|
const docsDir = path.join(rootPath, "docs");
|
|
325
|
-
const blueprintPath = path.join(docsDir, "blueprint.md");
|
|
326
|
-
try {
|
|
327
|
-
await fs.unlink(blueprintPath);
|
|
328
|
-
logger_1.logger.info("✅ Cleaned up docs/blueprint.md");
|
|
329
|
-
}
|
|
330
|
-
catch (err) {
|
|
331
|
-
logger_1.logger.debug(`Could not delete ${blueprintPath}: ${err}`);
|
|
332
|
-
}
|
|
333
375
|
try {
|
|
334
376
|
const files = await fs.readdir(docsDir);
|
|
335
377
|
if (files.length === 0) {
|
|
@@ -338,15 +380,8 @@ async function cleanupUnusedFiles(rootPath) {
|
|
|
338
380
|
}
|
|
339
381
|
}
|
|
340
382
|
catch (err) {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const metadataPath = path.join(rootPath, "metadata.json");
|
|
344
|
-
try {
|
|
345
|
-
await fs.unlink(metadataPath);
|
|
346
|
-
logger_1.logger.info("✅ Cleaned up metadata.json");
|
|
347
|
-
}
|
|
348
|
-
catch (err) {
|
|
349
|
-
logger_1.logger.debug(`Could not delete ${metadataPath}: ${err}`);
|
|
383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
384
|
+
logger_1.logger.debug(`Could not remove ${docsDir}: ${message}`);
|
|
350
385
|
}
|
|
351
386
|
const modifiedPath = path.join(rootPath, ".modified");
|
|
352
387
|
try {
|
|
@@ -354,7 +389,8 @@ async function cleanupUnusedFiles(rootPath) {
|
|
|
354
389
|
logger_1.logger.info("✅ Cleaned up .modified");
|
|
355
390
|
}
|
|
356
391
|
catch (err) {
|
|
357
|
-
|
|
392
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
393
|
+
logger_1.logger.debug(`Could not delete ${modifiedPath}: ${message}`);
|
|
358
394
|
}
|
|
359
395
|
}
|
|
360
396
|
async function uploadSecrets(rootPath, projectId) {
|
|
@@ -382,43 +418,42 @@ async function uploadSecrets(rootPath, projectId) {
|
|
|
382
418
|
}
|
|
383
419
|
}
|
|
384
420
|
catch (err) {
|
|
385
|
-
|
|
421
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
422
|
+
utils.logWarning(`Failed to upload GEMINI_API_KEY secret: ${message}`);
|
|
386
423
|
}
|
|
387
424
|
}
|
|
388
|
-
async function askToOpenAntigravity(rootPath, appName,
|
|
389
|
-
const agyCommand = await getAgyCommand(
|
|
390
|
-
|
|
391
|
-
|
|
425
|
+
async function askToOpenAntigravity(rootPath, appName, startAntigravity) {
|
|
426
|
+
const agyCommand = await getAgyCommand(startAntigravity);
|
|
427
|
+
logger_1.logger.info(`\n🎉 Your Firebase Studio project "${appName}" is now ready for Antigravity!`);
|
|
428
|
+
logger_1.logger.info("Antigravity is Google's agentic IDE, where you can collaborate with AI agents to build, test, and deploy your application.");
|
|
429
|
+
logger_1.logger.info("\nWhat to do next inside Antigravity:");
|
|
430
|
+
logger_1.logger.info(" 1. Review the README.md: It has been updated with specifics about this migrated project.");
|
|
431
|
+
logger_1.logger.info(" 2. Open the Agent Chat: Use the side panel or press Cmd+L (Ctrl+L on Windows/Linux). This is your main interface with the AI.");
|
|
432
|
+
logger_1.logger.info("\nFile any bugs at https://github.com/firebase/firebase-tools/issues");
|
|
433
|
+
if (!startAntigravity || !agyCommand) {
|
|
392
434
|
return;
|
|
393
435
|
}
|
|
394
436
|
const answer = await prompt.confirm({
|
|
395
|
-
message:
|
|
437
|
+
message: "Would you like to open it in Antigravity now?",
|
|
396
438
|
default: true,
|
|
397
439
|
});
|
|
398
440
|
if (answer) {
|
|
399
441
|
logger_1.logger.info(`⏳ Opening ${appName} in Antigravity...`);
|
|
400
442
|
try {
|
|
401
|
-
const
|
|
443
|
+
const antigravityProcess = (0, child_process_1.spawn)(agyCommand, ["."], {
|
|
402
444
|
cwd: rootPath,
|
|
403
445
|
stdio: "ignore",
|
|
404
446
|
detached: true,
|
|
447
|
+
shell: process.platform === "win32",
|
|
405
448
|
});
|
|
406
|
-
|
|
449
|
+
antigravityProcess.unref();
|
|
407
450
|
}
|
|
408
451
|
catch (err) {
|
|
409
452
|
utils.logWarning("Could not open Antigravity IDE automatically. Please open it manually.");
|
|
410
453
|
}
|
|
411
454
|
}
|
|
412
|
-
else {
|
|
413
|
-
logger_1.logger.info('\n👉 Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
|
|
414
|
-
}
|
|
415
455
|
}
|
|
416
|
-
async function migrate(rootPath, options = {
|
|
417
|
-
if (process.platform === "win32") {
|
|
418
|
-
throw new error_1.FirebaseError("Firebase Studio migration is currently not supported on Windows.", {
|
|
419
|
-
exit: 1,
|
|
420
|
-
});
|
|
421
|
-
}
|
|
456
|
+
async function migrate(rootPath, options = { startAntigravity: true }) {
|
|
422
457
|
const appType = await detectAppType(rootPath);
|
|
423
458
|
void track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "started" });
|
|
424
459
|
logger_1.logger.info("🚀 Starting Firebase Studio to Antigravity migration...");
|
|
@@ -426,13 +461,14 @@ async function migrate(rootPath, options = { startAgy: true }) {
|
|
|
426
461
|
await updateReadme(rootPath, blueprintContent, appName);
|
|
427
462
|
await createFirebaseConfigs(rootPath, projectId);
|
|
428
463
|
await uploadSecrets(rootPath, projectId);
|
|
429
|
-
await
|
|
430
|
-
await
|
|
464
|
+
await injectAntigravityContext(rootPath, projectId, appName);
|
|
465
|
+
await writeAntigravityConfigs(rootPath);
|
|
466
|
+
await setupAntigravityMcpServer(rootPath);
|
|
431
467
|
await cleanupUnusedFiles(rootPath);
|
|
432
468
|
const currentFolderName = path.basename(rootPath);
|
|
433
469
|
if (currentFolderName === "download") {
|
|
434
|
-
logger_1.logger.info(`\n💡 Tip: You
|
|
470
|
+
logger_1.logger.info(`\n💡 Tip: You may want to rename this folder to "${appName.toLowerCase().replace(/\s+/g, "-")}"`);
|
|
435
471
|
}
|
|
436
472
|
await track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "success" });
|
|
437
|
-
await askToOpenAntigravity(rootPath, appName, options.
|
|
473
|
+
await askToOpenAntigravity(rootPath, appName, options.startAntigravity);
|
|
438
474
|
}
|
|
@@ -3,15 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.askQuestions = askQuestions;
|
|
4
4
|
exports.actuate = actuate;
|
|
5
5
|
const clc = require("colorette");
|
|
6
|
-
const node_fs_1 = require("node:fs");
|
|
7
6
|
const path_1 = require("path");
|
|
8
7
|
const apiv2_1 = require("../../../apiv2");
|
|
9
|
-
const
|
|
8
|
+
const frameworks_1 = require("../../../frameworks");
|
|
9
|
+
const github = require("./github");
|
|
10
10
|
const prompt_1 = require("../../../prompt");
|
|
11
11
|
const logger_1 = require("../../../logger");
|
|
12
|
-
const frameworks_1 = require("../../../frameworks");
|
|
13
|
-
const constants_1 = require("../../../frameworks/constants");
|
|
14
|
-
const experiments = require("../../../experiments");
|
|
15
12
|
const getDefaultHostingSite_1 = require("../../../getDefaultHostingSite");
|
|
16
13
|
const utils_1 = require("../../../utils");
|
|
17
14
|
const interactive_1 = require("../../../hosting/interactive");
|
|
@@ -22,7 +19,51 @@ const INDEX_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/index.htm
|
|
|
22
19
|
const MISSING_TEMPLATE = (0, templates_1.readTemplateSync)("init/hosting/404.html");
|
|
23
20
|
const DEFAULT_IGNORES = ["firebase.json", "**/.*", "**/node_modules/**"];
|
|
24
21
|
async function askQuestions(setup, config, options) {
|
|
25
|
-
var _a, _b
|
|
22
|
+
var _a, _b;
|
|
23
|
+
const discoveredFramework = await (0, frameworks_1.discover)(config.projectDir, false);
|
|
24
|
+
if (discoveredFramework && discoveredFramework.mayWantBackend) {
|
|
25
|
+
const frameworkName = frameworks_1.WebFrameworks[discoveredFramework.framework]?.name ?? discoveredFramework.framework;
|
|
26
|
+
switch (discoveredFramework.framework) {
|
|
27
|
+
case "next":
|
|
28
|
+
case "angular":
|
|
29
|
+
case "nuxt":
|
|
30
|
+
case "nuxt2":
|
|
31
|
+
case "express":
|
|
32
|
+
case "svelekit":
|
|
33
|
+
case "sveltekit":
|
|
34
|
+
logger_1.logger.info();
|
|
35
|
+
const useAppHosting = await (0, prompt_1.confirm)({
|
|
36
|
+
message: `Detected a ${frameworkName} codebase with SSR features. We can't guarantee that ` +
|
|
37
|
+
`this site will work on Firebase Hosting, which is optimized for static sites. Another ` +
|
|
38
|
+
`product, Firebase App Hosting, was designed for SSR web apps. Would ` +
|
|
39
|
+
`you like to use App Hosting instead? Learn more here: ` +
|
|
40
|
+
`https://firebase.google.com/docs/app-hosting/product-comparison#hostings`,
|
|
41
|
+
default: true,
|
|
42
|
+
});
|
|
43
|
+
if (useAppHosting) {
|
|
44
|
+
setup.featureInfo || (setup.featureInfo = {});
|
|
45
|
+
setup.featureInfo.hosting = { redirectToAppHosting: true };
|
|
46
|
+
setup.features?.unshift("apphosting");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
logger_1.logger.info();
|
|
52
|
+
logger_1.logger.info(`Detected a ${frameworkName} codebase with SSR features. We can't guarantee that ` +
|
|
53
|
+
`this site will work on Firebase Hosting, which is optimized for static sites. Another ` +
|
|
54
|
+
`product, Firebase App Hosting, was designed for SSR web apps.`);
|
|
55
|
+
logger_1.logger.info(`Learn about App Hosting here: https://firebase.google.com/docs/app-hosting/product-comparison#hostings`);
|
|
56
|
+
logger_1.logger.info(`Learn how to deploy frameworks with App Hosting here: https://firebase.blog/posts/2025/06/app-hosting-frameworks/`);
|
|
57
|
+
const continueWithHosting = await (0, prompt_1.confirm)({
|
|
58
|
+
message: `Would you like to continue setting up Firebase Hosting?`,
|
|
59
|
+
default: false,
|
|
60
|
+
});
|
|
61
|
+
if (!continueWithHosting) {
|
|
62
|
+
throw new error_1.FirebaseError("Hosting initialization cancelled.", { exit: 1 });
|
|
63
|
+
}
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
26
67
|
setup.featureInfo = setup.featureInfo || {};
|
|
27
68
|
setup.featureInfo.hosting = {};
|
|
28
69
|
if (setup.projectId) {
|
|
@@ -48,75 +89,18 @@ async function askQuestions(setup, config, options) {
|
|
|
48
89
|
setup.featureInfo.hosting.newSiteId = await (0, interactive_1.pickHostingSiteName)("", createOptions);
|
|
49
90
|
}
|
|
50
91
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
setup.featureInfo.hosting.useWebFrameworks = true;
|
|
62
|
-
setup.featureInfo.hosting.useDiscoveredFramework = true;
|
|
63
|
-
setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
setup.featureInfo.hosting.useWebFrameworks = await (0, prompt_1.confirm)(`Do you want to use a web framework? (${clc.bold("experimental")})`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
if (setup.featureInfo.hosting.useWebFrameworks) {
|
|
70
|
-
(_a = setup.featureInfo.hosting).source ?? (_a.source = await (0, prompt_1.input)({
|
|
71
|
-
message: "What folder would you like to use for your web application's root directory?",
|
|
72
|
-
default: "hosting",
|
|
73
|
-
}));
|
|
74
|
-
discoveredFramework = await (0, frameworks_1.discover)((0, path_1.join)(config.projectDir, setup.featureInfo.hosting.source));
|
|
75
|
-
if (discoveredFramework) {
|
|
76
|
-
const name = frameworks_1.WebFrameworks[discoveredFramework.framework].name;
|
|
77
|
-
(_b = setup.featureInfo.hosting).useDiscoveredFramework ?? (_b.useDiscoveredFramework = await (0, prompt_1.confirm)({
|
|
78
|
-
message: `Detected an existing ${name} codebase in ${setup.featureInfo.hosting.source}, should we use this?`,
|
|
79
|
-
default: true,
|
|
80
|
-
}));
|
|
81
|
-
if (setup.featureInfo.hosting.useDiscoveredFramework)
|
|
82
|
-
setup.featureInfo.hosting.webFramework = discoveredFramework.framework;
|
|
83
|
-
}
|
|
84
|
-
const choices = [];
|
|
85
|
-
for (const value in frameworks_1.WebFrameworks) {
|
|
86
|
-
if (frameworks_1.WebFrameworks[value]) {
|
|
87
|
-
const { name, init } = frameworks_1.WebFrameworks[value];
|
|
88
|
-
if (init)
|
|
89
|
-
choices.push({ name, value });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const defaultChoice = choices.find(({ value }) => value === discoveredFramework?.framework)?.value;
|
|
93
|
-
(_c = setup.featureInfo.hosting).webFramework ?? (_c.webFramework = await (0, prompt_1.select)({
|
|
94
|
-
message: "Please choose the framework:",
|
|
95
|
-
default: defaultChoice,
|
|
96
|
-
choices,
|
|
97
|
-
}));
|
|
98
|
-
setup.featureInfo.hosting.region =
|
|
99
|
-
setup.featureInfo.hosting.region ||
|
|
100
|
-
(await (0, prompt_1.select)({
|
|
101
|
-
message: "In which region would you like to host server-side content, if applicable?",
|
|
102
|
-
default: constants_1.DEFAULT_REGION,
|
|
103
|
-
choices: constants_1.ALLOWED_SSR_REGIONS.filter((region) => region.recommended),
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
logger_1.logger.info();
|
|
108
|
-
logger_1.logger.info(`Your ${clc.bold("public")} directory is the folder (relative to your project directory) that`);
|
|
109
|
-
logger_1.logger.info(`will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you`);
|
|
110
|
-
logger_1.logger.info("have a build process for your assets, use your build's output directory.");
|
|
111
|
-
logger_1.logger.info();
|
|
112
|
-
(_d = setup.featureInfo.hosting).public ?? (_d.public = await (0, prompt_1.input)({
|
|
113
|
-
message: "What do you want to use as your public directory?",
|
|
114
|
-
default: "public",
|
|
115
|
-
}));
|
|
116
|
-
(_e = setup.featureInfo.hosting).spa ?? (_e.spa = await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
|
|
117
|
-
}
|
|
92
|
+
logger_1.logger.info();
|
|
93
|
+
logger_1.logger.info(`Your ${clc.bold("public")} directory is the folder (relative to your project directory) that`);
|
|
94
|
+
logger_1.logger.info(`will contain Hosting assets to be uploaded with ${clc.bold("firebase deploy")}. If you`);
|
|
95
|
+
logger_1.logger.info("have a build process for your assets, use your build's output directory.");
|
|
96
|
+
logger_1.logger.info();
|
|
97
|
+
(_a = setup.featureInfo.hosting).public ?? (_a.public = await (0, prompt_1.input)({
|
|
98
|
+
message: "What do you want to use as your public directory?",
|
|
99
|
+
default: "public",
|
|
100
|
+
}));
|
|
101
|
+
(_b = setup.featureInfo.hosting).spa ?? (_b.spa = await (0, prompt_1.confirm)("Configure as a single-page app (rewrite all urls to /index.html)?"));
|
|
118
102
|
if (await (0, prompt_1.confirm)("Set up automatic builds and deploys with GitHub?")) {
|
|
119
|
-
return
|
|
103
|
+
return github.initGitHub(setup);
|
|
120
104
|
}
|
|
121
105
|
}
|
|
122
106
|
async function actuate(setup, config, options) {
|
|
@@ -124,40 +108,26 @@ async function actuate(setup, config, options) {
|
|
|
124
108
|
if (!hostingInfo) {
|
|
125
109
|
throw new error_1.FirebaseError("Could not find hosting info in setup.featureInfo.hosting. This should not happen.", { exit: 2 });
|
|
126
110
|
}
|
|
111
|
+
if (hostingInfo.redirectToAppHosting) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
127
114
|
if (hostingInfo.newSiteId && setup.projectId) {
|
|
128
115
|
await (0, api_1.createSite)(setup.projectId, hostingInfo.newSiteId);
|
|
129
116
|
logger_1.logger.info();
|
|
130
117
|
(0, utils_1.logSuccess)(`Firebase Hosting site ${hostingInfo.newSiteId} created!`);
|
|
131
118
|
logger_1.logger.info();
|
|
132
119
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
setup.config.hosting = {
|
|
141
|
-
source: hostingInfo.source,
|
|
142
|
-
ignore: DEFAULT_IGNORES,
|
|
143
|
-
frameworksBackend: {
|
|
144
|
-
region: hostingInfo.region,
|
|
145
|
-
},
|
|
146
|
-
};
|
|
120
|
+
setup.config.hosting = {
|
|
121
|
+
public: hostingInfo.public,
|
|
122
|
+
ignore: DEFAULT_IGNORES,
|
|
123
|
+
};
|
|
124
|
+
if (hostingInfo.spa) {
|
|
125
|
+
setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }];
|
|
147
126
|
}
|
|
148
127
|
else {
|
|
149
|
-
|
|
150
|
-
public: hostingInfo.public,
|
|
151
|
-
ignore: DEFAULT_IGNORES,
|
|
152
|
-
};
|
|
153
|
-
if (hostingInfo.spa) {
|
|
154
|
-
setup.config.hosting.rewrites = [{ source: "**", destination: "/index.html" }];
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
await config.askWriteProjectFile(`${hostingInfo.public}/404.html`, MISSING_TEMPLATE, !!options.force);
|
|
158
|
-
}
|
|
159
|
-
const c = new apiv2_1.Client({ urlPrefix: "https://www.gstatic.com", auth: false });
|
|
160
|
-
const response = await c.get("/firebasejs/releases.json");
|
|
161
|
-
await config.askWriteProjectFile(`${hostingInfo.public}/index.html`, INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version), !!options.force);
|
|
128
|
+
await config.askWriteProjectFile((0, path_1.join)(hostingInfo.public ?? "public", "404.html"), MISSING_TEMPLATE, !!options.force);
|
|
162
129
|
}
|
|
130
|
+
const c = new apiv2_1.Client({ urlPrefix: "https://www.gstatic.com", auth: false });
|
|
131
|
+
const response = await c.get("/firebasejs/releases.json");
|
|
132
|
+
await config.askWriteProjectFile((0, path_1.join)(hostingInfo.public ?? "public", "index.html"), INDEX_TEMPLATE.replace(/{{VERSION}}/g, response.body.current.version), !!options.force);
|
|
163
133
|
}
|
package/lib/track.js
CHANGED
|
@@ -33,7 +33,7 @@ exports.GA4_PROPERTIES = {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
function usageEnabled() {
|
|
36
|
-
return (
|
|
36
|
+
return (!!process.env.IS_FIREBASE_CLI || (0, env_1.isFirebaseMcp)()) && !!configstore_1.configstore.get("usage");
|
|
37
37
|
}
|
|
38
38
|
const GA4_USER_PROPS = {
|
|
39
39
|
node_platform: {
|
package/package.json
CHANGED
|
@@ -5,12 +5,8 @@ description: Run initial checks and fix common migration issues
|
|
|
5
5
|
|
|
6
6
|
# Step 1: Check Compilation
|
|
7
7
|
|
|
8
|
-
Run \`npm run typecheck\` and \`npm run build\` to ensure the project is in a healthy state.
|
|
8
|
+
Run \`npm run typecheck\` and \`npm run build\` (depending on the app type) to ensure the project is in a healthy state.
|
|
9
9
|
|
|
10
|
-
# Step 2:
|
|
10
|
+
# Step 2: Cleanup Genkit config
|
|
11
11
|
|
|
12
|
-
If
|
|
13
|
-
|
|
14
|
-
# Step 3: Cleanup Genkit config
|
|
15
|
-
|
|
16
|
-
If genkit is otherwise unused in this project, remove the configuration in src/ai/genkit.ts and remove related dependencies in package.json.
|
|
12
|
+
If genkit is otherwise unused in this project, ask the user if they'd like to remove the configuration in src/ai/genkit.ts and remove related dependencies in package.json.
|