firebase-tools 15.8.1-migrateAngular.0 → 15.9.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.
@@ -71,7 +71,7 @@ exports.WARN_NO_BACKENDS = "To use this secret, your backend's service account m
71
71
  "It does not look like you have a backend yet. After creating a backend, grant access with " +
72
72
  clc.bold("firebase apphosting:secrets:grantaccess");
73
73
  exports.GRANT_ACCESS_IN_FUTURE = `To grant access in the future, run ${clc.bold("firebase apphosting:secrets:grantaccess")}`;
74
- async function selectBackendServiceAccounts(projectNumber, projectId, options) {
74
+ async function selectBackendServiceAccounts(projectNumber, projectId, nonInteractive) {
75
75
  const listBackends = await apphosting.listBackends(projectId, "-");
76
76
  if (listBackends.unreachable.length) {
77
77
  utils.logWarning(`Could not reach location(s) ${listBackends.unreachable.join(", ")}. You may need to run ` +
@@ -83,7 +83,7 @@ async function selectBackendServiceAccounts(projectNumber, projectId, options) {
83
83
  }
84
84
  if (listBackends.backends.length === 1) {
85
85
  const grant = await prompt.confirm({
86
- nonInteractive: options.nonInteractive,
86
+ nonInteractive,
87
87
  default: true,
88
88
  message: "To use this secret, your backend's service account must be granted access. Would you like to grant access now?",
89
89
  });
@@ -101,7 +101,7 @@ async function selectBackendServiceAccounts(projectNumber, projectId, options) {
101
101
  serviceAccountDisplay(metadata[0]) +
102
102
  ".\nGranting access to one backend will grant access to all backends.");
103
103
  const grant = await prompt.confirm({
104
- nonInteractive: options.nonInteractive,
104
+ nonInteractive,
105
105
  default: true,
106
106
  message: "Would you like to grant access to all backends now?",
107
107
  });
@@ -7,6 +7,8 @@ exports.grantEmailsSecretAccess = grantEmailsSecretAccess;
7
7
  exports.upsertSecret = upsertSecret;
8
8
  exports.fetchSecrets = fetchSecrets;
9
9
  exports.getSecretNameParts = getSecretNameParts;
10
+ exports.apphostingSecretsSetAction = apphostingSecretsSetAction;
11
+ const clc = require("colorette");
10
12
  const error_1 = require("../../error");
11
13
  const gcsm = require("../../gcp/secretManager");
12
14
  const gcb = require("../../gcp/cloudbuild");
@@ -16,6 +18,9 @@ const secretManager_1 = require("../../gcp/secretManager");
16
18
  const secretManager_2 = require("../../gcp/secretManager");
17
19
  const utils = require("../../utils");
18
20
  const prompt = require("../../prompt");
21
+ const dialogs = require("../../apphosting/secrets/dialogs");
22
+ const config = require("../../apphosting/config");
23
+ const projects_1 = require("../../management/projects");
19
24
  function toMulti(accounts) {
20
25
  const m = {
21
26
  buildServiceAccounts: [accounts.buildServiceAccount],
@@ -169,3 +174,57 @@ function getSecretNameParts(secret) {
169
174
  }
170
175
  return [name, version];
171
176
  }
177
+ async function apphostingSecretsSetAction(secretName, projectId, projectNumber, location, dataFile, nonInteractive) {
178
+ if (!projectNumber) {
179
+ projectNumber = (await (0, projects_1.getProject)(projectId)).projectNumber;
180
+ }
181
+ const created = await upsertSecret(projectId, secretName, location);
182
+ if (created === null) {
183
+ return;
184
+ }
185
+ else if (created) {
186
+ utils.logSuccess(`Created new secret projects/${projectId}/secrets/${secretName}`);
187
+ }
188
+ const secretValue = await utils.readSecretValue(`Enter a value for ${secretName}`, dataFile);
189
+ const version = await gcsm.addVersion(projectId, secretName, secretValue);
190
+ utils.logSuccess(`Created new secret version ${gcsm.toSecretVersionResourceName(version)}`);
191
+ utils.logBullet(`You can access the contents of the secret's latest value with ${clc.bold(`firebase apphosting:secrets:access ${secretName}\n`)}`);
192
+ if (!created) {
193
+ return;
194
+ }
195
+ const type = await prompt.select({
196
+ message: "Is this secret for production or only local testing?",
197
+ choices: [
198
+ { name: "Production", value: "production" },
199
+ { name: "Local testing only", value: "local" },
200
+ ],
201
+ nonInteractive: !!nonInteractive,
202
+ default: "production",
203
+ });
204
+ if (type === "local") {
205
+ const emailList = await prompt.input({
206
+ message: "Please enter a comma separated list of user or groups who should have access to this secret:",
207
+ });
208
+ if (emailList.length) {
209
+ await grantEmailsSecretAccess(projectId, [secretName], emailList.split(","));
210
+ }
211
+ else {
212
+ utils.logBullet("To grant access in the future run " +
213
+ clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --emails [email list]`));
214
+ }
215
+ await config.maybeAddSecretToYaml(secretName, config.APPHOSTING_EMULATORS_YAML_FILE);
216
+ return;
217
+ }
218
+ const accounts = await dialogs.selectBackendServiceAccounts(projectNumber, projectId, !!nonInteractive);
219
+ if (!accounts.buildServiceAccounts.length && !accounts.runServiceAccounts.length) {
220
+ utils.logWarning(`To use this secret in your backend, you must grant access. You can do so in the future with ${clc.bold("firebase apphosting:secrets:grantaccess")}`);
221
+ }
222
+ else {
223
+ await grantSecretAccess(projectId, projectNumber, secretName, accounts);
224
+ }
225
+ await config.maybeAddSecretToYaml(secretName, config.APPHOSTING_BASE_YAML_FILE);
226
+ utils.logBullet("To grant additional users access to this secret run " +
227
+ clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --email [email list]`) +
228
+ ".\nTo grant additional backends access to this secret run " +
229
+ clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --backend [backend ID]`));
230
+ }
@@ -1,18 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.command = void 0;
4
- const clc = require("colorette");
5
4
  const command_1 = require("../command");
6
5
  const projectUtils_1 = require("../projectUtils");
7
6
  const requireAuth_1 = require("../requireAuth");
8
7
  const gcsm = require("../gcp/secretManager");
9
8
  const apphosting = require("../gcp/apphosting");
10
9
  const requirePermissions_1 = require("../requirePermissions");
11
- const secrets = require("../apphosting/secrets");
12
- const dialogs = require("../apphosting/secrets/dialogs");
13
- const config = require("../apphosting/config");
14
- const utils = require("../utils");
15
- const prompt = require("../prompt");
10
+ const secrets_1 = require("../apphosting/secrets");
16
11
  exports.command = new command_1.Command("apphosting:secrets:set <secretName>")
17
12
  .description("create or update a secret for use in Firebase App Hosting")
18
13
  .option("-l, --location <location>", "optional location to retrict secret replication")
@@ -32,51 +27,5 @@ exports.command = new command_1.Command("apphosting:secrets:set <secretName>")
32
27
  .action(async (secretName, options) => {
33
28
  const projectId = (0, projectUtils_1.needProjectId)(options);
34
29
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
35
- const created = await secrets.upsertSecret(projectId, secretName, options.location);
36
- if (created === null) {
37
- return;
38
- }
39
- else if (created) {
40
- utils.logSuccess(`Created new secret projects/${projectId}/secrets/${secretName}`);
41
- }
42
- const secretValue = await utils.readSecretValue(`Enter a value for ${secretName}`, options.dataFile);
43
- const version = await gcsm.addVersion(projectId, secretName, secretValue);
44
- utils.logSuccess(`Created new secret version ${gcsm.toSecretVersionResourceName(version)}`);
45
- utils.logBullet(`You can access the contents of the secret's latest value with ${clc.bold(`firebase apphosting:secrets:access ${secretName}\n`)}`);
46
- if (!created) {
47
- return;
48
- }
49
- const type = await prompt.select({
50
- message: "Is this secret for production or only local testing?",
51
- choices: [
52
- { name: "Production", value: "production" },
53
- { name: "Local testing only", value: "local" },
54
- ],
55
- });
56
- if (type === "local") {
57
- const emailList = await prompt.input({
58
- message: "Please enter a comma separated list of user or groups who should have access to this secret:",
59
- });
60
- if (emailList.length) {
61
- await secrets.grantEmailsSecretAccess(projectId, [secretName], emailList.split(","));
62
- }
63
- else {
64
- utils.logBullet("To grant access in the future run " +
65
- clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --emails [email list]`));
66
- }
67
- await config.maybeAddSecretToYaml(secretName, config.APPHOSTING_EMULATORS_YAML_FILE);
68
- return;
69
- }
70
- const accounts = await dialogs.selectBackendServiceAccounts(projectNumber, projectId, options);
71
- if (!accounts.buildServiceAccounts.length && !accounts.runServiceAccounts.length) {
72
- utils.logWarning(`To use this secret in your backend, you must grant access. You can do so in the future with ${clc.bold("firebase apphosting:secrets:grantaccess")}`);
73
- }
74
- else {
75
- await secrets.grantSecretAccess(projectId, projectNumber, secretName, accounts);
76
- }
77
- await config.maybeAddSecretToYaml(secretName, config.APPHOSTING_BASE_YAML_FILE);
78
- utils.logBullet("To grant additional users access to this secret run " +
79
- clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --email [email list]`) +
80
- ".\nTo grant additional backends access to this secret run " +
81
- clc.bold(`firebase apphosting:secrets:grantaccess ${secretName} --backend [backend ID]`));
30
+ return (0, secrets_1.apphostingSecretsSetAction)(secretName, projectId, projectNumber, options.location, options.dataFile, options.nonInteractive);
82
31
  });
@@ -17,5 +17,5 @@ exports.command = new command_1.Command("studio:export <path>")
17
17
  }
18
18
  const rootPath = path.resolve(exportPath);
19
19
  logger_1.logger.info(`Exporting Studio apps from ${rootPath} to Antigravity...`);
20
- await (0, migrate_1.migrate)(rootPath, { noStartAgy: !options.startAgy });
20
+ await (0, migrate_1.migrate)(rootPath, options);
21
21
  });
@@ -24,6 +24,7 @@ const cloudSqlAdminClient = require("../gcp/cloudsql/cloudsqladmin");
24
24
  const errors = require("./errors");
25
25
  const provisionCloudSql_1 = require("./provisionCloudSql");
26
26
  const requireAuth_1 = require("../requireAuth");
27
+ const cloudbilling_1 = require("../gcp/cloudbilling");
27
28
  async function setupSchemaIfNecessary(instanceId, databaseId, options) {
28
29
  try {
29
30
  await (0, connect_1.setupIAMUsers)(instanceId, options);
@@ -429,7 +430,17 @@ async function ensureServiceIsConnectedToCloudSql(serviceName, instanceName, dat
429
430
  postgresql?.schemaValidation === "NONE") {
430
431
  const [, , , , , serviceId] = serviceName.split("/");
431
432
  const [, projectId, , , , instanceId] = postgresql.cloudSql.instance.split("/");
432
- throw new error_1.FirebaseError(`While checking the service ${serviceId}, ` + (0, provisionCloudSql_1.cloudSQLBeingCreated)(projectId, instanceId));
433
+ let isFreeTrial = false;
434
+ let billingEnabled = false;
435
+ try {
436
+ const instance = await cloudSqlAdminClient.getInstance(projectId, instanceId);
437
+ isFreeTrial = instance.settings?.userLabels?.["firebase-data-connect"] === "ft";
438
+ billingEnabled = await (0, cloudbilling_1.checkBillingEnabled)(projectId);
439
+ }
440
+ catch (err) {
441
+ }
442
+ throw new error_1.FirebaseError(`While checking the service ${serviceId}, ` +
443
+ (0, provisionCloudSql_1.cloudSQLBeingCreated)(projectId, instanceId, isFreeTrial, billingEnabled));
433
444
  }
434
445
  if (!currentSchema || !postgresql) {
435
446
  if (!linkIfNotConnected) {
@@ -54,36 +54,36 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "3.2.0",
58
- "expectedSize": 30823264,
59
- "expectedChecksum": "165dcd011685866ca7a1970b6f2605f0",
60
- "expectedChecksumSHA256": "a4d0a700ae38172324927c7054e7ade86dbdae87389be1d801665e46d3759451",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.2.0",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.0"
57
+ "version": "3.2.1",
58
+ "expectedSize": 30880608,
59
+ "expectedChecksum": "ef63443e5132e4ade7b89506fea60e29",
60
+ "expectedChecksumSHA256": "0a9c05413cb3048a4bef1968e55109ca67eb607e6d77355741cb9a7dcae51607",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.2.1",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.1"
63
63
  },
64
64
  "darwin_arm64": {
65
- "version": "3.2.0",
66
- "expectedSize": 30287890,
67
- "expectedChecksum": "b1c75fa410033ed93463ab41b2bd8e61",
68
- "expectedChecksumSHA256": "408c52c857af86df7bcf71b9ab7d9e1eeb29a12f042d79c76000e85f22a2e975",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.2.0",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.0"
65
+ "version": "3.2.1",
66
+ "expectedSize": 30339010,
67
+ "expectedChecksum": "866989b28ae5cad1084403bd4404dcee",
68
+ "expectedChecksumSHA256": "558e334c456c646b1ddf4b32ab43d976c5b03c191a4c92855e8c93cd32e1215a",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.2.1",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.1"
71
71
  },
72
72
  "win32": {
73
- "version": "3.2.0",
74
- "expectedSize": 31328256,
75
- "expectedChecksum": "474319dff7a1a1eb364c1b5144ae11f5",
76
- "expectedChecksumSHA256": "9f3852e35c60bb7adfc2bd24fd2d0a2f815659dbb6fec48bab0bd07bbd7d1651",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.2.0",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.0.exe"
73
+ "version": "3.2.1",
74
+ "expectedSize": 31385088,
75
+ "expectedChecksum": "cb46949156c889d5724f7fa80e8a613b",
76
+ "expectedChecksumSHA256": "1dd1f9ef7910d8295baac61741fde29fac536d82ff841a2d5cefe25e98f7cd98",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.2.1",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.1.exe"
79
79
  },
80
80
  "linux": {
81
- "version": "3.2.0",
82
- "expectedSize": 30744760,
83
- "expectedChecksum": "dc9f0766287d982a0dabf1e2f90aa726",
84
- "expectedChecksumSHA256": "82bd05e748526330b94b2d2d797507175ea1251b1760f64bc1d1e904f23c2f22",
85
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.2.0",
86
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.0"
81
+ "version": "3.2.1",
82
+ "expectedSize": 30798008,
83
+ "expectedChecksum": "8c29b6b526efcbf13de278bd3b5b598a",
84
+ "expectedChecksumSHA256": "d7eb7f7afd98dd9018039c83782312f975b160c3708eb49f8fecdf7d27508d82",
85
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.2.1",
86
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-3.2.1"
87
87
  }
88
88
  }
89
89
  }
@@ -142,7 +142,7 @@ exports.ALL_EXPERIMENTS = experiments({
142
142
  },
143
143
  fdcwebhooks: {
144
144
  shortDescription: "Enable Firebase Data Connect webhooks feature.",
145
- default: false,
145
+ default: true,
146
146
  public: false,
147
147
  },
148
148
  studioexport: {
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractMetadata = extractMetadata;
4
+ exports.uploadSecrets = uploadSecrets;
3
5
  exports.migrate = migrate;
4
6
  const fs = require("fs/promises");
5
7
  const path = require("path");
@@ -10,7 +12,8 @@ const prompt = require("../prompt");
10
12
  const apphosting = require("../gcp/apphosting");
11
13
  const utils = require("../utils");
12
14
  const templates_1 = require("../templates");
13
- const frameworks_1 = require("../frameworks");
15
+ const secrets_1 = require("../apphosting/secrets");
16
+ const env = require("../functions/env");
14
17
  async function downloadGitHubDir(apiUrl, localPath) {
15
18
  const response = await fetch(apiUrl);
16
19
  if (!response.ok) {
@@ -32,7 +35,7 @@ async function downloadGitHubDir(apiUrl, localPath) {
32
35
  }
33
36
  }
34
37
  }
35
- async function extractMetadata(rootPath) {
38
+ async function extractMetadata(rootPath, overrideProjectId) {
36
39
  const metadataPath = path.join(rootPath, "metadata.json");
37
40
  let metadata = {};
38
41
  try {
@@ -42,7 +45,7 @@ async function extractMetadata(rootPath) {
42
45
  catch (err) {
43
46
  logger_1.logger.debug(`Could not read metadata.json at ${metadataPath}: ${err}`);
44
47
  }
45
- let projectId = metadata.projectId;
48
+ let projectId = overrideProjectId || metadata.projectId;
46
49
  if (!projectId) {
47
50
  try {
48
51
  const firebasercContent = await fs.readFile(path.join(rootPath, ".firebaserc"), "utf8");
@@ -77,17 +80,13 @@ async function extractMetadata(rootPath) {
77
80
  }
78
81
  return { projectId, appName, blueprintContent };
79
82
  }
80
- async function updateReadme(rootPath, blueprintContent, appName, framework) {
83
+ async function updateReadme(rootPath, blueprintContent, appName) {
81
84
  const readmePath = path.join(rootPath, "README.md");
82
85
  const readmeTemplate = await (0, templates_1.readTemplate)("firebase-studio-export/readme_template.md");
83
- const startCommand = framework === "angular" ? "npm run start" : "npm run dev";
84
- const localUrl = framework === "angular" ? "http://localhost:4200" : "http://localhost:9002";
85
86
  const newReadme = readmeTemplate
86
87
  .replace(/\${appName}/g, appName)
87
88
  .replace("${exportDate}", new Date().toISOString().split("T")[0])
88
- .replace("${blueprintContent}", blueprintContent.replace(/# \*\*App Name\*\*: .*/, "").trim())
89
- .replace("${startCommand}", startCommand)
90
- .replace("${localUrl}", localUrl);
89
+ .replace("${blueprintContent}", blueprintContent.replace(/# \*\*App Name\*\*: .*/, "").trim());
91
90
  await fs.writeFile(readmePath, newReadme);
92
91
  logger_1.logger.info("āœ… Updated README.md with project details and origin info");
93
92
  }
@@ -147,7 +146,10 @@ async function injectAgyContext(rootPath, projectId, appName) {
147
146
  logger_1.logger.debug(`Could not read or write startup workflow: ${err}`);
148
147
  }
149
148
  }
150
- async function assertSystemState() {
149
+ async function assertSystemState(startAgy) {
150
+ if (startAgy === false) {
151
+ return;
152
+ }
151
153
  try {
152
154
  (0, child_process_1.execSync)("agy --version", { stdio: "ignore" });
153
155
  logger_1.logger.info("āœ… Antigravity IDE CLI (agy) detected");
@@ -214,7 +216,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
214
216
  logger_1.logger.info(`āœ… Created firebase.json with backendId: ${backendId}`);
215
217
  }
216
218
  }
217
- async function writeAgyConfigs(rootPath, framework) {
219
+ async function writeAgyConfigs(rootPath) {
218
220
  const vscodeDir = path.join(rootPath, ".vscode");
219
221
  await fs.mkdir(vscodeDir, { recursive: true });
220
222
  const tasksJson = {
@@ -250,32 +252,19 @@ async function writeAgyConfigs(rootPath, framework) {
250
252
  logger_1.logger.info("āœ… Updated .vscode/settings.json with startup preferences");
251
253
  const launchJson = {
252
254
  version: "0.2.0",
253
- configurations: [],
255
+ configurations: [
256
+ {
257
+ type: "node",
258
+ request: "launch",
259
+ name: "Next.js: debug server-side",
260
+ runtimeExecutable: "npm",
261
+ runtimeArgs: ["run", "dev"],
262
+ port: 9002,
263
+ console: "integratedTerminal",
264
+ preLaunchTask: "npm-install",
265
+ },
266
+ ],
254
267
  };
255
- if (framework === "angular") {
256
- launchJson.configurations.push({
257
- type: "node",
258
- request: "launch",
259
- name: "Angular: debug server-side",
260
- runtimeExecutable: "npm",
261
- runtimeArgs: ["run", "start"],
262
- port: 4200,
263
- console: "integratedTerminal",
264
- preLaunchTask: "npm-install",
265
- });
266
- }
267
- else {
268
- launchJson.configurations.push({
269
- type: "node",
270
- request: "launch",
271
- name: "Next.js: debug server-side",
272
- runtimeExecutable: "npm",
273
- runtimeArgs: ["run", "dev"],
274
- port: 9002,
275
- console: "integratedTerminal",
276
- preLaunchTask: "npm-install",
277
- });
278
- }
279
268
  await fs.writeFile(path.join(vscodeDir, "launch.json"), JSON.stringify(launchJson, null, 2));
280
269
  logger_1.logger.info("āœ… Created .vscode/launch.json");
281
270
  }
@@ -316,8 +305,36 @@ async function cleanupUnusedFiles(rootPath) {
316
305
  logger_1.logger.debug(`Could not delete ${modifiedPath}: ${err}`);
317
306
  }
318
307
  }
319
- async function askToOpenAntigravity(rootPath, appName, noStartAgyFlag) {
320
- if (noStartAgyFlag) {
308
+ async function uploadSecrets(rootPath, projectId) {
309
+ if (!projectId) {
310
+ return;
311
+ }
312
+ const envPath = path.join(rootPath, ".env");
313
+ try {
314
+ await fs.access(envPath);
315
+ }
316
+ catch {
317
+ return;
318
+ }
319
+ try {
320
+ const envContent = await fs.readFile(envPath, "utf8");
321
+ const parsedEnv = env.parse(envContent);
322
+ const geminiApiKey = parsedEnv.envs["GEMINI_API_KEY"];
323
+ if (geminiApiKey && geminiApiKey.trim().length > 0) {
324
+ logger_1.logger.info("ā³ Uploading GEMINI_API_KEY from .env to App Hosting secrets...");
325
+ await (0, secrets_1.apphostingSecretsSetAction)("GEMINI_API_KEY", projectId, undefined, undefined, envPath, true);
326
+ logger_1.logger.info("āœ… Uploaded GEMINI_API_KEY secret");
327
+ }
328
+ else {
329
+ logger_1.logger.debug("Skipping GEMINI_API_KEY upload: key is missing or blank in .env");
330
+ }
331
+ }
332
+ catch (err) {
333
+ utils.logWarning(`Failed to upload GEMINI_API_KEY secret: ${err}`);
334
+ }
335
+ }
336
+ async function askToOpenAntigravity(rootPath, appName, startAgy) {
337
+ if (startAgy === false) {
321
338
  logger_1.logger.info('\nšŸ‘‰ Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
322
339
  return;
323
340
  }
@@ -343,23 +360,19 @@ async function askToOpenAntigravity(rootPath, appName, noStartAgyFlag) {
343
360
  logger_1.logger.info('\nšŸ‘‰ Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
344
361
  }
345
362
  }
346
- async function migrate(rootPath, options = { noStartAgy: false }) {
363
+ async function migrate(rootPath, options = { startAgy: true }) {
347
364
  logger_1.logger.info("šŸš€ Starting Firebase Studio to Antigravity migration...");
348
- await assertSystemState();
349
- const { projectId, appName, blueprintContent } = await extractMetadata(rootPath);
350
- const discovery = await (0, frameworks_1.discover)(rootPath);
351
- const framework = discovery?.framework;
352
- if (framework) {
353
- logger_1.logger.info(`āœ… Detected framework: ${framework}`);
354
- }
355
- await updateReadme(rootPath, blueprintContent, appName, framework);
365
+ await assertSystemState(options.startAgy);
366
+ const { projectId, appName, blueprintContent } = await extractMetadata(rootPath, options.project);
367
+ await updateReadme(rootPath, blueprintContent, appName);
356
368
  await createFirebaseConfigs(rootPath, projectId);
369
+ await uploadSecrets(rootPath, projectId);
357
370
  await injectAgyContext(rootPath, projectId, appName);
358
- await writeAgyConfigs(rootPath, framework);
371
+ await writeAgyConfigs(rootPath);
359
372
  await cleanupUnusedFiles(rootPath);
360
373
  const currentFolderName = path.basename(rootPath);
361
374
  if (currentFolderName === "download") {
362
375
  logger_1.logger.info(`\nšŸ’” Tip: You might want to rename this folder to "${appName.toLowerCase().replace(/\s+/g, "-")}"`);
363
376
  }
364
- await askToOpenAntigravity(rootPath, appName, options.noStartAgy);
377
+ await askToOpenAntigravity(rootPath, appName, options.startAgy);
365
378
  }
@@ -23,6 +23,7 @@ const projectUtils_1 = require("../../projectUtils");
23
23
  const logger_1 = require("../../logger");
24
24
  const iam_1 = require("../iam");
25
25
  const error_1 = require("../../error");
26
+ const freeTrial_1 = require("../../dataconnect/freeTrial");
26
27
  const API_VERSION = "v1";
27
28
  const client = new apiv2_1.Client({
28
29
  urlPrefix: (0, api_1.cloudSQLAdminOrigin)(),
@@ -91,7 +92,7 @@ async function createInstance(args) {
91
92
  return;
92
93
  }
93
94
  catch (err) {
94
- handleAllowlistError(err, args.location);
95
+ await handleCreateInstanceError(err, args.location, args.projectId);
95
96
  throw err;
96
97
  }
97
98
  }
@@ -119,10 +120,14 @@ async function updateInstanceForDataConnect(instance, enableGoogleMlIntegration)
119
120
  });
120
121
  return pollRes;
121
122
  }
122
- function handleAllowlistError(err, region) {
123
- if (err.message.includes("Not allowed to set system label: firebase-data-connect")) {
123
+ async function handleCreateInstanceError(err, region, projectId) {
124
+ if (err?.message?.includes("Not allowed to set system label: firebase-data-connect")) {
124
125
  throw new error_1.FirebaseError(`Cloud SQL free trial instances are not yet available in ${region}. Please check https://firebase.google.com/docs/data-connect/ for a full list of available regions.`);
125
126
  }
127
+ if (err?.message?.includes("The billing account is not in good standing") &&
128
+ (await (0, freeTrial_1.checkFreeTrialInstanceUsed)(projectId))) {
129
+ throw new error_1.FirebaseError(`You have already used your Cloud SQL free trial. To create more instances, you need to attach a billing account to project ${projectId}.`);
130
+ }
126
131
  }
127
132
  function setDatabaseFlag(flag, flags = []) {
128
133
  const temp = flags.filter((f) => f.name !== flag.name);
@@ -64,8 +64,9 @@ Here are a list of prerequisite steps that must be completed before running a te
64
64
 
65
65
  * Goal (required): In one sentence or less, describe what you want the agent to do in this step.
66
66
  * Hint (optional): Provide additional information to help Gemini understand and navigate your app.
67
- * Final Screen Assertion (required for last step): Your final screen assertion should be phrased as an observation, such as 'The screen shows a
68
- success message' or 'The checkout page is visible'. Optional except for the last step, for which it is required.
67
+ * Final Screen Assertion (required for last step): Your final screen assertion should be phrased as an observation, such
68
+ as 'The screen shows a success message' or 'The checkout page is visible'. You can think of these as test assertions
69
+ that are checked at the end of the step. Optional except for the last step, for which it is required.
69
70
 
70
71
  The developer has optionally specified the following description for their test:
71
72
  * ${testDescription}
@@ -79,8 +80,8 @@ Here are a list of prerequisite steps that must be completed before running a te
79
80
  For example, if a step has a list in it, it should probably be broken up into multiple steps. Steps do not need
80
81
  to be too small though. The test case should provide a good balance between strict guidance and flexibility. As a
81
82
  rule of thumb, each step should require between 2-5 actions.
82
- * Include a hint and success criteria whenever possible. Specifically, try to always include a success criteria to help
83
- the agent determine when the goal has been completed.
83
+ * Include a hint and final screen assertion whenever possible. Specifically, try to always include a final screen assertion
84
+ to help the agent determine when the goal has been completed.
84
85
  * Avoid functionality that the app testing agent struggles with. The app testing agent struggles with the following:
85
86
  * Journeys that require specific timing (like observing that something should be visible for a certain number of
86
87
  seconds), interacting with moving or transient elements, etc.
@@ -96,7 +97,7 @@ Here are a list of prerequisite steps that must be completed before running a te
96
97
  above, convert the test description provided by the user to make it easier for the agent to follow so that the tests can
97
98
  be re-run reliably. If there is no test description, generate a test case that you think will be useful given the functionality
98
99
  of the app. Generate an explanation on why you generated the new test case the way you did, and then generate the
99
- new test case, which again is an array of steps where each step contains a goal, hint, and success criteria. Show this
100
+ new test case, which again is an array of steps where each step contains a goal, hint, and final screen assertion. Show this
100
101
  to the user and have them confirm before moving forward.
101
102
 
102
103
  ## Run Test
@@ -27,7 +27,7 @@ const AiStepSchema = zod_1.z
27
27
  finalScreenAssertion: zod_1.z
28
28
  .string()
29
29
  .optional()
30
- .describe("A description of criteria the agent should use to determine if the goal has been successfully completed."),
30
+ .describe("A description of what the screen should look like at the end of the step, to determine if the goal has been successfully completed."),
31
31
  })
32
32
  .describe("Step within a test case; run during the execution of the test.");
33
33
  const defaultDevices = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "15.8.1-migrateAngular.0",
3
+ "version": "15.9.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "mcpName": "io.github.firebase/firebase-mcp",
@@ -8,4 +8,4 @@ ${blueprintContent}
8
8
 
9
9
  ---
10
10
 
11
- To get started, run `${startCommand}` and visit `${localUrl}`.
11
+ To get started, run \`npm run dev\` and visit \`http://localhost:9002\`.
@@ -15,7 +15,7 @@
15
15
  "main": "index.js",
16
16
  "dependencies": {
17
17
  "firebase-admin": "^13.6.0",
18
- "firebase-functions": "^7.0.5",
18
+ "firebase-functions": "^7.1.0",
19
19
  "@apollo/server": "^5.2.0",
20
20
  "@as-integrations/express4": "^1.1.2"
21
21
  },
@@ -14,7 +14,7 @@
14
14
  "main": "index.js",
15
15
  "dependencies": {
16
16
  "firebase-admin": "^13.6.0",
17
- "firebase-functions": "^7.0.5",
17
+ "firebase-functions": "^7.1.0",
18
18
  "@apollo/server": "^5.2.0",
19
19
  "@as-integrations/express4": "^1.1.2"
20
20
  },
@@ -16,7 +16,7 @@
16
16
  "main": "lib/index.js",
17
17
  "dependencies": {
18
18
  "firebase-admin": "^13.6.0",
19
- "firebase-functions": "^7.0.5",
19
+ "firebase-functions": "^7.1.0",
20
20
  "@apollo/server": "^5.2.0",
21
21
  "@as-integrations/express4": "^1.1.2"
22
22
  },
@@ -15,7 +15,7 @@
15
15
  "main": "lib/index.js",
16
16
  "dependencies": {
17
17
  "firebase-admin": "^13.6.0",
18
- "firebase-functions": "^7.0.5",
18
+ "firebase-functions": "^7.1.0",
19
19
  "@apollo/server": "^5.2.0",
20
20
  "@as-integrations/express4": "^1.1.2"
21
21
  },