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.
@@ -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);
@@ -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
- logger_1.logger.info(`✅ Detected Firebase Project: ${projectId}`);
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 injectAgyContext(rootPath, projectId, appName) {
134
- const agentDir = path.join(rootPath, ".agent");
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 AGY skills from firebase/agent-skills...");
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 AGY skills, skipping. ${err}`);
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 AGY rules");
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 AGY startup workflow");
220
+ logger_1.logger.info("✅ Created Antigravity startup workflow");
184
221
  }
185
222
  catch (err) {
186
- logger_1.logger.debug(`Could not read or write startup workflow: ${err}`);
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 CLI (${cmd}) detected`);
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 CLI detected at ${macPath}`);
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 CLI (agy) not found in your PATH. To ensure a seamless migration, please download and install Antigravity: ${downloadLink}`);
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
- utils.logWarning(`Could not fetch backends from Firebase CLI, using default "studio". ${err}`);
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
- ".agent",
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 writeAgyConfigs(rootPath) {
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
- logger_1.logger.debug(`Could not read ${settingsPath}: ${err}`);
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
- logger_1.logger.debug(`Could not remove ${docsDir}: ${err}`);
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
- logger_1.logger.debug(`Could not delete ${modifiedPath}: ${err}`);
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
- utils.logWarning(`Failed to upload GEMINI_API_KEY secret: ${err}`);
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, startAgy) {
389
- const agyCommand = await getAgyCommand(startAgy);
390
- if (!startAgy || !agyCommand) {
391
- logger_1.logger.info('\n👉 Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
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: `Migration complete for ${appName}! Would you like to open it in Antigravity now?`,
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 agyProcess = (0, child_process_1.spawn)(agyCommand, ["."], {
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
- agyProcess.unref();
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 = { startAgy: true }) {
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 injectAgyContext(rootPath, projectId, appName);
430
- await writeAgyConfigs(rootPath);
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 might want to rename this folder to "${appName.toLowerCase().replace(/\s+/g, "-")}"`);
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.startAgy);
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 github_1 = require("./github");
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, _c, _d, _e;
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
- let discoveredFramework = experiments.isEnabled("webframeworks")
52
- ? await (0, frameworks_1.discover)(config.projectDir, false)
53
- : undefined;
54
- if (experiments.isEnabled("webframeworks")) {
55
- if (discoveredFramework &&
56
- (await (0, prompt_1.confirm)({
57
- message: `Detected an existing ${frameworks_1.WebFrameworks[discoveredFramework.framework].name} codebase in the current directory, do you want to use this?`,
58
- default: true,
59
- }))) {
60
- setup.featureInfo.hosting.source = ".";
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 (0, github_1.initGitHub)(setup);
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
- if (hostingInfo.webFramework) {
134
- if (!hostingInfo.useDiscoveredFramework) {
135
- if (hostingInfo.source && (0, node_fs_1.existsSync)(hostingInfo.source)) {
136
- (0, node_fs_1.rmSync)(hostingInfo.source, { recursive: true });
137
- }
138
- await frameworks_1.WebFrameworks[hostingInfo.webFramework].init(setup, config);
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
- setup.config.hosting = {
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 ((!!process.env.IS_FIREBASE_CLI || !!process.env.IS_FIREBASE_MCP) && !!configstore_1.configstore.get("usage"));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "15.9.2-ct-studioexport3.0",
3
+ "version": "15.10.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "mcpName": "io.github.firebase/firebase-mcp",
@@ -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: Verify Firebase Auth/Firestore
10
+ # Step 2: Cleanup Genkit config
11
11
 
12
- If the app uses Firebase services, ensure the environment variables are correctly set or provided via App Hosting.
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.