firebase-tools 10.7.0 → 10.8.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/lib/commands/ext-configure.js +26 -15
- package/lib/commands/ext-export.js +14 -5
- package/lib/commands/ext-install.js +31 -2
- package/lib/commands/ext-update.js +17 -10
- package/lib/commands/functions-delete.js +9 -2
- package/lib/commands/functions-secrets-set.js +1 -13
- package/lib/deploy/extensions/planner.js +12 -0
- package/lib/deploy/extensions/tasks.js +13 -0
- package/lib/deploy/functions/backend.js +67 -10
- package/lib/deploy/functions/build.js +28 -9
- package/lib/deploy/functions/checkIam.js +71 -56
- package/lib/deploy/functions/containerCleaner.js +8 -7
- package/lib/deploy/functions/deploy.js +49 -27
- package/lib/deploy/functions/functionsDeployHelper.js +48 -4
- package/lib/deploy/functions/prepare.js +125 -74
- package/lib/deploy/functions/pricing.js +2 -2
- package/lib/deploy/functions/release/executor.js +1 -1
- package/lib/deploy/functions/release/fabricator.js +94 -36
- package/lib/deploy/functions/release/index.js +16 -27
- package/lib/deploy/functions/release/planner.js +12 -7
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +13 -1
- package/lib/deploy/functions/runtimes/golang/index.js +3 -0
- package/lib/deploy/functions/runtimes/node/index.js +7 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +108 -1
- package/lib/deploy/functions/services/storage.js +6 -12
- package/lib/deploy/functions/validate.js +58 -8
- package/lib/deploy/hosting/convertConfig.js +6 -4
- package/lib/emulator/auth/cloudFunctions.js +6 -2
- package/lib/emulator/auth/operations.js +0 -1
- package/lib/emulator/auth/server.js +8 -1
- package/lib/emulator/auth/state.js +27 -24
- package/lib/emulator/controller.js +12 -9
- package/lib/emulator/databaseEmulator.js +36 -3
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/extensionsEmulator.js +3 -0
- package/lib/emulator/functionsEmulator.js +11 -9
- package/lib/emulator/functionsEmulatorRuntime.js +1 -1
- package/lib/emulator/functionsEmulatorShared.js +5 -1
- package/lib/emulator/functionsEmulatorShell.js +2 -3
- package/lib/emulator/functionsEmulatorUtils.js +5 -1
- package/lib/emulator/pubsubEmulator.js +13 -9
- package/lib/emulator/storage/apis/firebase.js +26 -4
- package/lib/ensureApiEnabled.js +1 -1
- package/lib/extensions/askUserForEventsConfig.js +97 -0
- package/lib/extensions/export.js +7 -0
- package/lib/extensions/extensionsApi.js +47 -7
- package/lib/extensions/manifest.js +1 -1
- package/lib/extensions/paramHelper.js +2 -0
- package/lib/extensions/updateHelper.js +7 -1
- package/lib/extensions/warnings.js +11 -4
- package/lib/functions/projectConfig.js +13 -8
- package/lib/functionsShellCommandAction.js +1 -1
- package/lib/gcp/cloudfunctions.js +9 -2
- package/lib/gcp/cloudfunctionsv2.js +28 -10
- package/lib/gcp/serviceusage.js +24 -0
- package/lib/previews.js +1 -1
- package/lib/serve/functions.js +16 -19
- package/lib/throttler/throttler.js +2 -1
- package/npm-shrinkwrap.json +214 -527
- package/package.json +3 -3
- package/templates/extensions/typescript/package.lint.json +2 -1
- package/templates/extensions/typescript/package.nolint.json +2 -1
- package/templates/init/functions/typescript/package.lint.json +1 -0
- package/templates/init/functions/typescript/package.nolint.json +1 -0
|
@@ -48,20 +48,25 @@ function calculateUpdate(want, have) {
|
|
|
48
48
|
return update;
|
|
49
49
|
}
|
|
50
50
|
exports.calculateUpdate = calculateUpdate;
|
|
51
|
-
function createDeploymentPlan(
|
|
51
|
+
function createDeploymentPlan(args) {
|
|
52
|
+
let { wantBackend, haveBackend, codebase, filters, deleteAll } = args;
|
|
52
53
|
let deployment = {};
|
|
53
|
-
|
|
54
|
+
wantBackend = backend.matchingBackend(wantBackend, (endpoint) => {
|
|
54
55
|
return (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
|
|
55
56
|
});
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const wantedEndpoint = backend.hasEndpoint(wantBackend);
|
|
58
|
+
haveBackend = backend.matchingBackend(haveBackend, (endpoint) => {
|
|
59
|
+
return wantedEndpoint(endpoint) || (0, functionsDeployHelper_1.endpointMatchesAnyFilter)(endpoint, filters);
|
|
58
60
|
});
|
|
59
|
-
const regions = new Set([
|
|
61
|
+
const regions = new Set([
|
|
62
|
+
...Object.keys(wantBackend.endpoints),
|
|
63
|
+
...Object.keys(haveBackend.endpoints),
|
|
64
|
+
]);
|
|
60
65
|
for (const region of regions) {
|
|
61
|
-
const changesets = calculateChangesets(
|
|
66
|
+
const changesets = calculateChangesets(wantBackend.endpoints[region] || {}, haveBackend.endpoints[region] || {}, (e) => `${codebase}-${e.region}-${e.availableMemoryMb || "default"}`, deleteAll);
|
|
62
67
|
deployment = Object.assign(Object.assign({}, deployment), changesets);
|
|
63
68
|
}
|
|
64
|
-
if (upgradedToGCFv2WithoutSettingConcurrency(
|
|
69
|
+
if (upgradedToGCFv2WithoutSettingConcurrency(wantBackend, haveBackend)) {
|
|
65
70
|
utils.logLabeledBullet("functions", "You are updating one or more functions to Google Cloud Functions v2, " +
|
|
66
71
|
"which introduces support for concurrent execution. New functions " +
|
|
67
72
|
"default to 80 concurrent executions, but existing functions keep the " +
|
|
@@ -187,7 +187,19 @@ function parseEndpoints(manifest, id, project, defaultRegion, runtime) {
|
|
|
187
187
|
region,
|
|
188
188
|
project,
|
|
189
189
|
runtime, entryPoint: ep.entryPoint }, triggered);
|
|
190
|
-
(0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables");
|
|
190
|
+
(0, proto_1.copyIfPresent)(parsed, ep, "availableMemoryMb", "maxInstances", "minInstances", "concurrency", "serviceAccountEmail", "timeoutSeconds", "vpc", "labels", "ingressSettings", "environmentVariables", "cpu");
|
|
191
|
+
(0, proto_1.renameIfPresent)(parsed, ep, "secretEnvironmentVariables", "secretEnvironmentVariables", (senvs) => {
|
|
192
|
+
if (senvs && senvs.length > 0) {
|
|
193
|
+
ep.secretEnvironmentVariables = [];
|
|
194
|
+
for (const { key, secret } of senvs) {
|
|
195
|
+
ep.secretEnvironmentVariables.push({
|
|
196
|
+
key,
|
|
197
|
+
secret: secret || key,
|
|
198
|
+
projectId: project,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
});
|
|
191
203
|
allParsed.push(parsed);
|
|
192
204
|
}
|
|
193
205
|
return allParsed;
|
|
@@ -105,6 +105,9 @@ class Delegate {
|
|
|
105
105
|
return p;
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
async discoverBuild(configValues, envs) {
|
|
109
|
+
return { requiredAPIs: [], endpoints: {}, params: [] };
|
|
110
|
+
}
|
|
108
111
|
async discoverSpec(configValues, envs) {
|
|
109
112
|
let discovered = await discovery.detectFromYaml(this.sourceDir, this.projectId, this.runtime);
|
|
110
113
|
if (!discovered) {
|
|
@@ -84,6 +84,10 @@ class Delegate {
|
|
|
84
84
|
}
|
|
85
85
|
async discoverSpec(config, env) {
|
|
86
86
|
if (previews_1.previews.functionsv2) {
|
|
87
|
+
if (!semver.valid(this.sdkVersion)) {
|
|
88
|
+
logger_1.logger.debug(`Could not parse firebase-functions version '${this.sdkVersion}' into semver. Falling back to parseTriggers.`);
|
|
89
|
+
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
90
|
+
}
|
|
87
91
|
if (semver.lt(this.sdkVersion, MIN_FUNCTIONS_SDK_VERSION)) {
|
|
88
92
|
(0, utils_1.logLabeledWarning)("functions", `You are using an old version of firebase-functions SDK (${this.sdkVersion}). ` +
|
|
89
93
|
`Please update firebase-functions SDK to >=${MIN_FUNCTIONS_SDK_VERSION}`);
|
|
@@ -106,5 +110,8 @@ class Delegate {
|
|
|
106
110
|
}
|
|
107
111
|
return parseTriggers.discoverBackend(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
108
112
|
}
|
|
113
|
+
async discoverBuild(config, env) {
|
|
114
|
+
return parseTriggers.discoverBuild(this.projectId, this.sourceDir, this.runtime, config, env);
|
|
115
|
+
}
|
|
109
116
|
}
|
|
110
117
|
exports.Delegate = Delegate;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.addResourcesToBackend = exports.mergeRequiredAPIs = exports.discoverBackend = exports.useStrategy = void 0;
|
|
3
|
+
exports.addResourcesToBackend = exports.addResourcesToBuild = exports.mergeRequiredAPIs = exports.discoverBackend = exports.discoverBuild = exports.useStrategy = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const _ = require("lodash");
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
@@ -49,6 +49,19 @@ function useStrategy(context) {
|
|
|
49
49
|
return Promise.resolve(true);
|
|
50
50
|
}
|
|
51
51
|
exports.useStrategy = useStrategy;
|
|
52
|
+
async function discoverBuild(projectId, sourceDir, runtime, configValues, envs) {
|
|
53
|
+
const triggerAnnotations = await parseTriggers(projectId, sourceDir, configValues, envs);
|
|
54
|
+
const want = {
|
|
55
|
+
requiredAPIs: [],
|
|
56
|
+
endpoints: {},
|
|
57
|
+
params: [],
|
|
58
|
+
};
|
|
59
|
+
for (const annotation of triggerAnnotations) {
|
|
60
|
+
addResourcesToBuild(projectId, runtime, annotation, want);
|
|
61
|
+
}
|
|
62
|
+
return want;
|
|
63
|
+
}
|
|
64
|
+
exports.discoverBuild = discoverBuild;
|
|
52
65
|
async function discoverBackend(projectId, sourceDir, runtime, configValues, envs) {
|
|
53
66
|
const triggerAnnotations = await parseTriggers(projectId, sourceDir, configValues, envs);
|
|
54
67
|
const want = Object.assign(Object.assign({}, backend.empty()), { environmentVariables: envs });
|
|
@@ -74,6 +87,100 @@ function mergeRequiredAPIs(backend) {
|
|
|
74
87
|
backend.requiredAPIs = merged;
|
|
75
88
|
}
|
|
76
89
|
exports.mergeRequiredAPIs = mergeRequiredAPIs;
|
|
90
|
+
function addResourcesToBuild(projectId, runtime, annotation, want) {
|
|
91
|
+
var _a;
|
|
92
|
+
Object.freeze(annotation);
|
|
93
|
+
const regions = annotation.regions || [api.functionsDefaultRegion];
|
|
94
|
+
let triggered;
|
|
95
|
+
const triggerCount = +!!annotation.httpsTrigger +
|
|
96
|
+
+!!annotation.eventTrigger +
|
|
97
|
+
+!!annotation.taskQueueTrigger +
|
|
98
|
+
+!!annotation.blockingTrigger;
|
|
99
|
+
if (triggerCount !== 1) {
|
|
100
|
+
throw new error_1.FirebaseError("Unexpected annotation generated by the Firebase Functions SDK. This should never happen.");
|
|
101
|
+
}
|
|
102
|
+
if (annotation.taskQueueTrigger) {
|
|
103
|
+
want.requiredAPIs.push({
|
|
104
|
+
api: "cloudtasks.googleapis.com",
|
|
105
|
+
reason: "Needed for task queue functions.",
|
|
106
|
+
});
|
|
107
|
+
triggered = {
|
|
108
|
+
taskQueueTrigger: {},
|
|
109
|
+
};
|
|
110
|
+
proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "invoker");
|
|
111
|
+
proto.copyIfPresent(triggered.taskQueueTrigger, annotation.taskQueueTrigger, "rateLimits");
|
|
112
|
+
if (annotation.taskQueueTrigger.retryConfig) {
|
|
113
|
+
triggered.taskQueueTrigger.retryConfig = Object.assign(annotation.taskQueueTrigger.retryConfig, {
|
|
114
|
+
maxRetryDurationSeconds: proto.secondsFromDuration(annotation.taskQueueTrigger.retryConfig.maxRetryDuration || "0"),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else if (annotation.httpsTrigger) {
|
|
119
|
+
if ((_a = annotation.labels) === null || _a === void 0 ? void 0 : _a["deployment-callable"]) {
|
|
120
|
+
delete annotation.labels["deployment-callable"];
|
|
121
|
+
triggered = { callableTrigger: {} };
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
const trigger = {};
|
|
125
|
+
if (annotation.failurePolicy) {
|
|
126
|
+
logger_1.logger.warn(`Ignoring retry policy for HTTPS function ${annotation.name}`);
|
|
127
|
+
}
|
|
128
|
+
if (annotation.httpsTrigger.invoker) {
|
|
129
|
+
trigger.invoker = annotation.httpsTrigger.invoker[0];
|
|
130
|
+
}
|
|
131
|
+
triggered = { httpsTrigger: trigger };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (annotation.schedule) {
|
|
135
|
+
want.requiredAPIs.push({
|
|
136
|
+
api: "cloudscheduler.googleapis.com",
|
|
137
|
+
reason: "Needed for scheduled functions.",
|
|
138
|
+
});
|
|
139
|
+
triggered = {
|
|
140
|
+
scheduleTrigger: {
|
|
141
|
+
schedule: annotation.schedule.schedule,
|
|
142
|
+
timeZone: annotation.schedule.timeZone || "what's the default timezone?",
|
|
143
|
+
retryConfig: annotation.schedule.retryConfig || {},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else if (annotation.blockingTrigger) {
|
|
148
|
+
if (events.v1.AUTH_BLOCKING_EVENTS.includes(annotation.blockingTrigger.eventType)) {
|
|
149
|
+
want.requiredAPIs.push({
|
|
150
|
+
api: "identitytoolkit.googleapis.com",
|
|
151
|
+
reason: "Needed for auth blocking functions.",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
triggered = {
|
|
155
|
+
blockingTrigger: {
|
|
156
|
+
eventType: annotation.blockingTrigger.eventType,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
triggered = {
|
|
162
|
+
eventTrigger: {
|
|
163
|
+
eventType: annotation.eventTrigger.eventType,
|
|
164
|
+
eventFilters: { resource: annotation.eventTrigger.resource },
|
|
165
|
+
retry: !!annotation.failurePolicy,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const endpointId = annotation.name;
|
|
170
|
+
const endpoint = Object.assign({ platform: annotation.platform || "gcfv1", region: regions, project: projectId, entryPoint: annotation.entryPoint, runtime: runtime, serviceAccount: annotation.serviceAccountEmail || "default" }, triggered);
|
|
171
|
+
if (annotation.vpcConnector != null) {
|
|
172
|
+
let maybeId = annotation.vpcConnector;
|
|
173
|
+
if (maybeId && !maybeId.includes("/")) {
|
|
174
|
+
maybeId = `projects/${projectId}/locations/$REGION/connectors/${maybeId}`;
|
|
175
|
+
}
|
|
176
|
+
endpoint.vpc = { connector: maybeId };
|
|
177
|
+
proto.renameIfPresent(endpoint.vpc, annotation, "egressSettings", "vpcConnectorEgressSettings");
|
|
178
|
+
}
|
|
179
|
+
proto.copyIfPresent(endpoint, annotation, "concurrency", "labels", "ingressSettings", "maxInstances", "minInstances", "availableMemoryMb");
|
|
180
|
+
proto.renameIfPresent(endpoint, annotation, "timeoutSeconds", "timeout", proto.secondsFromDuration);
|
|
181
|
+
want.endpoints[endpointId] = endpoint;
|
|
182
|
+
}
|
|
183
|
+
exports.addResourcesToBuild = addResourcesToBuild;
|
|
77
184
|
function addResourcesToBackend(projectId, runtime, annotation, want) {
|
|
78
185
|
var _a;
|
|
79
186
|
Object.freeze(annotation);
|
|
@@ -6,20 +6,14 @@ const logger_1 = require("../../../logger");
|
|
|
6
6
|
const error_1 = require("../../../error");
|
|
7
7
|
const location_1 = require("../../../gcp/location");
|
|
8
8
|
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
9
|
-
async function obtainStorageBindings(projectNumber
|
|
9
|
+
async function obtainStorageBindings(projectNumber) {
|
|
10
10
|
const storageResponse = await storage.getServiceAccount(projectNumber);
|
|
11
11
|
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
if (!pubsubBinding.members.find((m) => m === storageServiceAgent)) {
|
|
20
|
-
pubsubBinding.members.push(storageServiceAgent);
|
|
21
|
-
}
|
|
22
|
-
return [pubsubBinding];
|
|
12
|
+
const pubsubPublisherBinding = {
|
|
13
|
+
role: PUBSUB_PUBLISHER_ROLE,
|
|
14
|
+
members: [storageServiceAgent],
|
|
15
|
+
};
|
|
16
|
+
return [pubsubPublisherBinding];
|
|
23
17
|
}
|
|
24
18
|
exports.obtainStorageBindings = obtainStorageBindings;
|
|
25
19
|
async function ensureStorageTriggerRegion(endpoint) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreValid = void 0;
|
|
3
|
+
exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.endpointsAreValid = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const error_1 = require("../../error");
|
|
@@ -29,16 +29,66 @@ function endpointsAreValid(wantBackend) {
|
|
|
29
29
|
if ((endpoint.concurrency || 1) === 1) {
|
|
30
30
|
return false;
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
return mem < backend.MIN_MEMORY_FOR_CONCURRENCY;
|
|
32
|
+
return endpoint.cpu < backend.MIN_CPU_FOR_CONCURRENCY;
|
|
34
33
|
})
|
|
35
34
|
.map((endpoint) => endpoint.id);
|
|
36
35
|
if (tooSmallForConcurrency.length) {
|
|
37
|
-
const msg =
|
|
36
|
+
const msg = "The following functions are configured to allow concurrent " +
|
|
37
|
+
"execution and less than one full CPU. This is not supported: " +
|
|
38
|
+
tooSmallForConcurrency.join(",");
|
|
39
|
+
throw new error_1.FirebaseError(msg);
|
|
40
|
+
}
|
|
41
|
+
const gcfV1WithCPU = endpoints
|
|
42
|
+
.filter((endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined")
|
|
43
|
+
.map((endpoint) => endpoint.id);
|
|
44
|
+
if (gcfV1WithCPU.length) {
|
|
45
|
+
const msg = `Cannot set CPU on the functions ${gcfV1WithCPU.join(",")} because they are GCF gen 1`;
|
|
46
|
+
throw new error_1.FirebaseError(msg);
|
|
47
|
+
}
|
|
48
|
+
const invalidCPU = endpoints
|
|
49
|
+
.filter((endpoint) => {
|
|
50
|
+
if (typeof endpoint.cpu === "undefined") {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if (endpoint.cpu === "gcf_gen1") {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const cpu = endpoint.cpu;
|
|
57
|
+
if (cpu < 1) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return ![1, 2, 4, 6, 8].includes(cpu);
|
|
61
|
+
})
|
|
62
|
+
.map((endpoint) => endpoint.id);
|
|
63
|
+
if (invalidCPU.length) {
|
|
64
|
+
const msg = `The following functions have invalid CPU settings ${invalidCPU.join(",")}. Valid CPU options are (0, 1], 2, 4, 6, 8, or "gcf_gen1"`;
|
|
38
65
|
throw new error_1.FirebaseError(msg);
|
|
39
66
|
}
|
|
40
67
|
}
|
|
41
68
|
exports.endpointsAreValid = endpointsAreValid;
|
|
69
|
+
function endpointsAreUnique(backends) {
|
|
70
|
+
const endpointToCodebases = {};
|
|
71
|
+
for (const [codebase, b] of Object.entries(backends)) {
|
|
72
|
+
for (const endpoint of backend.allEndpoints(b)) {
|
|
73
|
+
const key = backend.functionName(endpoint);
|
|
74
|
+
const cs = endpointToCodebases[key] || new Set();
|
|
75
|
+
cs.add(codebase);
|
|
76
|
+
endpointToCodebases[key] = cs;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const conflicts = {};
|
|
80
|
+
for (const [fn, codebases] of Object.entries(endpointToCodebases)) {
|
|
81
|
+
if (codebases.size > 1) {
|
|
82
|
+
conflicts[fn] = Array.from(codebases);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Object.keys(conflicts).length === 0) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const msgs = Object.entries(conflicts).map(([fn, codebases]) => `${fn}: ${codebases.join(",")}`);
|
|
89
|
+
throw new error_1.FirebaseError("More than one codebase claims following functions:\n\t" + `${msgs.join("\n\t")}`);
|
|
90
|
+
}
|
|
91
|
+
exports.endpointsAreUnique = endpointsAreUnique;
|
|
42
92
|
function functionsDirectoryExists(sourceDir, projectDir) {
|
|
43
93
|
if (!fsutils.dirExistsSync(sourceDir)) {
|
|
44
94
|
const sourceDirName = path.relative(projectDir, sourceDir);
|
|
@@ -63,7 +113,7 @@ function functionIdsAreValid(functions) {
|
|
|
63
113
|
return fn.platform === "gcfv2" && !v2FunctionName.test(fn.id);
|
|
64
114
|
});
|
|
65
115
|
if (invalidV2Ids.length !== 0) {
|
|
66
|
-
const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only
|
|
116
|
+
const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` +
|
|
67
117
|
`case letters, numbers, hyphens, and not exceed 62 characters in length`;
|
|
68
118
|
throw new error_1.FirebaseError(msg);
|
|
69
119
|
}
|
|
@@ -77,13 +127,13 @@ async function secretsAreValid(projectId, wantBackend) {
|
|
|
77
127
|
await validateSecretVersions(projectId, endpoints);
|
|
78
128
|
}
|
|
79
129
|
exports.secretsAreValid = secretsAreValid;
|
|
130
|
+
const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
|
|
80
131
|
function validatePlatformTargets(endpoints) {
|
|
81
|
-
const
|
|
82
|
-
const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
|
|
132
|
+
const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
|
|
83
133
|
if (unsupported.length > 0) {
|
|
84
134
|
const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
|
|
85
135
|
throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
|
|
86
|
-
`Only ${
|
|
136
|
+
`Only ${secretsSupportedPlatforms.join(", ")} support secret environments.`);
|
|
87
137
|
}
|
|
88
138
|
}
|
|
89
139
|
async function validateSecretVersions(projectId, endpoints) {
|
|
@@ -40,10 +40,12 @@ async function convertConfig(context, payload, config, finalize) {
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
const endpointBeingDeployed = (serviceId, region = "us-central1") => {
|
|
43
|
-
var _a
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
var _a;
|
|
44
|
+
for (const { wantBackend } of Object.values(payload.functions || {})) {
|
|
45
|
+
const endpoint = (_a = wantBackend === null || wantBackend === void 0 ? void 0 : wantBackend.endpoints[region]) === null || _a === void 0 ? void 0 : _a[serviceId];
|
|
46
|
+
if (endpoint && (0, backend_1.isHttpsTriggered)(endpoint) && endpoint.platform === "gcfv2")
|
|
47
|
+
return endpoint;
|
|
48
|
+
}
|
|
47
49
|
return undefined;
|
|
48
50
|
};
|
|
49
51
|
const matchingEndpoint = async (serviceId, region = "us-central1") => {
|
|
@@ -62,8 +62,12 @@ class AuthCloudFunction {
|
|
|
62
62
|
phoneNumber: user.phoneNumber,
|
|
63
63
|
disabled: user.disabled,
|
|
64
64
|
metadata: {
|
|
65
|
-
creationTime: user.createdAt
|
|
66
|
-
|
|
65
|
+
creationTime: user.createdAt
|
|
66
|
+
? new Date(parseInt(user.createdAt, 10)).toISOString()
|
|
67
|
+
: undefined,
|
|
68
|
+
lastSignInTime: user.lastLoginAt
|
|
69
|
+
? new Date(parseInt(user.lastLoginAt, 10)).toISOString()
|
|
70
|
+
: undefined,
|
|
67
71
|
},
|
|
68
72
|
customClaims: JSON.parse(user.customAttributes || "{}"),
|
|
69
73
|
providerData: user.providerUserInfo,
|
|
@@ -1210,7 +1210,6 @@ function grantToken(state, reqBody) {
|
|
|
1210
1210
|
(0, errors_1.assert)(reqBody.grantType === "refresh_token", "INVALID_GRANT_TYPE");
|
|
1211
1211
|
(0, errors_1.assert)(reqBody.refreshToken, "MISSING_REFRESH_TOKEN");
|
|
1212
1212
|
const refreshTokenRecord = state.validateRefreshToken(reqBody.refreshToken);
|
|
1213
|
-
(0, errors_1.assert)(refreshTokenRecord, "INVALID_REFRESH_TOKEN");
|
|
1214
1213
|
(0, errors_1.assert)(!refreshTokenRecord.user.disabled, "USER_DISABLED");
|
|
1215
1214
|
const tokens = issueTokens(state, refreshTokenRecord.user, refreshTokenRecord.provider, {
|
|
1216
1215
|
extraClaims: refreshTokenRecord.extraClaims,
|
|
@@ -342,7 +342,7 @@ function toExegesisController(ops, getProjectStateById) {
|
|
|
342
342
|
}
|
|
343
343
|
function toExegesisOperation(operation) {
|
|
344
344
|
return (ctx) => {
|
|
345
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
345
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
346
346
|
let targetProjectId = ctx.params.path.targetProjectId || ((_a = ctx.requestBody) === null || _a === void 0 ? void 0 : _a.targetProjectId);
|
|
347
347
|
if (targetProjectId) {
|
|
348
348
|
if ((_b = ctx.api.operationObject.security) === null || _b === void 0 ? void 0 : _b.some((sec) => sec.Oauth2)) {
|
|
@@ -365,6 +365,13 @@ function toExegesisController(ops, getProjectStateById) {
|
|
|
365
365
|
}
|
|
366
366
|
targetTenantId = targetTenantId || (decoded === null || decoded === void 0 ? void 0 : decoded.payload.firebase.tenant);
|
|
367
367
|
}
|
|
368
|
+
if ((_h = ctx.requestBody) === null || _h === void 0 ? void 0 : _h.refreshToken) {
|
|
369
|
+
const refreshTokenRecord = (0, state_1.decodeRefreshToken)(ctx.requestBody.refreshToken);
|
|
370
|
+
if (refreshTokenRecord.tenantId && targetTenantId) {
|
|
371
|
+
(0, errors_2.assert)(refreshTokenRecord.tenantId === targetTenantId, "TENANT_ID_MISMATCH: ((Refresh token tenant ID does not match target tenant ID.))");
|
|
372
|
+
}
|
|
373
|
+
targetTenantId = targetTenantId || refreshTokenRecord.tenantId;
|
|
374
|
+
}
|
|
368
375
|
return operation(getProjectStateById(targetProjectId, targetTenantId), ctx.requestBody, ctx);
|
|
369
376
|
};
|
|
370
377
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BlockingFunctionEvents = exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
|
|
3
|
+
exports.decodeRefreshToken = exports.encodeRefreshToken = exports.BlockingFunctionEvents = exports.UsageMode = exports.TenantProjectState = exports.AgentProjectState = exports.ProjectState = exports.SIGNIN_METHOD_EMAIL_LINK = exports.PROVIDER_GAME_CENTER = exports.PROVIDER_CUSTOM = exports.PROVIDER_ANONYMOUS = exports.PROVIDER_PHONE = exports.PROVIDER_PASSWORD = void 0;
|
|
4
4
|
const utils_1 = require("./utils");
|
|
5
5
|
const cloudFunctions_1 = require("./cloudFunctions");
|
|
6
6
|
const errors_1 = require("./errors");
|
|
@@ -19,8 +19,6 @@ class ProjectState {
|
|
|
19
19
|
this.localIdForPhoneNumber = new Map();
|
|
20
20
|
this.localIdsForProviderEmail = new Map();
|
|
21
21
|
this.userIdForProviderRawId = new Map();
|
|
22
|
-
this.refreshTokens = new Map();
|
|
23
|
-
this.refreshTokensForLocalId = new Map();
|
|
24
22
|
this.oobs = new Map();
|
|
25
23
|
this.verificationCodes = new Map();
|
|
26
24
|
this.temporaryProofs = new Map();
|
|
@@ -73,13 +71,6 @@ class ProjectState {
|
|
|
73
71
|
deleteUser(user) {
|
|
74
72
|
this.users.delete(user.localId);
|
|
75
73
|
this.removeUserFromIndex(user);
|
|
76
|
-
const refreshTokens = this.refreshTokensForLocalId.get(user.localId);
|
|
77
|
-
if (refreshTokens) {
|
|
78
|
-
this.refreshTokensForLocalId.delete(user.localId);
|
|
79
|
-
for (const refreshToken of refreshTokens) {
|
|
80
|
-
this.refreshTokens.delete(refreshToken);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
74
|
this.authCloudFunction.dispatch("delete", user);
|
|
84
75
|
}
|
|
85
76
|
updateUserByLocalId(localId, fields, options = {}) {
|
|
@@ -282,26 +273,23 @@ class ProjectState {
|
|
|
282
273
|
}
|
|
283
274
|
createRefreshTokenFor(userInfo, provider, { extraClaims = {}, secondFactor, } = {}) {
|
|
284
275
|
const localId = userInfo.localId;
|
|
285
|
-
const
|
|
286
|
-
|
|
276
|
+
const refreshTokenRecord = {
|
|
277
|
+
_AuthEmulatorRefreshToken: "DO NOT MODIFY",
|
|
287
278
|
localId,
|
|
288
279
|
provider,
|
|
289
280
|
extraClaims,
|
|
281
|
+
projectId: this.projectId,
|
|
290
282
|
secondFactor,
|
|
291
283
|
tenantId: userInfo.tenantId,
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (!refreshTokens) {
|
|
295
|
-
refreshTokens = new Set();
|
|
296
|
-
this.refreshTokensForLocalId.set(localId, refreshTokens);
|
|
297
|
-
}
|
|
298
|
-
refreshTokens.add(refreshToken);
|
|
284
|
+
};
|
|
285
|
+
const refreshToken = encodeRefreshToken(refreshTokenRecord);
|
|
299
286
|
return refreshToken;
|
|
300
287
|
}
|
|
301
288
|
validateRefreshToken(refreshToken) {
|
|
302
|
-
const record =
|
|
303
|
-
|
|
304
|
-
|
|
289
|
+
const record = decodeRefreshToken(refreshToken);
|
|
290
|
+
(0, errors_1.assert)(record.projectId === this.projectId, "INVALID_REFRESH_TOKEN");
|
|
291
|
+
if (this instanceof TenantProjectState) {
|
|
292
|
+
(0, errors_1.assert)(record.tenantId === this.tenantId, "TENANT_ID_MISMATCH");
|
|
305
293
|
}
|
|
306
294
|
return {
|
|
307
295
|
user: this.getUserByLocalIdAssertingExists(record.localId),
|
|
@@ -356,8 +344,6 @@ class ProjectState {
|
|
|
356
344
|
this.localIdForPhoneNumber.clear();
|
|
357
345
|
this.localIdsForProviderEmail.clear();
|
|
358
346
|
this.userIdForProviderRawId.clear();
|
|
359
|
-
this.refreshTokens.clear();
|
|
360
|
-
this.refreshTokensForLocalId.clear();
|
|
361
347
|
}
|
|
362
348
|
getUserCount() {
|
|
363
349
|
return this.users.size;
|
|
@@ -617,6 +603,23 @@ var BlockingFunctionEvents;
|
|
|
617
603
|
BlockingFunctionEvents["BEFORE_CREATE"] = "beforeCreate";
|
|
618
604
|
BlockingFunctionEvents["BEFORE_SIGN_IN"] = "beforeSignIn";
|
|
619
605
|
})(BlockingFunctionEvents = exports.BlockingFunctionEvents || (exports.BlockingFunctionEvents = {}));
|
|
606
|
+
function encodeRefreshToken(refreshTokenRecord) {
|
|
607
|
+
return Buffer.from(JSON.stringify(refreshTokenRecord), "utf8").toString("base64");
|
|
608
|
+
}
|
|
609
|
+
exports.encodeRefreshToken = encodeRefreshToken;
|
|
610
|
+
function decodeRefreshToken(refreshTokenString) {
|
|
611
|
+
let refreshTokenRecord;
|
|
612
|
+
try {
|
|
613
|
+
const json = Buffer.from(refreshTokenString, "base64").toString("utf8");
|
|
614
|
+
refreshTokenRecord = JSON.parse(json);
|
|
615
|
+
}
|
|
616
|
+
catch (_a) {
|
|
617
|
+
throw new errors_1.BadRequestError("INVALID_REFRESH_TOKEN");
|
|
618
|
+
}
|
|
619
|
+
(0, errors_1.assert)(refreshTokenRecord._AuthEmulatorRefreshToken, "INVALID_REFRESH_TOKEN");
|
|
620
|
+
return refreshTokenRecord;
|
|
621
|
+
}
|
|
622
|
+
exports.decodeRefreshToken = decodeRefreshToken;
|
|
620
623
|
function getProviderEmailsForUser(user) {
|
|
621
624
|
var _a;
|
|
622
625
|
const emails = new Set();
|
|
@@ -268,16 +268,19 @@ async function startAll(options, showUI = true) {
|
|
|
268
268
|
const emulatableBackends = [];
|
|
269
269
|
const projectDir = (options.extDevDir || options.config.projectDir);
|
|
270
270
|
if (shouldStart(options, types_1.Emulators.FUNCTIONS)) {
|
|
271
|
-
const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions)
|
|
271
|
+
const functionsCfg = (0, projectConfig_1.normalizeAndValidate)(options.config.src.functions);
|
|
272
272
|
utils.assertIsStringOrUndefined(options.extDevDir);
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
for (const cfg of functionsCfg) {
|
|
274
|
+
const functionsDir = path.join(projectDir, cfg.source);
|
|
275
|
+
emulatableBackends.push({
|
|
276
|
+
functionsDir,
|
|
277
|
+
codebase: cfg.codebase,
|
|
278
|
+
env: Object.assign({}, options.extDevEnv),
|
|
279
|
+
secretEnv: [],
|
|
280
|
+
predefinedTriggers: options.extDevTriggers,
|
|
281
|
+
nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || cfg.runtime),
|
|
282
|
+
});
|
|
283
|
+
}
|
|
281
284
|
}
|
|
282
285
|
if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
|
|
283
286
|
const projectNumber = constants_1.Constants.isDemoProject(projectId)
|
|
@@ -56,7 +56,15 @@ class DatabaseEmulator {
|
|
|
56
56
|
if (!c.instance) {
|
|
57
57
|
continue;
|
|
58
58
|
}
|
|
59
|
-
|
|
59
|
+
try {
|
|
60
|
+
await this.updateRules(c.instance, c.rules);
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const rulesError = this.prettyPrintRulesError(c.rules, e);
|
|
64
|
+
this.logger.logLabeled("WARN", "database", rulesError);
|
|
65
|
+
this.logger.logLabeled("WARN", "database", "Failed to update rules");
|
|
66
|
+
throw new error_1.FirebaseError(`Failed to load initial ${constants_1.Constants.description(this.getName())} rules:\n${rulesError}`);
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
}
|
|
@@ -114,6 +122,7 @@ class DatabaseEmulator {
|
|
|
114
122
|
});
|
|
115
123
|
}
|
|
116
124
|
async updateRules(instance, rulesPath) {
|
|
125
|
+
var _a;
|
|
117
126
|
const rulesExt = path.extname(rulesPath);
|
|
118
127
|
const content = rulesExt === ".bolt"
|
|
119
128
|
? parseBoltRules(rulesPath).toString()
|
|
@@ -131,12 +140,36 @@ class DatabaseEmulator {
|
|
|
131
140
|
if (e.context && e.context.body) {
|
|
132
141
|
throw e.context.body.error;
|
|
133
142
|
}
|
|
134
|
-
throw e.original;
|
|
143
|
+
throw (_a = e.original) !== null && _a !== void 0 ? _a : e;
|
|
135
144
|
}
|
|
136
145
|
}
|
|
137
146
|
prettyPrintRulesError(filePath, error) {
|
|
147
|
+
let errStr;
|
|
148
|
+
switch (typeof error) {
|
|
149
|
+
case "string":
|
|
150
|
+
errStr = error;
|
|
151
|
+
break;
|
|
152
|
+
case "object":
|
|
153
|
+
if (error != null && "message" in error) {
|
|
154
|
+
const message = error.message;
|
|
155
|
+
errStr = `${message}`;
|
|
156
|
+
if (typeof message === "string") {
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(message);
|
|
159
|
+
if (typeof parsed === "object" && parsed.error) {
|
|
160
|
+
errStr = `${parsed.error}`;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (_) {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
errStr = `Unknown error: ${JSON.stringify(error)}`;
|
|
170
|
+
}
|
|
138
171
|
const relativePath = path.relative(process.cwd(), filePath);
|
|
139
|
-
return `${clc.cyan(relativePath)}:${
|
|
172
|
+
return `${clc.cyan(relativePath)}:${errStr.trim()}`;
|
|
140
173
|
}
|
|
141
174
|
}
|
|
142
175
|
exports.DatabaseEmulator = DatabaseEmulator;
|
|
@@ -83,15 +83,15 @@ exports.DownloadDetails = {
|
|
|
83
83
|
},
|
|
84
84
|
}
|
|
85
85
|
: {
|
|
86
|
-
version: "1.6.
|
|
87
|
-
downloadPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
88
|
-
unzipDir: path.join(CACHE_DIR, "ui-v1.6.
|
|
89
|
-
binaryPath: path.join(CACHE_DIR, "ui-v1.6.
|
|
86
|
+
version: "1.6.6",
|
|
87
|
+
downloadPath: path.join(CACHE_DIR, "ui-v1.6.6.zip"),
|
|
88
|
+
unzipDir: path.join(CACHE_DIR, "ui-v1.6.6"),
|
|
89
|
+
binaryPath: path.join(CACHE_DIR, "ui-v1.6.6", "server.bundle.js"),
|
|
90
90
|
opts: {
|
|
91
91
|
cacheDir: CACHE_DIR,
|
|
92
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.
|
|
93
|
-
expectedSize:
|
|
94
|
-
expectedChecksum: "
|
|
92
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.6.zip",
|
|
93
|
+
expectedSize: 3817247,
|
|
94
|
+
expectedChecksum: "c80a3f0ae1e3f682ace0a18a9cdd2861",
|
|
95
95
|
namePrefix: "ui",
|
|
96
96
|
},
|
|
97
97
|
},
|
|
@@ -152,6 +152,7 @@ class ExtensionsEmulator {
|
|
|
152
152
|
return emulatableBackend;
|
|
153
153
|
}
|
|
154
154
|
autoPopulatedParams(instance) {
|
|
155
|
+
var _a;
|
|
155
156
|
const projectId = this.args.projectId;
|
|
156
157
|
return {
|
|
157
158
|
PROJECT_ID: projectId !== null && projectId !== void 0 ? projectId : "",
|
|
@@ -159,6 +160,8 @@ class ExtensionsEmulator {
|
|
|
159
160
|
DATABASE_INSTANCE: projectId !== null && projectId !== void 0 ? projectId : "",
|
|
160
161
|
DATABASE_URL: `https://${projectId}.firebaseio.com`,
|
|
161
162
|
STORAGE_BUCKET: `${projectId}.appspot.com`,
|
|
163
|
+
ALLOWED_EVENT_TYPES: instance.allowedEventTypes ? instance.allowedEventTypes.join(",") : "",
|
|
164
|
+
EVENTARC_CHANNEL: (_a = instance.eventarcChannel) !== null && _a !== void 0 ? _a : "",
|
|
162
165
|
};
|
|
163
166
|
}
|
|
164
167
|
async checkAndWarnAPIs(instances) {
|