firebase-tools 10.2.1 → 10.2.2
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/appdistribution/options-parser-util.js +1 -1
- package/lib/auth.js +3 -3
- package/lib/command.js +1 -1
- package/lib/commands/apps-android-sha-create.js +2 -2
- package/lib/commands/apps-sdkconfig.js +1 -1
- package/lib/commands/database-rules-list.js +2 -2
- package/lib/commands/emulators-start.js +1 -1
- package/lib/commands/ext-dev-init.js +49 -49
- package/lib/commands/ext-export.js +12 -2
- package/lib/commands/ext-install.js +104 -104
- package/lib/commands/ext-uninstall.js +8 -8
- package/lib/commands/ext-update.js +9 -9
- package/lib/commands/functions-config-export.js +1 -1
- package/lib/commands/hosting-clone.js +3 -3
- package/lib/commands/remoteconfig-get.js +1 -1
- package/lib/deploy/extensions/deploymentSummary.js +3 -3
- package/lib/deploy/extensions/params.js +3 -0
- package/lib/deploy/extensions/planner.js +2 -1
- package/lib/deploy/extensions/tasks.js +1 -1
- package/lib/deploy/functions/backend.js +12 -5
- package/lib/deploy/functions/checkIam.js +1 -1
- package/lib/deploy/functions/containerCleaner.js +3 -3
- package/lib/deploy/functions/ensure.js +3 -3
- package/lib/deploy/functions/functionsDeployHelper.js +2 -2
- package/lib/deploy/functions/prepare.js +3 -2
- package/lib/deploy/functions/pricing.js +1 -1
- package/lib/deploy/functions/prompts.js +2 -2
- package/lib/deploy/functions/release/fabricator.js +3 -3
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/release/planner.js +11 -8
- package/lib/deploy/functions/release/reporter.js +3 -0
- package/lib/deploy/functions/runtimes/discovery/index.js +6 -6
- package/lib/deploy/functions/runtimes/discovery/parsing.js +1 -1
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +16 -11
- package/lib/deploy/functions/runtimes/golang/index.js +2 -2
- package/lib/deploy/functions/runtimes/node/index.js +26 -0
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +2 -2
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +28 -7
- package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
- package/lib/deploy/functions/validate.js +3 -3
- package/lib/deploy/hosting/deploy.js +2 -2
- package/lib/deploy/hosting/hashcache.js +21 -19
- package/lib/deploy/hosting/uploader.js +5 -5
- package/lib/deploy/remoteconfig/functions.js +2 -2
- package/lib/emulator/auth/cloudFunctions.js +1 -1
- package/lib/emulator/auth/operations.js +1 -1
- package/lib/emulator/constants.js +3 -0
- package/lib/emulator/controller.js +47 -19
- package/lib/emulator/download.js +18 -1
- package/lib/emulator/downloadableEmulators.js +1 -1
- package/lib/emulator/emulatorLogger.js +12 -1
- package/lib/emulator/extensions/validation.js +35 -0
- package/lib/emulator/extensionsEmulator.js +140 -0
- package/lib/emulator/functionsEmulator.js +86 -39
- package/lib/emulator/functionsEmulatorRuntime.js +44 -36
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/functionsEmulatorUtils.js +4 -4
- package/lib/emulator/functionsRuntimeWorker.js +2 -2
- package/lib/emulator/hub.js +4 -3
- package/lib/emulator/loggingEmulator.js +1 -1
- package/lib/emulator/pubsubEmulator.js +1 -1
- package/lib/emulator/registry.js +10 -2
- package/lib/emulator/storage/apis/firebase.js +31 -26
- package/lib/emulator/storage/apis/gcloud.js +7 -12
- package/lib/emulator/storage/files.js +36 -34
- package/lib/emulator/storage/index.js +2 -2
- package/lib/emulator/storage/metadata.js +2 -2
- package/lib/emulator/storage/rules/runtime.js +8 -7
- package/lib/emulator/types.js +3 -0
- package/lib/ensureApiEnabled.js +5 -1
- package/lib/error.js +1 -1
- package/lib/extensions/askUserForParam.js +1 -1
- package/lib/extensions/changelog.js +3 -1
- package/lib/extensions/checkProjectBilling.js +1 -1
- package/lib/extensions/displayExtensionInfo.js +1 -1
- package/lib/extensions/emulator/optionsHelper.js +24 -8
- package/lib/extensions/emulator/specHelper.js +10 -23
- package/lib/extensions/export.js +1 -51
- package/lib/extensions/extensionsApi.js +1 -1
- package/lib/extensions/extensionsHelper.js +13 -9
- package/lib/extensions/manifest.js +48 -0
- package/lib/extensions/metricsUtils.js +4 -4
- package/lib/extensions/paramHelper.js +4 -4
- package/lib/extensions/refs.js +1 -1
- package/lib/extensions/secretsUtils.js +3 -3
- package/lib/functional.js +1 -1
- package/lib/functions/env.js +2 -1
- package/lib/gcp/cloudfunctions.js +24 -5
- package/lib/gcp/cloudfunctionsv2.js +18 -5
- package/lib/gcp/cloudtasks.js +1 -1
- package/lib/gcp/docker.js +2 -2
- package/lib/gcp/run.js +2 -2
- package/lib/hosting/api.js +1 -1
- package/lib/hosting/proxy.js +2 -2
- package/lib/init/features/account.js +1 -1
- package/lib/management/database.js +1 -1
- package/lib/previews.js +1 -1
- package/lib/utils.js +1 -1
- package/npm-shrinkwrap.json +786 -393
- package/package.json +1 -1
- package/schema/firebase-config.json +5 -0
- package/templates/init/functions/javascript/package.lint.json +3 -3
- package/templates/init/functions/javascript/package.nolint.json +2 -2
- package/templates/init/functions/typescript/package.lint.json +7 -7
- package/templates/init/functions/typescript/package.nolint.json +3 -3
|
@@ -14,7 +14,7 @@ function endpointsAreValid(wantBackend) {
|
|
|
14
14
|
functionIdsAreValid(backend.allEndpoints(wantBackend));
|
|
15
15
|
const gcfV1WithConcurrency = backend
|
|
16
16
|
.allEndpoints(wantBackend)
|
|
17
|
-
.filter((endpoint) => (endpoint.concurrency || 1)
|
|
17
|
+
.filter((endpoint) => (endpoint.concurrency || 1) !== 1 && endpoint.platform === "gcfv1")
|
|
18
18
|
.map((endpoint) => endpoint.id);
|
|
19
19
|
if (gcfV1WithConcurrency.length) {
|
|
20
20
|
const msg = `Cannot set concurrency on the functions ${gcfV1WithConcurrency.join(",")} because they are GCF gen 1`;
|
|
@@ -23,7 +23,7 @@ function endpointsAreValid(wantBackend) {
|
|
|
23
23
|
const tooSmallForConcurrency = backend
|
|
24
24
|
.allEndpoints(wantBackend)
|
|
25
25
|
.filter((endpoint) => {
|
|
26
|
-
if ((endpoint.concurrency || 1)
|
|
26
|
+
if ((endpoint.concurrency || 1) === 1) {
|
|
27
27
|
return false;
|
|
28
28
|
}
|
|
29
29
|
const mem = endpoint.availableMemoryMb || backend.DEFAULT_MEMORY;
|
|
@@ -98,7 +98,7 @@ async function validateSecretVersions(projectId, endpoints) {
|
|
|
98
98
|
for (const result of results) {
|
|
99
99
|
if (result.status === "fulfilled") {
|
|
100
100
|
const sv = result.value;
|
|
101
|
-
if (sv.state
|
|
101
|
+
if (sv.state !== "ENABLED") {
|
|
102
102
|
errs.push(new error_1.FirebaseError(`Expected secret ${sv.secret.name}@${sv.versionId} to be in state ENABLED not ${sv.state}.`));
|
|
103
103
|
}
|
|
104
104
|
secretVersions[sv.secret.name] = sv;
|
|
@@ -53,7 +53,7 @@ async function deploy(context, options) {
|
|
|
53
53
|
await uploader.start();
|
|
54
54
|
}
|
|
55
55
|
catch (err) {
|
|
56
|
-
track("Hosting Deploy", "failure");
|
|
56
|
+
void track("Hosting Deploy", "failure");
|
|
57
57
|
throw err;
|
|
58
58
|
}
|
|
59
59
|
finally {
|
|
@@ -65,7 +65,7 @@ async function deploy(context, options) {
|
|
|
65
65
|
(0, utils_1.logLabeledSuccess)("hosting[" + deploy.site + "]", "file upload complete");
|
|
66
66
|
const dt = Date.now() - t0;
|
|
67
67
|
logger_1.logger.debug("[hosting] deploy completed after " + dt + "ms");
|
|
68
|
-
track("Hosting Deploy", "success", dt);
|
|
68
|
+
void track("Hosting Deploy", "success", dt);
|
|
69
69
|
return runDeploys(deploys, debugging);
|
|
70
70
|
}
|
|
71
71
|
const debugging = !!(options.debug || options.nonInteractive);
|
|
@@ -1,46 +1,48 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dump = exports.load = void 0;
|
|
2
4
|
const fs = require("fs-extra");
|
|
3
5
|
const path = require("path");
|
|
4
|
-
const
|
|
6
|
+
const logger_1 = require("../../logger");
|
|
5
7
|
function cachePath(cwd, name) {
|
|
6
|
-
return path.resolve(cwd,
|
|
8
|
+
return path.resolve(cwd, `.firebase/hosting.${name}.cache`);
|
|
7
9
|
}
|
|
8
|
-
|
|
10
|
+
function load(cwd, name) {
|
|
9
11
|
try {
|
|
10
|
-
const out =
|
|
11
|
-
const lines = fs.readFileSync(cachePath(cwd, name),
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
lines.split("\n").forEach(function (line) {
|
|
12
|
+
const out = new Map();
|
|
13
|
+
const lines = fs.readFileSync(cachePath(cwd, name), "utf8");
|
|
14
|
+
for (const line of lines.split("\n")) {
|
|
15
15
|
const d = line.split(",");
|
|
16
16
|
if (d.length === 3) {
|
|
17
|
-
out
|
|
17
|
+
out.set(d[0], { mtime: parseInt(d[1]), hash: d[2] });
|
|
18
18
|
}
|
|
19
|
-
}
|
|
19
|
+
}
|
|
20
20
|
return out;
|
|
21
21
|
}
|
|
22
22
|
catch (e) {
|
|
23
23
|
if (e.code === "ENOENT") {
|
|
24
|
-
logger.debug(
|
|
24
|
+
logger_1.logger.debug(`[hosting] hash cache [${name}] not populated`);
|
|
25
25
|
}
|
|
26
26
|
else {
|
|
27
|
-
logger.debug(
|
|
27
|
+
logger_1.logger.debug(`[hosting] hash cache [${name}] load error: ${e.message}`);
|
|
28
28
|
}
|
|
29
|
-
return
|
|
29
|
+
return new Map();
|
|
30
30
|
}
|
|
31
|
-
}
|
|
32
|
-
exports.
|
|
31
|
+
}
|
|
32
|
+
exports.load = load;
|
|
33
|
+
function dump(cwd, name, data) {
|
|
33
34
|
let st = "";
|
|
34
35
|
let count = 0;
|
|
35
36
|
for (const [path, d] of data) {
|
|
36
37
|
count++;
|
|
37
|
-
st += path
|
|
38
|
+
st += `${path},${d.mtime},${d.hash}\n`;
|
|
38
39
|
}
|
|
39
40
|
try {
|
|
40
41
|
fs.outputFileSync(cachePath(cwd, name), st, { encoding: "utf8" });
|
|
41
|
-
logger.debug(
|
|
42
|
+
logger_1.logger.debug(`[hosting] hash cache [${name}] stored for ${count} files`);
|
|
42
43
|
}
|
|
43
44
|
catch (e) {
|
|
44
|
-
logger.debug(
|
|
45
|
+
logger_1.logger.debug(`[hosting] unable to store hash cache [${name}]: ${e.stack}`);
|
|
45
46
|
}
|
|
46
|
-
}
|
|
47
|
+
}
|
|
48
|
+
exports.dump = dump;
|
|
@@ -11,7 +11,7 @@ const zlib = require("zlib");
|
|
|
11
11
|
const apiv2_1 = require("../../apiv2");
|
|
12
12
|
const queue_1 = require("../../throttler/queue");
|
|
13
13
|
const api_1 = require("../../api");
|
|
14
|
-
const
|
|
14
|
+
const hashcache_1 = require("./hashcache");
|
|
15
15
|
const logger_1 = require("../../logger");
|
|
16
16
|
const error_1 = require("../../error");
|
|
17
17
|
const MIN_UPLOAD_TIMEOUT = 30000;
|
|
@@ -54,7 +54,7 @@ class Uploader {
|
|
|
54
54
|
this.public = options.public || this.cwd;
|
|
55
55
|
this.files = options.files;
|
|
56
56
|
this.fileCount = this.files.length;
|
|
57
|
-
this.cache =
|
|
57
|
+
this.cache = (0, hashcache_1.load)(this.projectRoot, this.hashcacheName());
|
|
58
58
|
this.cacheNew = new Map();
|
|
59
59
|
this.sizeMap = {};
|
|
60
60
|
this.hashMap = {};
|
|
@@ -78,7 +78,7 @@ class Uploader {
|
|
|
78
78
|
.wait()
|
|
79
79
|
.then(this.queuePopulate.bind(this))
|
|
80
80
|
.then(() => {
|
|
81
|
-
|
|
81
|
+
(0, hashcache_1.dump)(this.projectRoot, this.hashcacheName(), this.cacheNew);
|
|
82
82
|
logger_1.logger.debug("[hosting][hash queue][FINAL]", this.hashQueue.stats());
|
|
83
83
|
this.populateQueue.close();
|
|
84
84
|
return this.populateQueue.wait();
|
|
@@ -91,7 +91,7 @@ class Uploader {
|
|
|
91
91
|
this.uploadQueue.wait().catch((err) => {
|
|
92
92
|
if (err.message.includes("content hash")) {
|
|
93
93
|
logger_1.logger.debug("[hosting][upload queue] upload failed with content hash error. Deleting hash cache");
|
|
94
|
-
|
|
94
|
+
(0, hashcache_1.dump)(this.projectRoot, this.hashcacheName(), new Map());
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
const fin = (err) => {
|
|
@@ -123,7 +123,7 @@ class Uploader {
|
|
|
123
123
|
const stats = fs.statSync(path.resolve(this.public, filePath));
|
|
124
124
|
const mtime = stats.mtime.getTime();
|
|
125
125
|
this.sizeMap[filePath] = stats.size;
|
|
126
|
-
const cached = this.cache
|
|
126
|
+
const cached = this.cache.get(filePath);
|
|
127
127
|
if (cached && cached.mtime === mtime) {
|
|
128
128
|
this.cacheNew.set(filePath, cached);
|
|
129
129
|
this.addHash(filePath, cached.hash);
|
|
@@ -20,10 +20,10 @@ async function getEtag(projectNumber, versionNumber) {
|
|
|
20
20
|
exports.getEtag = getEtag;
|
|
21
21
|
function validateInputRemoteConfigTemplate(template) {
|
|
22
22
|
const templateCopy = JSON.parse(JSON.stringify(template));
|
|
23
|
-
if (!templateCopy || templateCopy
|
|
23
|
+
if (!templateCopy || templateCopy === "null" || templateCopy === "undefined") {
|
|
24
24
|
throw new error_1.FirebaseError(`Invalid Remote Config template: ${JSON.stringify(templateCopy)}`);
|
|
25
25
|
}
|
|
26
|
-
if (typeof templateCopy.etag !== "string" || templateCopy.etag
|
|
26
|
+
if (typeof templateCopy.etag !== "string" || templateCopy.etag === "") {
|
|
27
27
|
throw new error_1.FirebaseError("ETag must be a non-empty string");
|
|
28
28
|
}
|
|
29
29
|
if (templateCopy.conditions && !Array.isArray(templateCopy.conditions)) {
|
|
@@ -35,7 +35,7 @@ class AuthCloudFunction {
|
|
|
35
35
|
catch (e) {
|
|
36
36
|
err = e;
|
|
37
37
|
}
|
|
38
|
-
if (err || (res === null || res === void 0 ? void 0 : res.status)
|
|
38
|
+
if (err || (res === null || res === void 0 ? void 0 : res.status) !== 200) {
|
|
39
39
|
this.logger.logLabeled("WARN", "functions", `Firebase Authentication function was not triggered due to emulation error. Please file a bug.`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -1378,7 +1378,7 @@ function mfaSignInFinalize(state, reqBody) {
|
|
|
1378
1378
|
(0, errors_1.assert)(sessionInfo, "MISSING_SESSION_INFO");
|
|
1379
1379
|
const phoneNumber = verifyPhoneNumber(state, sessionInfo, code);
|
|
1380
1380
|
let { user, signInProvider } = parsePendingCredential(state, reqBody.mfaPendingCredential);
|
|
1381
|
-
const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo
|
|
1381
|
+
const enrollment = (_b = user.mfaInfo) === null || _b === void 0 ? void 0 : _b.find((enrollment) => enrollment.unobfuscatedPhoneInfo === phoneNumber);
|
|
1382
1382
|
(0, errors_1.assert)(enrollment && enrollment.mfaEnrollmentId, "MFA_ENROLLMENT_NOT_FOUND");
|
|
1383
1383
|
user = state.updateUserByLocalId(user.localId, { lastLoginAt: Date.now().toString() });
|
|
1384
1384
|
(0, errors_1.assert)(!user.disabled, "USER_DISABLED");
|
|
@@ -8,6 +8,7 @@ const DEFAULT_PORTS = {
|
|
|
8
8
|
logging: 4500,
|
|
9
9
|
hosting: 5000,
|
|
10
10
|
functions: 5001,
|
|
11
|
+
extensions: 5001,
|
|
11
12
|
firestore: 8080,
|
|
12
13
|
pubsub: 8085,
|
|
13
14
|
database: 9000,
|
|
@@ -25,6 +26,7 @@ exports.FIND_AVAILBLE_PORT_BY_DEFAULT = {
|
|
|
25
26
|
pubsub: false,
|
|
26
27
|
auth: false,
|
|
27
28
|
storage: false,
|
|
29
|
+
extensions: false,
|
|
28
30
|
};
|
|
29
31
|
exports.EMULATOR_DESCRIPTION = {
|
|
30
32
|
ui: "Emulator UI",
|
|
@@ -37,6 +39,7 @@ exports.EMULATOR_DESCRIPTION = {
|
|
|
37
39
|
pubsub: "Pub/Sub Emulator",
|
|
38
40
|
auth: "Authentication Emulator",
|
|
39
41
|
storage: "Storage Emulator",
|
|
42
|
+
extensions: "Extensions Emulator",
|
|
40
43
|
};
|
|
41
44
|
const DEFAULT_HOST = "localhost";
|
|
42
45
|
class Constants {
|
|
@@ -35,8 +35,14 @@ const fsutils_1 = require("../fsutils");
|
|
|
35
35
|
const storage_1 = require("./storage");
|
|
36
36
|
const getDefaultDatabaseInstance_1 = require("../getDefaultDatabaseInstance");
|
|
37
37
|
const auth_2 = require("../auth");
|
|
38
|
+
const extensionsEmulator_1 = require("./extensionsEmulator");
|
|
39
|
+
const previews_1 = require("../previews");
|
|
40
|
+
const START_LOGGING_EMULATOR = utils.envOverride("START_LOGGING_EMULATOR", "false", (val) => val === "true");
|
|
38
41
|
async function getAndCheckAddress(emulator, options) {
|
|
39
42
|
var _a, _b, _c, _d;
|
|
43
|
+
if (emulator === types_1.Emulators.EXTENSIONS) {
|
|
44
|
+
emulator = types_1.Emulators.FUNCTIONS;
|
|
45
|
+
}
|
|
40
46
|
let host = ((_b = (_a = options.config.src.emulators) === null || _a === void 0 ? void 0 : _a[emulator]) === null || _b === void 0 ? void 0 : _b.host) || constants_1.Constants.getDefaultHost(emulator);
|
|
41
47
|
if (host === "localhost" && utils.isRunningInWSL()) {
|
|
42
48
|
host = "127.0.0.1";
|
|
@@ -56,7 +62,7 @@ async function getAndCheckAddress(emulator, options) {
|
|
|
56
62
|
if (!portOpen) {
|
|
57
63
|
if (findAvailablePort) {
|
|
58
64
|
const newPort = await portUtils.findAvailablePort(host, port);
|
|
59
|
-
if (newPort
|
|
65
|
+
if (newPort !== port) {
|
|
60
66
|
loggerForEmulator.logLabeled("WARN", emulator, `${constants_1.Constants.description(emulator)} unable to start on port ${port}, starting on ${newPort} instead.`);
|
|
61
67
|
port = newPort;
|
|
62
68
|
}
|
|
@@ -86,7 +92,7 @@ async function getAndCheckAddress(emulator, options) {
|
|
|
86
92
|
}
|
|
87
93
|
async function startEmulator(instance) {
|
|
88
94
|
const name = instance.getName();
|
|
89
|
-
track("Emulator Run", name);
|
|
95
|
+
void track("Emulator Run", name);
|
|
90
96
|
await registry_1.EmulatorRegistry.start(instance);
|
|
91
97
|
}
|
|
92
98
|
exports.startEmulator = startEmulator;
|
|
@@ -115,7 +121,11 @@ async function cleanShutdown() {
|
|
|
115
121
|
}
|
|
116
122
|
exports.cleanShutdown = cleanShutdown;
|
|
117
123
|
function filterEmulatorTargets(options) {
|
|
118
|
-
let targets = types_1.ALL_SERVICE_EMULATORS
|
|
124
|
+
let targets = [...types_1.ALL_SERVICE_EMULATORS];
|
|
125
|
+
if (previews_1.previews.extensionsemulator) {
|
|
126
|
+
targets.push(types_1.Emulators.EXTENSIONS);
|
|
127
|
+
}
|
|
128
|
+
targets = targets.filter((e) => {
|
|
119
129
|
return options.config.has(e) || options.config.has(`emulators.${e}`);
|
|
120
130
|
});
|
|
121
131
|
const onlyOptions = options.only;
|
|
@@ -222,7 +232,7 @@ async function startAll(options, showUI = true) {
|
|
|
222
232
|
if (shouldStart(options, types_1.Emulators.HUB)) {
|
|
223
233
|
const hubAddr = await getAndCheckAddress(types_1.Emulators.HUB, options);
|
|
224
234
|
const hub = new hub_1.EmulatorHub(Object.assign({ projectId }, hubAddr));
|
|
225
|
-
track("emulators:start", "hub");
|
|
235
|
+
void track("emulators:start", "hub");
|
|
226
236
|
await startEmulator(hub);
|
|
227
237
|
}
|
|
228
238
|
let exportMetadata = {
|
|
@@ -239,19 +249,43 @@ async function startAll(options, showUI = true) {
|
|
|
239
249
|
hubLogger.logLabeled("WARN", "emulators", `Could not find import/export metadata file, ${clc.bold("skipping data import!")}`);
|
|
240
250
|
}
|
|
241
251
|
}
|
|
252
|
+
const emulatableBackends = [];
|
|
253
|
+
const projectDir = (options.extDevDir || options.config.projectDir);
|
|
242
254
|
if (shouldStart(options, types_1.Emulators.FUNCTIONS)) {
|
|
243
|
-
const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
|
|
244
|
-
const functionsAddr = await getAndCheckAddress(types_1.Emulators.FUNCTIONS, options);
|
|
245
|
-
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
246
255
|
utils.assertDefined(options.config.src.functions);
|
|
247
256
|
utils.assertDefined(options.config.src.functions.source, "Error: 'functions.source' is not defined");
|
|
248
|
-
utils.assertIsStringOrUndefined(options.
|
|
249
|
-
const projectDir = options.extensionDir || options.config.projectDir;
|
|
257
|
+
utils.assertIsStringOrUndefined(options.extDevDir);
|
|
250
258
|
const functionsDir = path.join(projectDir, options.config.src.functions.source);
|
|
259
|
+
emulatableBackends.push({
|
|
260
|
+
functionsDir,
|
|
261
|
+
env: Object.assign({}, options.extDevEnv),
|
|
262
|
+
predefinedTriggers: options.extDevTriggers,
|
|
263
|
+
nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extDevNodeVersion || options.config.get("functions.runtime")),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
if (shouldStart(options, types_1.Emulators.EXTENSIONS) && previews_1.previews.extensionsemulator) {
|
|
267
|
+
const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
|
|
268
|
+
const aliases = (0, projectUtils_1.getAliases)(options, projectId);
|
|
269
|
+
const extensionEmulator = new extensionsEmulator_1.ExtensionsEmulator({
|
|
270
|
+
projectId,
|
|
271
|
+
projectDir: options.config.projectDir,
|
|
272
|
+
projectNumber,
|
|
273
|
+
aliases,
|
|
274
|
+
extensions: options.config.get("extensions"),
|
|
275
|
+
});
|
|
276
|
+
const extensionsBackends = await extensionEmulator.getExtensionBackends();
|
|
277
|
+
emulatableBackends.push(...extensionsBackends);
|
|
278
|
+
void track("Emulator Run", types_1.Emulators.EXTENSIONS);
|
|
279
|
+
registry_1.EmulatorRegistry.registerExtensionsEmulator();
|
|
280
|
+
}
|
|
281
|
+
if (emulatableBackends.length) {
|
|
282
|
+
const functionsLogger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS);
|
|
283
|
+
const functionsAddr = await getAndCheckAddress(types_1.Emulators.FUNCTIONS, options);
|
|
284
|
+
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
251
285
|
let inspectFunctions;
|
|
252
286
|
if (options.inspectFunctions) {
|
|
253
287
|
inspectFunctions = commandUtils.parseInspectionPort(options);
|
|
254
|
-
functionsLogger.logLabeled("WARN", "functions", `You are running the
|
|
288
|
+
functionsLogger.logLabeled("WARN", "functions", `You are running the Functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.`);
|
|
255
289
|
}
|
|
256
290
|
const emulatorsNotRunning = types_1.ALL_SERVICE_EMULATORS.filter((e) => {
|
|
257
291
|
return e !== types_1.Emulators.FUNCTIONS && !shouldStart(options, e);
|
|
@@ -260,14 +294,6 @@ async function startAll(options, showUI = true) {
|
|
|
260
294
|
functionsLogger.logLabeled("WARN", "functions", `The following emulators are not running, calls to these services from the Functions emulator will affect production: ${clc.bold(emulatorsNotRunning.join(", "))}`);
|
|
261
295
|
}
|
|
262
296
|
const account = (0, auth_2.getProjectDefaultAccount)(options.projectRoot);
|
|
263
|
-
const emulatableBackends = [
|
|
264
|
-
{
|
|
265
|
-
functionsDir,
|
|
266
|
-
env: Object.assign({}, options.extensionEnv),
|
|
267
|
-
predefinedTriggers: options.extensionTriggers,
|
|
268
|
-
nodeMajorVersion: (0, functionsEmulatorUtils_1.parseRuntimeVersion)(options.extensionNodeVersion || options.config.get("functions.runtime")),
|
|
269
|
-
},
|
|
270
|
-
];
|
|
271
297
|
const functionsEmulator = new functionsEmulator_1.FunctionsEmulator({
|
|
272
298
|
projectId,
|
|
273
299
|
projectDir,
|
|
@@ -425,13 +451,15 @@ async function startAll(options, showUI = true) {
|
|
|
425
451
|
if (showUI && !shouldStart(options, types_1.Emulators.UI)) {
|
|
426
452
|
hubLogger.logLabeled("WARN", "emulators", "The Emulator UI requires a project ID to start. Configure your default project with 'firebase use' or pass the --project flag.");
|
|
427
453
|
}
|
|
428
|
-
if (showUI && shouldStart(options, types_1.Emulators.UI)) {
|
|
454
|
+
if (showUI && (shouldStart(options, types_1.Emulators.UI) || START_LOGGING_EMULATOR)) {
|
|
429
455
|
const loggingAddr = await getAndCheckAddress(types_1.Emulators.LOGGING, options);
|
|
430
456
|
const loggingEmulator = new loggingEmulator_1.LoggingEmulator({
|
|
431
457
|
host: loggingAddr.host,
|
|
432
458
|
port: loggingAddr.port,
|
|
433
459
|
});
|
|
434
460
|
await startEmulator(loggingEmulator);
|
|
461
|
+
}
|
|
462
|
+
if (showUI && shouldStart(options, types_1.Emulators.UI)) {
|
|
435
463
|
const uiAddr = await getAndCheckAddress(types_1.Emulators.UI, options);
|
|
436
464
|
const ui = new ui_1.EmulatorUI(Object.assign({ projectId: projectId, auto_download: true }, uiAddr));
|
|
437
465
|
await startEmulator(ui);
|
package/lib/emulator/download.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.downloadEmulator = void 0;
|
|
3
|
+
exports.downloadExtensionVersion = exports.downloadEmulator = void 0;
|
|
4
4
|
const crypto = require("crypto");
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const path = require("path");
|
|
@@ -32,6 +32,23 @@ async function downloadEmulator(name) {
|
|
|
32
32
|
removeOldFiles(name, emulator);
|
|
33
33
|
}
|
|
34
34
|
exports.downloadEmulator = downloadEmulator;
|
|
35
|
+
async function downloadExtensionVersion(extensionVersionRef, sourceDownloadUri, targetDir) {
|
|
36
|
+
const emulatorLogger = emulatorLogger_1.EmulatorLogger.forExtension({ ref: extensionVersionRef });
|
|
37
|
+
emulatorLogger.logLabeled("BULLET", "extensions", `Starting download for ${extensionVersionRef} source code...`);
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(targetDir);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
emulatorLogger.logLabeled("BULLET", "extensions", `cache directory for ${extensionVersionRef} already exists...`);
|
|
43
|
+
}
|
|
44
|
+
emulatorLogger.logLabeled("BULLET", "extensions", `downloading ${sourceDownloadUri}...`);
|
|
45
|
+
const sourceCodeZip = await downloadUtils.downloadToTmp(sourceDownloadUri);
|
|
46
|
+
await unzip(sourceCodeZip, targetDir);
|
|
47
|
+
fs.chmodSync(targetDir, 0o755);
|
|
48
|
+
emulatorLogger.logLabeled("BULLET", "extensions", `Downloaded to ${targetDir}...`);
|
|
49
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
50
|
+
}
|
|
51
|
+
exports.downloadExtensionVersion = downloadExtensionVersion;
|
|
35
52
|
function unzip(zipPath, unzipDir) {
|
|
36
53
|
return new Promise((resolve, reject) => {
|
|
37
54
|
fs.createReadStream(zipPath)
|
|
@@ -34,7 +34,7 @@ class EmulatorLogger {
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
static forFunction(functionName) {
|
|
37
|
+
static forFunction(functionName, extensionLogInfo) {
|
|
38
38
|
return new EmulatorLogger({
|
|
39
39
|
metadata: {
|
|
40
40
|
emulator: {
|
|
@@ -43,6 +43,17 @@ class EmulatorLogger {
|
|
|
43
43
|
function: {
|
|
44
44
|
name: functionName,
|
|
45
45
|
},
|
|
46
|
+
extension: extensionLogInfo,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
static forExtension(extensionLogInfo) {
|
|
51
|
+
return new EmulatorLogger({
|
|
52
|
+
metadata: {
|
|
53
|
+
emulator: {
|
|
54
|
+
name: "extensions",
|
|
55
|
+
},
|
|
56
|
+
extension: extensionLogInfo,
|
|
46
57
|
},
|
|
47
58
|
});
|
|
48
59
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUnemulatedAPIs = void 0;
|
|
4
|
+
const planner = require("../../deploy/extensions/planner");
|
|
5
|
+
const ensureApiEnabled_1 = require("../../ensureApiEnabled");
|
|
6
|
+
const EMULATED_APIS = [
|
|
7
|
+
"storage-component.googleapis.com",
|
|
8
|
+
"firestore.googleapis.com",
|
|
9
|
+
"pubsub.googleapis.com",
|
|
10
|
+
"identitytoolkit.googleapis.com",
|
|
11
|
+
];
|
|
12
|
+
async function getUnemulatedAPIs(projectId, instances) {
|
|
13
|
+
var _a;
|
|
14
|
+
const unemulatedAPIs = {};
|
|
15
|
+
for (const i of instances) {
|
|
16
|
+
const extensionVersion = await planner.getExtensionVersion(i);
|
|
17
|
+
for (const api of (_a = extensionVersion.spec.apis) !== null && _a !== void 0 ? _a : []) {
|
|
18
|
+
if (!EMULATED_APIS.includes(api.apiName)) {
|
|
19
|
+
if (unemulatedAPIs[api.apiName]) {
|
|
20
|
+
unemulatedAPIs[api.apiName].instanceIds.push(i.instanceId);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const enabled = await (0, ensureApiEnabled_1.check)(projectId, api.apiName, "extensions", true);
|
|
24
|
+
unemulatedAPIs[api.apiName] = {
|
|
25
|
+
apiName: api.apiName,
|
|
26
|
+
instanceIds: [i.instanceId],
|
|
27
|
+
enabled,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.values(unemulatedAPIs);
|
|
34
|
+
}
|
|
35
|
+
exports.getUnemulatedAPIs = getUnemulatedAPIs;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExtensionsEmulator = void 0;
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const clc = require("cli-color");
|
|
8
|
+
const Table = require("cli-table");
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const planner = require("../deploy/extensions/planner");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const refs_1 = require("../extensions/refs");
|
|
13
|
+
const download_1 = require("./download");
|
|
14
|
+
const optionsHelper_1 = require("../extensions/emulator/optionsHelper");
|
|
15
|
+
const emulatorLogger_1 = require("./emulatorLogger");
|
|
16
|
+
const types_1 = require("./types");
|
|
17
|
+
const validation_1 = require("./extensions/validation");
|
|
18
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
19
|
+
const shortenUrl_1 = require("../shortenUrl");
|
|
20
|
+
class ExtensionsEmulator {
|
|
21
|
+
constructor(args) {
|
|
22
|
+
this.want = [];
|
|
23
|
+
this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
|
|
24
|
+
this.args = args;
|
|
25
|
+
}
|
|
26
|
+
async readManifest() {
|
|
27
|
+
var _a;
|
|
28
|
+
this.want = await planner.want({
|
|
29
|
+
projectId: this.args.projectId,
|
|
30
|
+
projectNumber: this.args.projectNumber,
|
|
31
|
+
aliases: (_a = this.args.aliases) !== null && _a !== void 0 ? _a : [],
|
|
32
|
+
projectDir: this.args.projectDir,
|
|
33
|
+
extensions: this.args.extensions,
|
|
34
|
+
checkLocal: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async ensureSourceCode(instance) {
|
|
38
|
+
if (!instance.ref) {
|
|
39
|
+
throw new error_1.FirebaseError(`No ref found for ${instance.instanceId}. Emulating local extensions is not yet supported.`);
|
|
40
|
+
}
|
|
41
|
+
const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
|
|
42
|
+
const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
|
|
43
|
+
path.join(os.homedir(), ".cache", "firebase", "extensions");
|
|
44
|
+
const sourceCodePath = path.join(cacheDir, ref);
|
|
45
|
+
if (!this.hasValidSource({ path: sourceCodePath, extRef: ref })) {
|
|
46
|
+
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
47
|
+
await (0, download_1.downloadExtensionVersion)(ref, extensionVersion.sourceDownloadUri, sourceCodePath);
|
|
48
|
+
this.installAndBuildSourceCode(sourceCodePath);
|
|
49
|
+
}
|
|
50
|
+
return sourceCodePath;
|
|
51
|
+
}
|
|
52
|
+
hasValidSource(args) {
|
|
53
|
+
const requiredFiles = [
|
|
54
|
+
"./extension.yaml",
|
|
55
|
+
"./functions/package.json",
|
|
56
|
+
"./functions/node_modules",
|
|
57
|
+
];
|
|
58
|
+
for (const requiredFile of requiredFiles) {
|
|
59
|
+
const f = path.join(args.path, requiredFile);
|
|
60
|
+
if (!fs.existsSync(f)) {
|
|
61
|
+
emulatorLogger_1.EmulatorLogger.forExtension({ ref: args.extRef }).logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extRef}, expected to find ${f}`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
installAndBuildSourceCode(sourceCodePath) {
|
|
68
|
+
const npmInstall = (0, child_process_1.spawnSync)("npm", ["--prefix", `/${sourceCodePath}/functions/`, "install"], {
|
|
69
|
+
encoding: "utf8",
|
|
70
|
+
});
|
|
71
|
+
if (npmInstall.error) {
|
|
72
|
+
throw npmInstall.error;
|
|
73
|
+
}
|
|
74
|
+
const npmRunGCPBuild = (0, child_process_1.spawnSync)("npm", ["--prefix", `/${sourceCodePath}/functions/`, "run", "gcp-build"], { encoding: "utf8" });
|
|
75
|
+
if (npmRunGCPBuild.error) {
|
|
76
|
+
throw npmRunGCPBuild.error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async getExtensionBackends() {
|
|
80
|
+
await this.readManifest();
|
|
81
|
+
await this.checkAndWarnAPIs(this.want);
|
|
82
|
+
return Promise.all(this.want.map((i) => {
|
|
83
|
+
return this.toEmulatableBackend(i);
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
async toEmulatableBackend(instance) {
|
|
87
|
+
const extensionDir = await this.ensureSourceCode(instance);
|
|
88
|
+
const functionsDir = path.join(extensionDir, "functions");
|
|
89
|
+
const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
|
|
90
|
+
const { extensionTriggers, nodeMajorVersion } = await (0, optionsHelper_1.getExtensionFunctionInfo)(extensionDir, instance.instanceId, env);
|
|
91
|
+
const extension = await planner.getExtension(instance);
|
|
92
|
+
const extensionVersion = await planner.getExtensionVersion(instance);
|
|
93
|
+
return {
|
|
94
|
+
functionsDir,
|
|
95
|
+
env,
|
|
96
|
+
predefinedTriggers: extensionTriggers,
|
|
97
|
+
nodeMajorVersion: nodeMajorVersion,
|
|
98
|
+
extensionInstanceId: instance.instanceId,
|
|
99
|
+
extension,
|
|
100
|
+
extensionVersion,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
autoPopulatedParams(instance) {
|
|
104
|
+
const projectId = this.args.projectId;
|
|
105
|
+
return {
|
|
106
|
+
PROJECT_ID: projectId !== null && projectId !== void 0 ? projectId : "",
|
|
107
|
+
EXT_INSTANCE_ID: instance.instanceId,
|
|
108
|
+
DATABASE_INSTANCE: projectId !== null && projectId !== void 0 ? projectId : "",
|
|
109
|
+
DATABASE_URL: `https://${projectId}.firebaseio.com`,
|
|
110
|
+
STORAGE_BUCKET: `${projectId}.appspot.com`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
async checkAndWarnAPIs(instances) {
|
|
114
|
+
const apisToWarn = await (0, validation_1.getUnemulatedAPIs)(this.args.projectId, instances);
|
|
115
|
+
if (apisToWarn.length) {
|
|
116
|
+
const table = new Table({
|
|
117
|
+
head: [
|
|
118
|
+
"API Name",
|
|
119
|
+
"Instances using this API",
|
|
120
|
+
`Enabled on ${this.args.projectId}`,
|
|
121
|
+
`Enable this API`,
|
|
122
|
+
],
|
|
123
|
+
style: { head: ["yellow"] },
|
|
124
|
+
});
|
|
125
|
+
for (const apiToWarn of apisToWarn) {
|
|
126
|
+
const enablementUri = await (0, shortenUrl_1.shortenUrl)((0, ensureApiEnabled_1.enableApiURI)(this.args.projectId, apiToWarn.apiName));
|
|
127
|
+
table.push([
|
|
128
|
+
apiToWarn.apiName,
|
|
129
|
+
apiToWarn.instanceIds,
|
|
130
|
+
apiToWarn.enabled ? "Yes" : "No",
|
|
131
|
+
apiToWarn.enabled ? "" : clc.bold.underline(enablementUri),
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
this.logger.logLabeled("WARN", "Extensions", `The following Extensions make calls to Google Cloud APIs that do not have Emulators. ` +
|
|
135
|
+
`These calls will go to production Google Cloud APIs which may have real effects on ${this.args.projectId}.\n` +
|
|
136
|
+
table.toString());
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.ExtensionsEmulator = ExtensionsEmulator;
|