firebase-tools 10.4.1 → 10.6.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/bin/firebase.js +1 -1
- package/lib/commands/emulators-start.js +6 -1
- package/lib/commands/ext-configure.js +4 -4
- package/lib/commands/ext-dev-emulators-start.js +5 -1
- package/lib/commands/ext-install.js +5 -4
- package/lib/commands/ext-update.js +2 -1
- package/lib/commands/functions-secrets-destroy.js +23 -3
- package/lib/commands/functions-secrets-prune.js +15 -12
- package/lib/commands/functions-secrets-set.js +51 -4
- package/lib/deploy/functions/backend.js +1 -5
- package/lib/deploy/functions/checkIam.js +44 -1
- package/lib/deploy/functions/prepare.js +13 -3
- package/lib/deploy/functions/release/fabricator.js +1 -3
- package/lib/deploy/functions/release/index.js +21 -0
- package/lib/deploy/functions/release/planner.js +1 -2
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +11 -10
- package/lib/deploy/functions/runtimes/node/index.js +1 -1
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +5 -19
- package/lib/deploy/functions/services/firebaseAlerts.js +1 -17
- package/lib/deploy/functions/services/index.js +2 -1
- package/lib/deploy/functions/services/storage.js +1 -6
- package/lib/emulator/auth/operations.js +21 -20
- package/lib/emulator/auth/state.js +79 -43
- package/lib/emulator/commandUtils.js +72 -2
- package/lib/emulator/controller.js +9 -0
- package/lib/emulator/downloadableEmulators.js +13 -6
- package/lib/emulator/extensions/postinstall.js +41 -0
- package/lib/emulator/functionsEmulator.js +8 -18
- package/lib/emulator/functionsEmulatorShared.js +41 -19
- package/lib/emulator/shared/request.js +19 -0
- package/lib/emulator/storage/apis/firebase.js +25 -33
- package/lib/emulator/storage/apis/gcloud.js +78 -63
- package/lib/emulator/storage/files.js +48 -52
- package/lib/emulator/storage/index.js +23 -3
- package/lib/emulator/storage/metadata.js +18 -8
- package/lib/emulator/storage/rules/manager.js +7 -17
- package/lib/emulator/storage/rules/utils.js +11 -3
- package/lib/emulator/storage/server.js +38 -12
- package/lib/extensions/askUserForParam.js +25 -20
- package/lib/extensions/emulator/optionsHelper.js +5 -7
- package/lib/extensions/emulator/triggerHelper.js +11 -14
- package/lib/extensions/extensionsApi.js +2 -1
- package/lib/extensions/extensionsHelper.js +11 -2
- package/lib/extensions/manifest.js +1 -1
- package/lib/extensions/paramHelper.js +23 -13
- package/lib/functions/env.js +10 -2
- package/lib/functions/runtimeConfigExport.js +10 -6
- package/lib/functions/secrets.js +99 -6
- package/lib/gcp/cloudfunctions.js +6 -13
- package/lib/gcp/cloudfunctionsv2.js +14 -23
- package/lib/gcp/cloudtasks.js +5 -3
- package/lib/gcp/secretManager.js +1 -1
- package/lib/requirePermissions.js +4 -1
- package/lib/utils.js +30 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/emulator/storage/list.js +0 -18
|
@@ -1,32 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.functionResourceToEmulatedTriggerDefintion = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const functionsEmulatorShared_1 = require("../../emulator/functionsEmulatorShared");
|
|
6
5
|
const emulatorLogger_1 = require("../../emulator/emulatorLogger");
|
|
7
6
|
const types_1 = require("../../emulator/types");
|
|
7
|
+
const proto = require("../../gcp/proto");
|
|
8
8
|
function functionResourceToEmulatedTriggerDefintion(resource) {
|
|
9
9
|
const etd = {
|
|
10
10
|
name: resource.name,
|
|
11
11
|
entryPoint: resource.name,
|
|
12
12
|
platform: "gcfv1",
|
|
13
13
|
};
|
|
14
|
-
const properties =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (properties.location) {
|
|
19
|
-
etd.regions = [properties.location];
|
|
20
|
-
}
|
|
21
|
-
if (properties.availableMemoryMb) {
|
|
22
|
-
etd.availableMemoryMb = properties.availableMemoryMb;
|
|
23
|
-
}
|
|
14
|
+
const properties = resource.properties || {};
|
|
15
|
+
proto.renameIfPresent(etd, properties, "timeoutSeconds", "timeout", proto.secondsFromDuration);
|
|
16
|
+
proto.renameIfPresent(etd, properties, "regions", "location", (str) => [str]);
|
|
17
|
+
proto.copyIfPresent(etd, properties, "availableMemoryMb");
|
|
24
18
|
if (properties.httpsTrigger) {
|
|
25
19
|
etd.httpsTrigger = properties.httpsTrigger;
|
|
26
20
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
if (properties.eventTrigger) {
|
|
22
|
+
etd.eventTrigger = {
|
|
23
|
+
eventType: properties.eventTrigger.eventType,
|
|
24
|
+
resource: properties.eventTrigger.resource,
|
|
25
|
+
service: (0, functionsEmulatorShared_1.getServiceFromEventType)(properties.eventTrigger.eventType),
|
|
26
|
+
};
|
|
30
27
|
}
|
|
31
28
|
else {
|
|
32
29
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.FUNCTIONS).log("WARN", `Function '${resource.name} is missing a trigger in extension.yaml. Please add one, as triggers defined in code are ignored.`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.getPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.Visibility = exports.RegistryLaunchStage = void 0;
|
|
3
|
+
exports.getExtension = exports.deleteExtension = exports.unpublishExtension = exports.publishExtensionVersion = exports.undeprecateExtensionVersion = exports.deprecateExtensionVersion = exports.registerPublisherProfile = exports.getPublisherProfile = exports.listExtensionVersions = exports.listExtensions = exports.getExtensionVersion = exports.getSource = exports.createSource = exports.updateInstanceFromRegistry = exports.updateInstance = exports.configureInstance = exports.listInstances = exports.getInstance = exports.deleteInstance = exports.createInstance = exports.ParamType = exports.FUNCTIONS_RESOURCE_TYPE = exports.Visibility = exports.RegistryLaunchStage = void 0;
|
|
4
4
|
const yaml = require("js-yaml");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const { marked } = require("marked");
|
|
@@ -26,6 +26,7 @@ var Visibility;
|
|
|
26
26
|
Visibility["UNLISTED"] = "unlisted";
|
|
27
27
|
Visibility["PUBLIC"] = "public";
|
|
28
28
|
})(Visibility = exports.Visibility || (exports.Visibility = {}));
|
|
29
|
+
exports.FUNCTIONS_RESOURCE_TYPE = "firebaseextensions.v1beta.function";
|
|
29
30
|
var ParamType;
|
|
30
31
|
(function (ParamType) {
|
|
31
32
|
ParamType["STRING"] = "STRING";
|
|
@@ -80,6 +80,9 @@ function getDBInstanceFromURL(databaseUrl = "") {
|
|
|
80
80
|
exports.getDBInstanceFromURL = getDBInstanceFromURL;
|
|
81
81
|
async function getFirebaseProjectParams(projectId, emulatorMode = false) {
|
|
82
82
|
var _a, _b;
|
|
83
|
+
if (!projectId) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
83
86
|
const body = emulatorMode
|
|
84
87
|
? await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)
|
|
85
88
|
: await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
|
|
@@ -263,7 +266,10 @@ async function promptForValidInstanceId(instanceId) {
|
|
|
263
266
|
}
|
|
264
267
|
exports.promptForValidInstanceId = promptForValidInstanceId;
|
|
265
268
|
async function ensureExtensionsApiEnabled(options) {
|
|
266
|
-
const projectId = (0, projectUtils_1.
|
|
269
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
270
|
+
if (!projectId) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
267
273
|
return await (0, ensureApiEnabled_1.ensure)(projectId, "firebaseextensions.googleapis.com", "extensions", options.markdown);
|
|
268
274
|
}
|
|
269
275
|
exports.ensureExtensionsApiEnabled = ensureExtensionsApiEnabled;
|
|
@@ -519,7 +525,10 @@ async function confirm(args) {
|
|
|
519
525
|
}
|
|
520
526
|
exports.confirm = confirm;
|
|
521
527
|
async function diagnoseAndFixProject(options) {
|
|
522
|
-
const projectId = (0, projectUtils_1.
|
|
528
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
529
|
+
if (!projectId) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
523
532
|
const ok = await (0, diagnose_1.diagnose)(projectId);
|
|
524
533
|
if (!ok) {
|
|
525
534
|
throw new error_1.FirebaseError("Unable to proceed until all issues are resolved.");
|
|
@@ -170,7 +170,7 @@ function showDeprecationWarning() {
|
|
|
170
170
|
}
|
|
171
171
|
exports.showDeprecationWarning = showDeprecationWarning;
|
|
172
172
|
function showPreviewWarning() {
|
|
173
|
-
utils.logLabeledWarning(extensionsHelper_1.logPrefix,
|
|
173
|
+
utils.logLabeledWarning(extensionsHelper_1.logPrefix, `See these changes in your Firebase Emulator by running "firebase emulators:start". ` +
|
|
174
174
|
`Run ${clc.bold("firebase deploy (--only extensions)")} to deploy the changes to your Firebase project. `);
|
|
175
175
|
}
|
|
176
176
|
exports.showPreviewWarning = showPreviewWarning;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
|
|
4
|
-
const _ = require("lodash");
|
|
5
4
|
const path = require("path");
|
|
6
5
|
const clc = require("cli-color");
|
|
7
6
|
const fs = require("fs-extra");
|
|
@@ -11,6 +10,7 @@ const extensionsHelper_1 = require("./extensionsHelper");
|
|
|
11
10
|
const askUserForParam = require("./askUserForParam");
|
|
12
11
|
const track = require("../track");
|
|
13
12
|
const env = require("../functions/env");
|
|
13
|
+
const utils_1 = require("../utils");
|
|
14
14
|
function getBaseParamBindings(params) {
|
|
15
15
|
let ret = {};
|
|
16
16
|
for (const [k, v] of Object.entries(params)) {
|
|
@@ -37,8 +37,9 @@ function setNewDefaults(params, newDefaults) {
|
|
|
37
37
|
}
|
|
38
38
|
exports.setNewDefaults = setNewDefaults;
|
|
39
39
|
function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
|
|
40
|
-
|
|
41
|
-
const
|
|
40
|
+
var _a, _b, _c, _d;
|
|
41
|
+
const specParams = (0, utils_1.cloneDeep)(((_c = (_b = (_a = extensionInstance === null || extensionInstance === void 0 ? void 0 : extensionInstance.config) === null || _a === void 0 ? void 0 : _a.source) === null || _b === void 0 ? void 0 : _b.spec) === null || _c === void 0 ? void 0 : _c.params) || []);
|
|
42
|
+
const currentParams = (0, utils_1.cloneDeep)(((_d = extensionInstance === null || extensionInstance === void 0 ? void 0 : extensionInstance.config) === null || _d === void 0 ? void 0 : _d.params) || {});
|
|
42
43
|
return setNewDefaults(specParams, currentParams);
|
|
43
44
|
}
|
|
44
45
|
exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
|
|
@@ -57,16 +58,22 @@ async function getParams(args) {
|
|
|
57
58
|
}
|
|
58
59
|
else if (args.paramsEnvPath) {
|
|
59
60
|
params = getParamsFromFile({
|
|
60
|
-
projectId: args.projectId,
|
|
61
61
|
paramSpecs: args.paramSpecs,
|
|
62
62
|
paramsEnvPath: args.paramsEnvPath,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
65
|
else {
|
|
66
66
|
const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
|
|
67
|
-
params = await askUserForParam.ask(
|
|
67
|
+
params = await askUserForParam.ask({
|
|
68
|
+
projectId: args.projectId,
|
|
69
|
+
instanceId: args.instanceId,
|
|
70
|
+
paramSpecs: args.paramSpecs,
|
|
71
|
+
firebaseProjectParams,
|
|
72
|
+
reconfiguring: !!args.reconfiguring,
|
|
73
|
+
});
|
|
68
74
|
}
|
|
69
|
-
|
|
75
|
+
const paramNames = Object.keys(params);
|
|
76
|
+
void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
|
|
70
77
|
return params;
|
|
71
78
|
}
|
|
72
79
|
exports.getParams = getParams;
|
|
@@ -85,7 +92,6 @@ async function getParamsForUpdate(args) {
|
|
|
85
92
|
}
|
|
86
93
|
else if (args.paramsEnvPath) {
|
|
87
94
|
params = getParamsFromFile({
|
|
88
|
-
projectId: args.projectId,
|
|
89
95
|
paramSpecs: args.newSpec.params,
|
|
90
96
|
paramsEnvPath: args.paramsEnvPath,
|
|
91
97
|
});
|
|
@@ -99,27 +105,31 @@ async function getParamsForUpdate(args) {
|
|
|
99
105
|
instanceId: args.instanceId,
|
|
100
106
|
});
|
|
101
107
|
}
|
|
102
|
-
|
|
108
|
+
const paramNames = Object.keys(params);
|
|
109
|
+
void track("Extension Params", paramNames.length ? "Not Present" : "Present", paramNames.length);
|
|
103
110
|
return params;
|
|
104
111
|
}
|
|
105
112
|
exports.getParamsForUpdate = getParamsForUpdate;
|
|
106
113
|
async function promptForNewParams(args) {
|
|
107
114
|
const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
|
|
108
115
|
const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
|
|
109
|
-
const
|
|
116
|
+
const sameParam = (param1) => (param2) => {
|
|
110
117
|
return param1.type === param2.type && param1.param === param2.param;
|
|
111
118
|
};
|
|
119
|
+
const paramDiff = (left, right) => {
|
|
120
|
+
return left.filter((aLeft) => !right.find(sameParam(aLeft)));
|
|
121
|
+
};
|
|
112
122
|
const oldParams = args.spec.params.filter((p) => Object.keys(args.currentParams).includes(p.param));
|
|
113
|
-
let paramsDiffDeletions =
|
|
123
|
+
let paramsDiffDeletions = paramDiff(oldParams, args.newSpec.params);
|
|
114
124
|
paramsDiffDeletions = (0, extensionsHelper_1.substituteParams)(paramsDiffDeletions, firebaseProjectParams);
|
|
115
|
-
let paramsDiffAdditions =
|
|
125
|
+
let paramsDiffAdditions = paramDiff(args.newSpec.params, oldParams);
|
|
116
126
|
paramsDiffAdditions = (0, extensionsHelper_1.substituteParams)(paramsDiffAdditions, firebaseProjectParams);
|
|
117
127
|
if (paramsDiffDeletions.length) {
|
|
118
128
|
logger_1.logger.info("The following params will no longer be used:");
|
|
119
|
-
|
|
129
|
+
for (const param of paramsDiffDeletions) {
|
|
120
130
|
logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
|
|
121
131
|
delete newParamBindingOptions[param.param.toUpperCase()];
|
|
122
|
-
}
|
|
132
|
+
}
|
|
123
133
|
}
|
|
124
134
|
if (paramsDiffAdditions.length) {
|
|
125
135
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
package/lib/functions/env.js
CHANGED
|
@@ -42,6 +42,15 @@ const LINE_RE = new RegExp("^" +
|
|
|
42
42
|
"\\s*" +
|
|
43
43
|
"(?:#[^\\n]*)?" +
|
|
44
44
|
"$", "gms");
|
|
45
|
+
const ESCAPE_SEQUENCES_TO_CHARACTERS = {
|
|
46
|
+
"\\n": "\n",
|
|
47
|
+
"\\r": "\r",
|
|
48
|
+
"\\t": "\t",
|
|
49
|
+
"\\v": "\v",
|
|
50
|
+
"\\\\": "\\",
|
|
51
|
+
"\\'": "'",
|
|
52
|
+
'\\"': '"',
|
|
53
|
+
};
|
|
45
54
|
function parse(data) {
|
|
46
55
|
const envs = {};
|
|
47
56
|
const errors = [];
|
|
@@ -54,8 +63,7 @@ function parse(data) {
|
|
|
54
63
|
if ((quotesMatch = /^(["'])(.*)\1$/ms.exec(v)) != null) {
|
|
55
64
|
v = quotesMatch[2];
|
|
56
65
|
if (quotesMatch[1] === '"') {
|
|
57
|
-
v = v.replace(
|
|
58
|
-
v = v.replace(/\\([\\'"])/g, "$1");
|
|
66
|
+
v = v.replace(/\\[nrtv\\'"]/g, (match) => ESCAPE_SEQUENCES_TO_CHARACTERS[match]);
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
envs[k] = v;
|
|
@@ -115,13 +115,17 @@ function hydrateEnvs(pInfos, prefix) {
|
|
|
115
115
|
return errMsg;
|
|
116
116
|
}
|
|
117
117
|
exports.hydrateEnvs = hydrateEnvs;
|
|
118
|
+
const CHARACTERS_TO_ESCAPE_SEQUENCES = {
|
|
119
|
+
"\n": "\\n",
|
|
120
|
+
"\r": "\\r",
|
|
121
|
+
"\t": "\\t",
|
|
122
|
+
"\v": "\\v",
|
|
123
|
+
"\\": "\\\\",
|
|
124
|
+
'"': '\\"',
|
|
125
|
+
"'": "\\'",
|
|
126
|
+
};
|
|
118
127
|
function escape(s) {
|
|
119
|
-
|
|
120
|
-
.replace("\n", "\\n")
|
|
121
|
-
.replace("\r", "\\r")
|
|
122
|
-
.replace("\t", "\\t")
|
|
123
|
-
.replace("\v", "\\v");
|
|
124
|
-
return result.replace(/(['"])/g, "\\$1");
|
|
128
|
+
return s.replace(/[\n\r\t\v\\"']/g, (ch) => CHARACTERS_TO_ESCAPE_SEQUENCES[ch]);
|
|
125
129
|
}
|
|
126
130
|
function toDotenvFormat(envs, header = "") {
|
|
127
131
|
const lines = envs.map(({ newKey, value }) => `${newKey}="${escape(value)}"`);
|
package/lib/functions/secrets.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pruneSecrets = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
|
|
3
|
+
exports.updateEndpointSecret = exports.pruneAndDestroySecrets = exports.pruneSecrets = exports.inUse = exports.of = exports.ensureSecret = exports.ensureValidKey = exports.labels = exports.isFirebaseManaged = void 0;
|
|
4
|
+
const utils = require("../utils");
|
|
5
|
+
const poller = require("../operation-poller");
|
|
6
|
+
const gcf = require("../gcp/cloudfunctions");
|
|
4
7
|
const secretManager_1 = require("../gcp/secretManager");
|
|
5
8
|
const error_1 = require("../error");
|
|
6
9
|
const utils_1 = require("../utils");
|
|
7
10
|
const prompt_1 = require("../prompt");
|
|
8
11
|
const env_1 = require("./env");
|
|
12
|
+
const logger_1 = require("../logger");
|
|
13
|
+
const api_1 = require("../api");
|
|
14
|
+
const functional_1 = require("../functional");
|
|
9
15
|
const FIREBASE_MANGED = "firebase-managed";
|
|
10
16
|
function isFirebaseManaged(secret) {
|
|
11
17
|
return Object.keys(secret.labels || []).includes(FIREBASE_MANGED);
|
|
@@ -17,8 +23,7 @@ function labels() {
|
|
|
17
23
|
exports.labels = labels;
|
|
18
24
|
function toUpperSnakeCase(key) {
|
|
19
25
|
return key
|
|
20
|
-
.replace(
|
|
21
|
-
.replace(".", "_")
|
|
26
|
+
.replace(/[.-]/g, "_")
|
|
22
27
|
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
23
28
|
.toUpperCase();
|
|
24
29
|
}
|
|
@@ -80,19 +85,40 @@ function of(endpoints) {
|
|
|
80
85
|
return endpoints.reduce((envs, endpoint) => [...envs, ...(endpoint.secretEnvironmentVariables || [])], []);
|
|
81
86
|
}
|
|
82
87
|
exports.of = of;
|
|
88
|
+
function inUse(projectInfo, secret, endpoint) {
|
|
89
|
+
const { projectId, projectNumber } = projectInfo;
|
|
90
|
+
for (const sev of of([endpoint])) {
|
|
91
|
+
if ((sev.projectId === projectId || sev.projectId === projectNumber) &&
|
|
92
|
+
sev.secret === secret.name) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
exports.inUse = inUse;
|
|
83
99
|
async function pruneSecrets(projectInfo, endpoints) {
|
|
84
100
|
const { projectId, projectNumber } = projectInfo;
|
|
85
101
|
const pruneKey = (name, version) => `${name}@${version}`;
|
|
86
102
|
const prunedSecrets = new Set();
|
|
87
103
|
const haveSecrets = await (0, secretManager_1.listSecrets)(projectId, `labels.${FIREBASE_MANGED}=true`);
|
|
88
104
|
for (const secret of haveSecrets) {
|
|
89
|
-
const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `state:
|
|
105
|
+
const versions = await (0, secretManager_1.listSecretVersions)(projectId, secret.name, `NOT state: DESTROYED`);
|
|
90
106
|
for (const version of versions) {
|
|
91
107
|
prunedSecrets.add(pruneKey(secret.name, version.versionId));
|
|
92
108
|
}
|
|
93
109
|
}
|
|
94
|
-
const
|
|
95
|
-
for (const
|
|
110
|
+
const secrets = [];
|
|
111
|
+
for (const secret of of(endpoints)) {
|
|
112
|
+
if (!secret.version) {
|
|
113
|
+
throw new error_1.FirebaseError(`Secret ${secret.secret} version is unexpectedly empty.`);
|
|
114
|
+
}
|
|
115
|
+
if (secret.projectId === projectId || secret.projectId === projectNumber) {
|
|
116
|
+
if (secret.version) {
|
|
117
|
+
secrets.push(Object.assign(Object.assign({}, secret), { version: secret.version }));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const sev of secrets) {
|
|
96
122
|
let name = sev.secret;
|
|
97
123
|
if (name.includes("/")) {
|
|
98
124
|
const secret = (0, secretManager_1.parseSecretResourceName)(name);
|
|
@@ -110,3 +136,70 @@ async function pruneSecrets(projectInfo, endpoints) {
|
|
|
110
136
|
.map(([secret, version]) => ({ projectId, version, secret, key: secret }));
|
|
111
137
|
}
|
|
112
138
|
exports.pruneSecrets = pruneSecrets;
|
|
139
|
+
async function pruneAndDestroySecrets(projectInfo, endpoints) {
|
|
140
|
+
const { projectId, projectNumber } = projectInfo;
|
|
141
|
+
logger_1.logger.debug("Pruning secrets to find unused secret versions...");
|
|
142
|
+
const unusedSecrets = await module.exports.pruneSecrets({ projectId, projectNumber }, endpoints);
|
|
143
|
+
if (unusedSecrets.length === 0) {
|
|
144
|
+
return { destroyed: [], erred: [] };
|
|
145
|
+
}
|
|
146
|
+
const destroyed = [];
|
|
147
|
+
const erred = [];
|
|
148
|
+
const msg = unusedSecrets.map((s) => `${s.secret}@${s.version}`);
|
|
149
|
+
logger_1.logger.debug(`Found unused secret versions: ${msg}. Destroying them...`);
|
|
150
|
+
const destroyResults = await utils.allSettled(unusedSecrets.map(async (sev) => {
|
|
151
|
+
await (0, secretManager_1.destroySecretVersion)(sev.projectId, sev.secret, sev.version);
|
|
152
|
+
return sev;
|
|
153
|
+
}));
|
|
154
|
+
for (const result of destroyResults) {
|
|
155
|
+
if (result.status === "fulfilled") {
|
|
156
|
+
destroyed.push(result.value);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
erred.push(result.reason);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { destroyed, erred };
|
|
163
|
+
}
|
|
164
|
+
exports.pruneAndDestroySecrets = pruneAndDestroySecrets;
|
|
165
|
+
async function updateEndpointSecret(projectInfo, secretVersion, endpoint) {
|
|
166
|
+
const { projectId, projectNumber } = projectInfo;
|
|
167
|
+
if (!inUse(projectInfo, secretVersion.secret, endpoint)) {
|
|
168
|
+
return endpoint;
|
|
169
|
+
}
|
|
170
|
+
const updatedSevs = [];
|
|
171
|
+
for (const sev of of([endpoint])) {
|
|
172
|
+
const updatedSev = Object.assign({}, sev);
|
|
173
|
+
if ((updatedSev.projectId === projectId || updatedSev.projectId === projectNumber) &&
|
|
174
|
+
updatedSev.secret === secretVersion.secret.name) {
|
|
175
|
+
updatedSev.version = secretVersion.versionId;
|
|
176
|
+
}
|
|
177
|
+
updatedSevs.push(updatedSev);
|
|
178
|
+
}
|
|
179
|
+
if (endpoint.platform === "gcfv1") {
|
|
180
|
+
const fn = gcf.functionFromEndpoint(endpoint, "");
|
|
181
|
+
const op = await gcf.updateFunction({
|
|
182
|
+
name: fn.name,
|
|
183
|
+
runtime: fn.runtime,
|
|
184
|
+
entryPoint: fn.entryPoint,
|
|
185
|
+
secretEnvironmentVariables: updatedSevs,
|
|
186
|
+
});
|
|
187
|
+
const gcfV1PollerOptions = {
|
|
188
|
+
apiOrigin: api_1.functionsOrigin,
|
|
189
|
+
apiVersion: gcf.API_VERSION,
|
|
190
|
+
masterTimeout: 25 * 60 * 1000,
|
|
191
|
+
maxBackoff: 10000,
|
|
192
|
+
pollerName: `update-${endpoint.region}-${endpoint.id}`,
|
|
193
|
+
operationResourceName: op.name,
|
|
194
|
+
};
|
|
195
|
+
const cfn = await poller.pollOperation(gcfV1PollerOptions);
|
|
196
|
+
return gcf.endpointFromFunction(cfn);
|
|
197
|
+
}
|
|
198
|
+
else if (endpoint.platform === "gcfv2") {
|
|
199
|
+
throw new error_1.FirebaseError(`Unsupported platform ${endpoint.platform}`);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
(0, functional_1.assertExhaustive)(endpoint.platform);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
exports.updateEndpointSecret = updateEndpointSecret;
|
|
@@ -232,12 +232,7 @@ function endpointFromFunction(gcfFunction) {
|
|
|
232
232
|
trigger = {
|
|
233
233
|
eventTrigger: {
|
|
234
234
|
eventType: gcfFunction.eventTrigger.eventType,
|
|
235
|
-
eventFilters:
|
|
236
|
-
{
|
|
237
|
-
attribute: "resource",
|
|
238
|
-
value: gcfFunction.eventTrigger.resource,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
235
|
+
eventFilters: { resource: gcfFunction.eventTrigger.resource },
|
|
241
236
|
retry: !!((_e = gcfFunction.eventTrigger.failurePolicy) === null || _e === void 0 ? void 0 : _e.retry),
|
|
242
237
|
},
|
|
243
238
|
};
|
|
@@ -254,7 +249,8 @@ function endpointFromFunction(gcfFunction) {
|
|
|
254
249
|
if (securityLevel) {
|
|
255
250
|
endpoint.securityLevel = securityLevel;
|
|
256
251
|
}
|
|
257
|
-
proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "
|
|
252
|
+
proto.copyIfPresent(endpoint, gcfFunction, "serviceAccountEmail", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "labels", "environmentVariables", "secretEnvironmentVariables", "sourceUploadUrl");
|
|
253
|
+
proto.renameIfPresent(endpoint, gcfFunction, "timeoutSeconds", "timeout", proto.secondsFromDuration);
|
|
258
254
|
if (gcfFunction.vpcConnector) {
|
|
259
255
|
endpoint.vpc = { connector: gcfFunction.vpcConnector };
|
|
260
256
|
proto.renameIfPresent(endpoint.vpc, gcfFunction, "egressSettings", "vpcConnectorEgressSettings");
|
|
@@ -278,13 +274,9 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
|
|
|
278
274
|
};
|
|
279
275
|
proto.copyIfPresent(gcfFunction, endpoint, "labels");
|
|
280
276
|
if (backend.isEventTriggered(endpoint)) {
|
|
281
|
-
const resourceFilter = backend.findEventFilter(endpoint, "resource");
|
|
282
|
-
if (!resourceFilter) {
|
|
283
|
-
throw new error_1.FirebaseError("Invalid event trigger definition. Expected event filter with 'resource' attribute.");
|
|
284
|
-
}
|
|
285
277
|
gcfFunction.eventTrigger = {
|
|
286
278
|
eventType: endpoint.eventTrigger.eventType,
|
|
287
|
-
resource:
|
|
279
|
+
resource: endpoint.eventTrigger.eventFilters.resource,
|
|
288
280
|
};
|
|
289
281
|
gcfFunction.eventTrigger.failurePolicy = endpoint.eventTrigger.retry
|
|
290
282
|
? { retry: {} }
|
|
@@ -311,7 +303,8 @@ function functionFromEndpoint(endpoint, sourceUploadUrl) {
|
|
|
311
303
|
gcfFunction.httpsTrigger.securityLevel = endpoint.securityLevel;
|
|
312
304
|
}
|
|
313
305
|
}
|
|
314
|
-
proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "
|
|
306
|
+
proto.copyIfPresent(gcfFunction, endpoint, "serviceAccountEmail", "availableMemoryMb", "minInstances", "maxInstances", "ingressSettings", "environmentVariables", "secretEnvironmentVariables");
|
|
307
|
+
proto.renameIfPresent(gcfFunction, endpoint, "timeout", "timeoutSeconds", proto.durationFromSeconds);
|
|
315
308
|
if (endpoint.vpc) {
|
|
316
309
|
proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnector", "connector");
|
|
317
310
|
proto.renameIfPresent(gcfFunction, endpoint.vpc, "vpcConnectorEgressSettings", "egressSettings");
|
|
@@ -166,9 +166,8 @@ function functionFromEndpoint(endpoint, source) {
|
|
|
166
166
|
serviceConfig: {},
|
|
167
167
|
};
|
|
168
168
|
proto.copyIfPresent(gcfFunction, endpoint, "labels");
|
|
169
|
-
proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "serviceAccountEmail", "ingressSettings");
|
|
169
|
+
proto.copyIfPresent(gcfFunction.serviceConfig, endpoint, "environmentVariables", "serviceAccountEmail", "ingressSettings", "timeoutSeconds");
|
|
170
170
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "availableMemory", "availableMemoryMb", (mb) => `${mb}M`);
|
|
171
|
-
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "timeoutSeconds", "timeout", proto.secondsFromDuration);
|
|
172
171
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "minInstanceCount", "minInstances");
|
|
173
172
|
proto.renameIfPresent(gcfFunction.serviceConfig, endpoint, "maxInstanceCount", "maxInstances");
|
|
174
173
|
if (endpoint.vpc) {
|
|
@@ -180,23 +179,19 @@ function functionFromEndpoint(endpoint, source) {
|
|
|
180
179
|
eventType: endpoint.eventTrigger.eventType,
|
|
181
180
|
};
|
|
182
181
|
if (gcfFunction.eventTrigger.eventType === v2_1.PUBSUB_PUBLISH_EVENT) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
gcfFunction.eventTrigger.pubsubTopic = pubsubFilter.value;
|
|
188
|
-
for (const filter of endpoint.eventTrigger.eventFilters) {
|
|
189
|
-
if (filter.attribute === "topic") {
|
|
182
|
+
gcfFunction.eventTrigger.pubsubTopic = endpoint.eventTrigger.eventFilters.topic;
|
|
183
|
+
gcfFunction.eventTrigger.eventFilters = [];
|
|
184
|
+
for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
|
|
185
|
+
if (attribute === "topic")
|
|
190
186
|
continue;
|
|
191
|
-
}
|
|
192
|
-
if (!gcfFunction.eventTrigger.eventFilters) {
|
|
193
|
-
gcfFunction.eventTrigger.eventFilters = [];
|
|
194
|
-
}
|
|
195
|
-
gcfFunction.eventTrigger.eventFilters.push(filter);
|
|
187
|
+
gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
|
|
196
188
|
}
|
|
197
189
|
}
|
|
198
190
|
else {
|
|
199
|
-
gcfFunction.eventTrigger.eventFilters =
|
|
191
|
+
gcfFunction.eventTrigger.eventFilters = [];
|
|
192
|
+
for (const [attribute, value] of Object.entries(endpoint.eventTrigger.eventFilters)) {
|
|
193
|
+
gcfFunction.eventTrigger.eventFilters.push({ attribute, value });
|
|
194
|
+
}
|
|
200
195
|
}
|
|
201
196
|
proto.renameIfPresent(gcfFunction.eventTrigger, endpoint.eventTrigger, "triggerRegion", "region");
|
|
202
197
|
if (endpoint.eventTrigger.retry) {
|
|
@@ -239,19 +234,16 @@ function endpointFromFunction(gcfFunction) {
|
|
|
239
234
|
trigger = {
|
|
240
235
|
eventTrigger: {
|
|
241
236
|
eventType: gcfFunction.eventTrigger.eventType,
|
|
242
|
-
eventFilters:
|
|
237
|
+
eventFilters: {},
|
|
243
238
|
retry: false,
|
|
244
239
|
},
|
|
245
240
|
};
|
|
246
241
|
if (gcfFunction.eventTrigger.pubsubTopic) {
|
|
247
|
-
trigger.eventTrigger.eventFilters.
|
|
248
|
-
attribute: "topic",
|
|
249
|
-
value: gcfFunction.eventTrigger.pubsubTopic,
|
|
250
|
-
});
|
|
242
|
+
trigger.eventTrigger.eventFilters.topic = gcfFunction.eventTrigger.pubsubTopic;
|
|
251
243
|
}
|
|
252
244
|
else {
|
|
253
245
|
for (const { attribute, value } of gcfFunction.eventTrigger.eventFilters || []) {
|
|
254
|
-
trigger.eventTrigger.eventFilters
|
|
246
|
+
trigger.eventTrigger.eventFilters[attribute] = value;
|
|
255
247
|
}
|
|
256
248
|
}
|
|
257
249
|
proto.renameIfPresent(trigger.eventTrigger, gcfFunction.eventTrigger, "region", "triggerRegion");
|
|
@@ -265,9 +257,8 @@ function endpointFromFunction(gcfFunction) {
|
|
|
265
257
|
const endpoint = Object.assign(Object.assign({ platform: "gcfv2", id,
|
|
266
258
|
project,
|
|
267
259
|
region }, trigger), { entryPoint: gcfFunction.buildConfig.entryPoint, runtime: gcfFunction.buildConfig.runtime, uri: gcfFunction.serviceConfig.uri });
|
|
268
|
-
proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", "environmentVariables");
|
|
260
|
+
proto.copyIfPresent(endpoint, gcfFunction.serviceConfig, "serviceAccountEmail", "ingressSettings", "environmentVariables", "timeoutSeconds");
|
|
269
261
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "availableMemoryMb", "availableMemory", megabytes);
|
|
270
|
-
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "timeout", "timeoutSeconds", proto.durationFromSeconds);
|
|
271
262
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "minInstances", "minInstanceCount");
|
|
272
263
|
proto.renameIfPresent(endpoint, gcfFunction.serviceConfig, "maxInstances", "maxInstanceCount");
|
|
273
264
|
proto.copyIfPresent(endpoint, gcfFunction, "labels");
|
package/lib/gcp/cloudtasks.js
CHANGED
|
@@ -13,7 +13,6 @@ const client = new apiv2_1.Client({
|
|
|
13
13
|
exports.DEFAULT_SETTINGS = {
|
|
14
14
|
rateLimits: {
|
|
15
15
|
maxConcurrentDispatches: 1000,
|
|
16
|
-
maxBurstSize: 100,
|
|
17
16
|
maxDispatchesPerSecond: 500,
|
|
18
17
|
},
|
|
19
18
|
state: "RUNNING",
|
|
@@ -133,10 +132,13 @@ exports.queueNameForEndpoint = queueNameForEndpoint;
|
|
|
133
132
|
function queueFromEndpoint(endpoint) {
|
|
134
133
|
const queue = Object.assign(Object.assign({}, JSON.parse(JSON.stringify(exports.DEFAULT_SETTINGS))), { name: queueNameForEndpoint(endpoint) });
|
|
135
134
|
if (endpoint.taskQueueTrigger.rateLimits) {
|
|
136
|
-
proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "
|
|
135
|
+
proto.copyIfPresent(queue.rateLimits, endpoint.taskQueueTrigger.rateLimits, "maxConcurrentDispatches", "maxDispatchesPerSecond");
|
|
137
136
|
}
|
|
138
137
|
if (endpoint.taskQueueTrigger.retryConfig) {
|
|
139
|
-
proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "
|
|
138
|
+
proto.copyIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxAttempts", "maxDoublings");
|
|
139
|
+
proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxRetryDuration", "maxRetrySeconds", proto.durationFromSeconds);
|
|
140
|
+
proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "maxBackoff", "maxBackoffSeconds", proto.durationFromSeconds);
|
|
141
|
+
proto.renameIfPresent(queue.retryConfig, endpoint.taskQueueTrigger.retryConfig, "minBackoff", "minBackoffSeconds", proto.durationFromSeconds);
|
|
140
142
|
}
|
|
141
143
|
return queue;
|
|
142
144
|
}
|
package/lib/gcp/secretManager.js
CHANGED
|
@@ -133,7 +133,7 @@ async function createSecret(projectId, name, labels) {
|
|
|
133
133
|
},
|
|
134
134
|
labels,
|
|
135
135
|
}, { queryParams: { secretId: name } });
|
|
136
|
-
return parseSecretResourceName(createRes.body.name);
|
|
136
|
+
return Object.assign(Object.assign({}, parseSecretResourceName(createRes.body.name)), { labels });
|
|
137
137
|
}
|
|
138
138
|
exports.createSecret = createSecret;
|
|
139
139
|
async function patchSecret(projectId, name, labels) {
|
|
@@ -9,7 +9,10 @@ const error_1 = require("./error");
|
|
|
9
9
|
const iam_1 = require("./gcp/iam");
|
|
10
10
|
const BASE_PERMISSIONS = ["firebase.projects.get"];
|
|
11
11
|
async function requirePermissions(options, permissions = []) {
|
|
12
|
-
const projectId = (0, projectUtils_1.
|
|
12
|
+
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
13
|
+
if (!projectId) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
13
16
|
const requiredPermissions = BASE_PERMISSIONS.concat(permissions).sort();
|
|
14
17
|
await (0, requireAuth_1.requireAuth)(options);
|
|
15
18
|
logger_1.logger.debug(`[iam] checking project ${projectId} for permissions ${JSON.stringify(requiredPermissions)}`);
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
|
|
3
|
+
exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.assertDefined = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.promiseWithSpinner = exports.setupLoggers = exports.tryParse = exports.tryStringify = exports.promiseProps = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.getInheritedOption = exports.consoleUrl = exports.envOverrides = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const url = require("url");
|
|
6
6
|
const clc = require("cli-color");
|
|
@@ -406,3 +406,32 @@ function groupBy(arr, f) {
|
|
|
406
406
|
}, {});
|
|
407
407
|
}
|
|
408
408
|
exports.groupBy = groupBy;
|
|
409
|
+
function cloneArray(arr) {
|
|
410
|
+
return arr.map((e) => cloneDeep(e));
|
|
411
|
+
}
|
|
412
|
+
function cloneObject(obj) {
|
|
413
|
+
const clone = {};
|
|
414
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
415
|
+
clone[k] = cloneDeep(v);
|
|
416
|
+
}
|
|
417
|
+
return clone;
|
|
418
|
+
}
|
|
419
|
+
function cloneDeep(obj) {
|
|
420
|
+
if (typeof obj !== "object" || !obj) {
|
|
421
|
+
return obj;
|
|
422
|
+
}
|
|
423
|
+
if (obj instanceof RegExp) {
|
|
424
|
+
return RegExp(obj, obj.flags);
|
|
425
|
+
}
|
|
426
|
+
if (obj instanceof Date) {
|
|
427
|
+
return new Date(obj);
|
|
428
|
+
}
|
|
429
|
+
if (Array.isArray(obj)) {
|
|
430
|
+
return cloneArray(obj);
|
|
431
|
+
}
|
|
432
|
+
if (obj instanceof Map) {
|
|
433
|
+
return new Map(obj.entries());
|
|
434
|
+
}
|
|
435
|
+
return cloneObject(obj);
|
|
436
|
+
}
|
|
437
|
+
exports.cloneDeep = cloneDeep;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.6.0",
|
|
4
4
|
"lockfileVersion": 2,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "firebase-tools",
|
|
9
|
-
"version": "10.
|
|
9
|
+
"version": "10.6.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@google-cloud/pubsub": "^2.18.4",
|