firebase-tools 10.6.0 → 10.7.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/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 +2 -5
- 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 +17 -2
- package/lib/deploy/functions/build.js +162 -0
- package/lib/deploy/functions/checkIam.js +6 -5
- package/lib/deploy/functions/deploy.js +14 -15
- package/lib/deploy/functions/ensure.js +4 -4
- package/lib/deploy/functions/functionsDeployHelper.js +54 -23
- package/lib/deploy/functions/prepare.js +85 -42
- 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 +43 -2
- package/lib/deploy/functions/release/index.js +10 -6
- package/lib/deploy/functions/release/planner.js +9 -6
- 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 +50 -3
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +24 -5
- 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/validate.js +8 -5
- package/lib/deploy/hosting/args.js +2 -0
- package/lib/deploy/hosting/convertConfig.js +37 -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 +5 -5
- package/lib/emulator/downloadableEmulators.js +39 -23
- package/lib/emulator/extensions/validation.js +2 -2
- package/lib/emulator/extensionsEmulator.js +85 -21
- package/lib/emulator/functionsEmulator.js +79 -7
- package/lib/emulator/functionsEmulatorShared.js +20 -1
- package/lib/emulator/registry.js +34 -12
- package/lib/emulator/storage/apis/firebase.js +7 -2
- package/lib/emulator/storage/apis/gcloud.js +6 -3
- package/lib/emulator/storage/files.js +9 -1
- package/lib/ensureApiEnabled.js +8 -4
- package/lib/extensions/changelog.js +1 -1
- package/lib/extensions/emulator/optionsHelper.js +4 -3
- package/lib/extensions/emulator/specHelper.js +7 -1
- package/lib/extensions/extensionsHelper.js +30 -24
- package/lib/extensions/manifest.js +27 -7
- package/lib/extensions/paramHelper.js +5 -5
- package/lib/extensions/provisioningHelper.js +2 -2
- package/lib/extensions/warnings.js +3 -3
- package/lib/functions/events/index.js +7 -0
- package/lib/functions/events/v1.js +6 -0
- package/lib/functions/projectConfig.js +24 -3
- package/lib/gcp/cloudfunctions.js +31 -5
- package/lib/gcp/cloudfunctionsv2.js +27 -2
- package/lib/gcp/identityPlatform.js +44 -0
- package/lib/gcp/secretManager.js +1 -1
- package/lib/metaprogramming.js +2 -0
- package/lib/previews.js +1 -1
- package/lib/serve/hosting.js +25 -12
- package/lib/serve/index.js +6 -0
- package/lib/track.js +15 -21
- package/npm-shrinkwrap.json +44 -2
- package/package.json +4 -1
- package/schema/firebase-config.json +6 -0
|
@@ -6,7 +6,7 @@ const clc = require("cli-color");
|
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
|
-
const
|
|
9
|
+
const track_1 = require("../track");
|
|
10
10
|
const utils = require("../utils");
|
|
11
11
|
const registry_1 = require("./registry");
|
|
12
12
|
const types_1 = require("./types");
|
|
@@ -95,7 +95,7 @@ async function getAndCheckAddress(emulator, options) {
|
|
|
95
95
|
}
|
|
96
96
|
async function startEmulator(instance) {
|
|
97
97
|
const name = instance.getName();
|
|
98
|
-
void track("Emulator Run", name);
|
|
98
|
+
void (0, track_1.track)("Emulator Run", name);
|
|
99
99
|
await registry_1.EmulatorRegistry.start(instance);
|
|
100
100
|
}
|
|
101
101
|
exports.startEmulator = startEmulator;
|
|
@@ -248,7 +248,7 @@ async function startAll(options, showUI = true) {
|
|
|
248
248
|
if (shouldStart(options, types_1.Emulators.HUB)) {
|
|
249
249
|
const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
|
|
250
250
|
const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
|
|
251
|
-
void track("emulators:start", "hub");
|
|
251
|
+
void (0, track_1.track)("emulators:start", "hub");
|
|
252
252
|
await startEmulator(hub);
|
|
253
253
|
}
|
|
254
254
|
let exportMetadata = {
|
|
@@ -294,8 +294,8 @@ async function startAll(options, showUI = true) {
|
|
|
294
294
|
const extensionsBackends = await extensionEmulator.getExtensionBackends();
|
|
295
295
|
const filteredExtensionsBackends = extensionEmulator.filterUnemulatedTriggers(options, extensionsBackends);
|
|
296
296
|
emulatableBackends.push(...filteredExtensionsBackends);
|
|
297
|
-
void track("Emulator Run", types_1.Emulators.EXTENSIONS);
|
|
298
|
-
|
|
297
|
+
void (0, track_1.track)("Emulator Run", types_1.Emulators.EXTENSIONS);
|
|
298
|
+
await startEmulator(extensionEmulator);
|
|
299
299
|
}
|
|
300
300
|
if (emulatableBackends.length) {
|
|
301
301
|
const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
|
|
@@ -29,13 +29,13 @@ exports.DownloadDetails = {
|
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
31
|
firestore: {
|
|
32
|
-
downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.
|
|
33
|
-
version: "1.14.
|
|
32
|
+
downloadPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.14.3.jar"),
|
|
33
|
+
version: "1.14.3",
|
|
34
34
|
opts: {
|
|
35
35
|
cacheDir: CACHE_DIR,
|
|
36
|
-
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.
|
|
37
|
-
expectedSize:
|
|
38
|
-
expectedChecksum: "
|
|
36
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.14.3.jar",
|
|
37
|
+
expectedSize: 60442855,
|
|
38
|
+
expectedChecksum: "63517534875818689639ee5dee57dd52",
|
|
39
39
|
namePrefix: "cloud-firestore-emulator",
|
|
40
40
|
},
|
|
41
41
|
},
|
|
@@ -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.extensionsemulator
|
|
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: "EXTENSIONS",
|
|
56
|
+
downloadPath: path.join(CACHE_DIR, "ui-vEXTENSIONS.zip"),
|
|
57
|
+
unzipDir: path.join(CACHE_DIR, "ui-vEXTENSIONS"),
|
|
58
|
+
binaryPath: path.join(CACHE_DIR, "ui-vEXTENSIONS", "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-vEXTENSIONS.zip",
|
|
62
62
|
expectedSize: -1,
|
|
63
63
|
expectedChecksum: "",
|
|
64
64
|
skipCache: true,
|
|
@@ -66,19 +66,35 @@ exports.DownloadDetails = {
|
|
|
66
66
|
namePrefix: "ui",
|
|
67
67
|
},
|
|
68
68
|
}
|
|
69
|
-
:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
: previews_1.previews.emulatoruisnapshot
|
|
70
|
+
? {
|
|
71
|
+
version: "SNAPSHOT",
|
|
72
|
+
downloadPath: path.join(CACHE_DIR, "ui-vSNAPSHOT.zip"),
|
|
73
|
+
unzipDir: path.join(CACHE_DIR, "ui-vSNAPSHOT"),
|
|
74
|
+
binaryPath: path.join(CACHE_DIR, "ui-vSNAPSHOT", "server.bundle.js"),
|
|
75
|
+
opts: {
|
|
76
|
+
cacheDir: CACHE_DIR,
|
|
77
|
+
remoteUrl: "https://storage.googleapis.com/firebase-preview-drop/emulator/ui-vSNAPSHOT.zip",
|
|
78
|
+
expectedSize: -1,
|
|
79
|
+
expectedChecksum: "",
|
|
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
|
+
},
|
|
80
97
|
},
|
|
81
|
-
},
|
|
82
98
|
pubsub: {
|
|
83
99
|
downloadPath: path.join(CACHE_DIR, "pubsub-emulator-0.1.0.zip"),
|
|
84
100
|
version: "0.1.0",
|
|
@@ -17,8 +17,8 @@ async function getUnemulatedAPIs(projectId, instances) {
|
|
|
17
17
|
var _a;
|
|
18
18
|
const unemulatedAPIs = {};
|
|
19
19
|
for (const i of instances) {
|
|
20
|
-
const
|
|
21
|
-
for (const api of (_a =
|
|
20
|
+
const extensionSpec = await planner.getExtensionSpec(i);
|
|
21
|
+
for (const api of (_a = extensionSpec.apis) !== null && _a !== void 0 ? _a : []) {
|
|
22
22
|
if (!EMULATED_APIS.includes(api.apiName)) {
|
|
23
23
|
if (unemulatedAPIs[api.apiName]) {
|
|
24
24
|
unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
|
|
@@ -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}`;
|
|
@@ -229,6 +240,7 @@ class FunctionsEmulator {
|
|
|
229
240
|
loadTriggerPromises.push(this.loadTriggers(backend, true));
|
|
230
241
|
}
|
|
231
242
|
await Promise.all(loadTriggerPromises);
|
|
243
|
+
await this.performPostLoadOperations();
|
|
232
244
|
return;
|
|
233
245
|
}
|
|
234
246
|
async stop() {
|
|
@@ -271,6 +283,7 @@ class FunctionsEmulator {
|
|
|
271
283
|
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
272
284
|
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
285
|
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
286
|
+
(0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
|
|
274
287
|
triggerDefinitions = (0, functionsEmulatorShared_1.emulatedFunctionsFromEndpoints)(endpoints);
|
|
275
288
|
}
|
|
276
289
|
const toSetup = triggerDefinitions.filter((definition) => {
|
|
@@ -327,6 +340,11 @@ class FunctionsEmulator {
|
|
|
327
340
|
break;
|
|
328
341
|
}
|
|
329
342
|
}
|
|
343
|
+
else if (definition.blockingTrigger) {
|
|
344
|
+
const { host, port } = this.getInfo();
|
|
345
|
+
url = FunctionsEmulator.getHttpFunctionUrl(host, port, this.args.projectId, definition.name, definition.region);
|
|
346
|
+
added = this.addBlockingTrigger(url, definition.blockingTrigger);
|
|
347
|
+
}
|
|
330
348
|
else {
|
|
331
349
|
this.logger.log("WARN", `Unsupported function type on ${definition.name}. Expected either httpsTrigger or eventTrigger.`);
|
|
332
350
|
}
|
|
@@ -350,6 +368,34 @@ class FunctionsEmulator {
|
|
|
350
368
|
this.startRuntime(emulatableBackend, { nodeBinary: emulatableBackend.nodeBinary });
|
|
351
369
|
}
|
|
352
370
|
}
|
|
371
|
+
async performPostLoadOperations() {
|
|
372
|
+
var _a, _b, _c, _d;
|
|
373
|
+
if (((_b = (_a = this.blockingFunctionsConfig.triggers) === null || _a === void 0 ? void 0 : _a.beforeCreate) === null || _b === void 0 ? void 0 : _b.functionUri) === "" &&
|
|
374
|
+
((_d = (_c = this.blockingFunctionsConfig.triggers) === null || _c === void 0 ? void 0 : _c.beforeSignIn) === null || _d === void 0 ? void 0 : _d.functionUri) === "") {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const authEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.AUTH);
|
|
378
|
+
if (!authEmu) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const path = `/identitytoolkit.googleapis.com/v2/projects/${this.getProjectId()}/config?updateMask=blockingFunctions`;
|
|
382
|
+
try {
|
|
383
|
+
await api.request("PATCH", path, {
|
|
384
|
+
origin: `http://${registry_1.EmulatorRegistry.getInfoHostString(authEmu.getInfo())}`,
|
|
385
|
+
headers: {
|
|
386
|
+
Authorization: "Bearer owner",
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
blockingFunctions: this.blockingFunctionsConfig,
|
|
390
|
+
},
|
|
391
|
+
json: true,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
this.logger.log("WARN", "Error updating blocking functions config to the auth emulator: " + err);
|
|
396
|
+
throw err;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
353
399
|
addRealtimeDatabaseTrigger(projectId, key, eventTrigger) {
|
|
354
400
|
const databaseEmu = registry_1.EmulatorRegistry.get(types_1.Emulators.DATABASE);
|
|
355
401
|
if (!databaseEmu) {
|
|
@@ -416,11 +462,10 @@ class FunctionsEmulator {
|
|
|
416
462
|
});
|
|
417
463
|
}
|
|
418
464
|
async addPubsubTrigger(triggerName, key, eventTrigger, signatureType, schedule) {
|
|
419
|
-
const
|
|
420
|
-
if (!
|
|
465
|
+
const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
|
|
466
|
+
if (!pubsubEmulator) {
|
|
421
467
|
return false;
|
|
422
468
|
}
|
|
423
|
-
const pubsubEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.PUBSUB);
|
|
424
469
|
logger_1.logger.debug(`addPubsubTrigger`, JSON.stringify({ eventTrigger }));
|
|
425
470
|
const resource = eventTrigger.resource;
|
|
426
471
|
let topic;
|
|
@@ -458,6 +503,31 @@ class FunctionsEmulator {
|
|
|
458
503
|
this.multicastTriggers[eventTriggerId] = triggers;
|
|
459
504
|
return true;
|
|
460
505
|
}
|
|
506
|
+
addBlockingTrigger(url, blockingTrigger) {
|
|
507
|
+
logger_1.logger.debug(`addBlockingTrigger`, JSON.stringify({ blockingTrigger }));
|
|
508
|
+
const eventType = blockingTrigger.eventType;
|
|
509
|
+
if (v1_1.AUTH_BLOCKING_EVENTS.includes(eventType)) {
|
|
510
|
+
if (blockingTrigger.eventType === v1_1.BEFORE_CREATE_EVENT) {
|
|
511
|
+
this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeCreate: {
|
|
512
|
+
functionUri: url,
|
|
513
|
+
} });
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
this.blockingFunctionsConfig.triggers = Object.assign(Object.assign({}, this.blockingFunctionsConfig.triggers), { beforeSignIn: {
|
|
517
|
+
functionUri: url,
|
|
518
|
+
} });
|
|
519
|
+
}
|
|
520
|
+
this.blockingFunctionsConfig.forwardInboundCredentials = {
|
|
521
|
+
accessToken: blockingTrigger.options.accessToken,
|
|
522
|
+
idToken: blockingTrigger.options.idToken,
|
|
523
|
+
refreshToken: blockingTrigger.options.refreshToken,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
return false;
|
|
528
|
+
}
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
461
531
|
getProjectId() {
|
|
462
532
|
return this.args.projectId;
|
|
463
533
|
}
|
|
@@ -781,7 +851,9 @@ class FunctionsEmulator {
|
|
|
781
851
|
for (const backend of this.args.emulatableBackends) {
|
|
782
852
|
loadTriggerPromises.push(this.loadTriggers(backend));
|
|
783
853
|
}
|
|
784
|
-
|
|
854
|
+
await Promise.all(loadTriggerPromises);
|
|
855
|
+
await this.performPostLoadOperations();
|
|
856
|
+
return;
|
|
785
857
|
}
|
|
786
858
|
async handleBackgroundTrigger(projectId, triggerKey, proto) {
|
|
787
859
|
const record = this.getTriggerRecordByKey(triggerKey);
|
|
@@ -809,7 +881,7 @@ class FunctionsEmulator {
|
|
|
809
881
|
reject({ code: 500, body: el.text });
|
|
810
882
|
}
|
|
811
883
|
});
|
|
812
|
-
void track(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
|
|
884
|
+
void (0, track_1.track)(EVENT_INVOKE, (0, functionsEmulatorShared_1.getFunctionService)(trigger));
|
|
813
885
|
worker.waitForDone().then(() => {
|
|
814
886
|
resolve({ status: "acknowledged" });
|
|
815
887
|
});
|
|
@@ -884,7 +956,7 @@ class FunctionsEmulator {
|
|
|
884
956
|
}
|
|
885
957
|
});
|
|
886
958
|
await worker.waitForSocketReady();
|
|
887
|
-
void track(EVENT_INVOKE, "https");
|
|
959
|
+
void (0, track_1.track)(EVENT_INVOKE, "https");
|
|
888
960
|
this.logger.log("DEBUG", `[functions] Runtime ready! Sending request!`);
|
|
889
961
|
if (!worker.lastArgs) {
|
|
890
962
|
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");
|
|
@@ -11,6 +11,8 @@ const proto_1 = require("../gcp/proto");
|
|
|
11
11
|
const manifest_1 = require("../extensions/manifest");
|
|
12
12
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
13
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,
|
|
@@ -44,6 +46,14 @@ class EmulatedTrigger {
|
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
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;
|
|
47
57
|
function emulatedFunctionsFromEndpoints(endpoints) {
|
|
48
58
|
const regionDefinitions = [];
|
|
49
59
|
for (const endpoint of endpoints) {
|
|
@@ -89,6 +99,12 @@ function emulatedFunctionsFromEndpoints(endpoints) {
|
|
|
89
99
|
def.eventTrigger = { eventType: "pubsub", resource: "" };
|
|
90
100
|
def.schedule = endpoint.scheduleTrigger;
|
|
91
101
|
}
|
|
102
|
+
else if (backend.isBlockingTriggered(endpoint)) {
|
|
103
|
+
def.blockingTrigger = {
|
|
104
|
+
eventType: endpoint.blockingTrigger.eventType,
|
|
105
|
+
options: endpoint.blockingTrigger.options || {},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
92
108
|
else {
|
|
93
109
|
}
|
|
94
110
|
regionDefinitions.push(def);
|
|
@@ -136,6 +152,9 @@ function getFunctionService(def) {
|
|
|
136
152
|
if (def.eventTrigger) {
|
|
137
153
|
return (_a = def.eventTrigger.service) !== null && _a !== void 0 ? _a : getServiceFromEventType(def.eventTrigger.eventType);
|
|
138
154
|
}
|
|
155
|
+
if (def.blockingTrigger) {
|
|
156
|
+
return def.blockingTrigger.eventType;
|
|
157
|
+
}
|
|
139
158
|
return "unknown";
|
|
140
159
|
}
|
|
141
160
|
exports.getFunctionService = getFunctionService;
|
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();
|
|
@@ -219,9 +219,13 @@ function createFirebaseEndpoints(emulator) {
|
|
|
219
219
|
res.header("x-goog-upload-chunk-granularity", "10000");
|
|
220
220
|
res.header("x-goog-upload-control-url", "");
|
|
221
221
|
res.header("x-goog-upload-status", "active");
|
|
222
|
-
const emulatorInfo = registry_1.EmulatorRegistry.getInfo(types_1.Emulators.STORAGE);
|
|
223
|
-
res.header("x-goog-upload-url", `http://${req.hostname}:${emulatorInfo === null || emulatorInfo === void 0 ? void 0 : emulatorInfo.port}/v0/b/${bucketId}/o?name=${objectId}&upload_id=${upload.id}&upload_protocol=resumable`);
|
|
224
222
|
res.header("x-gupload-uploadid", upload.id);
|
|
223
|
+
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
224
|
+
uploadUrl.pathname = `/v0/b/${bucketId}/o`;
|
|
225
|
+
uploadUrl.searchParams.set("name", objectId);
|
|
226
|
+
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
227
|
+
uploadUrl.searchParams.set("upload_protocol", "resumable");
|
|
228
|
+
res.header("x-goog-upload-url", uploadUrl.toString());
|
|
225
229
|
return res.sendStatus(200);
|
|
226
230
|
}
|
|
227
231
|
if (!req.query.upload_id) {
|
|
@@ -306,6 +310,7 @@ function createFirebaseEndpoints(emulator) {
|
|
|
306
310
|
}
|
|
307
311
|
throw err;
|
|
308
312
|
}
|
|
313
|
+
res.header("x-goog-upload-status", "final");
|
|
309
314
|
storedMetadata.addDownloadToken(false);
|
|
310
315
|
return res.status(200).json(new metadata_1.OutgoingFirebaseMetadata(storedMetadata));
|
|
311
316
|
}
|
|
@@ -200,9 +200,12 @@ function createCloudEndpoints(emulator) {
|
|
|
200
200
|
metadataRaw: JSON.stringify(req.body),
|
|
201
201
|
authorization: req.header("authorization"),
|
|
202
202
|
});
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
204
|
+
uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`;
|
|
205
|
+
uploadUrl.searchParams.set("name", name);
|
|
206
|
+
uploadUrl.searchParams.set("uploadType", "resumable");
|
|
207
|
+
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
208
|
+
return res.header("location", uploadUrl.toString()).sendStatus(200);
|
|
206
209
|
}
|
|
207
210
|
let metadataRaw;
|
|
208
211
|
let dataRaw;
|
|
@@ -348,7 +348,11 @@ class StorageLayer {
|
|
|
348
348
|
logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
|
|
349
349
|
continue;
|
|
350
350
|
}
|
|
351
|
-
|
|
351
|
+
let decodedBlobPath = decodeURIComponent(blobPath);
|
|
352
|
+
const decodedBlobPathSep = getPathSep(decodedBlobPath);
|
|
353
|
+
if (decodedBlobPathSep !== path.sep) {
|
|
354
|
+
decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
|
|
355
|
+
}
|
|
352
356
|
const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
|
|
353
357
|
const file = new StoredFile(metadata, blobDiskPath);
|
|
354
358
|
this._files.set(decodedBlobPath, file);
|
|
@@ -369,3 +373,7 @@ class StorageLayer {
|
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
375
|
exports.StorageLayer = StorageLayer;
|
|
376
|
+
function getPathSep(decodedPath) {
|
|
377
|
+
const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
|
|
378
|
+
return decodedPath[firstSepIndex];
|
|
379
|
+
}
|