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.
@@ -15,6 +15,66 @@ 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, appType) {
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
+ let updated = false;
40
+ if (!mcpConfig.mcpServers["firebase"]) {
41
+ mcpConfig.mcpServers["firebase"] = {
42
+ command: "npx",
43
+ args: ["-y", "firebase-tools@latest", "mcp", "--dir", path.resolve(rootPath)],
44
+ };
45
+ updated = true;
46
+ logger_1.logger.info(`✅ Configured Firebase MCP server in ${mcpConfigPath}`);
47
+ }
48
+ else {
49
+ logger_1.logger.info("ℹ️ Firebase MCP server already configured in Antigravity, skipping.");
50
+ }
51
+ if (appType === "FLUTTER") {
52
+ if (utils.commandExistsSync("dart")) {
53
+ if (!mcpConfig.mcpServers["dart"]) {
54
+ mcpConfig.mcpServers["dart"] = {
55
+ command: "dart",
56
+ args: ["mcp-server"],
57
+ };
58
+ updated = true;
59
+ logger_1.logger.info(`✅ Configured Dart MCP server in ${mcpConfigPath}`);
60
+ }
61
+ else {
62
+ logger_1.logger.info("ℹ️ Dart MCP server already configured in Antigravity, skipping.");
63
+ }
64
+ }
65
+ else {
66
+ utils.logWarning("Couldn't find Dart/Flutter on PATH. Install Flutter by following the instruction at https://docs.flutter.dev/install.");
67
+ }
68
+ }
69
+ if (updated) {
70
+ await fs.writeFile(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
71
+ }
72
+ }
73
+ catch (err) {
74
+ const message = err instanceof Error ? err.message : String(err);
75
+ utils.logWarning(`Could not configure Antigravity MCP server: ${message}`);
76
+ }
77
+ }
18
78
  async function detectAppType(rootPath) {
19
79
  try {
20
80
  await fs.access(path.join(rootPath, "pubspec.yaml"));
@@ -52,36 +112,23 @@ async function detectAppType(rootPath) {
52
112
  }
53
113
  return "OTHER";
54
114
  }
55
- async function downloadGitHubDir(apiUrl, localPath) {
56
- const response = await fetch(apiUrl);
57
- if (!response.ok) {
58
- throw new Error(`Failed to fetch directory listing: ${apiUrl}`);
59
- }
60
- const items = (await response.json());
61
- await fs.mkdir(localPath, { recursive: true });
62
- for (const item of items) {
63
- const itemLocalPath = path.join(localPath, item.name);
64
- if (item.type === "dir") {
65
- await downloadGitHubDir(item.url, itemLocalPath);
66
- }
67
- else if (item.type === "file") {
68
- const fileResponse = await fetch(item.download_url);
69
- if (fileResponse.ok) {
70
- const content = await fileResponse.arrayBuffer();
71
- await fs.writeFile(itemLocalPath, Buffer.from(content));
72
- }
73
- }
74
- }
75
- }
115
+ const isValidFirebaseProjectId = (projectId) => {
116
+ const projectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
117
+ return projectIdRegex.test(projectId);
118
+ };
76
119
  async function extractMetadata(rootPath, overrideProjectId) {
120
+ const studioJsonPath = path.join(rootPath, "studio.json");
77
121
  const metadataPath = path.join(rootPath, "metadata.json");
78
122
  let metadata = {};
79
- try {
80
- const metadataContent = await fs.readFile(metadataPath, "utf8");
81
- metadata = JSON.parse(metadataContent);
82
- }
83
- catch (err) {
84
- logger_1.logger.debug(`Could not read metadata.json at ${metadataPath}: ${err}`);
123
+ for (const metadataFile of [metadataPath, studioJsonPath]) {
124
+ try {
125
+ const metadataContent = await fs.readFile(metadataFile, "utf8");
126
+ metadata = JSON.parse(metadataContent);
127
+ logger_1.logger.info(`✅ Read ${metadataFile}`);
128
+ }
129
+ catch (err) {
130
+ logger_1.logger.debug(`Could not read metadata at ${metadataFile}: ${err}`);
131
+ }
85
132
  }
86
133
  logger_1.logger.debug(`overrideProjectId ${overrideProjectId}`);
87
134
  logger_1.logger.debug(`metadata.projectId ${metadata.projectId}`);
@@ -97,17 +144,21 @@ async function extractMetadata(rootPath, overrideProjectId) {
97
144
  }
98
145
  }
99
146
  if (projectId) {
100
- logger_1.logger.info(`✅ Detected Firebase Project: ${projectId}`);
147
+ if (!isValidFirebaseProjectId(projectId)) {
148
+ throw new error_1.FirebaseError(`Invalid project ID: ${projectId}.`, {
149
+ exit: 1,
150
+ });
151
+ }
152
+ logger_1.logger.info(`✅ Using Firebase Project: ${projectId}`);
101
153
  }
102
154
  else {
103
- 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.`);
155
+ logger_1.logger.debug(`❌ Failed to determine the Firebase Project ID. You can set a project later by setting the '--project' flag.`);
104
156
  }
105
157
  let appName = "firebase-studio-export";
106
- let blueprintContent = "";
107
158
  const blueprintPath = path.join(rootPath, "docs", "blueprint.md");
108
159
  try {
109
- blueprintContent = await fs.readFile(blueprintPath, "utf8");
110
- const nameMatch = blueprintContent.match(/# \*\*App Name\*\*: (.*)/);
160
+ const content = await fs.readFile(blueprintPath, "utf8");
161
+ const nameMatch = content.match(/# \*\*App Name\*\*: (.*)/);
111
162
  if (nameMatch && nameMatch[1]) {
112
163
  appName = nameMatch[1].trim();
113
164
  }
@@ -118,72 +169,75 @@ async function extractMetadata(rootPath, overrideProjectId) {
118
169
  if (appName !== "firebase-studio-export") {
119
170
  logger_1.logger.info(`✅ Detected App Name: ${appName}`);
120
171
  }
121
- return { projectId, appName, blueprintContent };
172
+ return { projectId, appName };
122
173
  }
123
- async function updateReadme(rootPath, blueprintContent, appName) {
174
+ async function updateReadme(rootPath, framework) {
124
175
  const readmePath = path.join(rootPath, "README.md");
125
176
  const readmeTemplate = await (0, templates_1.readTemplate)("firebase-studio-export/readme_template.md");
126
- const newReadme = readmeTemplate
127
- .replace(/\${appName}/g, appName)
177
+ const frameworkConfigs = {
178
+ NEXT_JS: { startCommand: "npm run dev", localUrl: "http://localhost:9002" },
179
+ ANGULAR: { startCommand: "npm run start", localUrl: "http://localhost:4200" },
180
+ FLUTTER: {
181
+ startCommand: "flutter run -d chrome --web-port=8080",
182
+ localUrl: "http://localhost:8080",
183
+ },
184
+ OTHER: { startCommand: "npm run dev", localUrl: "http://localhost:9002" },
185
+ };
186
+ const { startCommand, localUrl } = frameworkConfigs[framework];
187
+ let existingReadme = "";
188
+ try {
189
+ existingReadme = await fs.readFile(readmePath, "utf8");
190
+ }
191
+ catch (err) {
192
+ }
193
+ let newReadme = readmeTemplate
128
194
  .replace("${exportDate}", new Date().toISOString().split("T")[0])
129
- .replace("${blueprintContent}", blueprintContent.replace(/# \*\*App Name\*\*: .*/, "").trim());
195
+ .replace("${startCommand}", startCommand)
196
+ .replace("${localUrl}", localUrl);
197
+ if (existingReadme.trim()) {
198
+ newReadme += `\n\n---\n\n## Previous README.md contents:\n\n${existingReadme}`;
199
+ }
130
200
  await fs.writeFile(readmePath, newReadme);
131
201
  logger_1.logger.info("✅ Updated README.md with project details and origin info");
132
202
  }
133
- async function injectAgyContext(rootPath, projectId, appName) {
134
- const agentDir = path.join(rootPath, ".agent");
203
+ async function injectAntigravityContext(rootPath, projectId, appName) {
204
+ const agentDir = path.join(rootPath, ".agents");
135
205
  const rulesDir = path.join(agentDir, "rules");
136
206
  const workflowsDir = path.join(agentDir, "workflows");
137
207
  const skillsDir = path.join(agentDir, "skills");
138
208
  await fs.mkdir(rulesDir, { recursive: true });
139
209
  await fs.mkdir(workflowsDir, { recursive: true });
140
210
  await fs.mkdir(skillsDir, { recursive: true });
141
- logger_1.logger.info("⏳ Fetching AGY skills from firebase/agent-skills...");
211
+ logger_1.logger.info("⏳ Adding Antigravity skills...");
142
212
  try {
143
- const skillsResponse = await fetch("https://api.github.com/repos/firebase/agent-skills/contents/skills");
144
- if (!skillsResponse.ok) {
145
- throw new Error(`GitHub API returned ${skillsResponse.status}`);
146
- }
147
- const skillsData = (await skillsResponse.json());
148
- if (Array.isArray(skillsData)) {
149
- for (const item of skillsData) {
150
- if (item.type === "dir") {
151
- const skillName = item.name;
152
- const skillDir = path.join(skillsDir, skillName);
153
- await downloadGitHubDir(item.url, skillDir);
154
- }
155
- }
213
+ const result = (0, child_process_1.spawnSync)("npx", ["-y", "skills", "add", "firebase/agent-skills", "-a", "antigravity", "--skill", "*", "-y"], {
214
+ cwd: rootPath,
215
+ stdio: "ignore",
216
+ shell: process.platform === "win32",
217
+ });
218
+ if (result.error) {
219
+ throw result.error;
156
220
  }
157
- else {
158
- utils.logWarning("GitHub API response for skills is not an array.");
221
+ if (result.status !== 0) {
222
+ throw new Error(`npx skills add exited with code ${result.status}`);
159
223
  }
160
- logger_1.logger.info(`✅ Downloaded Firebase skills`);
161
- }
162
- 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`);
224
+ logger_1.logger.info(`✅ Added Antigravity skills`);
170
225
  }
171
226
  catch (err) {
172
- utils.logWarning(`Could not download Genkit skill, skipping. ${err}`);
227
+ utils.logWarning(`Could not add Antigravity skills, skipping. ${err}`);
173
228
  }
174
229
  const systemInstructionsTemplate = await (0, templates_1.readTemplate)("firebase-studio-export/system_instructions_template.md");
175
- const systemInstructions = systemInstructionsTemplate
176
- .replace("${projectId}", projectId || "None")
177
- .replace("${appName}", appName);
230
+ const systemInstructions = systemInstructionsTemplate.replace("${appName}", appName);
178
231
  await fs.writeFile(path.join(rulesDir, "migration-context.md"), systemInstructions);
179
- logger_1.logger.info("✅ Injected AGY rules");
232
+ logger_1.logger.info("✅ Injected Antigravity rules");
180
233
  try {
181
- const startupWorkflow = await (0, templates_1.readTemplate)("firebase-studio-export/workflows/startup_workflow.md");
182
- await fs.writeFile(path.join(workflowsDir, "startup.md"), startupWorkflow);
183
- logger_1.logger.info("✅ Created AGY startup workflow");
234
+ const cleanupWorkflow = await (0, templates_1.readTemplate)("firebase-studio-export/workflows/cleanup.md");
235
+ await fs.writeFile(path.join(workflowsDir, "cleanup.md"), cleanupWorkflow);
236
+ logger_1.logger.info("✅ Created Antigravity startup workflow");
184
237
  }
185
238
  catch (err) {
186
- logger_1.logger.debug(`Could not read or write startup workflow: ${err}`);
239
+ const message = err instanceof Error ? err.message : String(err);
240
+ logger_1.logger.debug(`Could not read or write startup workflow: ${message}`);
187
241
  }
188
242
  }
189
243
  async function getAgyCommand(startAgy) {
@@ -193,7 +247,7 @@ async function getAgyCommand(startAgy) {
193
247
  const commands = ["agy", "antigravity"];
194
248
  for (const cmd of commands) {
195
249
  if (utils.commandExistsSync(cmd)) {
196
- logger_1.logger.info(`✅ Antigravity IDE CLI (${cmd}) detected`);
250
+ logger_1.logger.info(`✅ Antigravity IDE detected`);
197
251
  return cmd;
198
252
  }
199
253
  }
@@ -201,14 +255,24 @@ async function getAgyCommand(startAgy) {
201
255
  const macPath = "/Applications/Antigravity.app/Contents/Resources/app/bin/agy";
202
256
  try {
203
257
  await fs.access(macPath);
204
- logger_1.logger.info(`✅ Antigravity IDE CLI detected at ${macPath}`);
258
+ logger_1.logger.info(`✅ Antigravity IDE detected at ${macPath}`);
205
259
  return macPath;
206
260
  }
207
261
  catch {
208
262
  }
209
263
  }
264
+ if (process.platform === "win32") {
265
+ const winPath = path.join(process.env.LOCALAPPDATA || "", "Programs", "Antigravity", "bin", "agy.exe");
266
+ try {
267
+ await fs.access(winPath);
268
+ logger_1.logger.info(`✅ Antigravity IDE CLI detected at ${winPath}`);
269
+ return winPath;
270
+ }
271
+ catch {
272
+ }
273
+ }
210
274
  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}`);
275
+ logger_1.logger.info(`⚠️ Antigravity IDE not found in your PATH. To ensure a seamless migration, please download and install Antigravity: ${downloadLink}`);
212
276
  return undefined;
213
277
  }
214
278
  async function createFirebaseConfigs(rootPath, projectId) {
@@ -234,12 +298,35 @@ async function createFirebaseConfigs(rootPath, projectId) {
234
298
  const backendsData = await apphosting.listBackends(projectId, "-");
235
299
  const backends = backendsData.backends || [];
236
300
  if (backends.length > 0) {
301
+ const backendIds = backends.map((b) => b.name.split("/").pop());
237
302
  const studioBackend = backends.find((b) => b.name.endsWith("/studio") || b.name.toLowerCase().includes("studio"));
303
+ let selectedBackendId = "";
238
304
  if (studioBackend) {
239
- backendId = studioBackend.name.split("/").pop();
305
+ selectedBackendId = studioBackend.name.split("/").pop();
240
306
  }
241
307
  else {
242
- backendId = backends[0].name.split("/").pop();
308
+ selectedBackendId = backendIds[0];
309
+ }
310
+ const confirmBackend = await prompt.confirm({
311
+ message: `Would you like to use the App Hosting backend "${selectedBackendId}"?`,
312
+ default: true,
313
+ nonInteractive: process.env.NODE_ENV === "test",
314
+ });
315
+ if (confirmBackend) {
316
+ backendId = selectedBackendId;
317
+ }
318
+ else {
319
+ logger_1.logger.info("Available App Hosting backends:");
320
+ for (const id of backendIds) {
321
+ logger_1.logger.info(` - ${id}`);
322
+ }
323
+ const inputBackendId = await prompt.input({
324
+ message: "Please enter the name of the backend you would like to use:",
325
+ });
326
+ if (!backendIds.includes(inputBackendId)) {
327
+ throw new error_1.FirebaseError(`Invalid backend selected: ${inputBackendId}`, { exit: 1 });
328
+ }
329
+ backendId = inputBackendId;
243
330
  }
244
331
  logger_1.logger.info(`✅ Selected App Hosting backend: ${backendId}`);
245
332
  }
@@ -248,7 +335,8 @@ async function createFirebaseConfigs(rootPath, projectId) {
248
335
  }
249
336
  }
250
337
  catch (err) {
251
- utils.logWarning(`Could not fetch backends from Firebase CLI, using default "studio". ${err}`);
338
+ const message = err instanceof Error ? err.message : String(err);
339
+ utils.logWarning(`Could not fetch backends from Firebase CLI, using default "studio". ${message}`);
252
340
  }
253
341
  const firebaseJson = {
254
342
  apphosting: {
@@ -256,7 +344,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
256
344
  ignore: [
257
345
  "node_modules",
258
346
  ".git",
259
- ".agent",
347
+ ".agents",
260
348
  ".idx",
261
349
  "firebase-debug.log",
262
350
  "firebase-debug.*.log",
@@ -268,20 +356,33 @@ async function createFirebaseConfigs(rootPath, projectId) {
268
356
  logger_1.logger.info(`✅ Created firebase.json with backendId: ${backendId}`);
269
357
  }
270
358
  }
271
- async function writeAgyConfigs(rootPath) {
359
+ async function writeAntigravityConfigs(rootPath, framework) {
272
360
  const vscodeDir = path.join(rootPath, ".vscode");
273
361
  await fs.mkdir(vscodeDir, { recursive: true });
274
362
  const tasksJson = {
275
363
  version: "2.0.0",
276
- tasks: [
277
- {
278
- label: "npm-install",
279
- type: "shell",
280
- command: "npm install",
281
- problemMatcher: [],
282
- },
283
- ],
364
+ tasks: [],
284
365
  };
366
+ if (framework === "FLUTTER") {
367
+ tasksJson.tasks.push({
368
+ label: "flutter-pub-get",
369
+ type: "shell",
370
+ command: "flutter pub get",
371
+ problemMatcher: [],
372
+ group: {
373
+ kind: "build",
374
+ isDefault: true,
375
+ },
376
+ });
377
+ }
378
+ else {
379
+ tasksJson.tasks.push({
380
+ label: "npm-install",
381
+ type: "shell",
382
+ command: "npm install",
383
+ problemMatcher: [],
384
+ });
385
+ }
285
386
  await fs.writeFile(path.join(vscodeDir, "tasks.json"), JSON.stringify(tasksJson, null, 2));
286
387
  logger_1.logger.info("✅ Created .vscode/tasks.json");
287
388
  const settingsPath = path.join(vscodeDir, "settings.json");
@@ -291,7 +392,8 @@ async function writeAgyConfigs(rootPath) {
291
392
  settings = JSON.parse(settingsContent);
292
393
  }
293
394
  catch (err) {
294
- logger_1.logger.debug(`Could not read ${settingsPath}: ${err}`);
395
+ const message = err instanceof Error ? err.message : String(err);
396
+ logger_1.logger.debug(`Could not read ${settingsPath}: ${message}`);
295
397
  }
296
398
  const cleanSettings = {};
297
399
  for (const [key, value] of Object.entries(settings)) {
@@ -304,32 +406,48 @@ async function writeAgyConfigs(rootPath) {
304
406
  logger_1.logger.info("✅ Updated .vscode/settings.json with startup preferences");
305
407
  const launchJson = {
306
408
  version: "0.2.0",
307
- configurations: [
308
- {
309
- type: "node",
310
- request: "launch",
311
- name: "Next.js: debug server-side",
312
- runtimeExecutable: "npm",
313
- runtimeArgs: ["run", "dev"],
314
- port: 9002,
315
- console: "integratedTerminal",
316
- preLaunchTask: "npm-install",
317
- },
318
- ],
409
+ configurations: [],
319
410
  };
411
+ if (framework === "ANGULAR") {
412
+ launchJson.configurations.push({
413
+ type: "node",
414
+ request: "launch",
415
+ name: "Angular: debug server-side",
416
+ runtimeExecutable: "npm",
417
+ runtimeArgs: ["run", "start"],
418
+ port: 4200,
419
+ console: "integratedTerminal",
420
+ preLaunchTask: "npm-install",
421
+ });
422
+ }
423
+ else if (framework === "NEXT_JS") {
424
+ launchJson.configurations.push({
425
+ type: "node",
426
+ request: "launch",
427
+ name: "Next.js: debug server-side",
428
+ runtimeExecutable: "npm",
429
+ runtimeArgs: ["run", "dev"],
430
+ port: 9002,
431
+ console: "integratedTerminal",
432
+ preLaunchTask: "npm-install",
433
+ });
434
+ }
435
+ else if (framework === "FLUTTER") {
436
+ launchJson.configurations.push({
437
+ name: "Flutter",
438
+ request: "launch",
439
+ type: "dart",
440
+ preLaunchTask: "flutter-pub-get",
441
+ });
442
+ }
443
+ else {
444
+ return;
445
+ }
320
446
  await fs.writeFile(path.join(vscodeDir, "launch.json"), JSON.stringify(launchJson, null, 2));
321
447
  logger_1.logger.info("✅ Created .vscode/launch.json");
322
448
  }
323
449
  async function cleanupUnusedFiles(rootPath) {
324
450
  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
451
  try {
334
452
  const files = await fs.readdir(docsDir);
335
453
  if (files.length === 0) {
@@ -338,23 +456,57 @@ async function cleanupUnusedFiles(rootPath) {
338
456
  }
339
457
  }
340
458
  catch (err) {
341
- logger_1.logger.debug(`Could not remove ${docsDir}: ${err}`);
459
+ const message = err instanceof Error ? err.message : String(err);
460
+ logger_1.logger.debug(`Could not remove ${docsDir}: ${message}`);
342
461
  }
343
- const metadataPath = path.join(rootPath, "metadata.json");
462
+ const modifiedPath = path.join(rootPath, ".modified");
463
+ try {
464
+ await fs.unlink(modifiedPath);
465
+ logger_1.logger.info("✅ Cleaned up .modified");
466
+ }
467
+ catch (err) {
468
+ const message = err instanceof Error ? err.message : String(err);
469
+ logger_1.logger.debug(`Could not delete ${modifiedPath}: ${message}`);
470
+ }
471
+ const mcpJsonPath = path.join(rootPath, ".idx", "mcp.json");
344
472
  try {
345
- await fs.unlink(metadataPath);
346
- logger_1.logger.info("✅ Cleaned up metadata.json");
473
+ await fs.unlink(mcpJsonPath);
474
+ logger_1.logger.info("✅ Cleaned up .idx/mcp.json");
347
475
  }
348
476
  catch (err) {
349
- logger_1.logger.debug(`Could not delete ${metadataPath}: ${err}`);
477
+ const message = err instanceof Error ? err.message : String(err);
478
+ logger_1.logger.debug(`Could not delete ${mcpJsonPath}: ${message}`);
350
479
  }
351
- const modifiedPath = path.join(rootPath, ".modified");
480
+ }
481
+ async function upgradeGenkitVersion(rootPath) {
482
+ const packageJsonPath = path.join(rootPath, "package.json");
352
483
  try {
353
- await fs.unlink(modifiedPath);
354
- logger_1.logger.info("✅ Cleaned up .modified");
484
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf8");
485
+ const packageJson = JSON.parse(packageJsonContent);
486
+ let modified = false;
487
+ const upgradeDeps = (deps) => {
488
+ if (!deps) {
489
+ return;
490
+ }
491
+ for (const [name, version] of Object.entries(deps)) {
492
+ if (name === "genkit" || name === "genkit-cli" || name.startsWith("@genkit-ai/")) {
493
+ if (version !== "1.29") {
494
+ deps[name] = "1.29";
495
+ modified = true;
496
+ }
497
+ }
498
+ }
499
+ };
500
+ upgradeDeps(packageJson.dependencies);
501
+ upgradeDeps(packageJson.devDependencies);
502
+ if (modified) {
503
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
504
+ logger_1.logger.info("✅ Upgraded Genkit version to 1.29 in package.json");
505
+ }
355
506
  }
356
507
  catch (err) {
357
- logger_1.logger.debug(`Could not delete ${modifiedPath}: ${err}`);
508
+ const message = err instanceof Error ? err.message : String(err);
509
+ logger_1.logger.debug(`Could not upgrade Genkit version: ${message}`);
358
510
  }
359
511
  }
360
512
  async function uploadSecrets(rootPath, projectId) {
@@ -382,57 +534,76 @@ async function uploadSecrets(rootPath, projectId) {
382
534
  }
383
535
  }
384
536
  catch (err) {
385
- utils.logWarning(`Failed to upload GEMINI_API_KEY secret: ${err}`);
537
+ const message = err instanceof Error ? err.message : String(err);
538
+ utils.logWarning(`Failed to upload GEMINI_API_KEY secret: ${message}`);
386
539
  }
387
540
  }
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.');
541
+ async function askToOpenAntigravity(rootPath, appName, startAntigravity) {
542
+ const agyCommand = await getAgyCommand(startAntigravity);
543
+ logger_1.logger.info(`\n🎉 Your Firebase Studio project "${appName}" is now ready for Antigravity!`);
544
+ logger_1.logger.info("Antigravity is Google's agentic IDE, where you can collaborate with AI agents to build, test, and deploy your application.");
545
+ logger_1.logger.info("\nWhat to do next inside Antigravity:");
546
+ logger_1.logger.info(" 1. Review the README.md: It has been updated with specifics about this migrated project.");
547
+ 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.");
548
+ logger_1.logger.info("\nFile any bugs at https://github.com/firebase/firebase-tools/issues");
549
+ if (!startAntigravity || !agyCommand) {
392
550
  return;
393
551
  }
394
552
  const answer = await prompt.confirm({
395
- message: `Migration complete for ${appName}! Would you like to open it in Antigravity now?`,
553
+ message: "Would you like to open it in Antigravity now?",
396
554
  default: true,
397
555
  });
398
556
  if (answer) {
399
557
  logger_1.logger.info(`⏳ Opening ${appName} in Antigravity...`);
400
558
  try {
401
- const agyProcess = (0, child_process_1.spawn)(agyCommand, ["."], {
559
+ const antigravityProcess = (0, child_process_1.spawn)(agyCommand, ["."], {
402
560
  cwd: rootPath,
403
561
  stdio: "ignore",
404
562
  detached: true,
563
+ shell: process.platform === "win32",
405
564
  });
406
- agyProcess.unref();
565
+ antigravityProcess.unref();
407
566
  }
408
567
  catch (err) {
409
568
  utils.logWarning("Could not open Antigravity IDE automatically. Please open it manually.");
410
569
  }
411
570
  }
412
- else {
413
- logger_1.logger.info('\n👉 Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
414
- }
415
571
  }
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
- });
572
+ async function checkDirectoryExists(dir) {
573
+ try {
574
+ const stat = await fs.stat(dir);
575
+ if (!stat.isDirectory()) {
576
+ throw new error_1.FirebaseError(`The path ${dir} is not a directory.`, { exit: 1 });
577
+ }
578
+ }
579
+ catch (err) {
580
+ if (err.code === "ENOENT") {
581
+ throw new error_1.FirebaseError(`The directory ${dir} does not exist.`, { exit: 1 });
582
+ }
583
+ throw err;
421
584
  }
585
+ }
586
+ async function migrate(rootPath, options = { startAntigravity: true }) {
587
+ await checkDirectoryExists(rootPath);
422
588
  const appType = await detectAppType(rootPath);
423
589
  void track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "started" });
424
590
  logger_1.logger.info("🚀 Starting Firebase Studio to Antigravity migration...");
425
- const { projectId, appName, blueprintContent } = await extractMetadata(rootPath, options.project);
426
- await updateReadme(rootPath, blueprintContent, appName);
591
+ const { projectId, appName } = await extractMetadata(rootPath, options.project);
592
+ if (appType) {
593
+ logger_1.logger.info(`✅ Detected framework: ${appType}`);
594
+ }
595
+ await updateReadme(rootPath, appType);
427
596
  await createFirebaseConfigs(rootPath, projectId);
428
597
  await uploadSecrets(rootPath, projectId);
429
- await injectAgyContext(rootPath, projectId, appName);
430
- await writeAgyConfigs(rootPath);
598
+ await upgradeGenkitVersion(rootPath);
599
+ await injectAntigravityContext(rootPath, projectId, appName);
600
+ await writeAntigravityConfigs(rootPath, appType);
601
+ await setupAntigravityMcpServer(rootPath, appType);
431
602
  await cleanupUnusedFiles(rootPath);
432
603
  const currentFolderName = path.basename(rootPath);
433
604
  if (currentFolderName === "download") {
434
- logger_1.logger.info(`\n💡 Tip: You might want to rename this folder to "${appName.toLowerCase().replace(/\s+/g, "-")}"`);
605
+ logger_1.logger.info(`\n💡 Tip: You may want to rename this folder to "${appName.toLowerCase().replace(/\s+/g, "-")}"`);
435
606
  }
436
607
  await track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "success" });
437
- await askToOpenAntigravity(rootPath, appName, options.startAgy);
608
+ await askToOpenAntigravity(rootPath, appName, options.startAntigravity);
438
609
  }