firebase-tools 14.12.1 → 14.14.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.
Files changed (56) hide show
  1. package/README.md +1 -1
  2. package/lib/commands/dataconnect-services-list.js +5 -5
  3. package/lib/commands/dataconnect-sql-grant.js +5 -0
  4. package/lib/commands/dataconnect-sql-setup.js +1 -3
  5. package/lib/crashlytics/getIssueDetails.js +41 -0
  6. package/lib/crashlytics/getSampleCrash.js +48 -0
  7. package/lib/dataconnect/client.js +23 -15
  8. package/lib/dataconnect/ensureApis.js +5 -9
  9. package/lib/dataconnect/errors.js +7 -1
  10. package/lib/dataconnect/fileUtils.js +5 -6
  11. package/lib/dataconnect/freeTrial.js +16 -39
  12. package/lib/dataconnect/provisionCloudSql.js +67 -70
  13. package/lib/dataconnect/schemaMigration.js +222 -170
  14. package/lib/deploy/dataconnect/deploy.js +9 -11
  15. package/lib/deploy/dataconnect/prepare.js +7 -10
  16. package/lib/deploy/dataconnect/release.js +42 -30
  17. package/lib/deploy/functions/backend.js +8 -2
  18. package/lib/deploy/functions/build.js +23 -1
  19. package/lib/deploy/functions/ensure.js +1 -1
  20. package/lib/deploy/functions/functionsDeployHelper.js +8 -1
  21. package/lib/deploy/functions/prepare.js +8 -4
  22. package/lib/deploy/functions/pricing.js +12 -5
  23. package/lib/deploy/functions/release/fabricator.js +25 -3
  24. package/lib/emulator/controller.js +7 -3
  25. package/lib/emulator/downloadableEmulatorInfo.json +18 -18
  26. package/lib/emulator/functionsEmulator.js +11 -1
  27. package/lib/experiments.js +4 -0
  28. package/lib/extensions/extensionsHelper.js +4 -15
  29. package/lib/extensions/utils.js +1 -12
  30. package/lib/firestore/api.js +25 -11
  31. package/lib/firestore/pretty-print.js +7 -0
  32. package/lib/functional.js +7 -1
  33. package/lib/functions/env.js +19 -15
  34. package/lib/functions/projectConfig.js +25 -2
  35. package/lib/functions/secrets.js +3 -0
  36. package/lib/gcp/cloudfunctionsv2.js +3 -31
  37. package/lib/gcp/cloudscheduler.js +1 -1
  38. package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
  39. package/lib/gcp/cloudsql/connect.js +3 -2
  40. package/lib/gcp/cloudsql/permissionsSetup.js +23 -16
  41. package/lib/gcp/k8s.js +32 -0
  42. package/lib/gcp/runv2.js +178 -0
  43. package/lib/gemini/fdcExperience.js +5 -3
  44. package/lib/init/features/dataconnect/index.js +266 -162
  45. package/lib/init/features/dataconnect/sdk.js +36 -20
  46. package/lib/init/features/project.js +4 -0
  47. package/lib/management/studio.js +1 -1
  48. package/lib/mcp/tools/core/init.js +7 -6
  49. package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
  50. package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
  51. package/lib/mcp/tools/crashlytics/index.js +7 -1
  52. package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
  53. package/lib/rtdb.js +1 -1
  54. package/package.json +1 -1
  55. package/schema/firebase-config.json +6 -0
  56. package/lib/extensions/resolveSource.js +0 -24
@@ -27,7 +27,6 @@ async function default_1(context, options) {
27
27
  await (0, ensureApis_1.ensureApis)(projectId);
28
28
  await (0, requireTosAcceptance_1.requireTosAcceptance)(firedata_1.DATA_CONNECT_TOS_ID)(options);
29
29
  const serviceCfgs = (0, fileUtils_1.readFirebaseJson)(options.config);
30
- utils.logLabeledBullet("dataconnect", `Preparing to deploy`);
31
30
  const filters = (0, filters_1.getResourceFilters)(options);
32
31
  const serviceInfos = await Promise.all(serviceCfgs.map((c) => (0, load_1.load)(projectId, options.config, c.source)));
33
32
  for (const si of serviceInfos) {
@@ -50,7 +49,7 @@ async function default_1(context, options) {
50
49
  serviceInfos,
51
50
  filters,
52
51
  };
53
- utils.logLabeledBullet("dataconnect", `Successfully prepared schema and connectors`);
52
+ utils.logLabeledBullet("dataconnect", `Successfully compiled schema and connectors`);
54
53
  if (options.dryRun) {
55
54
  for (const si of serviceInfos) {
56
55
  await (0, schemaMigration_1.diffSchema)(options, si.schema, (_c = (_b = (_a = si.dataConnectYaml.schema) === null || _a === void 0 ? void 0 : _a.datasource) === null || _b === void 0 ? void 0 : _b.postgresql) === null || _c === void 0 ? void 0 : _c.schemaValidation);
@@ -61,23 +60,21 @@ async function default_1(context, options) {
61
60
  return !filters || (filters === null || filters === void 0 ? void 0 : filters.some((f) => si.dataConnectYaml.serviceId === f.serviceId));
62
61
  })
63
62
  .map(async (s) => {
64
- var _a, _b;
63
+ var _a, _b, _c;
65
64
  const postgresDatasource = s.schema.datasources.find((d) => d.postgresql);
66
65
  if (postgresDatasource) {
67
- const instanceId = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql.instance.split("/").pop();
68
- const databaseId = (_b = postgresDatasource.postgresql) === null || _b === void 0 ? void 0 : _b.database;
66
+ const instanceId = (_b = (_a = postgresDatasource.postgresql) === null || _a === void 0 ? void 0 : _a.cloudSql) === null || _b === void 0 ? void 0 : _b.instance.split("/").pop();
67
+ const databaseId = (_c = postgresDatasource.postgresql) === null || _c === void 0 ? void 0 : _c.database;
69
68
  if (!instanceId || !databaseId) {
70
69
  return Promise.resolve();
71
70
  }
72
- const enableGoogleMlIntegration = (0, types_1.requiresVector)(s.deploymentMetadata);
73
- return (0, provisionCloudSql_1.provisionCloudSql)({
71
+ return (0, provisionCloudSql_1.setupCloudSql)({
74
72
  projectId,
75
73
  location: (0, names_1.parseServiceName)(s.serviceName).location,
76
74
  instanceId,
77
75
  databaseId,
78
- enableGoogleMlIntegration,
79
- waitForCreation: true,
80
- dryRun: options.dryRun,
76
+ requireGoogleMlIntegration: (0, types_1.requiresVector)(s.deploymentMetadata),
77
+ dryRun: true,
81
78
  });
82
79
  }
83
80
  }));
@@ -5,6 +5,8 @@ const client_1 = require("../../dataconnect/client");
5
5
  const prompts_1 = require("../../dataconnect/prompts");
6
6
  const schemaMigration_1 = require("../../dataconnect/schemaMigration");
7
7
  const projectUtils_1 = require("../../projectUtils");
8
+ const names_1 = require("../../dataconnect/names");
9
+ const logger_1 = require("../../logger");
8
10
  async function default_1(context, options) {
9
11
  const project = (0, projectUtils_1.needProjectId)(options);
10
12
  const serviceInfos = context.dataconnect.serviceInfos;
@@ -23,20 +25,7 @@ async function default_1(context, options) {
23
25
  validationMode: (_d = (_c = (_b = (_a = s.dataConnectYaml) === null || _a === void 0 ? void 0 : _a.schema) === null || _b === void 0 ? void 0 : _b.datasource) === null || _c === void 0 ? void 0 : _c.postgresql) === null || _d === void 0 ? void 0 : _d.schemaValidation,
24
26
  });
25
27
  });
26
- if (wantSchemas.length) {
27
- utils.logLabeledBullet("dataconnect", "Deploying Data Connect schemas...");
28
- for (const s of wantSchemas) {
29
- await (0, schemaMigration_1.migrateSchema)({
30
- options,
31
- schema: s.schema,
32
- validateOnly: false,
33
- schemaValidation: s.validationMode,
34
- });
35
- }
36
- utils.logLabeledBullet("dataconnect", "Schemas deployed.");
37
- }
38
- let wantConnectors = [];
39
- wantConnectors = wantConnectors.concat(...serviceInfos.map((si) => si.connectorInfo
28
+ const wantConnectors = serviceInfos.flatMap((si) => si.connectorInfo
40
29
  .filter((c) => {
41
30
  return (!filters ||
42
31
  filters.some((f) => {
@@ -44,30 +33,53 @@ async function default_1(context, options) {
44
33
  (f.connectorId === c.connectorYaml.connectorId || f.fullService));
45
34
  }));
46
35
  })
47
- .map((c) => c.connector)));
48
- const haveConnectors = await have(serviceInfos);
49
- const connectorsToDelete = filters
50
- ? []
51
- : haveConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
52
- if (wantConnectors.length) {
53
- utils.logLabeledBullet("dataconnect", "Deploying connectors...");
54
- await Promise.all(wantConnectors.map(async (c) => {
36
+ .map((c) => c.connector));
37
+ const remainingConnectors = await Promise.all(wantConnectors.map(async (c) => {
38
+ try {
39
+ await (0, client_1.upsertConnector)(c);
40
+ }
41
+ catch (err) {
42
+ logger_1.logger.debug("Error pre-deploying connector", c.name, err);
43
+ return c;
44
+ }
45
+ utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
46
+ return undefined;
47
+ }));
48
+ for (const s of wantSchemas) {
49
+ await (0, schemaMigration_1.migrateSchema)({
50
+ options,
51
+ schema: s.schema,
52
+ validateOnly: false,
53
+ schemaValidation: s.validationMode,
54
+ });
55
+ utils.logLabeledSuccess("dataconnect", `Migrated schema ${s.schema.name}`);
56
+ }
57
+ await Promise.all(remainingConnectors.map(async (c) => {
58
+ if (c) {
55
59
  await (0, client_1.upsertConnector)(c);
56
60
  utils.logLabeledSuccess("dataconnect", `Deployed connector ${c.name}`);
57
- }));
58
- for (const c of connectorsToDelete) {
59
- await (0, prompts_1.promptDeleteConnector)(options, c.name);
60
61
  }
61
- utils.logLabeledBullet("dataconnect", "Connectors deployed.");
62
+ }));
63
+ const allConnectors = await deployedConnectors(serviceInfos);
64
+ const connectorsToDelete = filters
65
+ ? []
66
+ : allConnectors.filter((h) => !wantConnectors.some((w) => w.name === h.name));
67
+ for (const c of connectorsToDelete) {
68
+ await (0, prompts_1.promptDeleteConnector)(options, c.name);
62
69
  }
63
- else {
64
- utils.logLabeledBullet("dataconnect", "No connectors to deploy.");
70
+ let consolePath = "/dataconnect";
71
+ if (serviceInfos.length === 1) {
72
+ const sn = (0, names_1.parseServiceName)(serviceInfos[0].serviceName);
73
+ consolePath += `/locations/${sn.location}/services/${sn.serviceId}/schema`;
65
74
  }
66
- utils.logLabeledSuccess("dataconnect", `Deployment complete! View your deployed schema and connectors at ${utils.consoleUrl(project, "/dataconnect")}`);
75
+ utils.logLabeledSuccess("dataconnect", `Deployment complete! View your deployed schema and connectors at
76
+
77
+ ${utils.consoleUrl(project, consolePath)}
78
+ `);
67
79
  return;
68
80
  }
69
81
  exports.default = default_1;
70
- async function have(serviceInfos) {
82
+ async function deployedConnectors(serviceInfos) {
71
83
  let connectors = [];
72
84
  for (const si of serviceInfos) {
73
85
  connectors = connectors.concat(await (0, client_1.listConnectors)(si.serviceName));
@@ -94,7 +94,7 @@ function secretVersionName(s) {
94
94
  return `projects/${s.projectId}/secrets/${s.secret}/versions/${(_a = s.version) !== null && _a !== void 0 ? _a : "latest"}`;
95
95
  }
96
96
  exports.secretVersionName = secretVersionName;
97
- exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2"];
97
+ exports.AllFunctionsPlatforms = ["gcfv1", "gcfv2", "run"];
98
98
  function isHttpsTriggered(triggered) {
99
99
  return {}.hasOwnProperty.call(triggered, "httpsTrigger");
100
100
  }
@@ -184,6 +184,7 @@ async function loadExistingBackend(ctx) {
184
184
  ctx.unreachableRegions = {
185
185
  gcfV1: [],
186
186
  gcfV2: [],
187
+ run: [],
187
188
  };
188
189
  const gcfV1Results = await gcf.listAllFunctions(ctx.projectId);
189
190
  for (const apiFunction of gcfV1Results.functions) {
@@ -212,7 +213,7 @@ async function loadExistingBackend(ctx) {
212
213
  }
213
214
  }
214
215
  async function checkAvailability(context, want) {
215
- var _a, _b, _c, _d;
216
+ var _a, _b, _c, _d, _e;
216
217
  if (!context.loadedExistingBackend) {
217
218
  await loadExistingBackend(context);
218
219
  }
@@ -248,6 +249,11 @@ async function checkAvailability(context, want) {
248
249
  context.unreachableRegions.gcfV2.join("\n") +
249
250
  "\nCloud Functions in these regions won't be deleted.");
250
251
  }
252
+ if ((_e = context.unreachableRegions) === null || _e === void 0 ? void 0 : _e.run.length) {
253
+ utils.logLabeledWarning("functions", "The following Cloud Run regions are currently unreachable:\n" +
254
+ context.unreachableRegions.run.join("\n") +
255
+ "\nCloud Run services in these regions won't be deleted.");
256
+ }
251
257
  }
252
258
  exports.checkAvailability = checkAvailability;
253
259
  function allEndpoints(backend) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
3
+ exports.applyPrefix = exports.toBackend = exports.envWithTypes = exports.resolveBackend = exports.AllIngressSettings = exports.AllVpcEgressSettings = exports.AllFunctionsPlatforms = exports.isBlockingTriggered = exports.isTaskQueueTriggered = exports.isScheduleTriggered = exports.isEventTriggered = exports.isCallableTriggered = exports.isHttpsTriggered = exports.of = exports.empty = void 0;
4
4
  const backend = require("./backend");
5
5
  const proto = require("../../gcp/proto");
6
6
  const api = require("../../.../../api");
@@ -324,3 +324,25 @@ function discoverTrigger(endpoint, region, r) {
324
324
  }
325
325
  (0, functional_1.assertExhaustive)(endpoint);
326
326
  }
327
+ function applyPrefix(build, prefix) {
328
+ if (!prefix) {
329
+ return;
330
+ }
331
+ const newEndpoints = {};
332
+ for (const [id, endpoint] of Object.entries(build.endpoints)) {
333
+ const newId = `${prefix}-${id}`;
334
+ if (newId.length > 63) {
335
+ throw new error_1.FirebaseError(`Function id '${newId}' exceeds 63 characters after applying prefix '${prefix}'. Please shorten the prefix or function name.`);
336
+ }
337
+ const fnIdRegex = /^[a-zA-Z][a-zA-Z0-9_-]{0,62}$/;
338
+ if (!fnIdRegex.test(newId)) {
339
+ throw new error_1.FirebaseError(`Function id '${newId}' is invalid after applying prefix '${prefix}'. Function names must start with a letter and can contain letters, numbers, underscores, and hyphens, with a maximum length of 63 characters.`);
340
+ }
341
+ newEndpoints[newId] = endpoint;
342
+ if (endpoint.secretEnvironmentVariables) {
343
+ endpoint.secretEnvironmentVariables = endpoint.secretEnvironmentVariables.map((secret) => (Object.assign(Object.assign({}, secret), { secret: `${prefix}-${secret.secret}` })));
344
+ }
345
+ }
346
+ build.endpoints = newEndpoints;
347
+ }
348
+ exports.applyPrefix = applyPrefix;
@@ -23,7 +23,7 @@ async function defaultServiceAccount(e) {
23
23
  if (e.platform === "gcfv1") {
24
24
  return `${metadata.projectId}@appspot.gserviceaccount.com`;
25
25
  }
26
- else if (e.platform === "gcfv2") {
26
+ else if (e.platform === "gcfv2" || e.platform === "run") {
27
27
  return await (0, computeEngine_1.getDefaultServiceAccount)(metadata.projectNumber);
28
28
  }
29
29
  (0, functional_1.assertExhaustive)(e.platform);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isEndpointFiltered = exports.isCodebaseFiltered = exports.groupEndpointsByCodebase = exports.targetCodebases = exports.getFunctionLabel = exports.getHumanFriendlyPlatformName = exports.getEndpointFilters = exports.parseFunctionSelector = exports.endpointMatchesFilter = exports.endpointMatchesAnyFilter = void 0;
4
4
  const backend = require("./backend");
5
5
  const projectConfig_1 = require("../../functions/projectConfig");
6
+ const functional_1 = require("../../functional");
6
7
  function endpointMatchesAnyFilter(endpoint, filters) {
7
8
  if (!filters) {
8
9
  return true;
@@ -71,7 +72,13 @@ function getHumanFriendlyPlatformName(platform) {
71
72
  if (platform === "gcfv1") {
72
73
  return "1st Gen";
73
74
  }
74
- return "2nd Gen";
75
+ else if (platform === "gcfv2") {
76
+ return "2nd Gen";
77
+ }
78
+ else if (platform === "run") {
79
+ return "Cloud Run";
80
+ }
81
+ (0, functional_1.assertExhaustive)(platform);
75
82
  }
76
83
  exports.getHumanFriendlyPlatformName = getHumanFriendlyPlatformName;
77
84
  function getFunctionLabel(fn) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureAllRequiredAPIsEnabled = exports.warnIfNewGenkitFunctionIsMissingSecrets = exports.loadCodebases = exports.resolveCpuAndConcurrency = exports.inferBlockingDetails = exports.updateEndpointTargetedStatus = exports.inferDetailsFromExisting = exports.prepare = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  const clc = require("colorette");
5
+ const proto = require("../../gcp/proto");
5
6
  const backend = require("./backend");
6
7
  const build = require("./build");
7
8
  const ensureApiEnabled = require("../../ensureApiEnabled");
@@ -76,6 +77,7 @@ async function prepare(context, options, payload) {
76
77
  projectId: projectId,
77
78
  projectAlias: options.projectAlias,
78
79
  };
80
+ proto.convertIfPresent(userEnvOpt, config, "configDir", (cd) => options.config.path(cd));
79
81
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
80
82
  const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
81
83
  const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
@@ -99,12 +101,12 @@ async function prepare(context, options, payload) {
99
101
  }
100
102
  }
101
103
  for (const endpoint of backend.allEndpoints(wantBackend)) {
102
- endpoint.environmentVariables = Object.assign({}, wantBackend.environmentVariables) || {};
104
+ endpoint.environmentVariables = Object.assign({}, (wantBackend.environmentVariables || {}));
103
105
  let resource;
104
106
  if (endpoint.platform === "gcfv1") {
105
107
  resource = `projects/${endpoint.project}/locations/${endpoint.region}/functions/${endpoint.id}`;
106
108
  }
107
- else if (endpoint.platform === "gcfv2") {
109
+ else if (endpoint.platform === "gcfv2" || endpoint.platform === "run") {
108
110
  resource = `projects/${endpoint.project}/locations/${endpoint.region}/services/${endpoint.id}`;
109
111
  }
110
112
  else {
@@ -309,8 +311,10 @@ async function loadCodebases(config, options, firebaseConfig, runtimeConfig, fil
309
311
  await runtimeDelegate.build();
310
312
  const firebaseEnvs = functionsEnv.loadFirebaseEnvs(firebaseConfig, projectId);
311
313
  (0, utils_1.logLabeledBullet)("functions", `Loading and analyzing source code for codebase ${codebase} to determine what to deploy`);
312
- wantBuilds[codebase] = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
313
- wantBuilds[codebase].runtime = codebaseConfig.runtime;
314
+ const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, Object.assign(Object.assign({}, firebaseEnvs), { GOOGLE_CLOUD_QUOTA_PROJECT: projectId }));
315
+ discoveredBuild.runtime = codebaseConfig.runtime;
316
+ build.applyPrefix(discoveredBuild, codebaseConfig.prefix || "");
317
+ wantBuilds[codebase] = discoveredBuild;
314
318
  }
315
319
  return wantBuilds;
316
320
  }
@@ -130,6 +130,7 @@ function monthlyMinInstanceCost(endpoints) {
130
130
  const usage = {
131
131
  gcfv1: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
132
132
  gcfv2: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
133
+ run: { 1: { ram: 0, cpu: 0 }, 2: { ram: 0, cpu: 0 } },
133
134
  };
134
135
  for (const endpoint of endpoints) {
135
136
  if (endpoint.minInstances === undefined || endpoint.minInstances === null) {
@@ -147,10 +148,10 @@ function monthlyMinInstanceCost(endpoints) {
147
148
  }
148
149
  else {
149
150
  const tier = V2_REGION_TO_TIER[endpoint.region];
150
- usage["gcfv2"][tier].ram =
151
- usage["gcfv2"][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
152
- usage["gcfv2"][tier].cpu =
153
- usage["gcfv2"][tier].cpu +
151
+ usage[endpoint.platform][tier].ram =
152
+ usage[endpoint.platform][tier].ram + ramGb * SECONDS_PER_MONTH * endpoint.minInstances;
153
+ usage[endpoint.platform][tier].cpu =
154
+ usage[endpoint.platform][tier].cpu +
154
155
  endpoint.cpu * SECONDS_PER_MONTH * endpoint.minInstances;
155
156
  }
156
157
  }
@@ -166,6 +167,12 @@ function monthlyMinInstanceCost(endpoints) {
166
167
  let v2CpuBill = usage["gcfv2"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["gcfv2"][2].cpu * exports.V2_RATES.idleVCpu[2];
167
168
  v2CpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
168
169
  v2CpuBill = Math.max(v2CpuBill, 0);
169
- return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill;
170
+ let runMemoryBill = usage["run"][1].ram * exports.V2_RATES.memoryGb[1] + usage["run"][2].ram * exports.V2_RATES.memoryGb[2];
171
+ runMemoryBill -= exports.V2_FREE_TIER.memoryGb * exports.V2_RATES.memoryGb[1];
172
+ runMemoryBill = Math.max(runMemoryBill, 0);
173
+ let runCpuBill = usage["run"][1].cpu * exports.V2_RATES.idleVCpu[1] + usage["run"][2].cpu * exports.V2_RATES.idleVCpu[2];
174
+ runCpuBill -= exports.V2_FREE_TIER.vCpu * exports.V2_RATES.vCpu[1];
175
+ runCpuBill = Math.max(runCpuBill, 0);
176
+ return v1MemoryBill + v1CpuBill + v2MemoryBill + v2CpuBill + runMemoryBill + runCpuBill;
170
177
  }
171
178
  exports.monthlyMinInstanceCost = monthlyMinInstanceCost;
@@ -136,6 +136,11 @@ class Fabricator {
136
136
  else if (endpoint.platform === "gcfv2") {
137
137
  await this.createV2Function(endpoint, scraperV2);
138
138
  }
139
+ else if (endpoint.platform === "run") {
140
+ throw new error_1.FirebaseError("Creating new Cloud Run functions is not supported yet.", {
141
+ exit: 1,
142
+ });
143
+ }
139
144
  else {
140
145
  (0, functional_1.assertExhaustive)(endpoint.platform);
141
146
  }
@@ -154,6 +159,9 @@ class Fabricator {
154
159
  else if (update.endpoint.platform === "gcfv2") {
155
160
  await this.updateV2Function(update.endpoint, scraperV2);
156
161
  }
162
+ else if (update.endpoint.platform === "run") {
163
+ throw new error_1.FirebaseError("Updating Cloud Run functions is not supported yet.", { exit: 1 });
164
+ }
157
165
  else {
158
166
  (0, functional_1.assertExhaustive)(update.endpoint.platform);
159
167
  }
@@ -162,11 +170,15 @@ class Fabricator {
162
170
  async deleteEndpoint(endpoint) {
163
171
  await this.deleteTrigger(endpoint);
164
172
  if (endpoint.platform === "gcfv1") {
165
- await this.deleteV1Function(endpoint);
173
+ return this.deleteV1Function(endpoint);
166
174
  }
167
- else {
168
- await this.deleteV2Function(endpoint);
175
+ else if (endpoint.platform === "gcfv2") {
176
+ return this.deleteV2Function(endpoint);
169
177
  }
178
+ else if (endpoint.platform === "run") {
179
+ throw new error_1.FirebaseError("Deleting Cloud Run functions is not supported yet.", { exit: 1 });
180
+ }
181
+ (0, functional_1.assertExhaustive)(endpoint.platform);
170
182
  }
171
183
  async createV1Function(endpoint, scraper) {
172
184
  var _a, _b;
@@ -471,6 +483,11 @@ class Fabricator {
471
483
  .catch(rethrowAs(endpoint, "set concurrency"));
472
484
  }
473
485
  async setTrigger(endpoint) {
486
+ if (endpoint.platform === "run") {
487
+ throw new error_1.FirebaseError("Setting triggers for Cloud Run functions is not supported yet.", {
488
+ exit: 1,
489
+ });
490
+ }
474
491
  if (backend.isScheduleTriggered(endpoint)) {
475
492
  if (endpoint.platform === "gcfv1") {
476
493
  await this.upsertScheduleV1(endpoint);
@@ -490,6 +507,11 @@ class Fabricator {
490
507
  }
491
508
  }
492
509
  async deleteTrigger(endpoint) {
510
+ if (endpoint.platform === "run") {
511
+ throw new error_1.FirebaseError("Deleting triggers for Cloud Run functions is not supported yet.", {
512
+ exit: 1,
513
+ });
514
+ }
493
515
  if (backend.isScheduleTriggered(endpoint)) {
494
516
  if (endpoint.platform === "gcfv1") {
495
517
  await this.deleteScheduleV1(endpoint);
@@ -5,6 +5,7 @@ const clc = require("colorette");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
  const fsConfig = require("../firestore/fsConfig");
8
+ const proto = require("../gcp/proto");
8
9
  const logger_1 = require("../logger");
9
10
  const track_1 = require("../track");
10
11
  const utils = require("../utils");
@@ -116,7 +117,7 @@ function shouldStart(options, name) {
116
117
  return true;
117
118
  }
118
119
  catch (err) {
119
- emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("WARN", "functions", `The functions emulator is configured but there is no functions source directory. Have you run ${clc.bold("firebase init functions")}?`);
120
+ emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("ERROR", "functions", `Failed to start Functions emulator: ${err.message}`);
120
121
  return false;
121
122
  }
122
123
  }
@@ -344,15 +345,18 @@ async function startAll(options, showUI = true, runningTestScript = false) {
344
345
  if (runtime && !(0, supported_1.isRuntime)(runtime)) {
345
346
  throw new error_1.FirebaseError(`Cannot load functions from ${functionsDir} because it has invalid runtime ${runtime}`);
346
347
  }
347
- emulatableBackends.push({
348
+ const backend = {
348
349
  functionsDir,
349
350
  runtime,
350
351
  codebase: cfg.codebase,
352
+ prefix: cfg.prefix,
351
353
  env: Object.assign({}, options.extDevEnv),
352
354
  secretEnv: [],
353
355
  predefinedTriggers: options.extDevTriggers,
354
356
  ignore: cfg.ignore,
355
- });
357
+ };
358
+ proto.convertIfPresent(backend, cfg, "configDir", (cd) => path.join(projectDir, cd));
359
+ emulatableBackends.push(backend);
356
360
  }
357
361
  }
358
362
  if (extensionEmulator) {
@@ -54,28 +54,28 @@
54
54
  },
55
55
  "dataconnect": {
56
56
  "darwin": {
57
- "version": "2.11.1",
58
- "expectedSize": 29352800,
59
- "expectedChecksum": "52d86a5546bbb9e2fcd67faa90b9f07e",
60
- "expectedChecksumSHA256": "217b66589c32d4378201100fa968e69f7c94537044b892be1aaa14d7f6ce6b12",
61
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.1",
62
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
57
+ "version": "2.11.2",
58
+ "expectedSize": 29447008,
59
+ "expectedChecksum": "13bc7d3bb0a0bbfe601991361e4413c2",
60
+ "expectedChecksumSHA256": "e3b029eb461f0fe6f0c825c7a71d42c7a09c2b8ee4fac10c3e187d78fe5083f6",
61
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.2",
62
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
63
63
  },
64
64
  "win32": {
65
- "version": "2.11.1",
66
- "expectedSize": 29841920,
67
- "expectedChecksum": "704cee75ad2d384cf28ac1683c8d1179",
68
- "expectedChecksumSHA256": "fadb5a1d0f03160c133389df9d452cc620cfdface041971a5356ac5a69e937ff",
69
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.1",
70
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1.exe"
65
+ "version": "2.11.2",
66
+ "expectedSize": 29934592,
67
+ "expectedChecksum": "032a0749781fc338b446d753dd543bf5",
68
+ "expectedChecksumSHA256": "2d0498b3ef94b4e777d6fc1d526279376caf3842549f39f13a8ca327393bf810",
69
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.2",
70
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2.exe"
71
71
  },
72
72
  "linux": {
73
- "version": "2.11.1",
74
- "expectedSize": 29282488,
75
- "expectedChecksum": "435656eab760033228bf7b7e77d56dde",
76
- "expectedChecksumSHA256": "0abb33c0bba0ed8ef1394cc1cab5a333ec81b3dddf48aa625abe8ca28f38840e",
77
- "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.1",
78
- "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.1"
73
+ "version": "2.11.2",
74
+ "expectedSize": 29368504,
75
+ "expectedChecksum": "065a7523f881952040ac678a9a1e9323",
76
+ "expectedChecksumSHA256": "41ca6561cf77107b5d1d829233844d21c46a5e4bb823c150b1b32591dc2463e0",
77
+ "remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.2",
78
+ "downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.2"
79
79
  }
80
80
  }
81
81
  }
@@ -28,8 +28,9 @@ const utils_1 = require("../utils");
28
28
  const adminSdkConfig_1 = require("./adminSdkConfig");
29
29
  const validate_1 = require("../deploy/functions/validate");
30
30
  const secretManager_1 = require("../gcp/secretManager");
31
- const runtimes = require("../deploy/functions/runtimes");
32
31
  const backend = require("../deploy/functions/backend");
32
+ const build = require("../deploy/functions/build");
33
+ const runtimes = require("../deploy/functions/runtimes");
33
34
  const functionsEnv = require("../functions/env");
34
35
  const v1_1 = require("../functions/events/v1");
35
36
  const build_1 = require("../deploy/functions/build");
@@ -82,6 +83,7 @@ class FunctionsEmulator {
82
83
  this.blockingFunctionsConfig = {};
83
84
  this.staticBackends = [];
84
85
  this.dynamicBackends = [];
86
+ this.watchers = [];
85
87
  this.debugMode = false;
86
88
  this.staticBackends = args.emulatableBackends;
87
89
  emulatorLogger_1.EmulatorLogger.setVerbosity(this.args.verbosity ? emulatorLogger_1.Verbosity[this.args.verbosity] : emulatorLogger_1.Verbosity["DEBUG"]);
@@ -271,6 +273,7 @@ class FunctionsEmulator {
271
273
  ],
272
274
  persistent: true,
273
275
  });
276
+ this.watchers.push(watcher);
274
277
  const debouncedLoadTriggers = (0, utils_1.debounce)(() => this.loadTriggers(backend), 1000);
275
278
  watcher.on("change", (filePath) => {
276
279
  this.logger.log("DEBUG", `File ${filePath} changed, reloading triggers`);
@@ -292,6 +295,10 @@ class FunctionsEmulator {
292
295
  for (const pool of Object.values(this.workerPools)) {
293
296
  pool.exit();
294
297
  }
298
+ for (const watcher of this.watchers) {
299
+ await watcher.close();
300
+ }
301
+ this.watchers = [];
295
302
  if (this.destroyServer) {
296
303
  await this.destroyServer();
297
304
  }
@@ -322,6 +329,7 @@ class FunctionsEmulator {
322
329
  projectId: this.args.projectId,
323
330
  projectAlias: this.args.projectAlias,
324
331
  isEmulator: true,
332
+ configDir: emulatableBackend.configDir,
325
333
  };
326
334
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
327
335
  const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
@@ -329,6 +337,7 @@ class FunctionsEmulator {
329
337
  await this.args.extensionsEmulator.addDynamicExtensions(emulatableBackend.codebase, discoveredBuild);
330
338
  await this.loadDynamicExtensionBackends();
331
339
  }
340
+ build.applyPrefix(discoveredBuild, emulatableBackend.prefix || "");
332
341
  const resolution = await (0, build_1.resolveBackend)({
333
342
  build: discoveredBuild,
334
343
  firebaseConfig: JSON.parse(firebaseConfig),
@@ -871,6 +880,7 @@ class FunctionsEmulator {
871
880
  getUserEnvs(backend) {
872
881
  const projectInfo = {
873
882
  functionsSource: backend.functionsDir,
883
+ configDir: backend.configDir,
874
884
  projectId: this.args.projectId,
875
885
  projectAlias: this.args.projectAlias,
876
886
  isEmulator: true,
@@ -48,6 +48,10 @@ exports.ALL_EXPERIMENTS = experiments({
48
48
  default: true,
49
49
  public: true,
50
50
  },
51
+ runfunctions: {
52
+ shortDescription: "Functions created using the V2 API target Cloud Run Functions (not production ready)",
53
+ public: false,
54
+ },
51
55
  emulatoruisnapshot: {
52
56
  shortDescription: "Load pre-release versions of the emulator UI",
53
57
  },
@@ -7,7 +7,7 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
7
7
  function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
8
8
  };
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
10
+ exports.diagnoseAndFixProject = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.displayReleaseNotes = exports.getPublisherProjectFromName = exports.createSourceFromLocation = exports.getMissingPublisherError = exports.uploadExtensionVersionFromLocalSource = exports.uploadExtensionVersionFromGitHubSource = exports.unpackExtensionState = exports.getNextVersionByStage = exports.ensureExtensionsPublisherApiEnabled = exports.ensureExtensionsApiEnabled = exports.checkExtensionsApiEnabled = exports.promptForExtensionRoot = exports.promptForValidRepoURI = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteSecretParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOPULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
11
11
  const clc = require("colorette");
12
12
  const ora = require("ora");
13
13
  const semver = require("semver");
@@ -21,10 +21,8 @@ const unzip_1 = require("./../unzip");
21
21
  marked_1.marked.use((0, marked_terminal_1.markedTerminal)());
22
22
  const api_1 = require("../api");
23
23
  const archiveDirectory_1 = require("../archiveDirectory");
24
- const utils_1 = require("./utils");
25
24
  const functionsConfig_1 = require("../functionsConfig");
26
25
  const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
27
- const resolveSource_1 = require("./resolveSource");
28
26
  const error_1 = require("../error");
29
27
  const diagnose_1 = require("./diagnose");
30
28
  const askUserForParam_1 = require("./askUserForParam");
@@ -37,7 +35,7 @@ const prompt_1 = require("../prompt");
37
35
  const refs = require("./refs");
38
36
  const localHelper_1 = require("./localHelper");
39
37
  const logger_1 = require("../logger");
40
- const utils_2 = require("../utils");
38
+ const utils_1 = require("../utils");
41
39
  const change_log_1 = require("./change-log");
42
40
  const getProjectNumber_1 = require("../getProjectNumber");
43
41
  const constants_1 = require("../emulator/constants");
@@ -61,7 +59,7 @@ var SourceOrigin;
61
59
  exports.logPrefix = "extensions";
62
60
  const VALID_LICENSES = ["apache-2.0"];
63
61
  exports.URL_REGEX = /^https:/;
64
- exports.EXTENSIONS_BUCKET_NAME = (0, utils_2.envOverride)("FIREBASE_EXTENSIONS_UPLOAD_BUCKET", "firebase-ext-eap-uploads");
62
+ exports.EXTENSIONS_BUCKET_NAME = (0, utils_1.envOverride)("FIREBASE_EXTENSIONS_UPLOAD_BUCKET", "firebase-ext-eap-uploads");
65
63
  const AUTOPOPULATED_PARAM_NAMES = [
66
64
  "PROJECT_ID",
67
65
  "STORAGE_BUCKET",
@@ -104,7 +102,7 @@ async function getFirebaseProjectParams(projectId, emulatorMode = false) {
104
102
  projectNumber = await (0, getProjectNumber_1.getProjectNumber)({ projectId });
105
103
  }
106
104
  catch (err) {
107
- (0, utils_2.logLabeledError)("extensions", `Unable to look up project number for ${projectId}.\n` +
105
+ (0, utils_1.logLabeledError)("extensions", `Unable to look up project number for ${projectId}.\n` +
108
106
  " If this is a real project, ensure that you are logged in and have access to it.\n" +
109
107
  " If this is a fake project, please use a project ID starting with 'demo-' to skip production calls.\n" +
110
108
  " Continuing with a fake project number - secrets and other features that require production access may behave unexpectedly.");
@@ -810,15 +808,6 @@ function displayReleaseNotes(args) {
810
808
  logger_1.logger.info(message);
811
809
  }
812
810
  exports.displayReleaseNotes = displayReleaseNotes;
813
- async function promptForOfficialExtension(message) {
814
- const officialExts = await (0, resolveSource_1.getExtensionRegistry)(true);
815
- return await (0, prompt_1.select)({
816
- message,
817
- choices: (0, utils_1.convertOfficialExtensionsToList)(officialExts),
818
- pageSize: Object.keys(officialExts).length,
819
- });
820
- }
821
- exports.promptForOfficialExtension = promptForOfficialExtension;
822
811
  async function promptForRepeatInstance(projectName, extensionName) {
823
812
  const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'. What would you like to do?`;
824
813
  return await (0, prompt_1.select)({