firebase-tools 10.5.0 → 10.7.1
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/command.js +4 -4
- package/lib/commands/deploy.js +1 -1
- package/lib/commands/emulators-start.js +7 -2
- package/lib/commands/ext-configure.js +15 -5
- package/lib/commands/ext-export.js +6 -5
- package/lib/commands/ext-install.js +28 -44
- package/lib/commands/ext-update.js +9 -1
- package/lib/commands/functions-delete.js +7 -3
- package/lib/commands/functions-secrets-destroy.js +23 -3
- package/lib/commands/functions-secrets-prune.js +15 -12
- package/lib/commands/functions-secrets-set.js +51 -4
- package/lib/commands/hosting-channel-deploy.js +2 -2
- package/lib/deploy/database/deploy.js +4 -0
- package/lib/deploy/database/index.js +1 -0
- package/lib/deploy/extensions/deploy.js +4 -4
- package/lib/deploy/extensions/deploymentSummary.js +8 -5
- package/lib/deploy/extensions/planner.js +36 -9
- package/lib/deploy/extensions/prepare.js +1 -1
- package/lib/deploy/extensions/secrets.js +2 -2
- package/lib/deploy/extensions/tasks.js +60 -21
- package/lib/deploy/functions/backend.js +37 -6
- package/lib/deploy/functions/build.js +162 -0
- package/lib/deploy/functions/checkIam.js +10 -6
- package/lib/deploy/functions/deploy.js +49 -28
- package/lib/deploy/functions/ensure.js +4 -4
- package/lib/deploy/functions/functionsDeployHelper.js +99 -24
- package/lib/deploy/functions/prepare.js +130 -62
- package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
- package/lib/deploy/functions/pricing.js +6 -3
- package/lib/deploy/functions/prompts.js +1 -7
- package/lib/deploy/functions/release/fabricator.js +70 -28
- package/lib/deploy/functions/release/index.js +41 -6
- package/lib/deploy/functions/release/planner.js +19 -12
- package/lib/deploy/functions/release/reporter.js +14 -11
- package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
- package/lib/deploy/functions/runtimes/node/index.js +1 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
- package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
- package/lib/deploy/functions/services/auth.js +95 -0
- package/lib/deploy/functions/services/index.js +41 -21
- package/lib/deploy/functions/services/storage.js +1 -6
- package/lib/deploy/functions/validate.js +32 -6
- package/lib/deploy/hosting/args.js +2 -0
- package/lib/deploy/hosting/convertConfig.js +39 -8
- package/lib/deploy/hosting/deploy.js +3 -3
- package/lib/deploy/hosting/prepare.js +2 -2
- package/lib/deploy/hosting/release.js +6 -2
- package/lib/deploy/index.js +82 -93
- package/lib/deploy/remoteconfig/deploy.js +4 -0
- package/lib/deploy/remoteconfig/index.js +3 -1
- package/lib/emulator/auth/operations.js +5 -0
- package/lib/emulator/auth/utils.js +3 -25
- package/lib/emulator/controller.js +17 -14
- package/lib/emulator/downloadableEmulators.js +39 -23
- package/lib/emulator/extensions/postinstall.js +41 -0
- package/lib/emulator/extensions/validation.js +2 -2
- package/lib/emulator/extensionsEmulator.js +85 -21
- package/lib/emulator/functionsEmulator.js +88 -10
- package/lib/emulator/functionsEmulatorShared.js +37 -21
- package/lib/emulator/functionsEmulatorShell.js +2 -3
- package/lib/emulator/pubsubEmulator.js +13 -9
- package/lib/emulator/registry.js +34 -12
- package/lib/emulator/storage/apis/firebase.js +13 -8
- package/lib/emulator/storage/apis/gcloud.js +15 -9
- package/lib/emulator/storage/files.js +14 -3
- package/lib/emulator/storage/index.js +9 -1
- package/lib/emulator/storage/metadata.js +18 -8
- package/lib/emulator/storage/rules/manager.js +7 -17
- package/lib/emulator/storage/server.js +38 -12
- package/lib/ensureApiEnabled.js +8 -4
- package/lib/extensions/askUserForParam.js +14 -11
- package/lib/extensions/changelog.js +1 -1
- package/lib/extensions/emulator/optionsHelper.js +9 -10
- package/lib/extensions/emulator/specHelper.js +7 -1
- package/lib/extensions/emulator/triggerHelper.js +11 -14
- package/lib/extensions/extensionsApi.js +2 -1
- package/lib/extensions/extensionsHelper.js +30 -24
- package/lib/extensions/manifest.js +28 -8
- package/lib/extensions/paramHelper.js +19 -13
- package/lib/extensions/provisioningHelper.js +2 -2
- package/lib/extensions/warnings.js +3 -3
- package/lib/functions/env.js +10 -2
- package/lib/functions/events/index.js +7 -0
- package/lib/functions/events/v1.js +6 -0
- package/lib/functions/projectConfig.js +32 -6
- package/lib/functions/runtimeConfigExport.js +10 -6
- package/lib/functions/secrets.js +99 -6
- package/lib/functionsShellCommandAction.js +1 -1
- package/lib/gcp/cloudfunctions.js +44 -18
- package/lib/gcp/cloudfunctionsv2.js +48 -25
- package/lib/gcp/cloudtasks.js +5 -3
- package/lib/gcp/identityPlatform.js +44 -0
- package/lib/gcp/secretManager.js +2 -2
- package/lib/metaprogramming.js +2 -0
- package/lib/previews.js +1 -1
- package/lib/serve/functions.js +16 -19
- package/lib/serve/hosting.js +25 -12
- package/lib/serve/index.js +6 -0
- package/lib/track.js +15 -21
- package/lib/utils.js +30 -1
- package/npm-shrinkwrap.json +44 -2
- package/package.json +4 -1
- package/schema/firebase-config.json +6 -0
|
@@ -18,13 +18,37 @@ const validation_1 = require("./extensions/validation");
|
|
|
18
18
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
19
19
|
const shortenUrl_1 = require("../shortenUrl");
|
|
20
20
|
const constants_1 = require("./constants");
|
|
21
|
+
const registry_1 = require("./registry");
|
|
21
22
|
class ExtensionsEmulator {
|
|
22
23
|
constructor(args) {
|
|
23
24
|
this.want = [];
|
|
25
|
+
this.backends = [];
|
|
24
26
|
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
|
|
25
27
|
this.pendingDownloads = new Map();
|
|
26
28
|
this.args = args;
|
|
27
29
|
}
|
|
30
|
+
start() {
|
|
31
|
+
this.logger.logLabeled("DEBUG", "Extensions", "Started Extensions emulator, this is a noop.");
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
}
|
|
34
|
+
stop() {
|
|
35
|
+
this.logger.logLabeled("DEBUG", "Extensions", "Stopping Extensions emulator, this is a noop.");
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
connect() {
|
|
39
|
+
this.logger.logLabeled("DEBUG", "Extensions", "Connecting Extensions emulator, this is a noop.");
|
|
40
|
+
return Promise.resolve();
|
|
41
|
+
}
|
|
42
|
+
getInfo() {
|
|
43
|
+
const info = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.FUNCTIONS);
|
|
44
|
+
if (!info) {
|
|
45
|
+
throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
|
|
46
|
+
}
|
|
47
|
+
return info;
|
|
48
|
+
}
|
|
49
|
+
getName() {
|
|
50
|
+
return types_1.Emulators.EXTENSIONS;
|
|
51
|
+
}
|
|
28
52
|
async readManifest() {
|
|
29
53
|
var _a;
|
|
30
54
|
this.want = await planner.want({
|
|
@@ -37,22 +61,30 @@ class ExtensionsEmulator {
|
|
|
37
61
|
});
|
|
38
62
|
}
|
|
39
63
|
async ensureSourceCode(instance) {
|
|
40
|
-
if (
|
|
41
|
-
|
|
64
|
+
if (instance.localPath) {
|
|
65
|
+
if (!this.hasValidSource({ path: instance.localPath, extTarget: instance.localPath })) {
|
|
66
|
+
throw new error_1.FirebaseError(`Tried to emulate local extension at ${instance.localPath}, but it was missing required files.`);
|
|
67
|
+
}
|
|
68
|
+
return instance.localPath;
|
|
42
69
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
70
|
+
else if (instance.ref) {
|
|
71
|
+
const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
|
|
72
|
+
const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
|
|
73
|
+
path.join(os.homedir(), ".cache", "firebase", "extensions");
|
|
74
|
+
const sourceCodePath = path.join(cacheDir, ref);
|
|
75
|
+
if (this.pendingDownloads.get(ref)) {
|
|
76
|
+
await this.pendingDownloads.get(ref);
|
|
77
|
+
}
|
|
78
|
+
if (!this.hasValidSource({ path: sourceCodePath, extTarget: ref })) {
|
|
79
|
+
const promise = this.downloadSource(instance, ref, sourceCodePath);
|
|
80
|
+
this.pendingDownloads.set(ref, promise);
|
|
81
|
+
await promise;
|
|
82
|
+
}
|
|
83
|
+
return sourceCodePath;
|
|
49
84
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.pendingDownloads.set(ref, promise);
|
|
53
|
-
await promise;
|
|
85
|
+
else {
|
|
86
|
+
throw new error_1.FirebaseError("Tried to emulate an extension instance without a ref or localPath. This should never happen.");
|
|
54
87
|
}
|
|
55
|
-
return sourceCodePath;
|
|
56
88
|
}
|
|
57
89
|
async downloadSource(instance, ref, sourceCodePath) {
|
|
58
90
|
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
@@ -71,7 +103,7 @@ class ExtensionsEmulator {
|
|
|
71
103
|
for (const requiredFile of requiredFiles) {
|
|
72
104
|
const f = path.join(args.path, requiredFile);
|
|
73
105
|
if (!fs.existsSync(f)) {
|
|
74
|
-
emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.
|
|
106
|
+
emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extTarget }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extTarget}, expected to find ${f}`);
|
|
75
107
|
return false;
|
|
76
108
|
}
|
|
77
109
|
}
|
|
@@ -92,27 +124,32 @@ class ExtensionsEmulator {
|
|
|
92
124
|
async getExtensionBackends() {
|
|
93
125
|
await this.readManifest();
|
|
94
126
|
await this.checkAndWarnAPIs(this.want);
|
|
95
|
-
|
|
127
|
+
this.backends = await Promise.all(this.want.map((i) => {
|
|
96
128
|
return this.toEmulatableBackend(i);
|
|
97
129
|
}));
|
|
130
|
+
return this.backends;
|
|
98
131
|
}
|
|
99
132
|
async toEmulatableBackend(instance) {
|
|
100
133
|
const extensionDir = await this.ensureSourceCode(instance);
|
|
101
134
|
const functionsDir = path.join(extensionDir, "functions");
|
|
102
135
|
const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
|
|
103
|
-
const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(
|
|
104
|
-
const
|
|
105
|
-
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
106
|
-
return {
|
|
136
|
+
const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
|
|
137
|
+
const emulatableBackend = {
|
|
107
138
|
functionsDir,
|
|
108
139
|
env: nonSecretEnv,
|
|
109
140
|
secretEnv: secretEnvVariables,
|
|
110
141
|
predefinedTriggers: extensionTriggers,
|
|
111
142
|
nodeMajorVersion: nodeMajorVersion,
|
|
112
143
|
extensionInstanceId: instance.instanceId,
|
|
113
|
-
extension,
|
|
114
|
-
extensionVersion,
|
|
115
144
|
};
|
|
145
|
+
if (instance.ref) {
|
|
146
|
+
emulatableBackend.extension = await planner.getExtension(instance);
|
|
147
|
+
emulatableBackend.extensionVersion = await planner.getExtensionVersion(instance);
|
|
148
|
+
}
|
|
149
|
+
else if (instance.localPath) {
|
|
150
|
+
emulatableBackend.extensionSpec = await planner.getExtensionSpec(instance);
|
|
151
|
+
}
|
|
152
|
+
return emulatableBackend;
|
|
116
153
|
}
|
|
117
154
|
autoPopulatedParams(instance) {
|
|
118
155
|
const projectId = this.args.projectId;
|
|
@@ -174,5 +211,32 @@ class ExtensionsEmulator {
|
|
|
174
211
|
}
|
|
175
212
|
return filteredBackends;
|
|
176
213
|
}
|
|
214
|
+
extensionDetailsUILink(backend) {
|
|
215
|
+
const uiInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.UI);
|
|
216
|
+
if (!uiInfo || !backend.extensionInstanceId) {
|
|
217
|
+
return "";
|
|
218
|
+
}
|
|
219
|
+
const uiUrl = registry_1.EmulatorRegistry.getInfoHostString(uiInfo);
|
|
220
|
+
return clc.underline(clc.bold(`http://${uiUrl}/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`));
|
|
221
|
+
}
|
|
222
|
+
extensionsInfoTable(options) {
|
|
223
|
+
var _a;
|
|
224
|
+
const filtedBackends = this.filterUnemulatedTriggers(options, this.backends);
|
|
225
|
+
const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
|
|
226
|
+
const tableHead = ["Extension Instance Name", "Extension Ref"];
|
|
227
|
+
if (uiRunning) {
|
|
228
|
+
tableHead.push("View in Emulator UI");
|
|
229
|
+
}
|
|
230
|
+
const table = new Table({ head: tableHead, style: { head: ["yellow"] } });
|
|
231
|
+
for (const b of filtedBackends) {
|
|
232
|
+
if (b.extensionInstanceId) {
|
|
233
|
+
const tableEntry = [b.extensionInstanceId, ((_a = b.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref) || "Local Extension"];
|
|
234
|
+
if (uiRunning)
|
|
235
|
+
tableEntry.push(this.extensionDetailsUILink(b));
|
|
236
|
+
table.push(tableEntry);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return table.toString();
|
|
240
|
+
}
|
|
177
241
|
}
|
|
178
242
|
exports.ExtensionsEmulator = ExtensionsEmulator;
|
|
@@ -13,7 +13,7 @@ const url_1 = require("url");
|
|
|
13
13
|
const events_1 = require("events");
|
|
14
14
|
const api = require("../api");
|
|
15
15
|
const logger_1 = require("../logger");
|
|
16
|
-
const
|
|
16
|
+
const track_1 = require("../track");
|
|
17
17
|
const constants_1 = require("./constants");
|
|
18
18
|
const types_1 = require("./types");
|
|
19
19
|
const chokidar = require("chokidar");
|
|
@@ -34,6 +34,7 @@ const secretManager_1 = require("../gcp/secretManager");
|
|
|
34
34
|
const runtimes = require("../deploy/functions/runtimes");
|
|
35
35
|
const backend = require("../deploy/functions/backend");
|
|
36
36
|
const functionsEnv = require("../functions/env");
|
|
37
|
+
const v1_1 = require("../functions/events/v1");
|
|
37
38
|
const EVENT_INVOKE = "functions:invoke";
|
|
38
39
|
const DATABASE_PATH_PATTERN = new RegExp("^projects/[^/]+/instances/([^/]+)/refs(/.*)$");
|
|
39
40
|
class FunctionsEmulator {
|
|
@@ -54,6 +55,16 @@ class FunctionsEmulator {
|
|
|
54
55
|
: types_1.FunctionsExecutionMode.AUTO;
|
|
55
56
|
this.workerPool = new functionsRuntimeWorker_1.RuntimeWorkerPool(mode);
|
|
56
57
|
this.workQueue = new workQueue_1.WorkQueue(mode);
|
|
58
|
+
this.blockingFunctionsConfig = {
|
|
59
|
+
triggers: {
|
|
60
|
+
beforeCreate: {
|
|
61
|
+
functionUri: "",
|
|
62
|
+
},
|
|
63
|
+
beforeSignIn: {
|
|
64
|
+
functionUri: "",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
};
|
|
57
68
|
}
|
|
58
69
|
static getHttpFunctionUrl(host, port, projectId, name, region) {
|
|
59
70
|
return `http://${host}:${port}/${projectId}/${region}/${name}`;
|
|
@@ -166,7 +177,9 @@ class FunctionsEmulator {
|
|
|
166
177
|
});
|
|
167
178
|
return hub;
|
|
168
179
|
}
|
|
169
|
-
async invokeTrigger(
|
|
180
|
+
async invokeTrigger(trigger, proto, runtimeOpts) {
|
|
181
|
+
const record = this.getTriggerRecordByKey(this.getTriggerKey(trigger));
|
|
182
|
+
const backend = record.backend;
|
|
170
183
|
const bundleTemplate = this.getBaseBundle();
|
|
171
184
|
const runtimeBundle = Object.assign(Object.assign({}, bundleTemplate), { proto });
|
|
172
185
|
if (this.args.debugPort) {
|
|
@@ -229,6 +242,7 @@ class FunctionsEmulator {
|
|
|
229
242
|
loadTriggerPromises.push(this.loadTriggers(backend, true));
|
|
230
243
|
}
|
|
231
244
|
await Promise.all(loadTriggerPromises);
|
|
245
|
+
await this.performPostLoadOperations();
|
|
232
246
|
return;
|
|
233
247
|
}
|
|
234
248
|
async stop() {
|
|
@@ -271,6 +285,10 @@ class FunctionsEmulator {
|
|
|
271
285
|
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
272
286
|
const discoveredBackend = await runtimeDelegate.discoverSpec(runtimeConfig, Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env));
|
|
273
287
|
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
288
|
+
(0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
|
|
289
|
+
for (const e of endpoints) {
|
|
290
|
+
e.codebase = emulatableBackend.codebase;
|
|
291
|
+
}
|
|
274
292
|
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
|
|
275
293
|
}
|
|
276
294
|
const toSetup = triggerDefinitions.filter((definition) => {
|
|
@@ -327,6 +345,11 @@ class FunctionsEmulator {
|
|
|
327
345
|
break;
|
|
328
346
|
}
|
|
329
347
|
}
|
|
348
|
+
else if (definition.blockingTrigger) {
|
|
349
|
+
const { host, port } = this.getInfo();
|
|
350
|
+
url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
|
|
351
|
+
added = this.addBlockingTrigger(url, definition.blockingTrigger);
|
|
352
|
+
}
|
|
330
353
|
else {
|
|
331
354
|
this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
|
|
332
355
|
}
|
|
@@ -350,6 +373,34 @@ class FunctionsEmulator {
|
|
|
350
373
|
this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
|
|
351
374
|
}
|
|
352
375
|
}
|
|
376
|
+
async performPostLoadOperations() {
|
|
377
|
+
var _a, _b, _c, _d;
|
|
378
|
+
if (((_b = (_a = this.blockingFunctionsConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) === "" &&
|
|
379
|
+
((_d = (_c = this.blockingFunctionsConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri) === "") {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
|
|
383
|
+
if (!authEmu) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
|
|
387
|
+
try {
|
|
388
|
+
await api.request("PATCH", path, {
|
|
389
|
+
origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
|
|
390
|
+
headers: {
|
|
391
|
+
Authorization: "Bearer owner",
|
|
392
|
+
},
|
|
393
|
+
data: {
|
|
394
|
+
blockingFunctions: this.blockingFunctionsConfig,
|
|
395
|
+
},
|
|
396
|
+
json: true,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
this.logger.log("WARN", "Error updating blocking functions config to the auth emulator: " + err);
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
353
404
|
addRealtimeDatabaseTrigger(projectId, key, eventTrigger) {
|
|
354
405
|
const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
|
|
355
406
|
if (!databaseEmu) {
|
|
@@ -416,11 +467,10 @@ class FunctionsEmulator {
|
|
|
416
467
|
});
|
|
417
468
|
}
|
|
418
469
|
async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
|
|
419
|
-
const
|
|
420
|
-
if (!
|
|
470
|
+
const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
|
|
471
|
+
if (!pubsubEmulator) {
|
|
421
472
|
return false;
|
|
422
473
|
}
|
|
423
|
-
const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
|
|
424
474
|
logger_1.logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger }));
|
|
425
475
|
const resource = eventTrigger.resource;
|
|
426
476
|
let topic;
|
|
@@ -458,6 +508,31 @@ class FunctionsEmulator {
|
|
|
458
508
|
this.multicastTriggers[eventTriggerId] = triggers;
|
|
459
509
|
return true;
|
|
460
510
|
}
|
|
511
|
+
addBlockingTrigger(url, blockingTrigger) {
|
|
512
|
+
logger_1.logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger }));
|
|
513
|
+
const eventType = blockingTrigger.eventType;
|
|
514
|
+
if (v1_1.AUTH_BLOCKING_EVENTS.includes(eventType)) {
|
|
515
|
+
if (blockingTrigger.eventType === v1_1.BEFORE_CREATE_EVENT) {
|
|
516
|
+
this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeCreate: {
|
|
517
|
+
functionUri: url,
|
|
518
|
+
} });
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeSignIn: {
|
|
522
|
+
functionUri: url,
|
|
523
|
+
} });
|
|
524
|
+
}
|
|
525
|
+
this.blockingFunctionsConfig.forwardInboundCredentials = {
|
|
526
|
+
accessToken: blockingTrigger.options.accessToken,
|
|
527
|
+
idToken: blockingTrigger.options.idToken,
|
|
528
|
+
refreshToken: blockingTrigger.options.refreshToken,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
461
536
|
getProjectId() {
|
|
462
537
|
return this.args.projectId;
|
|
463
538
|
}
|
|
@@ -509,6 +584,7 @@ class FunctionsEmulator {
|
|
|
509
584
|
};
|
|
510
585
|
}
|
|
511
586
|
setTriggersForTesting(triggers, backend) {
|
|
587
|
+
this.triggers = {};
|
|
512
588
|
triggers.forEach((def) => this.addTriggerRecord(def, { backend, ignored: false }));
|
|
513
589
|
}
|
|
514
590
|
getBaseBundle() {
|
|
@@ -781,7 +857,9 @@ class FunctionsEmulator {
|
|
|
781
857
|
for (const backend of this.args.emulatableBackends) {
|
|
782
858
|
loadTriggerPromises.push(this.loadTriggers(backend));
|
|
783
859
|
}
|
|
784
|
-
|
|
860
|
+
await Promise.all(loadTriggerPromises);
|
|
861
|
+
await this.performPostLoadOperations();
|
|
862
|
+
return;
|
|
785
863
|
}
|
|
786
864
|
async handleBackgroundTrigger(projectId, triggerKey, proto) {
|
|
787
865
|
const record = this.getTriggerRecordByKey(triggerKey);
|
|
@@ -790,7 +868,7 @@ class FunctionsEmulator {
|
|
|
790
868
|
}
|
|
791
869
|
const trigger = record.def;
|
|
792
870
|
const service = (0, functionsEmulatorShared_1.getFunctionService)(trigger);
|
|
793
|
-
const worker = await this.invokeTrigger(
|
|
871
|
+
const worker = await this.invokeTrigger(trigger, proto);
|
|
794
872
|
return new Promise((resolve, reject) => {
|
|
795
873
|
if (projectId !== this.args.projectId) {
|
|
796
874
|
if (service !== constants_1.Constants.SERVICE_REALTIME_DATABASE) {
|
|
@@ -809,7 +887,7 @@ class FunctionsEmulator {
|
|
|
809
887
|
reject({ code: 500, body: el.text });
|
|
810
888
|
}
|
|
811
889
|
});
|
|
812
|
-
void track(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
|
|
890
|
+
void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
|
|
813
891
|
worker.waitForDone().then(() => {
|
|
814
892
|
resolve({ status: "acknowledged" });
|
|
815
893
|
});
|
|
@@ -877,14 +955,14 @@ class FunctionsEmulator {
|
|
|
877
955
|
req.headers[functionsEmulatorShared_1.HttpConstants.CALLABLE_AUTH_HEADER] = encodeURIComponent(JSON.stringify(contextAuth));
|
|
878
956
|
}
|
|
879
957
|
}
|
|
880
|
-
const worker = await this.invokeTrigger(
|
|
958
|
+
const worker = await this.invokeTrigger(trigger);
|
|
881
959
|
worker.onLogs((el) => {
|
|
882
960
|
if (el.level === "FATAL") {
|
|
883
961
|
res.status(500).send(el.text);
|
|
884
962
|
}
|
|
885
963
|
});
|
|
886
964
|
await worker.waitForSocketReady();
|
|
887
|
-
void track(EVENT_INVOKE, "https");
|
|
965
|
+
void (0, track_1.track)(EVENT_INVOKE, "https");
|
|
888
966
|
this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
|
|
889
967
|
if (!worker.lastArgs) {
|
|
890
968
|
throw new error_1.FirebaseError("Cannot execute on a worker with no arguments");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
|
|
3
|
+
exports.toBackendInfo = exports.getSecretLocalPath = exports.getSignatureType = exports.formatHost = exports.findModuleRoot = exports.waitForBody = exports.getServiceFromEventType = exports.getFunctionService = exports.getTemporarySocketPath = exports.getEmulatedTriggersFromDefinitions = exports.emulatedFunctionsByRegion = exports.emulatedFunctionsFromEndpoints = exports.prepareEndpoints = exports.EmulatedTrigger = exports.HttpConstants = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const os = require("os");
|
|
6
6
|
const path = require("path");
|
|
@@ -8,9 +8,11 @@ const fs = require("fs");
|
|
|
8
8
|
const backend = require("../deploy/functions/backend");
|
|
9
9
|
const constants_1 = require("./constants");
|
|
10
10
|
const proto_1 = require("../gcp/proto");
|
|
11
|
-
const logger_1 = require("../logger");
|
|
12
11
|
const manifest_1 = require("../extensions/manifest");
|
|
13
12
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
13
|
+
const postinstall_1 = require("./extensions/postinstall");
|
|
14
|
+
const services_1 = require("../deploy/functions/services");
|
|
15
|
+
const prepare_1 = require("../deploy/functions/prepare");
|
|
14
16
|
const memoryLookup = {
|
|
15
17
|
"128MB": 128,
|
|
16
18
|
"256MB": 256,
|
|
@@ -33,12 +35,7 @@ class EmulatedTrigger {
|
|
|
33
35
|
return memoryLookup[this.definition.availableMemoryMb || "128MB"] * 1024 * 1024;
|
|
34
36
|
}
|
|
35
37
|
get timeoutMs() {
|
|
36
|
-
|
|
37
|
-
return this.definition.timeout * 1000;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
return parseInt((this.definition.timeout || "60s").split("s")[0], 10) * 1000;
|
|
41
|
-
}
|
|
38
|
+
return (this.definition.timeoutSeconds || 60) * 1000;
|
|
42
39
|
}
|
|
43
40
|
getRawFunction() {
|
|
44
41
|
if (!this.module) {
|
|
@@ -49,6 +46,14 @@ class EmulatedTrigger {
|
|
|
49
46
|
}
|
|
50
47
|
}
|
|
51
48
|
exports.EmulatedTrigger = EmulatedTrigger;
|
|
49
|
+
function prepareEndpoints(endpoints) {
|
|
50
|
+
const bkend = backend.of(...endpoints);
|
|
51
|
+
for (const ep of endpoints) {
|
|
52
|
+
(0, services_1.serviceForEndpoint)(ep).validateTrigger(ep, bkend);
|
|
53
|
+
}
|
|
54
|
+
(0, prepare_1.inferBlockingDetails)(bkend);
|
|
55
|
+
}
|
|
56
|
+
exports.prepareEndpoints = prepareEndpoints;
|
|
52
57
|
function emulatedFunctionsFromEndpoints(endpoints) {
|
|
53
58
|
const regionDefinitions = [];
|
|
54
59
|
for (const endpoint of endpoints) {
|
|
@@ -61,8 +66,9 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
61
66
|
region: endpoint.region,
|
|
62
67
|
name: endpoint.id,
|
|
63
68
|
id: `${endpoint.region}-${endpoint.id}`,
|
|
69
|
+
codebase: endpoint.codebase,
|
|
64
70
|
};
|
|
65
|
-
(0, proto_1.copyIfPresent)(def, endpoint, "
|
|
71
|
+
(0, proto_1.copyIfPresent)(def, endpoint, "availableMemoryMb", "labels", "timeoutSeconds", "platform", "secretEnvironmentVariables");
|
|
66
72
|
if (backend.isHttpsTriggered(endpoint)) {
|
|
67
73
|
def.httpsTrigger = endpoint.httpsTrigger;
|
|
68
74
|
}
|
|
@@ -73,25 +79,20 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
73
79
|
else if (backend.isEventTriggered(endpoint)) {
|
|
74
80
|
const eventTrigger = endpoint.eventTrigger;
|
|
75
81
|
if (endpoint.platform === "gcfv1") {
|
|
76
|
-
const resourceFilter = backend.findEventFilter(endpoint, "resource");
|
|
77
|
-
if (!resourceFilter) {
|
|
78
|
-
logger_1.logger.debug(`Invalid event trigger ${JSON.stringify(endpoint)}, expected event filter with resource attribute. Skipping.`);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
82
|
def.eventTrigger = {
|
|
82
83
|
eventType: eventTrigger.eventType,
|
|
83
|
-
resource:
|
|
84
|
+
resource: eventTrigger.eventFilters.resource,
|
|
84
85
|
};
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
const { resource, topic, bucket } = endpoint.eventTrigger.eventFilters;
|
|
89
|
+
const eventResource = resource || topic || bucket;
|
|
90
|
+
if (!eventResource) {
|
|
90
91
|
continue;
|
|
91
92
|
}
|
|
92
93
|
def.eventTrigger = {
|
|
93
94
|
eventType: eventTrigger.eventType,
|
|
94
|
-
resource:
|
|
95
|
+
resource: eventResource,
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
}
|
|
@@ -99,6 +100,12 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
99
100
|
def.eventTrigger = { eventType: "pubsub", resource: "" };
|
|
100
101
|
def.schedule = endpoint.scheduleTrigger;
|
|
101
102
|
}
|
|
103
|
+
else if (backend.isBlockingTriggered(endpoint)) {
|
|
104
|
+
def.blockingTrigger = {
|
|
105
|
+
eventType: endpoint.blockingTrigger.eventType,
|
|
106
|
+
options: endpoint.blockingTrigger.options || {},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
102
109
|
else {
|
|
103
110
|
}
|
|
104
111
|
regionDefinitions.push(def);
|
|
@@ -146,6 +153,9 @@ function getFunctionService(def) {
|
|
|
146
153
|
if (def.eventTrigger) {
|
|
147
154
|
return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
|
|
148
155
|
}
|
|
156
|
+
if (def.blockingTrigger) {
|
|
157
|
+
return def.blockingTrigger.eventType;
|
|
158
|
+
}
|
|
149
159
|
return "unknown";
|
|
150
160
|
}
|
|
151
161
|
exports.getFunctionService = getFunctionService;
|
|
@@ -244,7 +254,7 @@ function getSecretLocalPath(backend, projectDir) {
|
|
|
244
254
|
}
|
|
245
255
|
exports.getSecretLocalPath = getSecretLocalPath;
|
|
246
256
|
function toBackendInfo(e, cf3Triggers) {
|
|
247
|
-
var _a;
|
|
257
|
+
var _a, _b;
|
|
248
258
|
const envWithSecrets = Object.assign({}, e.env);
|
|
249
259
|
for (const s of e.secretEnv) {
|
|
250
260
|
envWithSecrets[s.key] = backend.secretVersionName(s);
|
|
@@ -252,10 +262,16 @@ function toBackendInfo(e, cf3Triggers) {
|
|
|
252
262
|
let extensionVersion = e.extensionVersion;
|
|
253
263
|
if (extensionVersion) {
|
|
254
264
|
extensionVersion = (0, extensionsHelper_1.substituteParams)(extensionVersion, e.env);
|
|
265
|
+
if ((_a = extensionVersion.spec) === null || _a === void 0 ? void 0 : _a.postinstallContent) {
|
|
266
|
+
extensionVersion.spec.postinstallContent = (0, postinstall_1.replaceConsoleLinks)(extensionVersion.spec.postinstallContent);
|
|
267
|
+
}
|
|
255
268
|
}
|
|
256
269
|
let extensionSpec = e.extensionSpec;
|
|
257
270
|
if (extensionSpec) {
|
|
258
271
|
extensionSpec = (0, extensionsHelper_1.substituteParams)(extensionSpec, e.env);
|
|
272
|
+
if (extensionSpec === null || extensionSpec === void 0 ? void 0 : extensionSpec.postinstallContent) {
|
|
273
|
+
extensionSpec.postinstallContent = (0, postinstall_1.replaceConsoleLinks)(extensionSpec.postinstallContent);
|
|
274
|
+
}
|
|
259
275
|
}
|
|
260
276
|
return JSON.parse(JSON.stringify({
|
|
261
277
|
directory: e.functionsDir,
|
|
@@ -264,7 +280,7 @@ function toBackendInfo(e, cf3Triggers) {
|
|
|
264
280
|
extension: e.extension,
|
|
265
281
|
extensionVersion: extensionVersion,
|
|
266
282
|
extensionSpec: extensionSpec,
|
|
267
|
-
functionTriggers: (
|
|
283
|
+
functionTriggers: (_b = e.predefinedTriggers) !== null && _b !== void 0 ? _b : cf3Triggers.filter((t) => t.codebase === e.codebase),
|
|
268
284
|
}));
|
|
269
285
|
}
|
|
270
286
|
exports.toBackendInfo = toBackendInfo;
|
|
@@ -7,9 +7,8 @@ const utils = require("../utils");
|
|
|
7
7
|
const logger_1 = require("../logger");
|
|
8
8
|
const error_1 = require("../error");
|
|
9
9
|
class FunctionsEmulatorShell {
|
|
10
|
-
constructor(emu
|
|
10
|
+
constructor(emu) {
|
|
11
11
|
this.emu = emu;
|
|
12
|
-
this.backend = backend;
|
|
13
12
|
this.urls = {};
|
|
14
13
|
this.triggers = emu.getTriggerDefinitions();
|
|
15
14
|
this.emulatedFunctions = this.triggers.map((t) => t.id);
|
|
@@ -42,7 +41,7 @@ class FunctionsEmulatorShell {
|
|
|
42
41
|
auth: opts.auth,
|
|
43
42
|
data,
|
|
44
43
|
};
|
|
45
|
-
this.emu.invokeTrigger(
|
|
44
|
+
this.emu.invokeTrigger(trigger, proto);
|
|
46
45
|
}
|
|
47
46
|
getTrigger(name) {
|
|
48
47
|
const result = this.triggers.find((trigger) => {
|
|
@@ -44,14 +44,7 @@ class PubsubEmulator {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return types_1.Emulators.PUBSUB;
|
|
46
46
|
}
|
|
47
|
-
async
|
|
48
|
-
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
|
|
49
|
-
const triggers = this.triggersForTopic.get(topicName) || [];
|
|
50
|
-
if (triggers.some((t) => t.triggerKey === triggerKey) &&
|
|
51
|
-
this.subscriptionForTopic.has(topicName)) {
|
|
52
|
-
this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
47
|
+
async maybeCreateTopicAndSub(topicName) {
|
|
55
48
|
const topic = this.pubsub.topic(topicName);
|
|
56
49
|
try {
|
|
57
50
|
this.logger.logLabeled("DEBUG", "pubsub", `Creating topic: ${topicName}`);
|
|
@@ -74,7 +67,7 @@ class PubsubEmulator {
|
|
|
74
67
|
catch (e) {
|
|
75
68
|
if (e && e.code === 6) {
|
|
76
69
|
this.logger.logLabeled("DEBUG", "pubsub", `Sub for ${topicName} exists`);
|
|
77
|
-
sub = topic.subscription(
|
|
70
|
+
sub = topic.subscription(subName);
|
|
78
71
|
}
|
|
79
72
|
else {
|
|
80
73
|
throw new error_1.FirebaseError(`Could not create sub ${subName}`, { original: e });
|
|
@@ -83,6 +76,17 @@ class PubsubEmulator {
|
|
|
83
76
|
sub.on("message", (message) => {
|
|
84
77
|
this.onMessage(topicName, message);
|
|
85
78
|
});
|
|
79
|
+
return sub;
|
|
80
|
+
}
|
|
81
|
+
async addTrigger(topicName, triggerKey, signatureType) {
|
|
82
|
+
this.logger.logLabeled("DEBUG", "pubsub", `addTrigger(${topicName}, ${triggerKey}, ${signatureType})`);
|
|
83
|
+
const sub = await this.maybeCreateTopicAndSub(topicName);
|
|
84
|
+
const triggers = this.triggersForTopic.get(topicName) || [];
|
|
85
|
+
if (triggers.some((t) => t.triggerKey === triggerKey) &&
|
|
86
|
+
this.subscriptionForTopic.has(topicName)) {
|
|
87
|
+
this.logger.logLabeled("DEBUG", "pubsub", "Trigger already exists");
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
86
90
|
triggers.push({ triggerKey, signatureType });
|
|
87
91
|
this.triggersForTopic.set(topicName, triggers);
|
|
88
92
|
this.subscriptionForTopic.set(topicName, sub);
|
package/lib/emulator/registry.js
CHANGED
|
@@ -14,11 +14,10 @@ class EmulatorRegistry {
|
|
|
14
14
|
}
|
|
15
15
|
this.set(instance.getName(), instance);
|
|
16
16
|
await instance.start();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.extensionsEmulatorRegistered = true;
|
|
17
|
+
if (instance.getName() !== types_1.Emulators.EXTENSIONS) {
|
|
18
|
+
const info = instance.getInfo();
|
|
19
|
+
await portUtils.waitForPortClosed(info.port, info.host);
|
|
20
|
+
}
|
|
22
21
|
}
|
|
23
22
|
static async stop(name) {
|
|
24
23
|
emulatorLogger_1.EmulatorLogger.forEmulator(name).logLabeled("BULLET", name, `Stopping ${constants_1.Constants.description(name)}`);
|
|
@@ -57,7 +56,7 @@ class EmulatorRegistry {
|
|
|
57
56
|
}
|
|
58
57
|
static isRunning(emulator) {
|
|
59
58
|
if (emulator === types_1.Emulators.EXTENSIONS) {
|
|
60
|
-
return this.
|
|
59
|
+
return this.INSTANCES.get(emulator) !== undefined && this.isRunning(types_1.Emulators.FUNCTIONS);
|
|
61
60
|
}
|
|
62
61
|
const instance = this.INSTANCES.get(emulator);
|
|
63
62
|
return instance !== undefined;
|
|
@@ -89,12 +88,36 @@ class EmulatorRegistry {
|
|
|
89
88
|
return `${host}:${port}`;
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
|
-
static
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
91
|
+
static url(emulator, req) {
|
|
92
|
+
const url = new URL("http://unknown/");
|
|
93
|
+
if (req) {
|
|
94
|
+
url.protocol = req.protocol;
|
|
95
|
+
const host = req.headers.host;
|
|
96
|
+
if (host) {
|
|
97
|
+
url.host = host;
|
|
98
|
+
return url;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const info = EmulatorRegistry.getInfo(emulator);
|
|
102
|
+
if (info) {
|
|
103
|
+
if (info.host === "0.0.0.0") {
|
|
104
|
+
url.hostname = "127.0.0.1";
|
|
105
|
+
}
|
|
106
|
+
else if (info.host === "::") {
|
|
107
|
+
url.hostname = "[::1]";
|
|
108
|
+
}
|
|
109
|
+
else if (info.host.includes(":")) {
|
|
110
|
+
url.hostname = `[${info.host}]`;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
url.hostname = info.host;
|
|
114
|
+
}
|
|
115
|
+
url.port = info.port.toString();
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.warn(`Cannot determine host and port of ${emulator}`);
|
|
96
119
|
}
|
|
97
|
-
return
|
|
120
|
+
return url;
|
|
98
121
|
}
|
|
99
122
|
static set(emulator, instance) {
|
|
100
123
|
this.INSTANCES.set(emulator, instance);
|
|
@@ -104,5 +127,4 @@ class EmulatorRegistry {
|
|
|
104
127
|
}
|
|
105
128
|
}
|
|
106
129
|
exports.EmulatorRegistry = EmulatorRegistry;
|
|
107
|
-
EmulatorRegistry.extensionsEmulatorRegistered = false;
|
|
108
130
|
EmulatorRegistry.INSTANCES = new Map();
|