firebase-tools 11.2.1 → 11.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/checkValidTargetFilters.js +2 -2
- package/lib/commands/ext-export.js +2 -0
- package/lib/deploy/extensions/planner.js +1 -0
- package/lib/deploy/extensions/prepare.js +18 -1
- package/lib/deploy/extensions/release.js +4 -0
- package/lib/deploy/functions/build.js +32 -51
- package/lib/deploy/functions/params.js +189 -0
- package/lib/deploy/functions/prepare.js +1 -1
- package/lib/deploy/lifecycleHooks.js +1 -1
- package/lib/deploy/storage/prepare.js +1 -1
- package/lib/emulator/downloadableEmulators.js +1 -1
- package/lib/emulator/functionsEmulator.js +6 -1
- package/lib/extensions/etags.js +28 -0
- package/lib/extensions/warnings.js +7 -1
- package/lib/functions/env.js +5 -1
- package/lib/functionsConfig.js +1 -1
- package/lib/gcp/rules.js +2 -3
- package/lib/hosting/api.js +9 -11
- package/lib/hosting/expireUtils.js +2 -2
- package/lib/management/projects.js +6 -7
- package/lib/profileReport.js +16 -14
- package/lib/rc.js +11 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -28,10 +28,10 @@ async function checkValidTargetFilters(options) {
|
|
|
28
28
|
if (options.except) {
|
|
29
29
|
return reject(new error_1.FirebaseError("Cannot specify both --only and --except"));
|
|
30
30
|
}
|
|
31
|
-
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions"].includes(t));
|
|
31
|
+
const nonFilteredTypes = deploy_1.VALID_DEPLOY_TARGETS.filter((t) => !["hosting", "functions", "firestore"].includes(t));
|
|
32
32
|
const targetsForNonFilteredTypes = targetsForTypes(only, ...nonFilteredTypes);
|
|
33
33
|
if (targetsForNonFilteredTypes.length && targetsHaveFilters(...targetsForNonFilteredTypes)) {
|
|
34
|
-
return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions and
|
|
34
|
+
return reject(new error_1.FirebaseError("Filters specified with colons (e.g. --only functions:func1,functions:func2) are only supported for functions, hosting, and firestore"));
|
|
35
35
|
}
|
|
36
36
|
const targetsForFunctions = targetsForTypes(only, "functions");
|
|
37
37
|
if (targetsForFunctions.length &&
|
|
@@ -4,6 +4,7 @@ exports.command = void 0;
|
|
|
4
4
|
const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
|
|
5
5
|
const command_1 = require("../command");
|
|
6
6
|
const planner = require("../deploy/extensions/planner");
|
|
7
|
+
const etags_1 = require("../extensions/etags");
|
|
7
8
|
const export_1 = require("../extensions/export");
|
|
8
9
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
9
10
|
const manifest = require("../extensions/manifest");
|
|
@@ -63,4 +64,5 @@ exports.command = new command_1.Command("ext:export")
|
|
|
63
64
|
nonInteractive: options.nonInteractive,
|
|
64
65
|
force: options.force,
|
|
65
66
|
}, true);
|
|
67
|
+
(0, etags_1.saveEtags)(options.rc, projectId, have);
|
|
66
68
|
});
|
|
@@ -13,6 +13,7 @@ const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
|
13
13
|
const secretsUtils_1 = require("../../extensions/secretsUtils");
|
|
14
14
|
const secrets_1 = require("./secrets");
|
|
15
15
|
const warnings_1 = require("../../extensions/warnings");
|
|
16
|
+
const etags_1 = require("../../extensions/etags");
|
|
16
17
|
async function prepare(context, options, payload) {
|
|
17
18
|
var _a;
|
|
18
19
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -28,6 +29,22 @@ async function prepare(context, options, payload) {
|
|
|
28
29
|
projectDir: options.config.projectDir,
|
|
29
30
|
extensions: options.config.get("extensions"),
|
|
30
31
|
});
|
|
32
|
+
const etagsChanged = (0, etags_1.detectEtagChanges)(options.rc, projectId, context.have);
|
|
33
|
+
if (etagsChanged.length) {
|
|
34
|
+
(0, warnings_1.outOfBandChangesWarning)(etagsChanged);
|
|
35
|
+
if (!options.force && options.nonInteractive) {
|
|
36
|
+
throw new error_1.FirebaseError("Pass the --force flag to overwrite out of band changes in non-interactive mode");
|
|
37
|
+
}
|
|
38
|
+
else if (!options.force &&
|
|
39
|
+
!options.nonInteractive &&
|
|
40
|
+
!(await prompt.promptOnce({
|
|
41
|
+
type: "confirm",
|
|
42
|
+
message: `Do you wish to continue deploying these extension instances?`,
|
|
43
|
+
default: false,
|
|
44
|
+
}))) {
|
|
45
|
+
throw new error_1.FirebaseError("Deployment cancelled");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
31
48
|
const usingSecrets = await Promise.all((_a = context.want) === null || _a === void 0 ? void 0 : _a.map(secrets_1.checkSpecForSecrets));
|
|
32
49
|
if (usingSecrets.some((i) => i)) {
|
|
33
50
|
await (0, secretsUtils_1.ensureSecretManagerApiEnabled)(options);
|
|
@@ -44,7 +61,7 @@ async function prepare(context, options, payload) {
|
|
|
44
61
|
!options.nonInteractive &&
|
|
45
62
|
!(await prompt.promptOnce({
|
|
46
63
|
type: "confirm",
|
|
47
|
-
message: `Do you wish to continue deploying these
|
|
64
|
+
message: `Do you wish to continue deploying these extension instances?`,
|
|
48
65
|
default: true,
|
|
49
66
|
}))) {
|
|
50
67
|
throw new error_1.FirebaseError("Deployment cancelled");
|
|
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.release = void 0;
|
|
4
4
|
const queue_1 = require("../../throttler/queue");
|
|
5
5
|
const tasks = require("./tasks");
|
|
6
|
+
const planner = require("./planner");
|
|
6
7
|
const error_1 = require("../../error");
|
|
7
8
|
const errors_1 = require("./errors");
|
|
8
9
|
const projectUtils_1 = require("../../projectUtils");
|
|
10
|
+
const etags_1 = require("../../extensions/etags");
|
|
9
11
|
async function release(context, options, payload) {
|
|
10
12
|
var _a, _b, _c, _d;
|
|
11
13
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
@@ -35,6 +37,8 @@ async function release(context, options, payload) {
|
|
|
35
37
|
deploymentQueue.process();
|
|
36
38
|
deploymentQueue.close();
|
|
37
39
|
await deploymentPromise;
|
|
40
|
+
const newHave = await planner.have(projectId);
|
|
41
|
+
(0, etags_1.saveEtags)(options.rc, projectId, newHave);
|
|
38
42
|
if (errorHandler.hasErrors()) {
|
|
39
43
|
errorHandler.print();
|
|
40
44
|
throw new error_1.FirebaseError(`Extensions deployment failed.`);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resolveBackend = exports.of = exports.empty = void 0;
|
|
3
|
+
exports.toBackend = exports.resolveBackend = exports.of = exports.empty = void 0;
|
|
4
4
|
const backend = require("./backend");
|
|
5
5
|
const proto = require("../../gcp/proto");
|
|
6
6
|
const api = require("../../.../../api");
|
|
7
|
+
const params = require("./params");
|
|
8
|
+
const previews_1 = require("../../previews");
|
|
7
9
|
const error_1 = require("../../error");
|
|
8
10
|
const functional_1 = require("../../functional");
|
|
9
11
|
function empty() {
|
|
@@ -20,45 +22,19 @@ function of(endpoints) {
|
|
|
20
22
|
return build;
|
|
21
23
|
}
|
|
22
24
|
exports.of = of;
|
|
23
|
-
function resolveInt(from) {
|
|
24
|
-
if (from == null) {
|
|
25
|
-
return 0;
|
|
26
|
-
}
|
|
27
|
-
else if (typeof from === "string") {
|
|
28
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
29
|
-
}
|
|
30
|
-
return from;
|
|
31
|
-
}
|
|
32
|
-
function resolveString(from) {
|
|
33
|
-
if (from == null) {
|
|
34
|
-
return "";
|
|
35
|
-
}
|
|
36
|
-
else if (from.includes("{{") && from.includes("}}")) {
|
|
37
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
38
|
-
}
|
|
39
|
-
return from;
|
|
40
|
-
}
|
|
41
|
-
function resolveBoolean(from) {
|
|
42
|
-
if (from == null) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
else if (typeof from === "string") {
|
|
46
|
-
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
47
|
-
}
|
|
48
|
-
return from;
|
|
49
|
-
}
|
|
50
25
|
function isMemoryOption(value) {
|
|
51
26
|
return value == null || [128, 256, 512, 1024, 2048, 4096, 8192].includes(value);
|
|
52
27
|
}
|
|
53
|
-
function resolveBackend(build, userEnvs) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
expectedEnv +
|
|
59
|
-
" but it was not present in the user dotenv files or Cloud Secret Manager");
|
|
60
|
-
}
|
|
28
|
+
async function resolveBackend(build, userEnvOpt, userEnvs) {
|
|
29
|
+
const projectId = userEnvOpt.projectId;
|
|
30
|
+
let paramValues = {};
|
|
31
|
+
if (previews_1.previews.functionsparams) {
|
|
32
|
+
paramValues = await params.resolveParams(build.params, projectId, userEnvs);
|
|
61
33
|
}
|
|
34
|
+
return toBackend(build, paramValues);
|
|
35
|
+
}
|
|
36
|
+
exports.resolveBackend = resolveBackend;
|
|
37
|
+
function toBackend(build, paramValues) {
|
|
62
38
|
const bkEndpoints = [];
|
|
63
39
|
for (const endpointId of Object.keys(build.endpoints)) {
|
|
64
40
|
const bdEndpoint = build.endpoints[endpointId];
|
|
@@ -67,7 +43,7 @@ function resolveBackend(build, userEnvs) {
|
|
|
67
43
|
regions = [api.functionsDefaultRegion];
|
|
68
44
|
}
|
|
69
45
|
for (const region of regions) {
|
|
70
|
-
const trigger = discoverTrigger(bdEndpoint);
|
|
46
|
+
const trigger = discoverTrigger(bdEndpoint, paramValues);
|
|
71
47
|
if (typeof bdEndpoint.platform === "undefined") {
|
|
72
48
|
throw new error_1.FirebaseError("platform can't be undefined");
|
|
73
49
|
}
|
|
@@ -76,21 +52,25 @@ function resolveBackend(build, userEnvs) {
|
|
|
76
52
|
}
|
|
77
53
|
let timeout;
|
|
78
54
|
if (bdEndpoint.timeoutSeconds) {
|
|
79
|
-
timeout = resolveInt(bdEndpoint.timeoutSeconds);
|
|
55
|
+
timeout = params.resolveInt(bdEndpoint.timeoutSeconds, paramValues);
|
|
80
56
|
}
|
|
81
57
|
else {
|
|
82
58
|
timeout = 60;
|
|
83
59
|
}
|
|
84
60
|
const bkEndpoint = Object.assign({ id: endpointId, project: bdEndpoint.project, region: region, entryPoint: bdEndpoint.entryPoint, platform: bdEndpoint.platform, runtime: bdEndpoint.runtime, timeoutSeconds: timeout }, trigger);
|
|
85
|
-
proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances",
|
|
86
|
-
|
|
87
|
-
|
|
61
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "maxInstances", "maxInstances", (from) => {
|
|
62
|
+
return params.resolveInt(from, paramValues);
|
|
63
|
+
});
|
|
64
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "minInstances", "minInstances", (from) => {
|
|
65
|
+
return params.resolveInt(from, paramValues);
|
|
66
|
+
});
|
|
67
|
+
proto.renameIfPresent(bkEndpoint, bdEndpoint, "concurrency", "concurrency", (from) => {
|
|
68
|
+
return params.resolveInt(from, paramValues);
|
|
69
|
+
});
|
|
88
70
|
proto.copyIfPresent(bkEndpoint, bdEndpoint, "ingressSettings", "availableMemoryMb", "environmentVariables", "labels");
|
|
89
71
|
proto.copyIfPresent(bkEndpoint, bdEndpoint, "secretEnvironmentVariables");
|
|
90
72
|
if (bdEndpoint.vpc) {
|
|
91
|
-
bkEndpoint.vpc = {
|
|
92
|
-
connector: resolveString(bdEndpoint.vpc.connector).replace("$REGION", region),
|
|
93
|
-
};
|
|
73
|
+
bkEndpoint.vpc = { connector: params.resolveString(bdEndpoint.vpc.connector, paramValues) };
|
|
94
74
|
proto.copyIfPresent(bkEndpoint.vpc, bdEndpoint.vpc, "egressSettings");
|
|
95
75
|
}
|
|
96
76
|
proto.renameIfPresent(bkEndpoint, bdEndpoint, "serviceAccountEmail", "serviceAccount");
|
|
@@ -104,8 +84,11 @@ function resolveBackend(build, userEnvs) {
|
|
|
104
84
|
bkend.requiredAPIs = build.requiredAPIs;
|
|
105
85
|
return bkend;
|
|
106
86
|
}
|
|
107
|
-
exports.
|
|
108
|
-
function discoverTrigger(endpoint) {
|
|
87
|
+
exports.toBackend = toBackend;
|
|
88
|
+
function discoverTrigger(endpoint, paramValues) {
|
|
89
|
+
const resolveInt = (from) => params.resolveInt(from, paramValues);
|
|
90
|
+
const resolveString = (from) => params.resolveString(from, paramValues);
|
|
91
|
+
const resolveBoolean = (from) => params.resolveBoolean(from, paramValues);
|
|
109
92
|
let trigger;
|
|
110
93
|
if ("httpsTrigger" in endpoint) {
|
|
111
94
|
const bkHttps = {};
|
|
@@ -122,15 +105,13 @@ function discoverTrigger(endpoint) {
|
|
|
122
105
|
}
|
|
123
106
|
else if ("eventTrigger" in endpoint) {
|
|
124
107
|
const bkEventFilters = {};
|
|
125
|
-
for (const key
|
|
126
|
-
|
|
127
|
-
bkEventFilters[key] = resolveString(endpoint.eventTrigger.eventFilters[key]);
|
|
128
|
-
}
|
|
108
|
+
for (const [key, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
|
|
109
|
+
bkEventFilters[key] = params.resolveString(value, paramValues);
|
|
129
110
|
}
|
|
130
111
|
const bkEvent = {
|
|
131
112
|
eventType: endpoint.eventTrigger.eventType,
|
|
132
113
|
eventFilters: bkEventFilters,
|
|
133
|
-
retry: resolveBoolean(endpoint.eventTrigger.retry),
|
|
114
|
+
retry: resolveBoolean(endpoint.eventTrigger.retry || false),
|
|
134
115
|
};
|
|
135
116
|
if (endpoint.eventTrigger.serviceAccount) {
|
|
136
117
|
bkEvent.serviceAccountEmail = endpoint.eventTrigger.serviceAccount;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveParams = exports.resolveBoolean = exports.resolveString = exports.resolveInt = void 0;
|
|
4
|
+
const logger_1 = require("../../logger");
|
|
5
|
+
const error_1 = require("../../error");
|
|
6
|
+
const prompt_1 = require("../../prompt");
|
|
7
|
+
const functional_1 = require("../../functional");
|
|
8
|
+
function isCEL(expr) {
|
|
9
|
+
return typeof expr === "string" && expr.includes("{{") && expr.includes("}}");
|
|
10
|
+
}
|
|
11
|
+
function dependenciesCEL(expr) {
|
|
12
|
+
const deps = [];
|
|
13
|
+
const paramCapture = /{{ params\.(\w+) }}/g;
|
|
14
|
+
let match;
|
|
15
|
+
while ((match = paramCapture.exec(expr)) != null) {
|
|
16
|
+
deps.push(match[1]);
|
|
17
|
+
}
|
|
18
|
+
return deps;
|
|
19
|
+
}
|
|
20
|
+
function resolveInt(from, paramValues) {
|
|
21
|
+
if (typeof from === "number") {
|
|
22
|
+
return from;
|
|
23
|
+
}
|
|
24
|
+
const match = /\A{{ params\.(\w+) }}\z/.exec(from);
|
|
25
|
+
if (!match) {
|
|
26
|
+
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
27
|
+
}
|
|
28
|
+
const referencedParamValue = paramValues[match[1]];
|
|
29
|
+
if (typeof referencedParamValue !== "number") {
|
|
30
|
+
throw new error_1.FirebaseError("Referenced numeric parameter '" +
|
|
31
|
+
match +
|
|
32
|
+
"' resolved to non-number value " +
|
|
33
|
+
referencedParamValue);
|
|
34
|
+
}
|
|
35
|
+
return referencedParamValue;
|
|
36
|
+
}
|
|
37
|
+
exports.resolveInt = resolveInt;
|
|
38
|
+
function resolveString(from, paramValues) {
|
|
39
|
+
if (!isCEL(from)) {
|
|
40
|
+
return from;
|
|
41
|
+
}
|
|
42
|
+
let output = from;
|
|
43
|
+
const paramCapture = /{{ params\.(\w+) }}/g;
|
|
44
|
+
let match;
|
|
45
|
+
while ((match = paramCapture.exec(from)) != null) {
|
|
46
|
+
const referencedParamValue = paramValues[match[1]];
|
|
47
|
+
if (typeof referencedParamValue !== "string") {
|
|
48
|
+
throw new error_1.FirebaseError("Referenced string parameter '" +
|
|
49
|
+
match[1] +
|
|
50
|
+
"' resolved to non-string value " +
|
|
51
|
+
referencedParamValue);
|
|
52
|
+
}
|
|
53
|
+
output = output.replace(`{{ params.${match[1]} }}`, referencedParamValue);
|
|
54
|
+
}
|
|
55
|
+
if (isCEL(output)) {
|
|
56
|
+
throw new error_1.FirebaseError("CEL evaluation of non-identity expression '" + from + "' not yet supported");
|
|
57
|
+
}
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
exports.resolveString = resolveString;
|
|
61
|
+
function resolveBoolean(from, paramValues) {
|
|
62
|
+
if (typeof from === "string" && /{{ params\.(\w+) }}/.test(from)) {
|
|
63
|
+
const match = /{{ params\.(\w+) }}/.exec(from);
|
|
64
|
+
const referencedParamValue = paramValues[match[1]];
|
|
65
|
+
if (typeof referencedParamValue !== "boolean") {
|
|
66
|
+
throw new error_1.FirebaseError("Referenced boolean parameter '" +
|
|
67
|
+
match +
|
|
68
|
+
"' resolved to non-boolean value " +
|
|
69
|
+
referencedParamValue);
|
|
70
|
+
}
|
|
71
|
+
return referencedParamValue;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof from === "string") {
|
|
74
|
+
throw new error_1.FirebaseError("CEL evaluation of expression '" + from + "' not yet supported");
|
|
75
|
+
}
|
|
76
|
+
return from;
|
|
77
|
+
}
|
|
78
|
+
exports.resolveBoolean = resolveBoolean;
|
|
79
|
+
function resolveDefaultCEL(type, expr, currentEnv) {
|
|
80
|
+
const deps = dependenciesCEL(expr);
|
|
81
|
+
const allDepsFound = deps.every((dep) => !!currentEnv[dep]);
|
|
82
|
+
if (!allDepsFound) {
|
|
83
|
+
throw new error_1.FirebaseError("Build specified parameter with un-resolvable default value " +
|
|
84
|
+
expr +
|
|
85
|
+
"; dependencies missing.");
|
|
86
|
+
}
|
|
87
|
+
switch (type) {
|
|
88
|
+
case "string":
|
|
89
|
+
return resolveString(expr, currentEnv);
|
|
90
|
+
case "int":
|
|
91
|
+
return resolveInt(expr, currentEnv);
|
|
92
|
+
default:
|
|
93
|
+
throw new error_1.FirebaseError("Build specified parameter with default " + expr + " of unsupported type");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function canSatisfyParam(param, value) {
|
|
97
|
+
if (param.type === "string") {
|
|
98
|
+
return typeof value === "string";
|
|
99
|
+
}
|
|
100
|
+
else if (param.type === "int") {
|
|
101
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
102
|
+
}
|
|
103
|
+
(0, functional_1.assertExhaustive)(param);
|
|
104
|
+
}
|
|
105
|
+
async function resolveParams(params, projectId, userEnvs) {
|
|
106
|
+
const paramValues = {};
|
|
107
|
+
for (const param of params.filter((param) => userEnvs.hasOwnProperty(param.param))) {
|
|
108
|
+
if (!canSatisfyParam(param, userEnvs[param.param])) {
|
|
109
|
+
throw new error_1.FirebaseError("Parameter " +
|
|
110
|
+
param.param +
|
|
111
|
+
" resolved to value from dotenv files " +
|
|
112
|
+
userEnvs[param.param] +
|
|
113
|
+
" of wrong type");
|
|
114
|
+
}
|
|
115
|
+
paramValues[param.param] = userEnvs[param.param];
|
|
116
|
+
}
|
|
117
|
+
for (const param of params.filter((param) => !userEnvs.hasOwnProperty(param.param))) {
|
|
118
|
+
let paramDefault = param.default;
|
|
119
|
+
if (paramDefault && isCEL(paramDefault)) {
|
|
120
|
+
paramDefault = resolveDefaultCEL(param.type, paramDefault, paramValues);
|
|
121
|
+
}
|
|
122
|
+
if (paramDefault && !canSatisfyParam(param, paramDefault)) {
|
|
123
|
+
throw new error_1.FirebaseError("Parameter " + param.param + " has default value " + paramDefault + " of wrong type");
|
|
124
|
+
}
|
|
125
|
+
paramValues[param.param] = await promptParam(param, paramDefault);
|
|
126
|
+
}
|
|
127
|
+
return paramValues;
|
|
128
|
+
}
|
|
129
|
+
exports.resolveParams = resolveParams;
|
|
130
|
+
async function promptParam(param, resolvedDefault) {
|
|
131
|
+
if (param.type === "string") {
|
|
132
|
+
return promptStringParam(param, resolvedDefault);
|
|
133
|
+
}
|
|
134
|
+
else if (param.type === "int") {
|
|
135
|
+
return promptIntParam(param, resolvedDefault);
|
|
136
|
+
}
|
|
137
|
+
(0, functional_1.assertExhaustive)(param);
|
|
138
|
+
}
|
|
139
|
+
async function promptStringParam(param, resolvedDefault) {
|
|
140
|
+
if (!param.input) {
|
|
141
|
+
const defaultToText = { type: "text", text: {} };
|
|
142
|
+
param.input = defaultToText;
|
|
143
|
+
}
|
|
144
|
+
switch (param.input.type) {
|
|
145
|
+
case "select":
|
|
146
|
+
throw new error_1.FirebaseError("Build specified string parameter " + param.param + " with unsupported input type 'select'");
|
|
147
|
+
case "text":
|
|
148
|
+
default:
|
|
149
|
+
let prompt = `Enter a value for ${param.label || param.param}`;
|
|
150
|
+
if (param.description) {
|
|
151
|
+
prompt += ` \n(${param.description})`;
|
|
152
|
+
}
|
|
153
|
+
return await (0, prompt_1.promptOnce)({
|
|
154
|
+
name: param.param,
|
|
155
|
+
type: "input",
|
|
156
|
+
default: resolvedDefault,
|
|
157
|
+
message: prompt,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function promptIntParam(param, resolvedDefault) {
|
|
162
|
+
if (!param.input) {
|
|
163
|
+
const defaultToText = { type: "text", text: {} };
|
|
164
|
+
param.input = defaultToText;
|
|
165
|
+
}
|
|
166
|
+
switch (param.input.type) {
|
|
167
|
+
case "select":
|
|
168
|
+
throw new error_1.FirebaseError("Build specified int parameter " + param.param + " with unsupported input type 'select'");
|
|
169
|
+
case "text":
|
|
170
|
+
default:
|
|
171
|
+
let prompt = `Enter a value for ${param.label || param.param}`;
|
|
172
|
+
if (param.description) {
|
|
173
|
+
prompt += ` \n(${param.description})`;
|
|
174
|
+
}
|
|
175
|
+
let res;
|
|
176
|
+
while (true) {
|
|
177
|
+
res = await (0, prompt_1.promptOnce)({
|
|
178
|
+
name: param.param,
|
|
179
|
+
type: "number",
|
|
180
|
+
default: resolvedDefault,
|
|
181
|
+
message: prompt,
|
|
182
|
+
});
|
|
183
|
+
if (Number.isInteger(res)) {
|
|
184
|
+
return res;
|
|
185
|
+
}
|
|
186
|
+
logger_1.logger.error(`${param.label || param.param} must be an integer; retrying...`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -78,7 +78,7 @@ async function prepare(context, options, payload) {
|
|
|
78
78
|
const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
|
|
79
79
|
const envs = Object.assign(Object.assign({}, userEnvs), firebaseEnvs);
|
|
80
80
|
const wantBuild = await runtimeDelegate.discoverBuild(runtimeConfig, firebaseEnvs);
|
|
81
|
-
const wantBackend = build.resolveBackend(wantBuild, userEnvs);
|
|
81
|
+
const wantBackend = await build.resolveBackend(wantBuild, userEnvOpt, userEnvs);
|
|
82
82
|
wantBackend.environmentVariables = envs;
|
|
83
83
|
for (const endpoint of backend.allEndpoints(wantBackend)) {
|
|
84
84
|
endpoint.environmentVariables = wantBackend.environmentVariables;
|
|
@@ -31,7 +31,7 @@ function runCommand(command, childOptions) {
|
|
|
31
31
|
reject(new Error("Command terminated with signal " + signal));
|
|
32
32
|
}
|
|
33
33
|
else if (code !== 0) {
|
|
34
|
-
reject(new Error("Command terminated with non-zero exit code" + code));
|
|
34
|
+
reject(new Error("Command terminated with non-zero exit code " + code));
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
37
|
resolve();
|
|
@@ -11,7 +11,7 @@ async function default_1(context, options) {
|
|
|
11
11
|
_.set(context, "storage.rules", rulesConfig);
|
|
12
12
|
const rulesDeploy = new rulesDeploy_1.RulesDeploy(options, rulesDeploy_1.RulesetServiceType.FIREBASE_STORAGE);
|
|
13
13
|
_.set(context, "storage.rulesDeploy", rulesDeploy);
|
|
14
|
-
if (
|
|
14
|
+
if (typeof rulesConfig === "object" && rulesConfig !== null) {
|
|
15
15
|
const defaultBucket = await gcp.storage.getDefaultBucket(options.project);
|
|
16
16
|
rulesConfig = [Object.assign(rulesConfig, { bucket: defaultBucket })];
|
|
17
17
|
_.set(context, "storage.rules", rulesConfig);
|
|
@@ -272,8 +272,13 @@ class FunctionsEmulator {
|
|
|
272
272
|
await runtimeDelegate.build();
|
|
273
273
|
logger_1.logger.debug(`Analyzing ${runtimeDelegate.name} backend spec`);
|
|
274
274
|
const environment = Object.assign(Object.assign(Object.assign(Object.assign({}, this.getSystemEnvs()), this.getEmulatorEnvs()), { FIREBASE_CONFIG: this.getFirebaseConfig() }), emulatableBackend.env);
|
|
275
|
+
const userEnvOpt = {
|
|
276
|
+
functionsSource: emulatableBackend.functionsDir,
|
|
277
|
+
projectId: this.args.projectId,
|
|
278
|
+
projectAlias: this.args.projectAlias,
|
|
279
|
+
};
|
|
275
280
|
const discoveredBuild = await runtimeDelegate.discoverBuild(runtimeConfig, environment);
|
|
276
|
-
const discoveredBackend = (0, build_1.resolveBackend)(discoveredBuild, environment);
|
|
281
|
+
const discoveredBackend = await (0, build_1.resolveBackend)(discoveredBuild, userEnvOpt, environment);
|
|
277
282
|
const endpoints = backend.allEndpoints(discoveredBackend);
|
|
278
283
|
(0, functionsEmulatorShared_1.prepareEndpoints)(endpoints);
|
|
279
284
|
for (const e of endpoints) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.detectEtagChanges = exports.saveEtags = void 0;
|
|
4
|
+
function saveEtags(rc, projectId, instances) {
|
|
5
|
+
rc.setEtags(projectId, "extensionInstances", etagsMap(instances));
|
|
6
|
+
}
|
|
7
|
+
exports.saveEtags = saveEtags;
|
|
8
|
+
function detectEtagChanges(rc, projectId, instances) {
|
|
9
|
+
const lastDeployedEtags = rc.getEtags(projectId).extensionInstances;
|
|
10
|
+
const currentEtags = etagsMap(instances);
|
|
11
|
+
if (!lastDeployedEtags || !Object.keys(lastDeployedEtags).length) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const changedExtensionInstances = Object.entries(lastDeployedEtags)
|
|
15
|
+
.filter(([instanceName, etag]) => etag !== currentEtags[instanceName])
|
|
16
|
+
.map((i) => i[0]);
|
|
17
|
+
const newExtensionInstances = Object.keys(currentEtags).filter((instanceName) => !lastDeployedEtags[instanceName]);
|
|
18
|
+
return newExtensionInstances.concat(changedExtensionInstances);
|
|
19
|
+
}
|
|
20
|
+
exports.detectEtagChanges = detectEtagChanges;
|
|
21
|
+
function etagsMap(instances) {
|
|
22
|
+
return instances.reduce((acc, i) => {
|
|
23
|
+
if (i.etag) {
|
|
24
|
+
acc[i.instanceId] = i.etag;
|
|
25
|
+
}
|
|
26
|
+
return acc;
|
|
27
|
+
}, {});
|
|
28
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.paramsFlagDeprecationWarning = exports.displayWarningsForDeploy = exports.displayWarningPrompts = void 0;
|
|
3
|
+
exports.outOfBandChangesWarning = exports.paramsFlagDeprecationWarning = exports.displayWarningsForDeploy = exports.displayWarningPrompts = void 0;
|
|
4
4
|
const { marked } = require("marked");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const types_1 = require("./types");
|
|
@@ -75,3 +75,9 @@ function paramsFlagDeprecationWarning() {
|
|
|
75
75
|
"See https://firebase.google.com/docs/extensions/manifest for more details");
|
|
76
76
|
}
|
|
77
77
|
exports.paramsFlagDeprecationWarning = paramsFlagDeprecationWarning;
|
|
78
|
+
function outOfBandChangesWarning(instanceIds) {
|
|
79
|
+
logger_1.logger.warn("The following instances may have been changed in the Firebase console or by another machine since the last deploy from this machine.\n\t" +
|
|
80
|
+
clc.bold(instanceIds.join("\n\t")) +
|
|
81
|
+
"\nIf you proceed with this deployment, those changes will be overwritten. To avoid this, run `firebase ext:export` to sync these changes to your local extensions manifest.");
|
|
82
|
+
}
|
|
83
|
+
exports.outOfBandChangesWarning = outOfBandChangesWarning;
|
package/lib/functions/env.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
|
|
3
|
+
exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.writeUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
@@ -145,6 +145,10 @@ function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, })
|
|
|
145
145
|
return findEnvfiles(functionsSource, projectId, projectAlias, isEmulator).length > 0;
|
|
146
146
|
}
|
|
147
147
|
exports.hasUserEnvs = hasUserEnvs;
|
|
148
|
+
function writeUserEnvs(toWrite, envOpts) {
|
|
149
|
+
throw new error_1.FirebaseError("Persisting user-defined parameters to .env files is not yet implemented.");
|
|
150
|
+
}
|
|
151
|
+
exports.writeUserEnvs = writeUserEnvs;
|
|
148
152
|
function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
|
|
149
153
|
var _a;
|
|
150
154
|
const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
|
package/lib/functionsConfig.js
CHANGED
|
@@ -70,7 +70,7 @@ async function setVariablesRecursive(projectId, configId, varPath, val) {
|
|
|
70
70
|
catch (e) {
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
-
if (
|
|
73
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
74
74
|
return Promise.all(Object.entries(parsed).map(([key, item]) => {
|
|
75
75
|
const newVarPath = varPath ? [varPath, key].join("/") : key;
|
|
76
76
|
return setVariablesRecursive(projectId, configId, newVarPath, item);
|
package/lib/gcp/rules.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.testRuleset = exports.updateOrCreateRelease = exports.updateRelease = exports.createRelease = exports.createRuleset = exports.deleteRuleset = exports.getRulesetId = exports.listAllRulesets = exports.listRulesets = exports.getRulesetContent = exports.listAllReleases = exports.listReleases = exports.getLatestRulesetName = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const api_1 = require("../api");
|
|
6
5
|
const apiv2_1 = require("../apiv2");
|
|
7
6
|
const logger_1 = require("../logger");
|
|
@@ -51,7 +50,7 @@ async function listAllReleases(projectId) {
|
|
|
51
50
|
}
|
|
52
51
|
pageToken = response.nextPageToken;
|
|
53
52
|
} while (pageToken);
|
|
54
|
-
return
|
|
53
|
+
return releases.sort((a, b) => b.createTime.localeCompare(a.createTime));
|
|
55
54
|
}
|
|
56
55
|
exports.listAllReleases = listAllReleases;
|
|
57
56
|
async function getRulesetContent(name) {
|
|
@@ -90,7 +89,7 @@ async function listAllRulesets(projectId) {
|
|
|
90
89
|
}
|
|
91
90
|
pageToken = response.nextPageToken;
|
|
92
91
|
} while (pageToken);
|
|
93
|
-
return
|
|
92
|
+
return rulesets.sort((a, b) => b.createTime.localeCompare(a.createTime));
|
|
94
93
|
}
|
|
95
94
|
exports.listAllRulesets = listAllRulesets;
|
|
96
95
|
function getRulesetId(ruleset) {
|
package/lib/hosting/api.js
CHANGED
|
@@ -43,7 +43,7 @@ async function getChannel(project = "-", site, channelId) {
|
|
|
43
43
|
return res.body;
|
|
44
44
|
}
|
|
45
45
|
catch (e) {
|
|
46
|
-
if (e.status === 404) {
|
|
46
|
+
if (e instanceof error_1.FirebaseError && e.status === 404) {
|
|
47
47
|
return null;
|
|
48
48
|
}
|
|
49
49
|
throw e;
|
|
@@ -51,23 +51,22 @@ async function getChannel(project = "-", site, channelId) {
|
|
|
51
51
|
}
|
|
52
52
|
exports.getChannel = getChannel;
|
|
53
53
|
async function listChannels(project = "-", site) {
|
|
54
|
-
var _a, _b;
|
|
55
54
|
const channels = [];
|
|
56
55
|
let nextPageToken = "";
|
|
57
56
|
for (;;) {
|
|
58
57
|
try {
|
|
59
58
|
const res = await apiClient.get(`/projects/${project}/sites/${site}/channels`, { queryParams: { pageToken: nextPageToken, pageSize: 10 } });
|
|
60
|
-
const c =
|
|
59
|
+
const c = res.body.channels;
|
|
61
60
|
if (c) {
|
|
62
61
|
channels.push(...c);
|
|
63
62
|
}
|
|
64
|
-
nextPageToken =
|
|
63
|
+
nextPageToken = res.body.nextPageToken || "";
|
|
65
64
|
if (!nextPageToken) {
|
|
66
65
|
return channels;
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
catch (e) {
|
|
70
|
-
if (e.status === 404) {
|
|
69
|
+
if (e instanceof error_1.FirebaseError && e.status === 404) {
|
|
71
70
|
throw new error_1.FirebaseError(`could not find channels for site "${site}"`, {
|
|
72
71
|
original: e,
|
|
73
72
|
});
|
|
@@ -116,23 +115,22 @@ async function createRelease(site, channel, version) {
|
|
|
116
115
|
}
|
|
117
116
|
exports.createRelease = createRelease;
|
|
118
117
|
async function listSites(project) {
|
|
119
|
-
var _a, _b;
|
|
120
118
|
const sites = [];
|
|
121
119
|
let nextPageToken = "";
|
|
122
120
|
for (;;) {
|
|
123
121
|
try {
|
|
124
122
|
const res = await apiClient.get(`/projects/${project}/sites`, { queryParams: { pageToken: nextPageToken, pageSize: 10 } });
|
|
125
|
-
const c =
|
|
123
|
+
const c = res.body.sites;
|
|
126
124
|
if (c) {
|
|
127
125
|
sites.push(...c);
|
|
128
126
|
}
|
|
129
|
-
nextPageToken =
|
|
127
|
+
nextPageToken = res.body.nextPageToken || "";
|
|
130
128
|
if (!nextPageToken) {
|
|
131
129
|
return sites;
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
catch (e) {
|
|
135
|
-
if (e.status === 404) {
|
|
133
|
+
if (e instanceof error_1.FirebaseError && e.status === 404) {
|
|
136
134
|
throw new error_1.FirebaseError(`could not find sites for project "${project}"`, {
|
|
137
135
|
original: e,
|
|
138
136
|
});
|
|
@@ -148,7 +146,7 @@ async function getSite(project, site) {
|
|
|
148
146
|
return res.body;
|
|
149
147
|
}
|
|
150
148
|
catch (e) {
|
|
151
|
-
if (e.status === 404) {
|
|
149
|
+
if (e instanceof error_1.FirebaseError && e.status === 404) {
|
|
152
150
|
throw new error_1.FirebaseError(`could not find site "${site}" for project "${project}"`, {
|
|
153
151
|
original: e,
|
|
154
152
|
});
|
|
@@ -158,7 +156,7 @@ async function getSite(project, site) {
|
|
|
158
156
|
}
|
|
159
157
|
exports.getSite = getSite;
|
|
160
158
|
async function createSite(project, site, appId = "") {
|
|
161
|
-
const res = await apiClient.post(`/projects/${project}/sites`, { appId: appId }, { queryParams: {
|
|
159
|
+
const res = await apiClient.post(`/projects/${project}/sites`, { appId: appId }, { queryParams: { siteId: site } });
|
|
162
160
|
return res.body;
|
|
163
161
|
}
|
|
164
162
|
exports.createSite = createSite;
|
|
@@ -16,8 +16,8 @@ const DURATIONS = {
|
|
|
16
16
|
};
|
|
17
17
|
exports.MAX_DURATION = 30 * Duration.DAY;
|
|
18
18
|
exports.DEFAULT_DURATION = 7 * Duration.DAY;
|
|
19
|
-
function calculateChannelExpireTTL(flag) {
|
|
20
|
-
const match = exports.DURATION_REGEX.exec(flag
|
|
19
|
+
function calculateChannelExpireTTL(flag = "") {
|
|
20
|
+
const match = exports.DURATION_REGEX.exec(flag);
|
|
21
21
|
if (!match) {
|
|
22
22
|
throw new error_1.FirebaseError(`"expires" flag must be a duration string (e.g. 24h or 7d) at most 30d`);
|
|
23
23
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.PROJECTS_CREATE_QUESTIONS = exports.ProjectParentResourceType = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const clc = require("cli-color");
|
|
6
5
|
const ora = require("ora");
|
|
7
6
|
const apiv2_1 = require("../apiv2");
|
|
@@ -110,15 +109,15 @@ async function selectProjectByPrompting() {
|
|
|
110
109
|
return await getFirebaseProject(projectId);
|
|
111
110
|
}
|
|
112
111
|
async function selectProjectFromList(projects = []) {
|
|
113
|
-
|
|
112
|
+
const choices = projects
|
|
114
113
|
.filter((p) => !!p)
|
|
115
114
|
.map((p) => {
|
|
116
115
|
return {
|
|
117
116
|
name: p.projectId + (p.displayName ? ` (${p.displayName})` : ""),
|
|
118
117
|
value: p.projectId,
|
|
119
118
|
};
|
|
120
|
-
})
|
|
121
|
-
|
|
119
|
+
})
|
|
120
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
122
121
|
if (choices.length >= 25) {
|
|
123
122
|
utils.logBullet(`Don't want to scroll through all your projects? If you know your project ID, ` +
|
|
124
123
|
`you can initialize it directly using ${clc.bold("firebase init --project <project_id>")}.\n`);
|
|
@@ -151,7 +150,7 @@ async function promptAvailableProjectId() {
|
|
|
151
150
|
});
|
|
152
151
|
}
|
|
153
152
|
else {
|
|
154
|
-
|
|
153
|
+
const choices = projects
|
|
155
154
|
.filter((p) => !!p)
|
|
156
155
|
.map((p) => {
|
|
157
156
|
const projectId = getProjectId(p);
|
|
@@ -159,8 +158,8 @@ async function promptAvailableProjectId() {
|
|
|
159
158
|
name: projectId + (p.displayName ? ` (${p.displayName})` : ""),
|
|
160
159
|
value: projectId,
|
|
161
160
|
};
|
|
162
|
-
})
|
|
163
|
-
|
|
161
|
+
})
|
|
162
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
164
163
|
return await (0, prompt_1.promptOnce)({
|
|
165
164
|
type: "list",
|
|
166
165
|
name: "id",
|
package/lib/profileReport.js
CHANGED
|
@@ -303,14 +303,14 @@ class ProfileReport {
|
|
|
303
303
|
});
|
|
304
304
|
});
|
|
305
305
|
const paths = Object.keys(unindexed);
|
|
306
|
-
|
|
306
|
+
for (const path of paths) {
|
|
307
307
|
const indices = Object.keys(unindexed[path]);
|
|
308
|
-
|
|
308
|
+
for (const index of indices) {
|
|
309
309
|
const data = unindexed[path][index];
|
|
310
310
|
const row = [path, extractReadableIndex(data.query), formatNumber(data.times)];
|
|
311
311
|
table.push(row);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
314
|
return table;
|
|
315
315
|
}
|
|
316
316
|
renderBandwidth(pureData) {
|
|
@@ -327,8 +327,10 @@ class ProfileReport {
|
|
|
327
327
|
times: b1.times + b2.times,
|
|
328
328
|
};
|
|
329
329
|
});
|
|
330
|
-
const paths =
|
|
331
|
-
|
|
330
|
+
const paths = Object.keys(data).sort((a, b) => {
|
|
331
|
+
return data[b].bytes - data[a].bytes;
|
|
332
|
+
});
|
|
333
|
+
for (const path of paths) {
|
|
332
334
|
const bandwidth = data[path];
|
|
333
335
|
const row = [
|
|
334
336
|
path,
|
|
@@ -337,7 +339,7 @@ class ProfileReport {
|
|
|
337
339
|
formatBytes(bandwidth.bytes / bandwidth.times),
|
|
338
340
|
];
|
|
339
341
|
table.push(row);
|
|
340
|
-
}
|
|
342
|
+
}
|
|
341
343
|
return table;
|
|
342
344
|
}
|
|
343
345
|
renderOutgoingBandwidth() {
|
|
@@ -392,12 +394,12 @@ class ProfileReport {
|
|
|
392
394
|
rejected: s1.rejected + s2.rejected,
|
|
393
395
|
};
|
|
394
396
|
});
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
const
|
|
398
|
-
return
|
|
399
|
-
}
|
|
400
|
-
|
|
397
|
+
const paths = Object.keys(data).sort((a, b) => {
|
|
398
|
+
const speedA = data[a].millis / data[a].times;
|
|
399
|
+
const speedB = data[b].millis / data[b].times;
|
|
400
|
+
return speedB - speedA;
|
|
401
|
+
});
|
|
402
|
+
for (const path of paths) {
|
|
401
403
|
const speed = data[path];
|
|
402
404
|
const row = [
|
|
403
405
|
path,
|
|
@@ -409,7 +411,7 @@ class ProfileReport {
|
|
|
409
411
|
row.push(formatNumber(speed.rejected));
|
|
410
412
|
}
|
|
411
413
|
table.push(row);
|
|
412
|
-
}
|
|
414
|
+
}
|
|
413
415
|
return table;
|
|
414
416
|
}
|
|
415
417
|
renderReadSpeed() {
|
package/lib/rc.js
CHANGED
|
@@ -25,7 +25,7 @@ exports.loadRC = loadRC;
|
|
|
25
25
|
class RC {
|
|
26
26
|
constructor(rcpath, data) {
|
|
27
27
|
this.path = rcpath;
|
|
28
|
-
this.data = Object.assign({ projects: {}, targets: {} }, data);
|
|
28
|
+
this.data = Object.assign({ projects: {}, targets: {}, etags: {} }, data);
|
|
29
29
|
}
|
|
30
30
|
static loadFile(rcpath) {
|
|
31
31
|
let data = {};
|
|
@@ -148,6 +148,16 @@ class RC {
|
|
|
148
148
|
}
|
|
149
149
|
return target;
|
|
150
150
|
}
|
|
151
|
+
getEtags(projectId) {
|
|
152
|
+
return this.data.etags[projectId] || { extensionInstances: {} };
|
|
153
|
+
}
|
|
154
|
+
setEtags(projectId, resourceType, etagData) {
|
|
155
|
+
if (!this.data.etags[projectId]) {
|
|
156
|
+
this.data.etags[projectId] = {};
|
|
157
|
+
}
|
|
158
|
+
this.data.etags[projectId][resourceType] = etagData;
|
|
159
|
+
this.save();
|
|
160
|
+
}
|
|
151
161
|
save() {
|
|
152
162
|
if (this.path) {
|
|
153
163
|
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2), {
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "11.2.
|
|
3
|
+
"version": "11.2.2",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "firebase-tools",
|
|
9
|
-
"version": "11.2.
|
|
9
|
+
"version": "11.2.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@google-cloud/pubsub": "^3.0.1",
|