firebase-tools 10.7.1 → 10.9.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-list.js +12 -20
- 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 +47 -14
- package/lib/deploy/functions/build.js +28 -9
- package/lib/deploy/functions/checkIam.js +65 -53
- package/lib/deploy/functions/containerCleaner.js +8 -7
- package/lib/deploy/functions/functionsDeployHelper.js +1 -1
- package/lib/deploy/functions/prepare.js +42 -15
- package/lib/deploy/functions/pricing.js +2 -2
- package/lib/deploy/functions/release/executor.js +1 -1
- package/lib/deploy/functions/release/fabricator.js +66 -11
- package/lib/deploy/functions/release/index.js +0 -21
- package/lib/deploy/functions/runtimes/discovery/index.js +2 -1
- 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 +23 -20
- 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 +80 -16
- package/lib/deploy/index.js +2 -1
- 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 +10 -5
- package/lib/emulator/databaseEmulator.js +36 -3
- package/lib/emulator/downloadableEmulators.js +18 -34
- package/lib/emulator/extensionsEmulator.js +4 -1
- package/lib/emulator/functionsEmulator.js +6 -7
- package/lib/emulator/functionsEmulatorRuntime.js +1 -1
- package/lib/emulator/functionsEmulatorShared.js +3 -0
- package/lib/emulator/functionsEmulatorUtils.js +5 -1
- package/lib/emulator/storage/apis/firebase.js +26 -4
- 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/frameworks/index.js +111 -0
- package/lib/functions/functionslog.js +4 -9
- package/lib/gcp/cloudfunctions.js +1 -1
- package/lib/gcp/cloudfunctionsv2.js +14 -9
- package/lib/gcp/serviceusage.js +24 -0
- package/lib/hosting/normalizedHostingConfigs.js +3 -0
- package/lib/previews.js +1 -1
- package/lib/serve/index.js +2 -1
- package/lib/throttler/throttler.js +2 -1
- package/npm-shrinkwrap.json +315 -534
- package/package.json +4 -4
- package/templates/extensions/javascript/package.lint.json +5 -5
- package/templates/extensions/javascript/package.nolint.json +3 -3
- package/templates/extensions/typescript/package.lint.json +8 -7
- 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 +5 -5
|
@@ -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.endpointsAreUnique = exports.endpointsAreValid = void 0;
|
|
3
|
+
exports.secretsAreValid = exports.functionIdsAreValid = exports.functionsDirectoryExists = exports.endpointsAreUnique = exports.cpuConfigIsValid = exports.endpointsAreValid = void 0;
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const error_1 = require("../../error");
|
|
@@ -11,34 +11,98 @@ const backend = require("./backend");
|
|
|
11
11
|
const utils = require("../../utils");
|
|
12
12
|
const secrets = require("../../functions/secrets");
|
|
13
13
|
const services_1 = require("./services");
|
|
14
|
+
function matchingIds(endpoints, filter) {
|
|
15
|
+
return endpoints
|
|
16
|
+
.filter(filter)
|
|
17
|
+
.map((endpoint) => endpoint.id)
|
|
18
|
+
.join(",");
|
|
19
|
+
}
|
|
20
|
+
const mem = (endpoint) => endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
|
|
21
|
+
const cpu = (endpoint) => {
|
|
22
|
+
var _a;
|
|
23
|
+
return endpoint.cpu === "gcf_gen1"
|
|
24
|
+
? backend.memoryToGen1Cpu(mem(endpoint))
|
|
25
|
+
: (_a = endpoint.cpu) !== null && _a !== void 0 ? _a : backend.memoryToGen2Cpu(mem(endpoint));
|
|
26
|
+
};
|
|
14
27
|
function endpointsAreValid(wantBackend) {
|
|
15
28
|
const endpoints = backend.allEndpoints(wantBackend);
|
|
16
29
|
functionIdsAreValid(endpoints);
|
|
17
30
|
for (const ep of endpoints) {
|
|
18
31
|
(0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, wantBackend);
|
|
19
32
|
}
|
|
20
|
-
const gcfV1WithConcurrency = endpoints
|
|
21
|
-
.filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1")
|
|
22
|
-
.map((endpoint) => endpoint.id);
|
|
33
|
+
const gcfV1WithConcurrency = matchingIds(endpoints, (endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1");
|
|
23
34
|
if (gcfV1WithConcurrency.length) {
|
|
24
|
-
const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency
|
|
35
|
+
const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency} because they are GCF gen 1`;
|
|
25
36
|
throw new error_1.FirebaseError(msg);
|
|
26
37
|
}
|
|
27
|
-
const tooSmallForConcurrency = endpoints
|
|
28
|
-
.filter((endpoint) => {
|
|
38
|
+
const tooSmallForConcurrency = matchingIds(endpoints, (endpoint) => {
|
|
29
39
|
if ((endpoint.concurrency || 1) === 1) {
|
|
30
40
|
return false;
|
|
31
41
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
35
|
-
.map((endpoint) => endpoint.id);
|
|
42
|
+
return cpu(endpoint) < backend.MIN_CPU_FOR_CONCURRENCY;
|
|
43
|
+
});
|
|
36
44
|
if (tooSmallForConcurrency.length) {
|
|
37
|
-
const msg =
|
|
45
|
+
const msg = "The following functions are configured to allow concurrent " +
|
|
46
|
+
"execution and less than one full CPU. This is not supported: " +
|
|
47
|
+
tooSmallForConcurrency;
|
|
38
48
|
throw new error_1.FirebaseError(msg);
|
|
39
49
|
}
|
|
50
|
+
cpuConfigIsValid(endpoints);
|
|
40
51
|
}
|
|
41
52
|
exports.endpointsAreValid = endpointsAreValid;
|
|
53
|
+
function cpuConfigIsValid(endpoints) {
|
|
54
|
+
const gcfV1WithCPU = matchingIds(endpoints, (endpoint) => endpoint.platform === "gcfv1" && typeof endpoint["cpu"] !== "undefined");
|
|
55
|
+
if (gcfV1WithCPU.length) {
|
|
56
|
+
const msg = `Cannot set CPU on the functions ${gcfV1WithCPU} because they are GCF gen 1`;
|
|
57
|
+
throw new error_1.FirebaseError(msg);
|
|
58
|
+
}
|
|
59
|
+
const invalidCPU = matchingIds(endpoints, (endpoint) => {
|
|
60
|
+
const c = cpu(endpoint);
|
|
61
|
+
if (c < 0.08) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
if (c < 1) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return ![1, 2, 4, 6, 8].includes(c);
|
|
68
|
+
});
|
|
69
|
+
if (invalidCPU.length) {
|
|
70
|
+
const msg = `The following functions have invalid CPU settings ${invalidCPU}. Valid CPU options are (0.08, 1], 2, 4, 6, 8, or "gcf_gen1"`;
|
|
71
|
+
throw new error_1.FirebaseError(msg);
|
|
72
|
+
}
|
|
73
|
+
const smallCPURegions = ["australia-southeast2", "asia-northeast3", "asia-south2"];
|
|
74
|
+
const tooBigCPUForRegion = matchingIds(endpoints, (endpoint) => smallCPURegions.includes(endpoint.region) && cpu(endpoint) > 4);
|
|
75
|
+
if (tooBigCPUForRegion) {
|
|
76
|
+
const msg = `The functions ${tooBigCPUForRegion} have > 4 CPU in a region that supports a maximum 4 CPU`;
|
|
77
|
+
throw new error_1.FirebaseError(msg);
|
|
78
|
+
}
|
|
79
|
+
const tooSmallCPUSmall = matchingIds(endpoints, (endpoint) => mem(endpoint) > 512 && cpu(endpoint) < 0.5);
|
|
80
|
+
if (tooSmallCPUSmall) {
|
|
81
|
+
const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 0.5 CPU is needed to set a memory limit greater than 512MiB`;
|
|
82
|
+
throw new error_1.FirebaseError(msg);
|
|
83
|
+
}
|
|
84
|
+
const tooSmallCPUBig = matchingIds(endpoints, (endpoint) => mem(endpoint) > 1024 && cpu(endpoint) < 1);
|
|
85
|
+
if (tooSmallCPUBig) {
|
|
86
|
+
const msg = `The functions ${tooSmallCPUSmall} have too little CPU for their memory allocation. A minimum of 1 CPU is needed to set a memory limit greater than 1GiB`;
|
|
87
|
+
throw new error_1.FirebaseError(msg);
|
|
88
|
+
}
|
|
89
|
+
const tooSmallMemory4CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 4 && mem(endpoint) < 2 << 10);
|
|
90
|
+
if (tooSmallMemory4CPU) {
|
|
91
|
+
const msg = `The functions ${tooSmallMemory4CPU} have too little memory for their CPU. Functions with 4 CPU require at least 2GiB`;
|
|
92
|
+
throw new error_1.FirebaseError(msg);
|
|
93
|
+
}
|
|
94
|
+
const tooSmallMemory6CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 6 && mem(endpoint) < 3 << 10);
|
|
95
|
+
if (tooSmallMemory6CPU) {
|
|
96
|
+
const msg = `The functions ${tooSmallMemory6CPU} have too little memory for their CPU. Functions with 6 CPU require at least 3GiB`;
|
|
97
|
+
throw new error_1.FirebaseError(msg);
|
|
98
|
+
}
|
|
99
|
+
const tooSmallMemory8CPU = matchingIds(endpoints, (endpoint) => cpu(endpoint) === 8 && mem(endpoint) < 4 << 10);
|
|
100
|
+
if (tooSmallMemory8CPU) {
|
|
101
|
+
const msg = `The functions ${tooSmallMemory8CPU} have too little memory for their CPU. Functions with 8 CPU require at least 4GiB`;
|
|
102
|
+
throw new error_1.FirebaseError(msg);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.cpuConfigIsValid = cpuConfigIsValid;
|
|
42
106
|
function endpointsAreUnique(backends) {
|
|
43
107
|
const endpointToCodebases = {};
|
|
44
108
|
for (const [codebase, b] of Object.entries(backends)) {
|
|
@@ -86,7 +150,7 @@ function functionIdsAreValid(functions) {
|
|
|
86
150
|
return fn.platform === "gcfv2" && !v2FunctionName.test(fn.id);
|
|
87
151
|
});
|
|
88
152
|
if (invalidV2Ids.length !== 0) {
|
|
89
|
-
const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only
|
|
153
|
+
const msg = `${invalidV2Ids.map((f) => f.id).join(", ")} v2 function name(s) can only contain lower ` +
|
|
90
154
|
`case letters, numbers, hyphens, and not exceed 62 characters in length`;
|
|
91
155
|
throw new error_1.FirebaseError(msg);
|
|
92
156
|
}
|
|
@@ -100,13 +164,13 @@ async function secretsAreValid(projectId, wantBackend) {
|
|
|
100
164
|
await validateSecretVersions(projectId, endpoints);
|
|
101
165
|
}
|
|
102
166
|
exports.secretsAreValid = secretsAreValid;
|
|
167
|
+
const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
|
|
103
168
|
function validatePlatformTargets(endpoints) {
|
|
104
|
-
const
|
|
105
|
-
const unsupported = endpoints.filter((e) => !supportedPlatforms.includes(e.platform));
|
|
169
|
+
const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
|
|
106
170
|
if (unsupported.length > 0) {
|
|
107
171
|
const errs = unsupported.map((e) => `${e.id}[platform=${e.platform}]`);
|
|
108
172
|
throw new error_1.FirebaseError(`Tried to set secret environment variables on ${errs.join(", ")}. ` +
|
|
109
|
-
`Only ${
|
|
173
|
+
`Only ${secretsSupportedPlatforms.join(", ")} support secret environments.`);
|
|
110
174
|
}
|
|
111
175
|
}
|
|
112
176
|
async function validateSecretVersions(projectId, endpoints) {
|
package/lib/deploy/index.js
CHANGED
|
@@ -18,6 +18,7 @@ const FunctionsTarget = require("./functions");
|
|
|
18
18
|
const StorageTarget = require("./storage");
|
|
19
19
|
const RemoteConfigTarget = require("./remoteconfig");
|
|
20
20
|
const ExtensionsTarget = require("./extensions");
|
|
21
|
+
const frameworks_1 = require("../frameworks");
|
|
21
22
|
const TARGETS = {
|
|
22
23
|
hosting: HostingTarget,
|
|
23
24
|
database: DatabaseTarget,
|
|
@@ -45,7 +46,7 @@ const deploy = async function (targetNames, options, customContext = {}) {
|
|
|
45
46
|
if (previews_1.previews.frameworkawareness && targetNames.includes("hosting")) {
|
|
46
47
|
const config = options.config.get("hosting");
|
|
47
48
|
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
|
|
48
|
-
await
|
|
49
|
+
await (0, frameworks_1.prepareFrameworks)(targetNames, context, options);
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
for (const targetName of targetNames) {
|
|
@@ -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();
|
|
@@ -37,9 +37,10 @@ const config_1 = require("./storage/rules/config");
|
|
|
37
37
|
const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
|
|
38
38
|
const auth_2 = require("../auth");
|
|
39
39
|
const extensionsEmulator_1 = require("./extensionsEmulator");
|
|
40
|
-
const previews_1 = require("../previews");
|
|
41
40
|
const projectConfig_1 = require("../functions/projectConfig");
|
|
42
41
|
const downloadableEmulators_1 = require("./downloadableEmulators");
|
|
42
|
+
const frameworks_1 = require("../frameworks");
|
|
43
|
+
const previews_1 = require("../previews");
|
|
43
44
|
const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
|
|
44
45
|
async function getAndCheckAddress(emulator, options) {
|
|
45
46
|
var _a, _b, _c, _d;
|
|
@@ -125,9 +126,7 @@ async function cleanShutdown() {
|
|
|
125
126
|
exports.cleanShutdown = cleanShutdown;
|
|
126
127
|
function filterEmulatorTargets(options) {
|
|
127
128
|
let targets = [...types_1.ALL_SERVICE_EMULATORS];
|
|
128
|
-
|
|
129
|
-
targets.push(types_1.Emulators.EXTENSIONS);
|
|
130
|
-
}
|
|
129
|
+
targets.push(types_1.Emulators.EXTENSIONS);
|
|
131
130
|
targets = targets.filter((e) => {
|
|
132
131
|
return options.config.has(e) || options.config.has(`emulators.${e}`);
|
|
133
132
|
});
|
|
@@ -245,6 +244,12 @@ async function startAll(options, showUI = true) {
|
|
|
245
244
|
}
|
|
246
245
|
}
|
|
247
246
|
}
|
|
247
|
+
if (previews_1.previews.frameworkawareness) {
|
|
248
|
+
const config = options.config.get("hosting");
|
|
249
|
+
if (Array.isArray(config) ? config.some((it) => it.source) : config.source) {
|
|
250
|
+
await (0, frameworks_1.prepareFrameworks)(targets, options, options);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
248
253
|
if (shouldStart(options, types_1.Emulators.HUB)) {
|
|
249
254
|
const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
|
|
250
255
|
const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
|
|
@@ -282,7 +287,7 @@ async function startAll(options, showUI = true) {
|
|
|
282
287
|
});
|
|
283
288
|
}
|
|
284
289
|
}
|
|
285
|
-
if (shouldStart(options, types_1.Emulators.EXTENSIONS)
|
|
290
|
+
if (shouldStart(options, types_1.Emulators.EXTENSIONS)) {
|
|
286
291
|
const projectNumber = constants_1.Constants.isDemoProject(projectId)
|
|
287
292
|
? constants_1.Constants.FAKE_PROJECT_NUMBER
|
|
288
293
|
: await (0, projectUtils_1.needProjectNumber)(options);
|
|
@@ -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;
|
|
@@ -50,15 +50,15 @@ exports.DownloadDetails = {
|
|
|
50
50
|
namePrefix: "cloud-storage-rules-emulator",
|
|
51
51
|
},
|
|
52
52
|
},
|
|
53
|
-
ui: previews_1.previews.
|
|
53
|
+
ui: previews_1.previews.emulatoruisnapshot
|
|
54
54
|
? {
|
|
55
|
-
version: "
|
|
56
|
-
downloadPath: path.join(CACHE_DIR, "ui-
|
|
57
|
-
unzipDir: path.join(CACHE_DIR, "ui-
|
|
58
|
-
binaryPath: path.join(CACHE_DIR, "ui-
|
|
55
|
+
version: "SNAPSHOT",
|
|
56
|
+
downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
|
|
57
|
+
unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
|
|
58
|
+
binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
|
|
59
59
|
opts: {
|
|
60
60
|
cacheDir: CACHE_DIR,
|
|
61
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-
|
|
61
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
|
|
62
62
|
expectedSize: -1,
|
|
63
63
|
expectedChecksum: "",
|
|
64
64
|
skipCache: true,
|
|
@@ -66,35 +66,19 @@ exports.DownloadDetails = {
|
|
|
66
66
|
namePrefix: "ui",
|
|
67
67
|
},
|
|
68
68
|
}
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
skipCache: true,
|
|
81
|
-
skipChecksumAndSize: true,
|
|
82
|
-
namePrefix: "ui",
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
: {
|
|
86
|
-
version: "1.6.5",
|
|
87
|
-
downloadPath: path.join(CACHE_DIR, "ui-v1.6.5.zip"),
|
|
88
|
-
unzipDir: path.join(CACHE_DIR, "ui-v1.6.5"),
|
|
89
|
-
binaryPath: path.join(CACHE_DIR, "ui-v1.6.5", "server.bundle.js"),
|
|
90
|
-
opts: {
|
|
91
|
-
cacheDir: CACHE_DIR,
|
|
92
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.6.5.zip",
|
|
93
|
-
expectedSize: 3816994,
|
|
94
|
-
expectedChecksum: "92dfff4b2ef8ab616e8a60cc93e0a00b",
|
|
95
|
-
namePrefix: "ui",
|
|
96
|
-
},
|
|
69
|
+
: {
|
|
70
|
+
version: "1.7.0",
|
|
71
|
+
downloadPath: path.join(CACHE_DIR, "ui-v1.7.0.zip"),
|
|
72
|
+
unzipDir: path.join(CACHE_DIR, "ui-v1.7.0"),
|
|
73
|
+
binaryPath: path.join(CACHE_DIR, "ui-v1.7.0", "server.bundle.js"),
|
|
74
|
+
opts: {
|
|
75
|
+
cacheDir: CACHE_DIR,
|
|
76
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-v1.7.0.zip",
|
|
77
|
+
expectedSize: 4053708,
|
|
78
|
+
expectedChecksum: "aea9ae19091df5974a88a8847aaf127c",
|
|
79
|
+
namePrefix: "ui",
|
|
97
80
|
},
|
|
81
|
+
},
|
|
98
82
|
pubsub: {
|
|
99
83
|
downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
|
|
100
84
|
version: "0.1.0",
|