firebase-tools 10.3.0 → 10.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/accountExporter.js +95 -84
- package/lib/commands/deploy.js +1 -1
- package/lib/commands/experimental-functions-shell.js +1 -1
- package/lib/commands/ext-configure.js +13 -6
- package/lib/commands/ext-export.js +7 -1
- package/lib/commands/ext-install.js +12 -7
- package/lib/commands/ext-update.js +5 -3
- package/lib/commands/functions-config-export.js +5 -3
- package/lib/commands/functions-shell.js +1 -1
- package/lib/commands/hosting-channel-create.js +2 -2
- package/lib/commands/hosting-channel-delete.js +2 -2
- package/lib/commands/hosting-channel-deploy.js +2 -2
- package/lib/commands/hosting-channel-list.js +2 -2
- package/lib/commands/hosting-channel-open.js +2 -2
- package/lib/commands/hosting-sites-delete.js +2 -2
- package/lib/commands/serve.js +1 -1
- package/lib/commands/target-apply.js +2 -2
- package/lib/commands/target-clear.js +2 -2
- package/lib/commands/target-remove.js +2 -2
- package/lib/commands/target.js +2 -2
- package/lib/config.js +14 -4
- package/lib/deploy/extensions/planner.js +9 -3
- package/lib/deploy/functions/deploy.js +3 -7
- package/lib/deploy/functions/prepare.js +7 -5
- package/lib/deploy/functions/prepareFunctionsUpload.js +7 -13
- package/lib/deploy/functions/release/fabricator.js +13 -1
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +12 -5
- package/lib/deploy/hosting/deploy.js +10 -0
- package/lib/emulator/auth/apiSpec.js +37 -0
- package/lib/emulator/commandUtils.js +2 -2
- package/lib/emulator/controller.js +14 -8
- package/lib/emulator/downloadableEmulators.js +5 -5
- package/lib/emulator/extensionsEmulator.js +3 -0
- package/lib/emulator/functionsEmulator.js +4 -4
- package/lib/emulator/functionsEmulatorShared.js +17 -1
- package/lib/emulator/storage/apis/firebase.js +4 -6
- package/lib/emulator/storage/files.js +5 -5
- package/lib/emulator/storage/index.js +6 -9
- package/lib/emulator/storage/rules/config.js +6 -5
- package/lib/emulator/storage/rules/manager.js +49 -32
- package/lib/emulator/storage/rules/runtime.js +4 -0
- package/lib/emulator/storage/rules/utils.js +2 -2
- package/lib/emulator/storage/server.js +1 -1
- package/lib/extensions/askUserForParam.js +103 -28
- package/lib/extensions/manifest.js +38 -6
- package/lib/extensions/paramHelper.js +28 -6
- package/lib/fsutils.js +14 -1
- package/lib/functions/projectConfig.js +34 -0
- package/lib/gcp/cloudfunctions.js +5 -4
- package/lib/init/features/functions/index.js +4 -2
- package/lib/init/features/hosting/index.js +32 -41
- package/lib/init/features/index.js +22 -12
- package/lib/init/index.js +28 -11
- package/lib/requireConfig.js +11 -9
- package/lib/serve/functions.js +5 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/schema/firebase-config.json +93 -36
- package/lib/prepareUpload.js +0 -44
|
@@ -72,7 +72,7 @@ class StorageLayer {
|
|
|
72
72
|
const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
|
|
73
73
|
let authorized = skipAuth || hasValidDownloadToken;
|
|
74
74
|
if (!authorized) {
|
|
75
|
-
authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
|
|
75
|
+
authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
|
|
76
76
|
}
|
|
77
77
|
if (!authorized) {
|
|
78
78
|
throw new errors_1.ForbiddenError("Failed auth");
|
|
@@ -102,7 +102,7 @@ class StorageLayer {
|
|
|
102
102
|
async handleDeleteObject(request, skipAuth = false) {
|
|
103
103
|
const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
|
|
104
104
|
const authorized = skipAuth ||
|
|
105
|
-
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
|
|
105
|
+
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
|
|
106
106
|
if (!authorized) {
|
|
107
107
|
throw new errors_1.ForbiddenError();
|
|
108
108
|
}
|
|
@@ -134,7 +134,7 @@ class StorageLayer {
|
|
|
134
134
|
async handleUpdateObjectMetadata(request, skipAuth = false) {
|
|
135
135
|
const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
|
|
136
136
|
const authorized = skipAuth ||
|
|
137
|
-
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), types_1.RulesetOperationMethod.UPDATE, {
|
|
137
|
+
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
|
|
138
138
|
before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
|
|
139
139
|
after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
|
|
140
140
|
}, request.authorization));
|
|
@@ -163,7 +163,7 @@ class StorageLayer {
|
|
|
163
163
|
customMetadata: upload.metadata.metadata,
|
|
164
164
|
}, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
|
|
165
165
|
const authorized = skipAuth ||
|
|
166
|
-
(await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
|
|
166
|
+
(await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization));
|
|
167
167
|
if (!authorized) {
|
|
168
168
|
this._persistence.deleteFile(upload.path);
|
|
169
169
|
throw new errors_1.ForbiddenError();
|
|
@@ -210,7 +210,7 @@ class StorageLayer {
|
|
|
210
210
|
async handleListObjects(request, skipAuth = false) {
|
|
211
211
|
var _a, _b, _c;
|
|
212
212
|
const authorized = skipAuth ||
|
|
213
|
-
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), types_1.RulesetOperationMethod.LIST, {}, request.authorization));
|
|
213
|
+
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.prefix].join("/"), request.bucketId, types_1.RulesetOperationMethod.LIST, {}, request.authorization));
|
|
214
214
|
if (!authorized) {
|
|
215
215
|
throw new errors_1.ForbiddenError();
|
|
216
216
|
}
|
|
@@ -18,9 +18,9 @@ class StorageEmulator {
|
|
|
18
18
|
this.args = args;
|
|
19
19
|
this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
|
|
20
20
|
this._rulesRuntime = new runtime_1.StorageRulesRuntime();
|
|
21
|
-
this._rulesManager =
|
|
21
|
+
this._rulesManager = (0, manager_1.createStorageRulesManager)(this.args.rules, this._rulesRuntime);
|
|
22
22
|
this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
|
|
23
|
-
this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)(() => this.
|
|
23
|
+
this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)((resource) => this._rulesManager.getRuleset(resource)), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
|
|
24
24
|
this._uploadService = new upload_1.UploadService(this._persistence);
|
|
25
25
|
}
|
|
26
26
|
get storageLayer() {
|
|
@@ -29,8 +29,8 @@ class StorageEmulator {
|
|
|
29
29
|
get uploadService() {
|
|
30
30
|
return this._uploadService;
|
|
31
31
|
}
|
|
32
|
-
get
|
|
33
|
-
return this._rulesManager
|
|
32
|
+
get rulesManager() {
|
|
33
|
+
return this._rulesManager;
|
|
34
34
|
}
|
|
35
35
|
get logger() {
|
|
36
36
|
return this._logger;
|
|
@@ -43,19 +43,16 @@ class StorageEmulator {
|
|
|
43
43
|
async start() {
|
|
44
44
|
const { host, port } = this.getInfo();
|
|
45
45
|
await this._rulesRuntime.start(this.args.auto_download);
|
|
46
|
-
await this._rulesManager.
|
|
46
|
+
await this._rulesManager.start();
|
|
47
47
|
this._app = await (0, server_1.createApp)(this.args.projectId, this);
|
|
48
48
|
const server = this._app.listen(port, host);
|
|
49
49
|
this.destroyServer = utils.createDestroyer(server);
|
|
50
50
|
}
|
|
51
51
|
async connect() {
|
|
52
52
|
}
|
|
53
|
-
async setRules(rules) {
|
|
54
|
-
return this._rulesManager.setSourceFile(rules);
|
|
55
|
-
}
|
|
56
53
|
async stop() {
|
|
57
54
|
await this._persistence.deleteAll();
|
|
58
|
-
await this._rulesManager.
|
|
55
|
+
await this._rulesManager.stop();
|
|
59
56
|
return this.destroyServer ? this.destroyServer() : Promise.resolve();
|
|
60
57
|
}
|
|
61
58
|
getInfo() {
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getStorageRulesConfig = void 0;
|
|
4
4
|
const error_1 = require("../../../error");
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const fsutils_1 = require("../../../fsutils");
|
|
6
|
+
function getSourceFile(rules, options) {
|
|
7
|
+
const path = options.config.path(rules);
|
|
8
|
+
return { name: path, content: (0, fsutils_1.readFile)(path) };
|
|
7
9
|
}
|
|
8
10
|
function getStorageRulesConfig(projectId, options) {
|
|
9
11
|
const storageConfig = options.config.data.storage;
|
|
@@ -14,8 +16,7 @@ function getStorageRulesConfig(projectId, options) {
|
|
|
14
16
|
if (!storageConfig.rules) {
|
|
15
17
|
throw new error_1.FirebaseError("Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration");
|
|
16
18
|
}
|
|
17
|
-
|
|
18
|
-
return [{ resource, rules: getAbsoluteRulesPath(storageConfig.rules, options) }];
|
|
19
|
+
return getSourceFile(storageConfig.rules, options);
|
|
19
20
|
}
|
|
20
21
|
const results = [];
|
|
21
22
|
const { rc } = options;
|
|
@@ -25,7 +26,7 @@ function getStorageRulesConfig(projectId, options) {
|
|
|
25
26
|
}
|
|
26
27
|
rc.requireTarget(projectId, "storage", targetConfig.target);
|
|
27
28
|
rc.target(projectId, "storage", targetConfig.target).forEach((resource) => {
|
|
28
|
-
results.push({ resource, rules:
|
|
29
|
+
results.push({ resource, rules: getSourceFile(targetConfig.rules, options) });
|
|
29
30
|
});
|
|
30
31
|
}
|
|
31
32
|
return results;
|
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createStorageRulesManager = void 0;
|
|
4
4
|
const chokidar = require("chokidar");
|
|
5
|
-
const fs = require("fs");
|
|
6
5
|
const emulatorLogger_1 = require("../../emulatorLogger");
|
|
7
6
|
const types_1 = require("../../types");
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const runtime_1 = require("./runtime");
|
|
8
|
+
function createStorageRulesManager(rules, runtime) {
|
|
9
|
+
return Array.isArray(rules)
|
|
10
|
+
? new ResourceBasedStorageRulesManager(rules, runtime)
|
|
11
|
+
: new DefaultStorageRulesManager(rules, runtime);
|
|
12
|
+
}
|
|
13
|
+
exports.createStorageRulesManager = createStorageRulesManager;
|
|
14
|
+
class DefaultStorageRulesManager {
|
|
15
|
+
constructor(_rules, _runtime) {
|
|
11
16
|
this._runtime = _runtime;
|
|
12
17
|
this._watcher = new chokidar.FSWatcher();
|
|
13
18
|
this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
|
|
19
|
+
this._rules = _rules;
|
|
20
|
+
}
|
|
21
|
+
start() {
|
|
22
|
+
return this.updateSourceFile(this._rules);
|
|
14
23
|
}
|
|
15
|
-
|
|
24
|
+
getRuleset() {
|
|
16
25
|
return this._ruleset;
|
|
17
26
|
}
|
|
18
|
-
async
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let rulesFile;
|
|
22
|
-
if (typeof rules === "string") {
|
|
23
|
-
this._sourceFile = { name: rules, content: readSourceFile(rules) };
|
|
24
|
-
rulesFile = rules;
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
this._sourceFile = rules;
|
|
28
|
-
rulesFile = rules.name;
|
|
29
|
-
}
|
|
27
|
+
async updateSourceFile(rules) {
|
|
28
|
+
const prevRulesFile = this._rules.name;
|
|
29
|
+
this._rules = rules;
|
|
30
30
|
const issues = await this.loadRuleset();
|
|
31
|
-
this.updateWatcher(
|
|
31
|
+
this.updateWatcher(rules.name, prevRulesFile);
|
|
32
32
|
return issues;
|
|
33
33
|
}
|
|
34
|
-
async
|
|
35
|
-
delete this._sourceFile;
|
|
36
|
-
delete this._ruleset;
|
|
34
|
+
async stop() {
|
|
37
35
|
await this._watcher.close();
|
|
38
36
|
}
|
|
39
37
|
updateWatcher(rulesFile, prevRulesFile) {
|
|
@@ -49,12 +47,11 @@ class StorageRulesManager {
|
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
49
|
async loadRuleset() {
|
|
52
|
-
const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this.
|
|
50
|
+
const { ruleset, issues } = await this._runtime.loadRuleset({ files: [this._rules] });
|
|
53
51
|
if (ruleset) {
|
|
54
52
|
this._ruleset = ruleset;
|
|
55
53
|
return issues;
|
|
56
54
|
}
|
|
57
|
-
delete this._ruleset;
|
|
58
55
|
issues.all.forEach((issue) => {
|
|
59
56
|
try {
|
|
60
57
|
const parsedIssue = JSON.parse(issue);
|
|
@@ -67,15 +64,35 @@ class StorageRulesManager {
|
|
|
67
64
|
return issues;
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
67
|
+
class ResourceBasedStorageRulesManager {
|
|
68
|
+
constructor(_rulesConfig, _runtime) {
|
|
69
|
+
this._runtime = _runtime;
|
|
70
|
+
this._rulesManagers = new Map();
|
|
71
|
+
for (const { resource, rules } of _rulesConfig) {
|
|
72
|
+
this.createRulesManager(resource, rules);
|
|
73
|
+
}
|
|
74
74
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
async start() {
|
|
76
|
+
const allIssues = new runtime_1.StorageRulesIssues();
|
|
77
|
+
for (const rulesManager of this._rulesManagers.values()) {
|
|
78
|
+
allIssues.extend(await rulesManager.start());
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
+
return allIssues;
|
|
81
|
+
}
|
|
82
|
+
getRuleset(resource) {
|
|
83
|
+
var _a;
|
|
84
|
+
return (_a = this._rulesManagers.get(resource)) === null || _a === void 0 ? void 0 : _a.getRuleset();
|
|
85
|
+
}
|
|
86
|
+
updateSourceFile(rules, resource) {
|
|
87
|
+
const rulesManager = this._rulesManagers.get(resource) || this.createRulesManager(resource, rules);
|
|
88
|
+
return rulesManager.updateSourceFile(rules);
|
|
89
|
+
}
|
|
90
|
+
async stop() {
|
|
91
|
+
await Promise.all(Array.from(this._rulesManagers.values(), async (rulesManager) => await rulesManager.stop()));
|
|
92
|
+
}
|
|
93
|
+
createRulesManager(resource, rules) {
|
|
94
|
+
const rulesManager = new DefaultStorageRulesManager(rules, this._runtime);
|
|
95
|
+
this._rulesManagers.set(resource, rulesManager);
|
|
96
|
+
return rulesManager;
|
|
80
97
|
}
|
|
81
98
|
}
|
|
@@ -49,6 +49,10 @@ class StorageRulesIssues {
|
|
|
49
49
|
exist() {
|
|
50
50
|
return !!(this.errors.length || this.warnings.length);
|
|
51
51
|
}
|
|
52
|
+
extend(other) {
|
|
53
|
+
this.errors.push(...other.errors);
|
|
54
|
+
this.warnings.push(...other.warnings);
|
|
55
|
+
}
|
|
52
56
|
}
|
|
53
57
|
exports.StorageRulesIssues = StorageRulesIssues;
|
|
54
58
|
class StorageRulesRuntime {
|
|
@@ -5,9 +5,9 @@ const emulatorLogger_1 = require("../../emulatorLogger");
|
|
|
5
5
|
const types_1 = require("../../types");
|
|
6
6
|
function getRulesValidator(rulesetProvider) {
|
|
7
7
|
return {
|
|
8
|
-
validate: async (path, method, variableOverrides, authorization) => {
|
|
8
|
+
validate: async (path, bucketId, method, variableOverrides, authorization) => {
|
|
9
9
|
return await isPermitted({
|
|
10
|
-
ruleset: rulesetProvider(),
|
|
10
|
+
ruleset: rulesetProvider(bucketId),
|
|
11
11
|
file: variableOverrides,
|
|
12
12
|
path,
|
|
13
13
|
method,
|
|
@@ -58,7 +58,7 @@ function createApp(defaultProjectId, emulator) {
|
|
|
58
58
|
}
|
|
59
59
|
const name = file.name;
|
|
60
60
|
const content = file.content;
|
|
61
|
-
const issues = await emulator.
|
|
61
|
+
const issues = await emulator.rulesManager.updateSourceFile({ name, content }, req.params.bucketId);
|
|
62
62
|
if (issues.errors.length > 0) {
|
|
63
63
|
res.status(400).json({
|
|
64
64
|
message: "There was an error updating rules, see logs for more details",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.getInquirerDefault = exports.promptCreateSecret = exports.askForParam = exports.ask = exports.checkResponse = exports.SecretLocation = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const { marked } = require("marked");
|
|
@@ -12,10 +12,15 @@ const utils_1 = require("./utils");
|
|
|
12
12
|
const logger_1 = require("../logger");
|
|
13
13
|
const prompt_1 = require("../prompt");
|
|
14
14
|
const utils = require("../utils");
|
|
15
|
+
var SecretLocation;
|
|
16
|
+
(function (SecretLocation) {
|
|
17
|
+
SecretLocation[SecretLocation["CLOUD"] = 1] = "CLOUD";
|
|
18
|
+
SecretLocation[SecretLocation["LOCAL"] = 2] = "LOCAL";
|
|
19
|
+
})(SecretLocation = exports.SecretLocation || (exports.SecretLocation = {}));
|
|
15
20
|
var SecretUpdateAction;
|
|
16
21
|
(function (SecretUpdateAction) {
|
|
17
|
-
SecretUpdateAction[SecretUpdateAction["LEAVE"] =
|
|
18
|
-
SecretUpdateAction[SecretUpdateAction["SET_NEW"] =
|
|
22
|
+
SecretUpdateAction[SecretUpdateAction["LEAVE"] = 1] = "LEAVE";
|
|
23
|
+
SecretUpdateAction[SecretUpdateAction["SET_NEW"] = 2] = "SET_NEW";
|
|
19
24
|
})(SecretUpdateAction || (SecretUpdateAction = {}));
|
|
20
25
|
function checkResponse(response, spec) {
|
|
21
26
|
let valid = true;
|
|
@@ -55,9 +60,35 @@ function checkResponse(response, spec) {
|
|
|
55
60
|
return valid;
|
|
56
61
|
}
|
|
57
62
|
exports.checkResponse = checkResponse;
|
|
58
|
-
async function
|
|
63
|
+
async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
|
|
64
|
+
if (_.isEmpty(paramSpecs)) {
|
|
65
|
+
logger_1.logger.debug("No params were specified for this extension.");
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
|
|
69
|
+
const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
|
|
70
|
+
const result = {};
|
|
71
|
+
const promises = _.map(substituted, (paramSpec) => {
|
|
72
|
+
return async () => {
|
|
73
|
+
result[paramSpec.param] = await askForParam({
|
|
74
|
+
projectId,
|
|
75
|
+
instanceId,
|
|
76
|
+
paramSpec,
|
|
77
|
+
reconfiguring,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
|
|
82
|
+
logger_1.logger.info();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
exports.ask = ask;
|
|
86
|
+
async function askForParam(args) {
|
|
87
|
+
const paramSpec = args.paramSpec;
|
|
59
88
|
let valid = false;
|
|
60
89
|
let response = "";
|
|
90
|
+
let responseForLocal;
|
|
91
|
+
let secretLocations = [];
|
|
61
92
|
const description = paramSpec.description || "";
|
|
62
93
|
const label = paramSpec.label.trim();
|
|
63
94
|
logger_1.logger.info(`\n${clc.bold(label)}${clc.bold(paramSpec.required ? "" : " (Optional)")}: ${marked(description).trim()}`);
|
|
@@ -92,16 +123,23 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
|
|
|
92
123
|
}
|
|
93
124
|
},
|
|
94
125
|
message: "Which options do you want enabled for this parameter? " +
|
|
95
|
-
"Press Space to select, then Enter to confirm your choices. "
|
|
96
|
-
"You may select multiple options.",
|
|
126
|
+
"Press Space to select, then Enter to confirm your choices. ",
|
|
97
127
|
choices: (0, utils_1.convertExtensionOptionToLabeledList)(paramSpec.options),
|
|
98
128
|
});
|
|
99
129
|
valid = checkResponse(response, paramSpec);
|
|
100
130
|
break;
|
|
101
131
|
case extensionsApi_1.ParamType.SECRET:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
132
|
+
do {
|
|
133
|
+
secretLocations = await promptSecretLocations(paramSpec);
|
|
134
|
+
} while (!isValidSecretLocations(secretLocations, paramSpec));
|
|
135
|
+
if (secretLocations.includes(SecretLocation.CLOUD.toString())) {
|
|
136
|
+
response = args.reconfiguring
|
|
137
|
+
? await promptReconfigureSecret(args.projectId, args.instanceId, paramSpec)
|
|
138
|
+
: await promptCreateSecret(args.projectId, args.instanceId, paramSpec);
|
|
139
|
+
}
|
|
140
|
+
if (secretLocations.includes(SecretLocation.LOCAL.toString())) {
|
|
141
|
+
responseForLocal = await promptLocalSecret(args.instanceId, paramSpec);
|
|
142
|
+
}
|
|
105
143
|
valid = true;
|
|
106
144
|
break;
|
|
107
145
|
default:
|
|
@@ -114,9 +152,64 @@ async function askForParam(projectId, instanceId, paramSpec, reconfiguring) {
|
|
|
114
152
|
valid = checkResponse(response, paramSpec);
|
|
115
153
|
}
|
|
116
154
|
}
|
|
117
|
-
return response;
|
|
155
|
+
return Object.assign({ baseValue: response }, (responseForLocal ? { local: responseForLocal } : {}));
|
|
118
156
|
}
|
|
119
157
|
exports.askForParam = askForParam;
|
|
158
|
+
function isValidSecretLocations(secretLocations, paramSpec) {
|
|
159
|
+
if (paramSpec.required) {
|
|
160
|
+
return !!secretLocations.length;
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
async function promptSecretLocations(paramSpec) {
|
|
165
|
+
if (paramSpec.required) {
|
|
166
|
+
return await (0, prompt_1.promptOnce)({
|
|
167
|
+
name: "input",
|
|
168
|
+
type: "checkbox",
|
|
169
|
+
message: "Where would you like to store your secrets? You must select at least one value",
|
|
170
|
+
choices: [
|
|
171
|
+
{
|
|
172
|
+
checked: true,
|
|
173
|
+
name: "Google Cloud Secret Manager",
|
|
174
|
+
value: SecretLocation.CLOUD.toString(),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
checked: false,
|
|
178
|
+
name: "Local file (Only used by Firebase Emulator)",
|
|
179
|
+
value: SecretLocation.LOCAL.toString(),
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
return await (0, prompt_1.promptOnce)({
|
|
185
|
+
name: "input",
|
|
186
|
+
type: "checkbox",
|
|
187
|
+
message: "Where would you like to store your secrets? " +
|
|
188
|
+
"If you don't want to set this optional secret, leave both options unselected to skip it",
|
|
189
|
+
choices: [
|
|
190
|
+
{
|
|
191
|
+
checked: false,
|
|
192
|
+
name: "Google Cloud Secret Manager",
|
|
193
|
+
value: SecretLocation.CLOUD.toString(),
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
checked: false,
|
|
197
|
+
name: "Local file (Only used by Firebase Emulator)",
|
|
198
|
+
value: SecretLocation.LOCAL.toString(),
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function promptLocalSecret(instanceId, paramSpec) {
|
|
204
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
|
|
205
|
+
const value = await (0, prompt_1.promptOnce)({
|
|
206
|
+
name: paramSpec.param,
|
|
207
|
+
type: "input",
|
|
208
|
+
message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
|
|
209
|
+
`Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
|
|
210
|
+
});
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
120
213
|
async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
121
214
|
const action = await (0, prompt_1.promptOnce)({
|
|
122
215
|
type: "list",
|
|
@@ -210,21 +303,3 @@ function getInquirerDefault(options, def) {
|
|
|
210
303
|
return defaultOption ? defaultOption.label || defaultOption.value : "";
|
|
211
304
|
}
|
|
212
305
|
exports.getInquirerDefault = getInquirerDefault;
|
|
213
|
-
async function ask(projectId, instanceId, paramSpecs, firebaseProjectParams, reconfiguring) {
|
|
214
|
-
if (_.isEmpty(paramSpecs)) {
|
|
215
|
-
logger_1.logger.debug("No params were specified for this extension.");
|
|
216
|
-
return {};
|
|
217
|
-
}
|
|
218
|
-
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "answer the questions below to configure your extension:");
|
|
219
|
-
const substituted = (0, extensionsHelper_1.substituteParams)(paramSpecs, firebaseProjectParams);
|
|
220
|
-
const result = {};
|
|
221
|
-
const promises = _.map(substituted, (paramSpec) => {
|
|
222
|
-
return async () => {
|
|
223
|
-
result[paramSpec.param] = await askForParam(projectId, instanceId, paramSpec, reconfiguring);
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
await promises.reduce((prev, cur) => prev.then(cur), Promise.resolve());
|
|
227
|
-
logger_1.logger.info();
|
|
228
|
-
return result;
|
|
229
|
-
}
|
|
230
|
-
exports.ask = ask;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeToManifest = void 0;
|
|
3
|
+
exports.showPreviewWarning = exports.showDeprecationWarning = exports.readInstanceParam = exports.getInstanceRef = exports.instanceExists = exports.loadConfig = exports.removeFromManifest = exports.writeLocalSecrets = exports.writeToManifest = exports.ENV_DIRECTORY = void 0;
|
|
4
4
|
const clc = require("cli-color");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const refs = require("./refs");
|
|
@@ -11,7 +11,8 @@ const paramHelper_1 = require("./paramHelper");
|
|
|
11
11
|
const error_1 = require("../error");
|
|
12
12
|
const utils = require("../utils");
|
|
13
13
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
14
|
-
const
|
|
14
|
+
const extensionsApi_1 = require("./extensionsApi");
|
|
15
|
+
exports.ENV_DIRECTORY = "extensions";
|
|
15
16
|
async function writeToManifest(specs, config, options, allowOverwrite = false) {
|
|
16
17
|
if (config.has("extensions") &&
|
|
17
18
|
Object.keys(config.get("extensions")).length &&
|
|
@@ -36,8 +37,33 @@ async function writeToManifest(specs, config, options, allowOverwrite = false) {
|
|
|
36
37
|
}
|
|
37
38
|
writeExtensionsToFirebaseJson(specs, config);
|
|
38
39
|
await writeEnvFiles(specs, config, options.force);
|
|
40
|
+
await writeLocalSecrets(specs, config, options.force);
|
|
39
41
|
}
|
|
40
42
|
exports.writeToManifest = writeToManifest;
|
|
43
|
+
async function writeLocalSecrets(specs, config, force) {
|
|
44
|
+
for (const spec of specs) {
|
|
45
|
+
if (!spec.paramSpecs) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const writeBuffer = {};
|
|
49
|
+
const locallyOverridenSecretParams = spec.paramSpecs.filter((p) => p.type === extensionsApi_1.ParamType.SECRET && spec.params[p.param].local);
|
|
50
|
+
for (const paramSpec of locallyOverridenSecretParams) {
|
|
51
|
+
const key = paramSpec.param;
|
|
52
|
+
const localValue = spec.params[key].local;
|
|
53
|
+
writeBuffer[key] = localValue;
|
|
54
|
+
}
|
|
55
|
+
const content = Object.entries(writeBuffer)
|
|
56
|
+
.sort((a, b) => {
|
|
57
|
+
return a[0].localeCompare(b[0]);
|
|
58
|
+
})
|
|
59
|
+
.map((r) => `${r[0]}=${r[1]}`)
|
|
60
|
+
.join("\n");
|
|
61
|
+
if (content) {
|
|
62
|
+
await config.askWriteProjectFile(`extensions/${spec.instanceId}.secret.local`, content, force);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.writeLocalSecrets = writeLocalSecrets;
|
|
41
67
|
function removeFromManifest(instanceId, config) {
|
|
42
68
|
if (!instanceExists(instanceId, config)) {
|
|
43
69
|
throw new error_1.FirebaseError(`Extension instance ${instanceId} not found in firebase.json.`);
|
|
@@ -49,8 +75,14 @@ function removeFromManifest(instanceId, config) {
|
|
|
49
75
|
logger_1.logger.info(`Removed extension instance ${instanceId} from firebase.json`);
|
|
50
76
|
config.deleteProjectFile(`extensions/${instanceId}.env`);
|
|
51
77
|
logger_1.logger.info(`Removed extension instance environment config extensions/${instanceId}.env`);
|
|
52
|
-
config.
|
|
53
|
-
|
|
78
|
+
if (config.projectFileExists(`extensions/${instanceId}.env.local`)) {
|
|
79
|
+
config.deleteProjectFile(`extensions/${instanceId}.env.local`);
|
|
80
|
+
logger_1.logger.info(`Removed extension instance local environment config extensions/${instanceId}.env.local`);
|
|
81
|
+
}
|
|
82
|
+
if (config.projectFileExists(`extensions/${instanceId}.secret.local`)) {
|
|
83
|
+
config.deleteProjectFile(`extensions/${instanceId}.secret.local`);
|
|
84
|
+
logger_1.logger.info(`Removed extension instance local secret config extensions/${instanceId}.secret.local`);
|
|
85
|
+
}
|
|
54
86
|
}
|
|
55
87
|
exports.removeFromManifest = removeFromManifest;
|
|
56
88
|
function loadConfig(options) {
|
|
@@ -88,7 +120,7 @@ async function writeEnvFiles(specs, config, force) {
|
|
|
88
120
|
.sort((a, b) => {
|
|
89
121
|
return a[0].localeCompare(b[0]);
|
|
90
122
|
})
|
|
91
|
-
.map((r) => `${r[0]}=${r[1]}`)
|
|
123
|
+
.map((r) => `${r[0]}=${r[1].baseValue}`)
|
|
92
124
|
.join("\n");
|
|
93
125
|
await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
|
|
94
126
|
}
|
|
@@ -125,7 +157,7 @@ function readInstanceParam(args) {
|
|
|
125
157
|
}
|
|
126
158
|
exports.readInstanceParam = readInstanceParam;
|
|
127
159
|
function readParamsFile(projectDir, fileName) {
|
|
128
|
-
const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName);
|
|
160
|
+
const paramPath = path.join(projectDir, exports.ENV_DIRECTORY, fileName);
|
|
129
161
|
const params = (0, paramHelper_1.readEnvFile)(paramPath);
|
|
130
162
|
return params;
|
|
131
163
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = void 0;
|
|
3
|
+
exports.readEnvFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = exports.setNewDefaults = exports.buildBindingOptionsWithBaseValue = exports.getBaseParamBindings = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const clc = require("cli-color");
|
|
@@ -11,6 +11,22 @@ const extensionsHelper_1 = require("./extensionsHelper");
|
|
|
11
11
|
const askUserForParam = require("./askUserForParam");
|
|
12
12
|
const track = require("../track");
|
|
13
13
|
const env = require("../functions/env");
|
|
14
|
+
function getBaseParamBindings(params) {
|
|
15
|
+
let ret = {};
|
|
16
|
+
for (const [k, v] of Object.entries(params)) {
|
|
17
|
+
ret = Object.assign(Object.assign({}, ret), { [k]: v.baseValue });
|
|
18
|
+
}
|
|
19
|
+
return ret;
|
|
20
|
+
}
|
|
21
|
+
exports.getBaseParamBindings = getBaseParamBindings;
|
|
22
|
+
function buildBindingOptionsWithBaseValue(baseParams) {
|
|
23
|
+
let paramOptions = {};
|
|
24
|
+
for (const [k, v] of Object.entries(baseParams)) {
|
|
25
|
+
paramOptions = Object.assign(Object.assign({}, paramOptions), { [k]: { baseValue: v } });
|
|
26
|
+
}
|
|
27
|
+
return paramOptions;
|
|
28
|
+
}
|
|
29
|
+
exports.buildBindingOptionsWithBaseValue = buildBindingOptionsWithBaseValue;
|
|
14
30
|
function setNewDefaults(params, newDefaults) {
|
|
15
31
|
params.forEach((param) => {
|
|
16
32
|
if (newDefaults[param.param.toUpperCase()]) {
|
|
@@ -88,6 +104,7 @@ async function getParamsForUpdate(args) {
|
|
|
88
104
|
}
|
|
89
105
|
exports.getParamsForUpdate = getParamsForUpdate;
|
|
90
106
|
async function promptForNewParams(args) {
|
|
107
|
+
const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
|
|
91
108
|
const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
|
|
92
109
|
const comparer = (param1, param2) => {
|
|
93
110
|
return param1.type === param2.type && param1.param === param2.param;
|
|
@@ -101,17 +118,22 @@ async function promptForNewParams(args) {
|
|
|
101
118
|
logger_1.logger.info("The following params will no longer be used:");
|
|
102
119
|
paramsDiffDeletions.forEach((param) => {
|
|
103
120
|
logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
|
|
104
|
-
delete
|
|
121
|
+
delete newParamBindingOptions[param.param.toUpperCase()];
|
|
105
122
|
});
|
|
106
123
|
}
|
|
107
124
|
if (paramsDiffAdditions.length) {
|
|
108
125
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
|
109
126
|
for (const param of paramsDiffAdditions) {
|
|
110
|
-
const chosenValue = await askUserForParam.askForParam(
|
|
111
|
-
|
|
127
|
+
const chosenValue = await askUserForParam.askForParam({
|
|
128
|
+
projectId: args.projectId,
|
|
129
|
+
instanceId: args.instanceId,
|
|
130
|
+
paramSpec: param,
|
|
131
|
+
reconfiguring: false,
|
|
132
|
+
});
|
|
133
|
+
newParamBindingOptions[param.param] = chosenValue;
|
|
112
134
|
}
|
|
113
135
|
}
|
|
114
|
-
return
|
|
136
|
+
return newParamBindingOptions;
|
|
115
137
|
}
|
|
116
138
|
exports.promptForNewParams = promptForNewParams;
|
|
117
139
|
function getParamsFromFile(args) {
|
|
@@ -127,7 +149,7 @@ function getParamsFromFile(args) {
|
|
|
127
149
|
const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
|
|
128
150
|
(0, extensionsHelper_1.validateCommandLineParams)(params, args.paramSpecs);
|
|
129
151
|
logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
|
|
130
|
-
return params;
|
|
152
|
+
return buildBindingOptionsWithBaseValue(params);
|
|
131
153
|
}
|
|
132
154
|
function readEnvFile(envPath) {
|
|
133
155
|
const buf = fs.readFileSync(path.resolve(envPath), "utf8");
|
package/lib/fsutils.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.dirExistsSync = exports.fileExistsSync = void 0;
|
|
3
|
+
exports.readFile = exports.dirExistsSync = exports.fileExistsSync = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
const error_1 = require("./error");
|
|
5
6
|
function fileExistsSync(path) {
|
|
6
7
|
try {
|
|
7
8
|
return (0, fs_1.statSync)(path).isFile();
|
|
@@ -20,3 +21,15 @@ function dirExistsSync(path) {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
exports.dirExistsSync = dirExistsSync;
|
|
24
|
+
function readFile(path) {
|
|
25
|
+
try {
|
|
26
|
+
return (0, fs_1.readFileSync)(path).toString();
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
if (e.code === "ENOENT") {
|
|
30
|
+
throw new error_1.FirebaseError(`File not found: ${path}`);
|
|
31
|
+
}
|
|
32
|
+
throw e;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.readFile = readFile;
|