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.
- package/README.md +1 -1
- package/lib/commands/dataconnect-services-list.js +5 -5
- package/lib/commands/dataconnect-sql-grant.js +5 -0
- package/lib/commands/dataconnect-sql-setup.js +1 -3
- package/lib/crashlytics/getIssueDetails.js +41 -0
- package/lib/crashlytics/getSampleCrash.js +48 -0
- package/lib/dataconnect/client.js +23 -15
- package/lib/dataconnect/ensureApis.js +5 -9
- package/lib/dataconnect/errors.js +7 -1
- package/lib/dataconnect/fileUtils.js +5 -6
- package/lib/dataconnect/freeTrial.js +16 -39
- package/lib/dataconnect/provisionCloudSql.js +67 -70
- package/lib/dataconnect/schemaMigration.js +222 -170
- package/lib/deploy/dataconnect/deploy.js +9 -11
- package/lib/deploy/dataconnect/prepare.js +7 -10
- package/lib/deploy/dataconnect/release.js +42 -30
- package/lib/deploy/functions/backend.js +8 -2
- package/lib/deploy/functions/build.js +23 -1
- package/lib/deploy/functions/ensure.js +1 -1
- package/lib/deploy/functions/functionsDeployHelper.js +8 -1
- package/lib/deploy/functions/prepare.js +8 -4
- package/lib/deploy/functions/pricing.js +12 -5
- package/lib/deploy/functions/release/fabricator.js +25 -3
- package/lib/emulator/controller.js +7 -3
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/emulator/functionsEmulator.js +11 -1
- package/lib/experiments.js +4 -0
- package/lib/extensions/extensionsHelper.js +4 -15
- package/lib/extensions/utils.js +1 -12
- package/lib/firestore/api.js +25 -11
- package/lib/firestore/pretty-print.js +7 -0
- package/lib/functional.js +7 -1
- package/lib/functions/env.js +19 -15
- package/lib/functions/projectConfig.js +25 -2
- package/lib/functions/secrets.js +3 -0
- package/lib/gcp/cloudfunctionsv2.js +3 -31
- package/lib/gcp/cloudscheduler.js +1 -1
- package/lib/gcp/cloudsql/cloudsqladmin.js +2 -14
- package/lib/gcp/cloudsql/connect.js +3 -2
- package/lib/gcp/cloudsql/permissionsSetup.js +23 -16
- package/lib/gcp/k8s.js +32 -0
- package/lib/gcp/runv2.js +178 -0
- package/lib/gemini/fdcExperience.js +5 -3
- package/lib/init/features/dataconnect/index.js +266 -162
- package/lib/init/features/dataconnect/sdk.js +36 -20
- package/lib/init/features/project.js +4 -0
- package/lib/management/studio.js +1 -1
- package/lib/mcp/tools/core/init.js +7 -6
- package/lib/mcp/tools/crashlytics/get_issue_details.js +33 -0
- package/lib/mcp/tools/crashlytics/get_sample_crash.js +43 -0
- package/lib/mcp/tools/crashlytics/index.js +7 -1
- package/lib/mcp/tools/crashlytics/list_top_issues.js +2 -1
- package/lib/rtdb.js +1 -1
- package/package.json +1 -1
- package/schema/firebase-config.json +6 -0
- 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
|
|
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 = (
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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[
|
|
151
|
-
usage[
|
|
152
|
-
usage[
|
|
153
|
-
usage[
|
|
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
|
-
|
|
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
|
-
|
|
173
|
+
return this.deleteV1Function(endpoint);
|
|
166
174
|
}
|
|
167
|
-
else {
|
|
168
|
-
|
|
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("
|
|
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
|
-
|
|
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.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.11.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
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.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.11.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
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.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.11.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.11.
|
|
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,
|
package/lib/experiments.js
CHANGED
|
@@ -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.
|
|
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
|
|
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,
|
|
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,
|
|
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)({
|