firebase-tools 10.1.2 → 10.2.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/api.js +1 -0
- package/lib/apiv2.js +92 -48
- package/lib/archiveDirectory.js +63 -73
- package/lib/auth.js +62 -25
- package/lib/commands/ext-configure.js +1 -0
- package/lib/commands/ext-dev-usage.js +3 -8
- package/lib/commands/ext-install.js +1 -0
- package/lib/commands/ext-uninstall.js +1 -0
- package/lib/commands/ext-update.js +1 -0
- package/lib/commands/functions-secrets-access.js +17 -0
- package/lib/commands/functions-secrets-destroy.js +40 -0
- package/lib/commands/functions-secrets-get.js +21 -0
- package/lib/commands/functions-secrets-prune.js +50 -0
- package/lib/commands/functions-secrets-set.js +46 -0
- package/lib/commands/index.js +7 -3
- package/lib/commands/login.js +1 -1
- package/lib/database/metadata.js +16 -24
- package/lib/deploy/functions/backend.js +11 -1
- package/lib/deploy/functions/ensure.js +112 -0
- package/lib/deploy/functions/ensureCloudBuildEnabled.js +0 -49
- package/lib/deploy/functions/prepare.js +13 -19
- package/lib/deploy/functions/release/fabricator.js +4 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +1 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -0
- package/lib/deploy/functions/validate.js +83 -1
- package/lib/deploy/hosting/convertConfig.js +45 -24
- package/lib/deploy/hosting/prepare.js +1 -1
- package/lib/emulator/controller.js +3 -1
- package/lib/emulator/emulatorLogger.js +7 -0
- package/lib/emulator/functionsEmulator.js +113 -79
- package/lib/emulator/functionsEmulatorRuntime.js +100 -83
- package/lib/emulator/functionsEmulatorShared.js +51 -1
- package/lib/emulator/functionsEmulatorShell.js +1 -2
- package/lib/emulator/functionsRuntimeWorker.js +1 -1
- package/lib/emulator/storage/apis/gcloud.js +2 -2
- package/lib/emulator/storage/files.js +8 -3
- package/lib/extensions/askUserForParam.js +1 -1
- package/lib/extensions/diagnose.js +56 -0
- package/lib/extensions/extensionsApi.js +0 -1
- package/lib/extensions/extensionsHelper.js +10 -17
- package/lib/extensions/resolveSource.js +1 -53
- package/lib/extensions/secretsUtils.js +1 -1
- package/lib/extensions/updateHelper.js +0 -14
- package/lib/extensions/utils.js +4 -2
- package/lib/functions/env.js +5 -7
- package/lib/functions/secrets.js +112 -0
- package/lib/gcp/cloudbilling.js +8 -19
- package/lib/gcp/cloudfunctions.js +24 -48
- package/lib/gcp/cloudlogging.js +8 -11
- package/lib/gcp/cloudmonitoring.js +8 -5
- package/lib/gcp/cloudscheduler.js +7 -18
- package/lib/gcp/firedata.js +5 -4
- package/lib/gcp/firestore.js +5 -5
- package/lib/gcp/iam.js +18 -33
- package/lib/gcp/resourceManager.js +8 -13
- package/lib/gcp/runtimeconfig.js +31 -53
- package/lib/gcp/secretManager.js +137 -77
- package/lib/gcp/storage.js +25 -29
- package/lib/previews.js +1 -1
- package/lib/serve/functions.js +2 -2
- package/lib/utils.js +6 -1
- package/npm-shrinkwrap.json +962 -987
- package/package.json +5 -3
- package/schema/firebase-config.json +387 -12
- package/templates/init/hosting/index.html +1 -1
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertConfig = void 0;
|
|
4
|
+
const error_1 = require("../../error");
|
|
5
|
+
function has(obj, k) {
|
|
6
|
+
return obj[k] !== undefined;
|
|
7
|
+
}
|
|
4
8
|
function extractPattern(type, spec) {
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
let glob = "";
|
|
10
|
+
let regex = "";
|
|
11
|
+
if ("source" in spec) {
|
|
12
|
+
glob = spec.source;
|
|
13
|
+
}
|
|
14
|
+
if ("glob" in spec) {
|
|
15
|
+
glob = spec.glob;
|
|
16
|
+
}
|
|
17
|
+
if ("regex" in spec) {
|
|
18
|
+
regex = spec.regex;
|
|
19
|
+
}
|
|
7
20
|
if (glob && regex) {
|
|
8
|
-
throw new FirebaseError(
|
|
21
|
+
throw new error_1.FirebaseError(`Cannot specify a ${type} pattern with both a glob and regex.`);
|
|
9
22
|
}
|
|
10
23
|
else if (glob) {
|
|
11
24
|
return { glob: glob };
|
|
@@ -13,33 +26,38 @@ function extractPattern(type, spec) {
|
|
|
13
26
|
else if (regex) {
|
|
14
27
|
return { regex: regex };
|
|
15
28
|
}
|
|
16
|
-
throw new FirebaseError(
|
|
29
|
+
throw new error_1.FirebaseError(`Cannot specify a ${type} with no pattern (either a glob or regex required).`);
|
|
17
30
|
}
|
|
18
|
-
|
|
31
|
+
function convertConfig(config) {
|
|
32
|
+
if (Array.isArray(config)) {
|
|
33
|
+
throw new error_1.FirebaseError(`convertConfig should be given a single configuration, not an array.`, {
|
|
34
|
+
exit: 2,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
19
37
|
const out = {};
|
|
20
38
|
if (!config) {
|
|
21
39
|
return out;
|
|
22
40
|
}
|
|
23
|
-
if (
|
|
24
|
-
out.rewrites = config.rewrites.map(
|
|
41
|
+
if (Array.isArray(config.rewrites)) {
|
|
42
|
+
out.rewrites = config.rewrites.map((rewrite) => {
|
|
25
43
|
const vRewrite = extractPattern("rewrite", rewrite);
|
|
26
|
-
if (rewrite
|
|
44
|
+
if ("destination" in rewrite) {
|
|
27
45
|
vRewrite.path = rewrite.destination;
|
|
28
46
|
}
|
|
29
|
-
else if (rewrite
|
|
47
|
+
else if ("function" in rewrite) {
|
|
30
48
|
vRewrite.function = rewrite.function;
|
|
31
49
|
}
|
|
32
|
-
else if (rewrite
|
|
50
|
+
else if ("dynamicLinks" in rewrite) {
|
|
33
51
|
vRewrite.dynamicLinks = rewrite.dynamicLinks;
|
|
34
52
|
}
|
|
35
|
-
else if (rewrite
|
|
53
|
+
else if ("run" in rewrite) {
|
|
36
54
|
vRewrite.run = Object.assign({ region: "us-central1" }, rewrite.run);
|
|
37
55
|
}
|
|
38
56
|
return vRewrite;
|
|
39
57
|
});
|
|
40
58
|
}
|
|
41
|
-
if (
|
|
42
|
-
out.redirects = config.redirects.map(
|
|
59
|
+
if (Array.isArray(config.redirects)) {
|
|
60
|
+
out.redirects = config.redirects.map((redirect) => {
|
|
43
61
|
const vRedirect = extractPattern("redirect", redirect);
|
|
44
62
|
vRedirect.location = redirect.destination;
|
|
45
63
|
if (redirect.type) {
|
|
@@ -48,17 +66,19 @@ module.exports = function (config) {
|
|
|
48
66
|
return vRedirect;
|
|
49
67
|
});
|
|
50
68
|
}
|
|
51
|
-
if (
|
|
52
|
-
out.headers = config.headers.map(
|
|
69
|
+
if (Array.isArray(config.headers)) {
|
|
70
|
+
out.headers = config.headers.map((header) => {
|
|
53
71
|
const vHeader = extractPattern("header", header);
|
|
54
72
|
vHeader.headers = {};
|
|
55
|
-
(header.headers
|
|
56
|
-
|
|
57
|
-
|
|
73
|
+
if (Array.isArray(header.headers) && header.headers.length) {
|
|
74
|
+
header.headers.forEach((h) => {
|
|
75
|
+
vHeader.headers[h.key] = h.value;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
58
78
|
return vHeader;
|
|
59
79
|
});
|
|
60
80
|
}
|
|
61
|
-
if (
|
|
81
|
+
if (has(config, "cleanUrls")) {
|
|
62
82
|
out.cleanUrls = config.cleanUrls;
|
|
63
83
|
}
|
|
64
84
|
if (config.trailingSlash === true) {
|
|
@@ -67,11 +87,12 @@ module.exports = function (config) {
|
|
|
67
87
|
else if (config.trailingSlash === false) {
|
|
68
88
|
out.trailingSlashBehavior = "REMOVE";
|
|
69
89
|
}
|
|
70
|
-
if (
|
|
90
|
+
if (has(config, "appAssociation")) {
|
|
71
91
|
out.appAssociation = config.appAssociation;
|
|
72
92
|
}
|
|
73
|
-
if (
|
|
93
|
+
if (has(config, "i18n")) {
|
|
74
94
|
out.i18n = config.i18n;
|
|
75
95
|
}
|
|
76
96
|
return out;
|
|
77
|
-
}
|
|
97
|
+
}
|
|
98
|
+
exports.convertConfig = convertConfig;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
const _ = require("lodash");
|
|
3
3
|
const api = require("../../api");
|
|
4
|
-
const convertConfig = require("./convertConfig");
|
|
4
|
+
const { convertConfig } = require("./convertConfig");
|
|
5
5
|
const deploymentTool = require("../../deploymentTool");
|
|
6
6
|
const { FirebaseError } = require("../../error");
|
|
7
7
|
const { normalizedHostingConfigs } = require("../../hosting/normalizedHostingConfigs");
|
|
@@ -246,7 +246,8 @@ async function startAll(options, showUI = true) {
|
|
|
246
246
|
utils.assertDefined(options.config.src.functions);
|
|
247
247
|
utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
|
|
248
248
|
utils.assertIsStringOrUndefined(options.extensionDir);
|
|
249
|
-
const
|
|
249
|
+
const projectDir = options.extensionDir || options.config.projectDir;
|
|
250
|
+
const functionsDir = path.join(projectDir, options.config.src.functions.source);
|
|
250
251
|
let inspectFunctions;
|
|
251
252
|
if (options.inspectFunctions) {
|
|
252
253
|
inspectFunctions = commandUtils.parseInspectionPort(options);
|
|
@@ -269,6 +270,7 @@ async function startAll(options, showUI = true) {
|
|
|
269
270
|
];
|
|
270
271
|
const functionsEmulator = new functionsEmulator_1.FunctionsEmulator({
|
|
271
272
|
projectId,
|
|
273
|
+
projectDir,
|
|
272
274
|
emulatableBackends,
|
|
273
275
|
account,
|
|
274
276
|
host: functionsAddr.host,
|
|
@@ -13,6 +13,7 @@ const TYPE_VERBOSITY = {
|
|
|
13
13
|
USER: 2,
|
|
14
14
|
WARN: 2,
|
|
15
15
|
WARN_ONCE: 2,
|
|
16
|
+
ERROR: 2,
|
|
16
17
|
};
|
|
17
18
|
var Verbosity;
|
|
18
19
|
(function (Verbosity) {
|
|
@@ -79,6 +80,9 @@ class EmulatorLogger {
|
|
|
79
80
|
case "SUCCESS":
|
|
80
81
|
utils.logSuccess(text, "info", mergedData);
|
|
81
82
|
break;
|
|
83
|
+
case "ERROR":
|
|
84
|
+
utils.logBullet(text, "error", mergedData);
|
|
85
|
+
break;
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
88
|
handleRuntimeLog(log, ignore = []) {
|
|
@@ -196,6 +200,9 @@ You can probably fix this by running "npm install ${systemLog.data.name}@latest"
|
|
|
196
200
|
EmulatorLogger.warnOnceCache.add(text);
|
|
197
201
|
}
|
|
198
202
|
break;
|
|
203
|
+
case "ERROR":
|
|
204
|
+
utils.logLabeledError(label, text, "error", mergedData);
|
|
205
|
+
break;
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
static shouldSupress(type) {
|
|
@@ -27,10 +27,14 @@ const workQueue_1 = require("./workQueue");
|
|
|
27
27
|
const utils_1 = require("../utils");
|
|
28
28
|
const defaultCredentials_1 = require("../defaultCredentials");
|
|
29
29
|
const adminSdkConfig_1 = require("./adminSdkConfig");
|
|
30
|
-
const functionsEnv = require("../functions/env");
|
|
31
30
|
const types_2 = require("./events/types");
|
|
32
31
|
const validate_1 = require("../deploy/functions/validate");
|
|
32
|
+
const runtimes_1 = require("../deploy/functions/runtimes");
|
|
33
|
+
const backend = require("../deploy/functions/backend");
|
|
34
|
+
const functionsEnv = require("../functions/env");
|
|
35
|
+
const secretManager_1 = require("../gcp/secretManager");
|
|
33
36
|
const EVENT_INVOKE = "functions:invoke";
|
|
37
|
+
const LOCAL_SECRETS_FILE = ".secret.local";
|
|
34
38
|
const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
|
|
35
39
|
class FunctionsEmulator {
|
|
36
40
|
constructor(args) {
|
|
@@ -44,9 +48,7 @@ class FunctionsEmulator {
|
|
|
44
48
|
this.args.disabledRuntimeFeatures = this.args.disabledRuntimeFeatures || {};
|
|
45
49
|
this.args.disabledRuntimeFeatures.timeout = true;
|
|
46
50
|
}
|
|
47
|
-
this.adminSdkConfig = {
|
|
48
|
-
projectId: this.args.projectId,
|
|
49
|
-
};
|
|
51
|
+
this.adminSdkConfig = Object.assign(Object.assign({}, this.args.adminSdkConfig), { projectId: this.args.projectId });
|
|
50
52
|
const mode = this.args.debugPort
|
|
51
53
|
? types_1.FunctionsExecutionMode.SEQUENTIAL
|
|
52
54
|
: types_1.FunctionsExecutionMode.AUTO;
|
|
@@ -159,25 +161,17 @@ class FunctionsEmulator {
|
|
|
159
161
|
});
|
|
160
162
|
return hub;
|
|
161
163
|
}
|
|
162
|
-
startFunctionRuntime(backend,
|
|
163
|
-
const bundleTemplate = this.getBaseBundle(
|
|
164
|
-
const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), {
|
|
165
|
-
firestore: this.getEmulatorInfo(types_1.Emulators.FIRESTORE),
|
|
166
|
-
database: this.getEmulatorInfo(types_1.Emulators.DATABASE),
|
|
167
|
-
pubsub: this.getEmulatorInfo(types_1.Emulators.PUBSUB),
|
|
168
|
-
auth: this.getEmulatorInfo(types_1.Emulators.AUTH),
|
|
169
|
-
storage: this.getEmulatorInfo(types_1.Emulators.STORAGE),
|
|
170
|
-
}, nodeMajorVersion: backend.nodeMajorVersion, proto,
|
|
171
|
-
triggerId,
|
|
172
|
-
targetName });
|
|
164
|
+
async startFunctionRuntime(backend, trigger, proto, runtimeOpts) {
|
|
165
|
+
const bundleTemplate = this.getBaseBundle();
|
|
166
|
+
const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
|
|
173
167
|
if (!backend.nodeBinary) {
|
|
174
|
-
throw new error_1.FirebaseError(`No node binary for ${
|
|
168
|
+
throw new error_1.FirebaseError(`No node binary for ${trigger.id}. This should never happen.`);
|
|
175
169
|
}
|
|
176
170
|
const opts = runtimeOpts || {
|
|
177
171
|
nodeBinary: backend.nodeBinary,
|
|
178
172
|
extensionTriggers: backend.predefinedTriggers,
|
|
179
173
|
};
|
|
180
|
-
const worker = this.invokeRuntime(
|
|
174
|
+
const worker = await this.invokeRuntime(backend, trigger, runtimeBundle, opts);
|
|
181
175
|
return worker;
|
|
182
176
|
}
|
|
183
177
|
async start() {
|
|
@@ -188,13 +182,15 @@ class FunctionsEmulator {
|
|
|
188
182
|
for (const e of this.args.emulatableBackends) {
|
|
189
183
|
e.env = Object.assign(Object.assign({}, credentialEnv), e.env);
|
|
190
184
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
185
|
+
if (Object.keys(this.adminSdkConfig || {}).length <= 1) {
|
|
186
|
+
const adminSdkConfig = await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(this.args.projectId);
|
|
187
|
+
if (adminSdkConfig) {
|
|
188
|
+
this.adminSdkConfig = adminSdkConfig;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.logger.logLabeled("WARN", "functions", "Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect.");
|
|
192
|
+
this.adminSdkConfig = (0, adminSdkConfig_1.constructDefaultAdminSdkConfig)(this.args.projectId);
|
|
193
|
+
}
|
|
198
194
|
}
|
|
199
195
|
const { host, port } = this.getInfo();
|
|
200
196
|
this.workQueue.start();
|
|
@@ -242,14 +238,25 @@ class FunctionsEmulator {
|
|
|
242
238
|
if (!emulatableBackend.nodeBinary) {
|
|
243
239
|
throw new error_1.FirebaseError(`No node binary for ${emulatableBackend.functionsDir}. This should never happen.`);
|
|
244
240
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
241
|
+
let triggerDefinitions;
|
|
242
|
+
if (emulatableBackend.predefinedTriggers) {
|
|
243
|
+
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsByRegion)(emulatableBackend.predefinedTriggers);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const runtimeDelegate = await (0, runtimes_1.getRuntimeDelegate)({
|
|
247
|
+
projectId: this.args.projectId,
|
|
248
|
+
projectDir: this.args.projectDir,
|
|
249
|
+
sourceDir: emulatableBackend.functionsDir,
|
|
250
|
+
});
|
|
251
|
+
logger_1.logger.debug(`Validating ${runtimeDelegate.name} source`);
|
|
252
|
+
await runtimeDelegate.validate();
|
|
253
|
+
logger_1.logger.debug(`Building ${runtimeDelegate.name} source`);
|
|
254
|
+
await runtimeDelegate.build();
|
|
255
|
+
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
256
|
+
const discoveredBackend = await runtimeDelegate.discoverSpec({}, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
|
|
257
|
+
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
258
|
+
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
|
|
259
|
+
}
|
|
253
260
|
const toSetup = triggerDefinitions.filter((definition) => {
|
|
254
261
|
if (force) {
|
|
255
262
|
return true;
|
|
@@ -305,7 +312,7 @@ class FunctionsEmulator {
|
|
|
305
312
|
}
|
|
306
313
|
}
|
|
307
314
|
else {
|
|
308
|
-
this.logger.log("WARN", `
|
|
315
|
+
this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
|
|
309
316
|
}
|
|
310
317
|
const ignored = !added;
|
|
311
318
|
this.addTriggerRecord(definition, { backend: emulatableBackend, ignored, url });
|
|
@@ -331,7 +338,7 @@ class FunctionsEmulator {
|
|
|
331
338
|
}
|
|
332
339
|
const result = DATABASE_PATH_PATTERN.exec(eventTrigger.resource);
|
|
333
340
|
if (result === null || result.length !== 3) {
|
|
334
|
-
this.logger.log("WARN", `Event
|
|
341
|
+
this.logger.log("WARN", `Event function "${key}" has malformed "resource" member. ` + `${eventTrigger.resource}`);
|
|
335
342
|
return Promise.reject();
|
|
336
343
|
}
|
|
337
344
|
const instance = result[1];
|
|
@@ -347,7 +354,7 @@ class FunctionsEmulator {
|
|
|
347
354
|
setTriggersPath += `?ns=${instance}`;
|
|
348
355
|
}
|
|
349
356
|
else {
|
|
350
|
-
this.logger.log("WARN", `No project in use. Registering function
|
|
357
|
+
this.logger.log("WARN", `No project in use. Registering function for sentinel namespace '${constants_1.Constants.DEFAULT_DATABASE_EMULATOR_NAMESPACE}'`);
|
|
351
358
|
}
|
|
352
359
|
return api
|
|
353
360
|
.request("POST", setTriggersPath, {
|
|
@@ -362,7 +369,7 @@ class FunctionsEmulator {
|
|
|
362
369
|
return true;
|
|
363
370
|
})
|
|
364
371
|
.catch((err) => {
|
|
365
|
-
this.logger.log("WARN", "Error adding
|
|
372
|
+
this.logger.log("WARN", "Error adding Realtime Database function: " + err);
|
|
366
373
|
throw err;
|
|
367
374
|
});
|
|
368
375
|
}
|
|
@@ -371,7 +378,9 @@ class FunctionsEmulator {
|
|
|
371
378
|
if (!firestoreEmu) {
|
|
372
379
|
return Promise.resolve(false);
|
|
373
380
|
}
|
|
374
|
-
const bundle = JSON.stringify({
|
|
381
|
+
const bundle = JSON.stringify({
|
|
382
|
+
eventTrigger: Object.assign(Object.assign({}, eventTrigger), { service: "firestore.googleapis.com" }),
|
|
383
|
+
});
|
|
375
384
|
logger_1.logger.debug(`addFirestoreTrigger`, JSON.stringify(bundle));
|
|
376
385
|
return api
|
|
377
386
|
.request("PUT", `/emulator/v1/projects/${projectId}/triggers/${key}`, {
|
|
@@ -383,7 +392,7 @@ class FunctionsEmulator {
|
|
|
383
392
|
return true;
|
|
384
393
|
})
|
|
385
394
|
.catch((err) => {
|
|
386
|
-
this.logger.log("WARN", "Error adding
|
|
395
|
+
this.logger.log("WARN", "Error adding firestore function: " + err);
|
|
387
396
|
throw err;
|
|
388
397
|
});
|
|
389
398
|
}
|
|
@@ -452,7 +461,7 @@ class FunctionsEmulator {
|
|
|
452
461
|
const record = this.triggers[triggerKey];
|
|
453
462
|
if (!record) {
|
|
454
463
|
logger_1.logger.debug(`Could not find key=${triggerKey} in ${JSON.stringify(this.triggers)}`);
|
|
455
|
-
throw new error_1.FirebaseError(`No
|
|
464
|
+
throw new error_1.FirebaseError(`No function with key ${triggerKey}`);
|
|
456
465
|
}
|
|
457
466
|
return record;
|
|
458
467
|
}
|
|
@@ -475,30 +484,12 @@ class FunctionsEmulator {
|
|
|
475
484
|
setTriggersForTesting(triggers, backend) {
|
|
476
485
|
triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
|
|
477
486
|
}
|
|
478
|
-
getBaseBundle(
|
|
487
|
+
getBaseBundle() {
|
|
479
488
|
return {
|
|
480
|
-
|
|
481
|
-
projectId: this.args.projectId,
|
|
482
|
-
triggerId: "",
|
|
483
|
-
targetName: "",
|
|
484
|
-
emulators: {
|
|
485
|
-
firestore: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FIRESTORE),
|
|
486
|
-
database: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.DATABASE),
|
|
487
|
-
pubsub: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.PUBSUB),
|
|
488
|
-
auth: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.AUTH),
|
|
489
|
-
storage: registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE),
|
|
490
|
-
},
|
|
491
|
-
adminSdkConfig: {
|
|
492
|
-
databaseURL: this.adminSdkConfig.databaseURL,
|
|
493
|
-
storageBucket: this.adminSdkConfig.storageBucket,
|
|
494
|
-
},
|
|
489
|
+
proto: {},
|
|
495
490
|
disabled_features: this.args.disabledRuntimeFeatures,
|
|
496
491
|
};
|
|
497
492
|
}
|
|
498
|
-
getRequestedNodeRuntimeVersion(frb) {
|
|
499
|
-
const pkg = require(path.join(frb.cwd, "package.json"));
|
|
500
|
-
return frb.nodeMajorVersion || (pkg.engines && pkg.engines.node);
|
|
501
|
-
}
|
|
502
493
|
getNodeBinary(backend) {
|
|
503
494
|
const pkg = require(path.join(backend.functionsDir, "package.json"));
|
|
504
495
|
if ((!pkg.engines || !pkg.engines.node) && !backend.nodeMajorVersion) {
|
|
@@ -525,7 +516,9 @@ class FunctionsEmulator {
|
|
|
525
516
|
if (requestedMajorVersion === hostMajorVersion) {
|
|
526
517
|
this.logger.logLabeled("SUCCESS", "functions", `Using node@${requestedMajorVersion} from host.`);
|
|
527
518
|
}
|
|
528
|
-
|
|
519
|
+
else {
|
|
520
|
+
this.logger.log("WARN", `Your requested "node" version "${requestedMajorVersion}" doesn't match your global version "${hostMajorVersion}". Using node@${hostMajorVersion} from host.`);
|
|
521
|
+
}
|
|
529
522
|
return process.execPath;
|
|
530
523
|
}
|
|
531
524
|
getUserEnvs(backend) {
|
|
@@ -544,16 +537,16 @@ class FunctionsEmulator {
|
|
|
544
537
|
}
|
|
545
538
|
return {};
|
|
546
539
|
}
|
|
547
|
-
getSystemEnvs(
|
|
540
|
+
getSystemEnvs(trigger) {
|
|
548
541
|
const envs = {};
|
|
549
542
|
envs.GCLOUD_PROJECT = this.args.projectId;
|
|
550
543
|
envs.K_REVISION = "1";
|
|
551
544
|
envs.PORT = "80";
|
|
552
|
-
if (
|
|
553
|
-
const service =
|
|
545
|
+
if (trigger) {
|
|
546
|
+
const service = trigger.name;
|
|
554
547
|
const target = service.replace(/-/g, ".");
|
|
555
548
|
envs.FUNCTION_TARGET = target;
|
|
556
|
-
envs.FUNCTION_SIGNATURE_TYPE =
|
|
549
|
+
envs.FUNCTION_SIGNATURE_TYPE = (0, functionsEmulatorShared_1.getSignatureType)(trigger);
|
|
557
550
|
envs.K_SERVICE = service;
|
|
558
551
|
}
|
|
559
552
|
return envs;
|
|
@@ -605,12 +598,50 @@ class FunctionsEmulator {
|
|
|
605
598
|
projectId: this.args.projectId,
|
|
606
599
|
});
|
|
607
600
|
}
|
|
608
|
-
getRuntimeEnvs(backend,
|
|
609
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getUserEnvs(backend)), this.getSystemEnvs(
|
|
601
|
+
getRuntimeEnvs(backend, trigger) {
|
|
602
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, this.getUserEnvs(backend)), this.getSystemEnvs(trigger)), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), backend.env);
|
|
603
|
+
}
|
|
604
|
+
async resolveSecretEnvs(backend, trigger) {
|
|
605
|
+
let secretEnvs = {};
|
|
606
|
+
try {
|
|
607
|
+
const data = fs.readFileSync(path.join(backend.functionsDir, LOCAL_SECRETS_FILE), "utf8");
|
|
608
|
+
secretEnvs = functionsEnv.parseStrict(data);
|
|
609
|
+
}
|
|
610
|
+
catch (e) {
|
|
611
|
+
if (e.code !== "ENOENT") {
|
|
612
|
+
this.logger.logLabeled("ERROR", "functions", `Failed to read local secrets file ${LOCAL_SECRETS_FILE}: ${e.message}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const secrets = trigger.secretEnvironmentVariables || [];
|
|
616
|
+
const accesses = secrets
|
|
617
|
+
.filter((s) => !secretEnvs[s.secret])
|
|
618
|
+
.map(async (s) => {
|
|
619
|
+
this.logger.logLabeled("INFO", "functions", `Trying to access secret ${s.key}@latest`);
|
|
620
|
+
const value = await (0, secretManager_1.accessSecretVersion)(this.getProjectId(), s.key, "latest");
|
|
621
|
+
return [s.secret, value];
|
|
622
|
+
});
|
|
623
|
+
const accessResults = await (0, utils_1.allSettled)(accesses);
|
|
624
|
+
const errs = [];
|
|
625
|
+
for (const result of accessResults) {
|
|
626
|
+
if (result.status === "rejected") {
|
|
627
|
+
errs.push(result.reason);
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const [k, v] = result.value;
|
|
631
|
+
secretEnvs[k] = v;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (errs.length > 0) {
|
|
635
|
+
this.logger.logLabeled("ERROR", "functions", "Unable to access secret environment variables from Google Cloud Secret Manager. " +
|
|
636
|
+
"Make sure the credential used for the Functions Emulator have access " +
|
|
637
|
+
`or provide override values in ${LOCAL_SECRETS_FILE}:\n\t` +
|
|
638
|
+
errs.join("\n\t"));
|
|
639
|
+
}
|
|
640
|
+
return secretEnvs;
|
|
610
641
|
}
|
|
611
|
-
invokeRuntime(frb, opts
|
|
612
|
-
if (this.workerPool.readyForWork(
|
|
613
|
-
return this.workerPool.submitWork(
|
|
642
|
+
async invokeRuntime(backend, trigger, frb, opts) {
|
|
643
|
+
if (this.workerPool.readyForWork(trigger.id)) {
|
|
644
|
+
return this.workerPool.submitWork(trigger.id, frb, opts);
|
|
614
645
|
}
|
|
615
646
|
const emitter = new events_1.EventEmitter();
|
|
616
647
|
const args = [path.join(__dirname, "functionsEmulatorRuntime")];
|
|
@@ -619,7 +650,7 @@ class FunctionsEmulator {
|
|
|
619
650
|
}
|
|
620
651
|
if (this.args.debugPort) {
|
|
621
652
|
if (process.env.FIREPIT_VERSION && process.execPath == opts.nodeBinary) {
|
|
622
|
-
const requestedMajorNodeVersion = this.
|
|
653
|
+
const requestedMajorNodeVersion = this.getNodeBinary(backend);
|
|
623
654
|
this.logger.log("WARN", `To enable function inspection, please run "${process.execPath} is:npm i node@${requestedMajorNodeVersion} --save-dev" in your functions directory`);
|
|
624
655
|
}
|
|
625
656
|
else {
|
|
@@ -627,15 +658,17 @@ class FunctionsEmulator {
|
|
|
627
658
|
args.unshift(`--inspect=${host}:${this.args.debugPort}`);
|
|
628
659
|
}
|
|
629
660
|
}
|
|
630
|
-
const pnpPath = path.join(
|
|
661
|
+
const pnpPath = path.join(backend.functionsDir, ".pnp.js");
|
|
631
662
|
if (fs.existsSync(pnpPath)) {
|
|
632
663
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).logLabeled("WARN_ONCE", "functions", "Detected yarn@2 with PnP. " +
|
|
633
664
|
"Cloud Functions for Firebase requires a node_modules folder to work correctly and is therefore incompatible with PnP. " +
|
|
634
665
|
"See https://yarnpkg.com/getting-started/migration#step-by-step for more information.");
|
|
635
666
|
}
|
|
667
|
+
const runtimeEnv = this.getRuntimeEnvs(backend, trigger);
|
|
668
|
+
const secretEnvs = await this.resolveSecretEnvs(backend, trigger);
|
|
636
669
|
const childProcess = spawn(opts.nodeBinary, args, {
|
|
637
|
-
|
|
638
|
-
|
|
670
|
+
cwd: backend.functionsDir,
|
|
671
|
+
env: Object.assign(Object.assign(Object.assign({ node: opts.nodeBinary }, process.env), runtimeEnv), secretEnvs),
|
|
639
672
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
640
673
|
});
|
|
641
674
|
if (!childProcess.stderr) {
|
|
@@ -666,6 +699,7 @@ class FunctionsEmulator {
|
|
|
666
699
|
childProcess.on("exit", resolve);
|
|
667
700
|
}),
|
|
668
701
|
events: emitter,
|
|
702
|
+
cwd: backend.functionsDir,
|
|
669
703
|
shutdown: () => {
|
|
670
704
|
childProcess.kill();
|
|
671
705
|
},
|
|
@@ -677,8 +711,8 @@ class FunctionsEmulator {
|
|
|
677
711
|
return childProcess.send(JSON.stringify(args));
|
|
678
712
|
},
|
|
679
713
|
};
|
|
680
|
-
this.workerPool.addWorker(
|
|
681
|
-
return this.workerPool.submitWork(
|
|
714
|
+
this.workerPool.addWorker(trigger.id, runtime);
|
|
715
|
+
return this.workerPool.submitWork(trigger.id, frb, opts);
|
|
682
716
|
}
|
|
683
717
|
async disableBackgroundTriggers() {
|
|
684
718
|
Object.values(this.triggers).forEach((record) => {
|
|
@@ -704,7 +738,7 @@ class FunctionsEmulator {
|
|
|
704
738
|
}
|
|
705
739
|
const trigger = record.def;
|
|
706
740
|
const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
|
|
707
|
-
const worker = this.startFunctionRuntime(record.backend, trigger
|
|
741
|
+
const worker = await this.startFunctionRuntime(record.backend, trigger, proto);
|
|
708
742
|
return new Promise((resolve, reject) => {
|
|
709
743
|
if (projectId !== this.args.projectId) {
|
|
710
744
|
if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
|
|
@@ -738,7 +772,7 @@ class FunctionsEmulator {
|
|
|
738
772
|
return registry_1.EmulatorRegistry.getInfo(emulator);
|
|
739
773
|
}
|
|
740
774
|
tokenFromAuthHeader(authHeader) {
|
|
741
|
-
const match =
|
|
775
|
+
const match = /^Bearer (.*)$/.exec(authHeader);
|
|
742
776
|
if (!match) {
|
|
743
777
|
return;
|
|
744
778
|
}
|
|
@@ -770,7 +804,7 @@ class FunctionsEmulator {
|
|
|
770
804
|
if (!this.triggers[triggerId]) {
|
|
771
805
|
res
|
|
772
806
|
.status(404)
|
|
773
|
-
.send(`Function ${triggerId} does not exist, valid
|
|
807
|
+
.send(`Function ${triggerId} does not exist, valid functions are: ${Object.keys(this.triggers).join(", ")}`);
|
|
774
808
|
return;
|
|
775
809
|
}
|
|
776
810
|
const record = this.getTriggerRecordByKey(triggerId);
|
|
@@ -791,7 +825,7 @@ class FunctionsEmulator {
|
|
|
791
825
|
req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
|
|
792
826
|
}
|
|
793
827
|
}
|
|
794
|
-
const worker = this.startFunctionRuntime(record.backend, trigger
|
|
828
|
+
const worker = await this.startFunctionRuntime(record.backend, trigger);
|
|
795
829
|
worker.onLogs((el) => {
|
|
796
830
|
if (el.level === "FATAL") {
|
|
797
831
|
res.status(500).send(el.text);
|