firebase-tools 15.10.1 → 15.11.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.
@@ -35,6 +35,8 @@ const v1_1 = require("../functions/events/v1");
35
35
  const build_1 = require("../deploy/functions/build");
36
36
  const env_1 = require("./env");
37
37
  const python_1 = require("../functions/python");
38
+ const supported_1 = require("../deploy/functions/runtimes/supported");
39
+ const dart_1 = require("../deploy/functions/runtimes/dart");
38
40
  const EVENT_INVOKE_GA4 = "functions_invoke";
39
41
  const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
40
42
  class IPCConn {
@@ -83,6 +85,7 @@ class FunctionsEmulator {
83
85
  this.staticBackends = [];
84
86
  this.dynamicBackends = [];
85
87
  this.watchers = [];
88
+ this.watchCleanups = [];
86
89
  this.debugMode = false;
87
90
  this.staticBackends = args.emulatableBackends;
88
91
  emulatorLogger_1.EmulatorLogger.setVerbosity(this.args.verbosity ? emulatorLogger_1.Verbosity[this.args.verbosity] : emulatorLogger_1.Verbosity["DEBUG"]);
@@ -208,7 +211,7 @@ class FunctionsEmulator {
208
211
  async sendRequest(trigger, body) {
209
212
  const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
210
213
  const pool = this.workerPools[record.backend.codebase];
211
- if (!pool.readyForWork(trigger.id)) {
214
+ if (!pool.readyForWork(trigger.id, record.backend.runtime)) {
212
215
  try {
213
216
  await this.startRuntime(record.backend, trigger);
214
217
  }
@@ -217,7 +220,7 @@ class FunctionsEmulator {
217
220
  return;
218
221
  }
219
222
  }
220
- const worker = pool.getIdleWorker(trigger.id);
223
+ const worker = pool.getIdleWorker(trigger.id, record.backend.runtime);
221
224
  if (this.debugMode) {
222
225
  await worker.sendDebugMsg({
223
226
  functionTarget: trigger.entryPoint,
@@ -229,10 +232,13 @@ class FunctionsEmulator {
229
232
  "Content-Type": "application/json",
230
233
  "Content-Length": `${reqBody.length}`,
231
234
  };
235
+ const isDart = (0, supported_1.isLanguageRuntime)(record.backend.runtime, "dart");
236
+ const path = isDart ? `/${trigger.entryPoint}` : `/`;
232
237
  return new Promise((resolve, reject) => {
233
238
  const req = http.request({
234
239
  ...worker.runtime.conn.httpReqOpts(),
235
- path: `/`,
240
+ method: "POST",
241
+ path: path,
236
242
  headers: headers,
237
243
  }, resolve);
238
244
  req.on("error", reject);
@@ -264,23 +270,43 @@ class FunctionsEmulator {
264
270
  async connect() {
265
271
  for (const backend of this.staticBackends) {
266
272
  this.logger.logLabeled("BULLET", "functions", `Watching "${backend.functionsDir}" for Cloud Functions...`);
267
- const watcher = chokidar.watch(backend.functionsDir, {
268
- ignored: [
269
- /.+?[\\\/]node_modules[\\\/].+?/,
270
- /(^|[\/\\])\../,
271
- /.+\.log/,
272
- /.+?[\\\/]venv[\\\/].+?/,
273
- ...(backend.ignore?.map((i) => `**/${i}`) ?? []),
274
- ],
275
- persistent: true,
276
- });
277
- this.watchers.push(watcher);
278
- const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
279
- watcher.on("change", (filePath) => {
280
- this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
281
- return debouncedLoadTriggers();
282
- });
283
273
  await this.loadTriggers(backend, true);
274
+ const isDart = (0, supported_1.isLanguageRuntime)(backend.runtime, "dart");
275
+ if (isDart) {
276
+ const runtimeDelegateContext = {
277
+ projectId: this.args.projectId,
278
+ projectDir: this.args.projectDir,
279
+ sourceDir: backend.functionsDir,
280
+ runtime: backend.runtime,
281
+ };
282
+ const delegate = await runtimes.getRuntimeDelegate(runtimeDelegateContext);
283
+ this.logger.logLabeled("BULLET", "functions", `Starting build_runner watch for Dart functions...`);
284
+ const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
285
+ const cleanup = await delegate.watch(() => {
286
+ this.logger.log("DEBUG", "build_runner rebuilt, reloading triggers");
287
+ debouncedLoadTriggers();
288
+ });
289
+ this.watchCleanups.push(cleanup);
290
+ this.logger.logLabeled("SUCCESS", "functions", `build_runner initial build completed`);
291
+ }
292
+ else {
293
+ const watcher = chokidar.watch(backend.functionsDir, {
294
+ ignored: [
295
+ /(^|[\/\\])\../,
296
+ /.+\.log/,
297
+ /.+?[\\\/]node_modules[\\\/].+?/,
298
+ /.+?[\\\/]venv[\\\/].+?/,
299
+ ...(backend.ignore?.map((i) => `**/${i}`) ?? []),
300
+ ],
301
+ persistent: true,
302
+ });
303
+ this.watchers.push(watcher);
304
+ const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
305
+ watcher.on("change", (filePath) => {
306
+ this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
307
+ return debouncedLoadTriggers();
308
+ });
309
+ }
284
310
  }
285
311
  await this.performPostLoadOperations();
286
312
  return;
@@ -300,6 +326,10 @@ class FunctionsEmulator {
300
326
  await watcher.close();
301
327
  }
302
328
  this.watchers = [];
329
+ for (const cleanup of this.watchCleanups) {
330
+ await cleanup();
331
+ }
332
+ this.watchCleanups = [];
303
333
  if (this.destroyServer) {
304
334
  await this.destroyServer();
305
335
  }
@@ -1098,13 +1128,52 @@ class FunctionsEmulator {
1098
1128
  conn: new TCPConn("127.0.0.1", port),
1099
1129
  };
1100
1130
  }
1131
+ async startDart(backend, envs) {
1132
+ if (this.debugMode) {
1133
+ this.logger.log("WARN", "--inspect-functions not supported for Dart functions. Ignored.");
1134
+ }
1135
+ const port = await portfinder.getPortPromise({
1136
+ port: 8081 + (0, utils_1.randomInt)(0, 1000),
1137
+ });
1138
+ const args = ["run", "--no-serve-devtools", dart_1.DART_ENTRY_POINT];
1139
+ const dartEnvs = { ...envs };
1140
+ delete dartEnvs.FUNCTION_TARGET;
1141
+ delete dartEnvs.FUNCTION_SIGNATURE_TYPE;
1142
+ const bin = backend.bin || "dart";
1143
+ logger_1.logger.debug(`Starting Dart runtime with args: ${args.join(" ")} on port ${port}`);
1144
+ const childProcess = spawn(bin, args, {
1145
+ cwd: backend.functionsDir,
1146
+ env: {
1147
+ ...process.env,
1148
+ ...dartEnvs,
1149
+ HOST: "127.0.0.1",
1150
+ PORT: port.toString(),
1151
+ },
1152
+ stdio: ["pipe", "pipe", "pipe"],
1153
+ });
1154
+ childProcess.stdout?.on("data", (chunk) => {
1155
+ this.logger.log("DEBUG", `[dart] ${chunk.toString("utf8")}`);
1156
+ });
1157
+ childProcess.stderr?.on("data", (chunk) => {
1158
+ this.logger.log("DEBUG", `[dart] ${chunk.toString("utf8")}`);
1159
+ });
1160
+ return {
1161
+ process: childProcess,
1162
+ events: new events_1.EventEmitter(),
1163
+ cwd: backend.functionsDir,
1164
+ conn: new TCPConn("127.0.0.1", port),
1165
+ };
1166
+ }
1101
1167
  async startRuntime(backend, trigger) {
1102
1168
  const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
1103
1169
  const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
1104
1170
  let runtime;
1105
- if (backend.runtime.startsWith("python")) {
1171
+ if ((0, supported_1.isLanguageRuntime)(backend.runtime, "python")) {
1106
1172
  runtime = await this.startPython(backend, { ...runtimeEnv, ...secretEnvs });
1107
1173
  }
1174
+ else if ((0, supported_1.isLanguageRuntime)(backend.runtime, "dart")) {
1175
+ runtime = await this.startDart(backend, { ...runtimeEnv, ...secretEnvs });
1176
+ }
1108
1177
  else {
1109
1178
  runtime = await this.startNode(backend, { ...runtimeEnv, ...secretEnvs });
1110
1179
  }
@@ -1113,7 +1182,7 @@ class FunctionsEmulator {
1113
1182
  ref: backend.extensionVersion?.ref,
1114
1183
  };
1115
1184
  const pool = this.workerPools[backend.codebase];
1116
- const worker = pool.addWorker(trigger, runtime, extensionLogInfo);
1185
+ const worker = pool.addWorker(trigger, runtime, extensionLogInfo, backend.runtime);
1117
1186
  await worker.waitForSocketReady();
1118
1187
  return worker;
1119
1188
  }
@@ -1172,10 +1241,20 @@ class FunctionsEmulator {
1172
1241
  });
1173
1242
  this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
1174
1243
  const url = new url_1.URL(`${req.protocol}://${req.hostname}${req.url}`);
1175
- const path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${req.params.trigger_name}\/?`), "/");
1244
+ let path = `${url.pathname}${url.search}`.replace(new RegExp(`\/${this.args.projectId}\/[^\/]*\/${req.params.trigger_name}\/?`), "/");
1245
+ const isDart = (0, supported_1.isLanguageRuntime)(record.backend.runtime, "dart");
1246
+ if (isDart) {
1247
+ const isBackgroundRoute = req.url.startsWith("/functions/projects/");
1248
+ if (isBackgroundRoute || path === "/") {
1249
+ path = `/${trigger.entryPoint}`;
1250
+ }
1251
+ else {
1252
+ path = `/${trigger.entryPoint}${path}`;
1253
+ }
1254
+ }
1176
1255
  this.logger.log("DEBUG", `[functions] Got req.url=${req.url}, mapping to path=${path}`);
1177
1256
  const pool = this.workerPools[record.backend.codebase];
1178
- if (!pool.readyForWork(trigger.id)) {
1257
+ if (!pool.readyForWork(trigger.id, record.backend.runtime)) {
1179
1258
  try {
1180
1259
  await this.startRuntime(record.backend, trigger);
1181
1260
  }
@@ -1196,7 +1275,7 @@ class FunctionsEmulator {
1196
1275
  method,
1197
1276
  path,
1198
1277
  headers: req.headers,
1199
- }, res, reqBody, debugBundle);
1278
+ }, res, reqBody, debugBundle, record.backend.runtime);
1200
1279
  }
1201
1280
  }
1202
1281
  exports.FunctionsEmulator = FunctionsEmulator;
@@ -8,6 +8,7 @@ const events_1 = require("events");
8
8
  const emulatorLogger_1 = require("./emulatorLogger");
9
9
  const error_1 = require("../error");
10
10
  const discovery_1 = require("../deploy/functions/runtimes/discovery");
11
+ const supported_1 = require("../deploy/functions/runtimes/supported");
11
12
  var RuntimeWorkerState;
12
13
  (function (RuntimeWorkerState) {
13
14
  RuntimeWorkerState["CREATED"] = "CREATED";
@@ -226,13 +227,14 @@ class RuntimeWorkerPool {
226
227
  this.mode = mode;
227
228
  this.workers = new Map();
228
229
  }
229
- getKey(triggerId) {
230
+ getKey(triggerId, runtime) {
230
231
  if (this.mode === types_1.FunctionsExecutionMode.SEQUENTIAL) {
231
232
  return "~shared~";
232
233
  }
233
- else {
234
- return triggerId || "~diagnostic~";
234
+ if ((0, supported_1.isLanguageRuntime)(runtime, "dart")) {
235
+ return "~dart-shared~";
235
236
  }
237
+ return triggerId || "~diagnostic~";
236
238
  }
237
239
  refresh() {
238
240
  for (const arr of this.workers.values()) {
@@ -261,13 +263,13 @@ class RuntimeWorkerPool {
261
263
  });
262
264
  }
263
265
  }
264
- readyForWork(triggerId) {
265
- const idleWorker = this.getIdleWorker(triggerId);
266
+ readyForWork(triggerId, runtime) {
267
+ const idleWorker = this.getIdleWorker(triggerId, runtime);
266
268
  return !!idleWorker;
267
269
  }
268
- async submitRequest(triggerId, req, resp, body, debug) {
270
+ async submitRequest(triggerId, req, resp, body, debug, runtime) {
269
271
  this.log(`submitRequest(triggerId=${triggerId})`);
270
- const worker = this.getIdleWorker(triggerId);
272
+ const worker = this.getIdleWorker(triggerId, runtime);
271
273
  if (!worker) {
272
274
  throw new error_1.FirebaseError("Internal Error: can't call submitRequest without checking for idle workers");
273
275
  }
@@ -276,11 +278,11 @@ class RuntimeWorkerPool {
276
278
  }
277
279
  return worker.request(req, resp, body, !!debug);
278
280
  }
279
- getIdleWorker(triggerId) {
281
+ getIdleWorker(triggerId, runtime) {
280
282
  this.cleanUpWorkers();
281
- const triggerWorkers = this.getTriggerWorkers(triggerId);
283
+ const triggerWorkers = this.getTriggerWorkers(triggerId, runtime);
282
284
  if (!triggerWorkers.length) {
283
- this.setTriggerWorkers(triggerId, []);
285
+ this.setTriggerWorkers(triggerId, [], runtime);
284
286
  return;
285
287
  }
286
288
  for (const worker of triggerWorkers) {
@@ -290,21 +292,22 @@ class RuntimeWorkerPool {
290
292
  }
291
293
  return;
292
294
  }
293
- addWorker(trigger, runtime, extensionLogInfo) {
294
- this.log(`addWorker(${this.getKey(trigger?.id)})`);
295
+ addWorker(trigger, runtime, extensionLogInfo, runtimeType) {
296
+ const key = this.getKey(trigger?.id, runtimeType);
297
+ this.log(`addWorker(${key})`);
295
298
  const disableTimeout = !trigger?.id || this.mode === types_1.FunctionsExecutionMode.SEQUENTIAL;
296
299
  const worker = new RuntimeWorker(trigger?.id, runtime, extensionLogInfo, disableTimeout ? undefined : trigger?.timeoutSeconds);
297
- const keyWorkers = this.getTriggerWorkers(trigger?.id);
300
+ const keyWorkers = this.getTriggerWorkers(trigger?.id, runtimeType);
298
301
  keyWorkers.push(worker);
299
- this.setTriggerWorkers(trigger?.id, keyWorkers);
302
+ this.setTriggerWorkers(trigger?.id, keyWorkers, runtimeType);
300
303
  this.log(`Adding worker with key ${worker.triggerKey}, total=${keyWorkers.length}`);
301
304
  return worker;
302
305
  }
303
- getTriggerWorkers(triggerId) {
304
- return this.workers.get(this.getKey(triggerId)) || [];
306
+ getTriggerWorkers(triggerId, runtime) {
307
+ return this.workers.get(this.getKey(triggerId, runtime)) || [];
305
308
  }
306
- setTriggerWorkers(triggerId, workers) {
307
- this.workers.set(this.getKey(triggerId), workers);
309
+ setTriggerWorkers(triggerId, workers, runtime) {
310
+ this.workers.set(this.getKey(triggerId, runtime), workers);
308
311
  }
309
312
  cleanUpWorkers() {
310
313
  for (const [key, keyWorkers] of this.workers.entries()) {
@@ -6,6 +6,7 @@ exports.migrate = migrate;
6
6
  const fs = require("fs/promises");
7
7
  const path = require("path");
8
8
  const child_process_1 = require("child_process");
9
+ const semver = require("semver");
9
10
  const logger_1 = require("../logger");
10
11
  const prompt = require("../prompt");
11
12
  const apphosting = require("../gcp/apphosting");
@@ -38,12 +39,23 @@ async function setupAntigravityMcpServer(rootPath, appType) {
38
39
  }
39
40
  let updated = false;
40
41
  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}`);
42
+ if (utils.commandExistsSync("npx")) {
43
+ const confirmFirebase = await prompt.confirm({
44
+ message: "Would you like to enable the Firebase MCP server for Antigravity?",
45
+ default: true,
46
+ });
47
+ if (confirmFirebase) {
48
+ mcpConfig.mcpServers["firebase"] = {
49
+ command: "npx",
50
+ args: ["-y", "firebase-tools@latest", "mcp", "--dir", path.resolve(rootPath)],
51
+ };
52
+ updated = true;
53
+ logger_1.logger.info(`✅ Configured Firebase MCP server in ${mcpConfigPath}`);
54
+ }
55
+ }
56
+ else {
57
+ logger_1.logger.info("ℹ️ npx not found on PATH, skipping Firebase MCP server configuration.");
58
+ }
47
59
  }
48
60
  else {
49
61
  logger_1.logger.info("ℹ️ Firebase MCP server already configured in Antigravity, skipping.");
@@ -51,12 +63,18 @@ async function setupAntigravityMcpServer(rootPath, appType) {
51
63
  if (appType === "FLUTTER") {
52
64
  if (utils.commandExistsSync("dart")) {
53
65
  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}`);
66
+ const confirmDart = await prompt.confirm({
67
+ message: "Would you like to enable the Dart MCP server for Antigravity?",
68
+ default: true,
69
+ });
70
+ if (confirmDart) {
71
+ mcpConfig.mcpServers["dart"] = {
72
+ command: "dart",
73
+ args: ["mcp-server"],
74
+ };
75
+ updated = true;
76
+ logger_1.logger.info(`✅ Configured Dart MCP server in ${mcpConfigPath}`);
77
+ }
60
78
  }
61
79
  else {
62
80
  logger_1.logger.info("ℹ️ Dart MCP server already configured in Antigravity, skipping.");
@@ -208,9 +226,32 @@ async function injectAntigravityContext(rootPath, projectId, appName) {
208
226
  await fs.mkdir(rulesDir, { recursive: true });
209
227
  await fs.mkdir(workflowsDir, { recursive: true });
210
228
  await fs.mkdir(skillsDir, { recursive: true });
229
+ const installLocation = await prompt.select({
230
+ message: "Where would you like to install Firebase project skills?",
231
+ choices: [
232
+ { name: "Locally in the project", value: "local" },
233
+ { name: "Globally for all projects", value: "global" },
234
+ ],
235
+ default: "local",
236
+ nonInteractive: process.env.NODE_ENV === "test",
237
+ });
211
238
  logger_1.logger.info("⏳ Adding Antigravity skills...");
212
239
  try {
213
- const result = (0, child_process_1.spawnSync)("npx", ["-y", "skills", "add", "firebase/agent-skills", "-a", "antigravity", "--skill", "*", "-y"], {
240
+ const args = [
241
+ "-y",
242
+ "skills",
243
+ "add",
244
+ "firebase/agent-skills",
245
+ "-a",
246
+ "gemini-cli",
247
+ "--skill",
248
+ "*",
249
+ "-y",
250
+ ];
251
+ if (installLocation === "global") {
252
+ args.push("-g");
253
+ }
254
+ const result = (0, child_process_1.spawnSync)("npx", args, {
214
255
  cwd: rootPath,
215
256
  stdio: "ignore",
216
257
  shell: process.platform === "win32",
@@ -484,24 +525,26 @@ async function upgradeGenkitVersion(rootPath) {
484
525
  const packageJsonContent = await fs.readFile(packageJsonPath, "utf8");
485
526
  const packageJson = JSON.parse(packageJsonContent);
486
527
  let modified = false;
487
- const upgradeDeps = (deps) => {
488
- if (!deps) {
528
+ const targetVersion = "1.29.0";
529
+ const checkAndUpgrade = (deps) => {
530
+ if (!deps || !deps["genkit-cli"]) {
489
531
  return;
490
532
  }
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
- }
533
+ const currentVersion = deps["genkit-cli"];
534
+ if (currentVersion.startsWith("^")) {
535
+ return;
536
+ }
537
+ const coerced = semver.coerce(currentVersion);
538
+ if (coerced && semver.lt(coerced, targetVersion)) {
539
+ deps["genkit-cli"] = "^1.29";
540
+ modified = true;
498
541
  }
499
542
  };
500
- upgradeDeps(packageJson.dependencies);
501
- upgradeDeps(packageJson.devDependencies);
543
+ checkAndUpgrade(packageJson.dependencies);
544
+ checkAndUpgrade(packageJson.devDependencies);
502
545
  if (modified) {
503
546
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
504
- logger_1.logger.info("✅ Upgraded Genkit version to 1.29 in package.json");
547
+ logger_1.logger.info("✅ Upgraded genkit-cli version to 1.29 in package.json");
505
548
  }
506
549
  }
507
550
  catch (err) {
@@ -588,6 +631,7 @@ async function migrate(rootPath, options = { startAntigravity: true }) {
588
631
  const appType = await detectAppType(rootPath);
589
632
  void track.trackGA4("firebase_studio_migrate", { app_type: appType, result: "started" });
590
633
  logger_1.logger.info("🚀 Starting Firebase Studio to Antigravity migration...");
634
+ logger_1.logger.info("\nFile any bugs at https://github.com/firebase/firebase-tools/issues");
591
635
  const { projectId, appName } = await extractMetadata(rootPath, options.project);
592
636
  if (appType) {
593
637
  logger_1.logger.info(`✅ Detected framework: ${appType}`);
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setup = setup;
4
+ const spawn = require("cross-spawn");
5
+ const prompt_1 = require("../../../prompt");
6
+ const supported_1 = require("../../../deploy/functions/runtimes/supported");
7
+ const templates_1 = require("../../../templates");
8
+ const PUBSPEC_TEMPLATE = (0, templates_1.readTemplateSync)("init/functions/dart/pubspec.yaml");
9
+ const MAIN_TEMPLATE = (0, templates_1.readTemplateSync)("init/functions/dart/server.dart");
10
+ const GITIGNORE_TEMPLATE = (0, templates_1.readTemplateSync)("init/functions/dart/_gitignore");
11
+ async function setup(setup, config) {
12
+ await config.askWriteProjectFile(`${setup.functions.source}/pubspec.yaml`, PUBSPEC_TEMPLATE);
13
+ await config.askWriteProjectFile(`${setup.functions.source}/.gitignore`, GITIGNORE_TEMPLATE);
14
+ await config.askWriteProjectFile(`${setup.functions.source}/bin/server.dart`, MAIN_TEMPLATE);
15
+ config.set("functions.runtime", (0, supported_1.latest)("dart"));
16
+ config.set("functions.ignore", [".dart_tool", "build"]);
17
+ const install = await (0, prompt_1.confirm)({
18
+ message: "Do you want to install dependencies now?",
19
+ default: true,
20
+ });
21
+ if (install) {
22
+ const installProcess = spawn("dart", ["pub", "get"], {
23
+ cwd: config.path(setup.functions.source),
24
+ stdio: ["inherit", "inherit", "inherit"],
25
+ });
26
+ await new Promise((resolve, reject) => {
27
+ installProcess.on("exit", resolve);
28
+ installProcess.on("error", reject);
29
+ });
30
+ }
31
+ }
@@ -144,6 +144,10 @@ async function languageSetup(setup) {
144
144
  value: "python",
145
145
  });
146
146
  }
147
+ choices.push({
148
+ name: "Dart",
149
+ value: "dart",
150
+ });
147
151
  const language = await (0, prompt_1.select)({
148
152
  message: "What language would you like to use to write Cloud Functions?",
149
153
  default: "javascript",
@@ -173,6 +177,16 @@ async function languageSetup(setup) {
173
177
  cbconfig.ignore = ["venv", ".git", "firebase-debug.log", "firebase-debug.*.log", "*.local"];
174
178
  cbconfig.runtime = supported.latest("python");
175
179
  break;
180
+ case "dart":
181
+ cbconfig.ignore = [
182
+ ".dart_tool",
183
+ ".git",
184
+ "firebase-debug.log",
185
+ "firebase-debug.*.log",
186
+ "*.local",
187
+ ];
188
+ cbconfig.runtime = supported.latest("dart");
189
+ break;
176
190
  }
177
191
  setup.functions.languageChoice = language;
178
192
  }