firebase-tools 15.9.1 → 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.
@@ -19,6 +19,7 @@ class HubExport {
19
19
  this.projectId = projectId;
20
20
  this.options = options;
21
21
  this.exportPath = options.path;
22
+ this.exportTargets = options.targets ?? [...types_1.IMPORT_EXPORT_EMULATORS];
22
23
  this.tmpDir = fs.mkdtempSync(`firebase-export-${new Date().getTime()}`);
23
24
  }
24
25
  static readMetadata(exportPath) {
@@ -36,14 +37,14 @@ class HubExport {
36
37
  }
37
38
  }
38
39
  async exportAll() {
39
- const toExport = types_1.ALL_EMULATORS.filter(shouldExport);
40
+ const toExport = types_1.ALL_EMULATORS.filter(shouldExport).filter((e) => this.exportTargets.includes(e));
40
41
  if (toExport.length === 0) {
41
42
  throw new error_1.FirebaseError("No running emulators support import/export.");
42
43
  }
43
44
  const metadata = {
44
45
  version: hub_1.EmulatorHub.CLI_VERSION,
45
46
  };
46
- if (shouldExport(types_1.Emulators.FIRESTORE)) {
47
+ if (shouldExport(types_1.Emulators.FIRESTORE) && toExport.includes(types_1.Emulators.FIRESTORE)) {
47
48
  metadata.firestore = {
48
49
  version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.FIRESTORE).version,
49
50
  path: "firestore_export",
@@ -51,28 +52,28 @@ class HubExport {
51
52
  };
52
53
  await this.exportFirestore(metadata);
53
54
  }
54
- if (shouldExport(types_1.Emulators.DATABASE)) {
55
+ if (shouldExport(types_1.Emulators.DATABASE) && toExport.includes(types_1.Emulators.DATABASE)) {
55
56
  metadata.database = {
56
57
  version: (0, downloadableEmulators_1.getDownloadDetails)(types_1.Emulators.DATABASE).version,
57
58
  path: "database_export",
58
59
  };
59
60
  await this.exportDatabase(metadata);
60
61
  }
61
- if (shouldExport(types_1.Emulators.AUTH)) {
62
+ if (shouldExport(types_1.Emulators.AUTH) && toExport.includes(types_1.Emulators.AUTH)) {
62
63
  metadata.auth = {
63
64
  version: hub_1.EmulatorHub.CLI_VERSION,
64
65
  path: "auth_export",
65
66
  };
66
67
  await this.exportAuth(metadata);
67
68
  }
68
- if (shouldExport(types_1.Emulators.STORAGE)) {
69
+ if (shouldExport(types_1.Emulators.STORAGE) && toExport.includes(types_1.Emulators.STORAGE)) {
69
70
  metadata.storage = {
70
71
  version: hub_1.EmulatorHub.CLI_VERSION,
71
72
  path: "storage_export",
72
73
  };
73
74
  await this.exportStorage(metadata);
74
75
  }
75
- if (shouldExport(types_1.Emulators.DATACONNECT)) {
76
+ if (shouldExport(types_1.Emulators.DATACONNECT) && toExport.includes(types_1.Emulators.DATACONNECT)) {
76
77
  metadata.dataconnect = {
77
78
  version: hub_1.EmulatorHub.CLI_VERSION,
78
79
  path: "dataconnect_export",
@@ -104,6 +104,11 @@ exports.ALL_EXPERIMENTS = experiments({
104
104
  default: true,
105
105
  public: false,
106
106
  },
107
+ apphostinglocalbuilds: {
108
+ shortDescription: "Enable App Hosting local builds",
109
+ default: false,
110
+ public: false,
111
+ },
107
112
  dataconnect: {
108
113
  shortDescription: "Deprecated. Previosuly, enabled Data Connect related features.",
109
114
  fullDescription: "Deprecated. Previously, enabled Data Connect related features.",
@@ -145,11 +150,6 @@ exports.ALL_EXPERIMENTS = experiments({
145
150
  default: true,
146
151
  public: false,
147
152
  },
148
- studioexport: {
149
- shortDescription: "Enable the experimental studio:export command.",
150
- default: false,
151
- public: false,
152
- },
153
153
  });
154
154
  function isValidExperiment(name) {
155
155
  return Object.keys(exports.ALL_EXPERIMENTS).includes(name);
@@ -7,13 +7,88 @@ const fs = require("fs/promises");
7
7
  const path = require("path");
8
8
  const child_process_1 = require("child_process");
9
9
  const logger_1 = require("../logger");
10
- const error_1 = require("../error");
11
10
  const prompt = require("../prompt");
12
11
  const apphosting = require("../gcp/apphosting");
13
12
  const utils = require("../utils");
14
13
  const templates_1 = require("../templates");
14
+ const track = require("../track");
15
15
  const secrets_1 = require("../apphosting/secrets");
16
16
  const env = require("../functions/env");
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
+ }
55
+ async function detectAppType(rootPath) {
56
+ try {
57
+ await fs.access(path.join(rootPath, "pubspec.yaml"));
58
+ return "FLUTTER";
59
+ }
60
+ catch {
61
+ }
62
+ try {
63
+ await fs.access(path.join(rootPath, "angular.json"));
64
+ return "ANGULAR";
65
+ }
66
+ catch {
67
+ }
68
+ try {
69
+ const packageJsonPath = path.join(rootPath, "package.json");
70
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf8");
71
+ const packageJson = JSON.parse(packageJsonContent);
72
+ const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
73
+ if (deps.next) {
74
+ return "NEXT_JS";
75
+ }
76
+ if (deps["@angular/core"]) {
77
+ return "ANGULAR";
78
+ }
79
+ }
80
+ catch {
81
+ }
82
+ for (const configFile of ["next.config.js", "next.config.mjs"]) {
83
+ try {
84
+ await fs.access(path.join(rootPath, configFile));
85
+ return "NEXT_JS";
86
+ }
87
+ catch {
88
+ }
89
+ }
90
+ return "OTHER";
91
+ }
17
92
  async function downloadGitHubDir(apiUrl, localPath) {
18
93
  const response = await fetch(apiUrl);
19
94
  if (!response.ok) {
@@ -35,6 +110,10 @@ async function downloadGitHubDir(apiUrl, localPath) {
35
110
  }
36
111
  }
37
112
  }
113
+ const isValidFirebaseProjectId = (projectId) => {
114
+ const projectIdRegex = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
115
+ return projectIdRegex.test(projectId);
116
+ };
38
117
  async function extractMetadata(rootPath, overrideProjectId) {
39
118
  const metadataPath = path.join(rootPath, "metadata.json");
40
119
  let metadata = {};
@@ -45,6 +124,8 @@ async function extractMetadata(rootPath, overrideProjectId) {
45
124
  catch (err) {
46
125
  logger_1.logger.debug(`Could not read metadata.json at ${metadataPath}: ${err}`);
47
126
  }
127
+ logger_1.logger.debug(`overrideProjectId ${overrideProjectId}`);
128
+ logger_1.logger.debug(`metadata.projectId ${metadata.projectId}`);
48
129
  let projectId = overrideProjectId || metadata.projectId;
49
130
  if (!projectId) {
50
131
  try {
@@ -57,10 +138,15 @@ async function extractMetadata(rootPath, overrideProjectId) {
57
138
  }
58
139
  }
59
140
  if (projectId) {
60
- 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}`);
61
147
  }
62
148
  else {
63
- logger_1.logger.info(`āœ… Failed to determine the Firebase Project ID`);
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.`);
64
150
  }
65
151
  let appName = "firebase-studio-export";
66
152
  let blueprintContent = "";
@@ -90,15 +176,15 @@ async function updateReadme(rootPath, blueprintContent, appName) {
90
176
  await fs.writeFile(readmePath, newReadme);
91
177
  logger_1.logger.info("āœ… Updated README.md with project details and origin info");
92
178
  }
93
- async function injectAgyContext(rootPath, projectId, appName) {
94
- const agentDir = path.join(rootPath, ".agent");
179
+ async function injectAntigravityContext(rootPath, projectId, appName) {
180
+ const agentDir = path.join(rootPath, ".agents");
95
181
  const rulesDir = path.join(agentDir, "rules");
96
182
  const workflowsDir = path.join(agentDir, "workflows");
97
183
  const skillsDir = path.join(agentDir, "skills");
98
184
  await fs.mkdir(rulesDir, { recursive: true });
99
185
  await fs.mkdir(workflowsDir, { recursive: true });
100
186
  await fs.mkdir(skillsDir, { recursive: true });
101
- logger_1.logger.info("ā³ Fetching AGY skills from firebase/agent-skills...");
187
+ logger_1.logger.info("ā³ Fetching Antigravity skills from firebase/agent-skills...");
102
188
  try {
103
189
  const skillsResponse = await fetch("https://api.github.com/repos/firebase/agent-skills/contents/skills");
104
190
  if (!skillsResponse.ok) {
@@ -120,44 +206,58 @@ async function injectAgyContext(rootPath, projectId, appName) {
120
206
  logger_1.logger.info(`āœ… Downloaded Firebase skills`);
121
207
  }
122
208
  catch (err) {
123
- utils.logWarning(`Could not download AGY skills, skipping. ${err}`);
124
- }
125
- logger_1.logger.info("ā³ Fetching Genkit skill...");
126
- try {
127
- const genkitSkillDir = path.join(skillsDir, "developing-genkit-js");
128
- await downloadGitHubDir("https://api.github.com/repos/genkit-ai/skills/contents/skills/developing-genkit-js?ref=main", genkitSkillDir);
129
- logger_1.logger.info(`āœ… Downloaded Genkit skill`);
130
- }
131
- catch (err) {
132
- utils.logWarning(`Could not download Genkit skill, skipping. ${err}`);
209
+ utils.logWarning(`Could not download Antigravity skills, skipping. ${err}`);
133
210
  }
134
211
  const systemInstructionsTemplate = await (0, templates_1.readTemplate)("firebase-studio-export/system_instructions_template.md");
135
212
  const systemInstructions = systemInstructionsTemplate
136
213
  .replace("${projectId}", projectId || "None")
137
214
  .replace("${appName}", appName);
138
215
  await fs.writeFile(path.join(rulesDir, "migration-context.md"), systemInstructions);
139
- logger_1.logger.info("āœ… Injected AGY rules");
216
+ logger_1.logger.info("āœ… Injected Antigravity rules");
140
217
  try {
141
218
  const startupWorkflow = await (0, templates_1.readTemplate)("firebase-studio-export/workflows/startup_workflow.md");
142
219
  await fs.writeFile(path.join(workflowsDir, "startup.md"), startupWorkflow);
143
- logger_1.logger.info("āœ… Created AGY startup workflow");
220
+ logger_1.logger.info("āœ… Created Antigravity startup workflow");
144
221
  }
145
222
  catch (err) {
146
- 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}`);
147
225
  }
148
226
  }
149
- async function assertSystemState(startAgy) {
150
- if (startAgy === false) {
151
- return;
227
+ async function getAgyCommand(startAgy) {
228
+ if (!startAgy) {
229
+ return undefined;
230
+ }
231
+ const commands = ["agy", "antigravity"];
232
+ for (const cmd of commands) {
233
+ if (utils.commandExistsSync(cmd)) {
234
+ logger_1.logger.info(`āœ… Antigravity IDE detected`);
235
+ return cmd;
236
+ }
152
237
  }
153
- try {
154
- (0, child_process_1.execSync)("agy --version", { stdio: "ignore" });
155
- logger_1.logger.info("āœ… Antigravity IDE CLI (agy) detected");
238
+ if (process.platform === "darwin") {
239
+ const macPath = "/Applications/Antigravity.app/Contents/Resources/app/bin/agy";
240
+ try {
241
+ await fs.access(macPath);
242
+ logger_1.logger.info(`āœ… Antigravity IDE detected at ${macPath}`);
243
+ return macPath;
244
+ }
245
+ catch {
246
+ }
156
247
  }
157
- catch (err) {
158
- const downloadLink = "https://antigravity.google/download";
159
- throw new error_1.FirebaseError(`Antigravity IDE CLI (agy) not found in your PATH. To ensure a seamless migration, please download and install Antigravity: ${downloadLink}`, { exit: 1 });
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
+ }
160
257
  }
258
+ const downloadLink = "https://antigravity.google/download";
259
+ logger_1.logger.info(`āš ļø Antigravity IDE not found in your PATH. To ensure a seamless migration, please download and install Antigravity: ${downloadLink}`);
260
+ return undefined;
161
261
  }
162
262
  async function createFirebaseConfigs(rootPath, projectId) {
163
263
  if (!projectId) {
@@ -196,7 +296,8 @@ async function createFirebaseConfigs(rootPath, projectId) {
196
296
  }
197
297
  }
198
298
  catch (err) {
199
- 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}`);
200
301
  }
201
302
  const firebaseJson = {
202
303
  apphosting: {
@@ -204,7 +305,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
204
305
  ignore: [
205
306
  "node_modules",
206
307
  ".git",
207
- ".agent",
308
+ ".agents",
208
309
  ".idx",
209
310
  "firebase-debug.log",
210
311
  "firebase-debug.*.log",
@@ -216,7 +317,7 @@ async function createFirebaseConfigs(rootPath, projectId) {
216
317
  logger_1.logger.info(`āœ… Created firebase.json with backendId: ${backendId}`);
217
318
  }
218
319
  }
219
- async function writeAgyConfigs(rootPath) {
320
+ async function writeAntigravityConfigs(rootPath) {
220
321
  const vscodeDir = path.join(rootPath, ".vscode");
221
322
  await fs.mkdir(vscodeDir, { recursive: true });
222
323
  const tasksJson = {
@@ -239,7 +340,8 @@ async function writeAgyConfigs(rootPath) {
239
340
  settings = JSON.parse(settingsContent);
240
341
  }
241
342
  catch (err) {
242
- 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}`);
243
345
  }
244
346
  const cleanSettings = {};
245
347
  for (const [key, value] of Object.entries(settings)) {
@@ -270,14 +372,6 @@ async function writeAgyConfigs(rootPath) {
270
372
  }
271
373
  async function cleanupUnusedFiles(rootPath) {
272
374
  const docsDir = path.join(rootPath, "docs");
273
- const blueprintPath = path.join(docsDir, "blueprint.md");
274
- try {
275
- await fs.unlink(blueprintPath);
276
- logger_1.logger.info("āœ… Cleaned up docs/blueprint.md");
277
- }
278
- catch (err) {
279
- logger_1.logger.debug(`Could not delete ${blueprintPath}: ${err}`);
280
- }
281
375
  try {
282
376
  const files = await fs.readdir(docsDir);
283
377
  if (files.length === 0) {
@@ -286,15 +380,8 @@ async function cleanupUnusedFiles(rootPath) {
286
380
  }
287
381
  }
288
382
  catch (err) {
289
- logger_1.logger.debug(`Could not remove ${docsDir}: ${err}`);
290
- }
291
- const metadataPath = path.join(rootPath, "metadata.json");
292
- try {
293
- await fs.unlink(metadataPath);
294
- logger_1.logger.info("āœ… Cleaned up metadata.json");
295
- }
296
- catch (err) {
297
- 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}`);
298
385
  }
299
386
  const modifiedPath = path.join(rootPath, ".modified");
300
387
  try {
@@ -302,7 +389,8 @@ async function cleanupUnusedFiles(rootPath) {
302
389
  logger_1.logger.info("āœ… Cleaned up .modified");
303
390
  }
304
391
  catch (err) {
305
- 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}`);
306
394
  }
307
395
  }
308
396
  async function uploadSecrets(rootPath, projectId) {
@@ -330,49 +418,57 @@ async function uploadSecrets(rootPath, projectId) {
330
418
  }
331
419
  }
332
420
  catch (err) {
333
- 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}`);
334
423
  }
335
424
  }
336
- async function askToOpenAntigravity(rootPath, appName, startAgy) {
337
- if (startAgy === false) {
338
- 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) {
339
434
  return;
340
435
  }
341
436
  const answer = await prompt.confirm({
342
- 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?",
343
438
  default: true,
344
439
  });
345
440
  if (answer) {
346
441
  logger_1.logger.info(`ā³ Opening ${appName} in Antigravity...`);
347
442
  try {
348
- const agyProcess = (0, child_process_1.spawn)("agy", ["."], {
443
+ const antigravityProcess = (0, child_process_1.spawn)(agyCommand, ["."], {
349
444
  cwd: rootPath,
350
445
  stdio: "ignore",
351
446
  detached: true,
447
+ shell: process.platform === "win32",
352
448
  });
353
- agyProcess.unref();
449
+ antigravityProcess.unref();
354
450
  }
355
451
  catch (err) {
356
452
  utils.logWarning("Could not open Antigravity IDE automatically. Please open it manually.");
357
453
  }
358
454
  }
359
- else {
360
- logger_1.logger.info('\nšŸ‘‰ Next steps: Open this folder in Antigravity and run the "Initial Project Setup" workflow.');
361
- }
362
455
  }
363
- async function migrate(rootPath, options = { startAgy: true }) {
456
+ async function migrate(rootPath, options = { startAntigravity: true }) {
457
+ const appType = await detectAppType(rootPath);
458
+ void track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "started" });
364
459
  logger_1.logger.info("šŸš€ Starting Firebase Studio to Antigravity migration...");
365
- await assertSystemState(options.startAgy);
366
460
  const { projectId, appName, blueprintContent } = await extractMetadata(rootPath, options.project);
367
461
  await updateReadme(rootPath, blueprintContent, appName);
368
462
  await createFirebaseConfigs(rootPath, projectId);
369
463
  await uploadSecrets(rootPath, projectId);
370
- await injectAgyContext(rootPath, projectId, appName);
371
- await writeAgyConfigs(rootPath);
464
+ await injectAntigravityContext(rootPath, projectId, appName);
465
+ await writeAntigravityConfigs(rootPath);
466
+ await setupAntigravityMcpServer(rootPath);
372
467
  await cleanupUnusedFiles(rootPath);
373
468
  const currentFolderName = path.basename(rootPath);
374
469
  if (currentFolderName === "download") {
375
- 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, "-")}"`);
376
471
  }
377
- await askToOpenAntigravity(rootPath, appName, options.startAgy);
472
+ await track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "success" });
473
+ await askToOpenAntigravity(rootPath, appName, options.startAntigravity);
378
474
  }
@@ -187,12 +187,22 @@ function functionFromEndpoint(endpoint) {
187
187
  return String(cpu);
188
188
  });
189
189
  if (endpoint.vpc) {
190
- proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnector", "connector");
191
- proto.renameIfPresent(gcfFunction.serviceConfig, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
190
+ if (endpoint.vpc.connector) {
191
+ gcfFunction.serviceConfig.vpcConnector = endpoint.vpc.connector;
192
+ gcfFunction.serviceConfig.vpcConnectorEgressSettings = endpoint.vpc.egressSettings || null;
193
+ }
194
+ else if (endpoint.vpc.networkInterfaces) {
195
+ gcfFunction.serviceConfig.directVpcNetworkInterface = endpoint.vpc.networkInterfaces;
196
+ gcfFunction.serviceConfig.directVpcEgress = endpoint.vpc.egressSettings
197
+ ? `VPC_EGRESS_${endpoint.vpc.egressSettings}`
198
+ : null;
199
+ }
192
200
  }
193
201
  else if (endpoint.vpc === null) {
194
202
  gcfFunction.serviceConfig.vpcConnector = null;
195
203
  gcfFunction.serviceConfig.vpcConnectorEgressSettings = null;
204
+ gcfFunction.serviceConfig.directVpcNetworkInterface = null;
205
+ gcfFunction.serviceConfig.directVpcEgress = null;
196
206
  }
197
207
  if (backend.isEventTriggered(endpoint)) {
198
208
  gcfFunction.eventTrigger = {
@@ -384,6 +394,17 @@ function endpointFromFunction(gcfFunction) {
384
394
  endpoint.vpc = { connector: gcfFunction.serviceConfig.vpcConnector };
385
395
  proto.renameIfPresent(endpoint.vpc, gcfFunction.serviceConfig, "egressSettings", "vpcConnectorEgressSettings");
386
396
  }
397
+ else if (gcfFunction.serviceConfig.directVpcNetworkInterface) {
398
+ endpoint.vpc = { networkInterfaces: gcfFunction.serviceConfig.directVpcNetworkInterface };
399
+ if (gcfFunction.serviceConfig.directVpcEgress) {
400
+ if (!gcfFunction.serviceConfig.directVpcEgress.startsWith("VPC_EGRESS_")) {
401
+ throw new error_1.FirebaseError(`Unexpected VPC egress setting: ${gcfFunction.serviceConfig.directVpcEgress}`);
402
+ }
403
+ if (gcfFunction.serviceConfig.directVpcEgress !== "VPC_EGRESS_UNSPECIFIED") {
404
+ endpoint.vpc.egressSettings = gcfFunction.serviceConfig.directVpcEgress.substring("VPC_EGRESS_".length);
405
+ }
406
+ }
407
+ }
387
408
  const serviceName = gcfFunction.serviceConfig.service;
388
409
  if (!serviceName) {
389
410
  logger_1.logger.debug("Got a v2 function without a service name." +
@@ -125,9 +125,9 @@ async function setupSQLPermissions(instanceId, databaseId, schemaInfo, options,
125
125
  }
126
126
  async function greenFieldSchemaSetup(instanceId, databaseId, schema, options) {
127
127
  const revokes = [];
128
- if (await checkSQLRoleIsGranted(options, instanceId, databaseId, "cloudsqlsuperuser", (0, permissions_1.firebaseowner)(databaseId))) {
128
+ if (await checkSQLRoleIsGranted(options, instanceId, databaseId, "cloudsqlsuperuser", (0, permissions_1.firebaseowner)(databaseId, schema))) {
129
129
  logger_1.logger.warn("Detected cloudsqlsuperuser was previously given to firebase owner, revoking to improve database security.");
130
- revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${(0, permissions_1.firebaseowner)(databaseId)}"`);
130
+ revokes.push(`REVOKE "cloudsqlsuperuser" FROM "${(0, permissions_1.firebaseowner)(databaseId, schema)}"`);
131
131
  }
132
132
  const user = (await (0, connect_1.getIAMUser)(options)).user;
133
133
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -222,10 +222,10 @@ async function brownfieldSqlSetup(instanceId, databaseId, schemaInfo, options, s
222
222
  ];
223
223
  await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, brownfieldSetupCmds, silent, true);
224
224
  }
225
- async function grantRoleTo(options, instanceId, databaseId, role, email) {
225
+ async function grantRoleTo(options, instanceId, databaseId, role, email, schema = permissions_1.DEFAULT_SCHEMA) {
226
226
  const projectId = (0, projectUtils_1.needProjectId)(options);
227
227
  const { user, mode } = (0, connect_2.toDatabaseUser)(email);
228
228
  await cloudSqlAdminClient.createUser(projectId, instanceId, mode, user);
229
- const fdcSqlRole = exports.fdcSqlRoleMap[role](databaseId);
229
+ const fdcSqlRole = exports.fdcSqlRoleMap[role](databaseId, schema);
230
230
  await (0, connect_1.executeSqlCmdsAsSuperUser)(options, instanceId, databaseId, [`GRANT "${fdcSqlRole}" TO "${user}"`], false);
231
231
  }