firebase-tools 10.2.2 → 10.4.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/commands/deploy.js +1 -1
- package/lib/commands/experimental-functions-shell.js +1 -1
- package/lib/commands/ext-configure.js +68 -7
- package/lib/commands/ext-export.js +10 -9
- package/lib/commands/ext-install.js +73 -9
- package/lib/commands/ext-uninstall.js +9 -0
- package/lib/commands/ext-update.js +58 -3
- package/lib/commands/functions-config-export.js +2 -2
- 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 +9 -3
- package/lib/deploy/extensions/planner.js +15 -9
- package/lib/deploy/functions/backend.js +10 -1
- package/lib/deploy/functions/checkIam.js +4 -4
- package/lib/deploy/functions/prepare.js +2 -1
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/planner.js +34 -20
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +6 -1
- package/lib/deploy/functions/runtimes/node/index.js +27 -0
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +36 -13
- package/lib/deploy/functions/services/firebaseAlerts.js +30 -0
- package/lib/deploy/functions/services/index.js +9 -1
- package/lib/deploy/functions/services/storage.js +10 -4
- package/lib/deploy/functions/triggerRegionHelper.js +1 -1
- package/lib/emulator/auth/apiSpec.js +37 -0
- package/lib/emulator/commandUtils.js +2 -2
- package/lib/emulator/constants.js +1 -0
- package/lib/emulator/controller.js +9 -7
- package/lib/emulator/extensions/validation.js +37 -2
- package/lib/emulator/extensionsEmulator.js +47 -9
- package/lib/emulator/functionsEmulator.js +17 -12
- package/lib/emulator/functionsEmulatorShared.js +34 -11
- package/lib/emulator/storage/apis/firebase.js +316 -341
- package/lib/emulator/storage/apis/gcloud.js +238 -113
- package/lib/emulator/storage/crc.js +5 -1
- package/lib/emulator/storage/errors.js +9 -0
- package/lib/emulator/storage/files.js +161 -304
- package/lib/emulator/storage/index.js +25 -74
- package/lib/emulator/storage/metadata.js +63 -49
- package/lib/emulator/storage/multipart.js +62 -0
- package/lib/emulator/storage/persistence.js +78 -0
- package/lib/emulator/storage/rules/config.js +34 -0
- package/lib/emulator/storage/rules/manager.js +98 -0
- package/lib/emulator/storage/rules/runtime.js +4 -0
- package/lib/emulator/storage/rules/utils.js +48 -0
- package/lib/emulator/storage/server.js +2 -2
- package/lib/emulator/storage/upload.js +106 -0
- package/lib/extensions/askUserForParam.js +77 -28
- package/lib/extensions/emulator/optionsHelper.js +35 -3
- package/lib/extensions/extensionsHelper.js +19 -10
- package/lib/extensions/manifest.js +142 -14
- package/lib/extensions/paramHelper.js +32 -9
- package/lib/fsutils.js +14 -1
- package/lib/functions/env.js +4 -6
- package/lib/functions/events/v2.js +11 -0
- package/lib/gcp/cloudfunctions.js +20 -7
- package/lib/gcp/cloudfunctionsv2.js +30 -12
- package/lib/gcp/resourceManager.js +4 -4
- package/lib/requireConfig.js +11 -9
- package/lib/serve/functions.js +2 -1
- package/lib/utils.js +14 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/lib/deploy/extensions/params.js +0 -42
- package/lib/deploy/functions/eventTypes.js +0 -10
- package/lib/prepareUpload.js +0 -44
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UploadService = exports.NotCancellableError = exports.UploadNotActiveError = exports.UploadStatus = exports.UploadType = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
var UploadType;
|
|
7
|
+
(function (UploadType) {
|
|
8
|
+
UploadType[UploadType["MULTIPART"] = 0] = "MULTIPART";
|
|
9
|
+
UploadType[UploadType["RESUMABLE"] = 1] = "RESUMABLE";
|
|
10
|
+
})(UploadType = exports.UploadType || (exports.UploadType = {}));
|
|
11
|
+
var UploadStatus;
|
|
12
|
+
(function (UploadStatus) {
|
|
13
|
+
UploadStatus[UploadStatus["ACTIVE"] = 0] = "ACTIVE";
|
|
14
|
+
UploadStatus[UploadStatus["CANCELLED"] = 1] = "CANCELLED";
|
|
15
|
+
UploadStatus[UploadStatus["FINISHED"] = 2] = "FINISHED";
|
|
16
|
+
})(UploadStatus = exports.UploadStatus || (exports.UploadStatus = {}));
|
|
17
|
+
class UploadNotActiveError extends Error {
|
|
18
|
+
}
|
|
19
|
+
exports.UploadNotActiveError = UploadNotActiveError;
|
|
20
|
+
class NotCancellableError extends Error {
|
|
21
|
+
}
|
|
22
|
+
exports.NotCancellableError = NotCancellableError;
|
|
23
|
+
class UploadService {
|
|
24
|
+
constructor(_persistence) {
|
|
25
|
+
this._persistence = _persistence;
|
|
26
|
+
this.reset();
|
|
27
|
+
}
|
|
28
|
+
reset() {
|
|
29
|
+
this._uploads = new Map();
|
|
30
|
+
}
|
|
31
|
+
multipartUpload(request) {
|
|
32
|
+
const upload = this.startMultipartUpload(request, request.dataRaw.byteLength);
|
|
33
|
+
this._persistence.deleteFile(upload.path, true);
|
|
34
|
+
this._persistence.appendBytes(upload.path, request.dataRaw);
|
|
35
|
+
return upload;
|
|
36
|
+
}
|
|
37
|
+
startMultipartUpload(request, sizeInBytes) {
|
|
38
|
+
const id = (0, uuid_1.v4)();
|
|
39
|
+
const upload = {
|
|
40
|
+
id: (0, uuid_1.v4)(),
|
|
41
|
+
bucketId: request.bucketId,
|
|
42
|
+
objectId: request.objectId,
|
|
43
|
+
type: UploadType.MULTIPART,
|
|
44
|
+
path: this.getStagingFileName(id, request.bucketId, request.objectId),
|
|
45
|
+
status: UploadStatus.FINISHED,
|
|
46
|
+
metadata: JSON.parse(request.metadataRaw),
|
|
47
|
+
size: sizeInBytes,
|
|
48
|
+
authorization: request.authorization,
|
|
49
|
+
};
|
|
50
|
+
this._uploads.set(upload.id, upload);
|
|
51
|
+
return upload;
|
|
52
|
+
}
|
|
53
|
+
startResumableUpload(request) {
|
|
54
|
+
const id = (0, uuid_1.v4)();
|
|
55
|
+
const upload = {
|
|
56
|
+
id: id,
|
|
57
|
+
bucketId: request.bucketId,
|
|
58
|
+
objectId: request.objectId,
|
|
59
|
+
type: UploadType.RESUMABLE,
|
|
60
|
+
path: this.getStagingFileName(id, request.bucketId, request.objectId),
|
|
61
|
+
status: UploadStatus.ACTIVE,
|
|
62
|
+
metadata: JSON.parse(request.metadataRaw),
|
|
63
|
+
size: 0,
|
|
64
|
+
authorization: request.authorization,
|
|
65
|
+
};
|
|
66
|
+
this._uploads.set(upload.id, upload);
|
|
67
|
+
this._persistence.deleteFile(upload.path, true);
|
|
68
|
+
return upload;
|
|
69
|
+
}
|
|
70
|
+
continueResumableUpload(uploadId, dataRaw) {
|
|
71
|
+
const upload = this.getResumableUpload(uploadId);
|
|
72
|
+
if (upload.status !== UploadStatus.ACTIVE) {
|
|
73
|
+
throw new UploadNotActiveError();
|
|
74
|
+
}
|
|
75
|
+
this._persistence.appendBytes(upload.path, dataRaw);
|
|
76
|
+
upload.size += dataRaw.byteLength;
|
|
77
|
+
return upload;
|
|
78
|
+
}
|
|
79
|
+
getResumableUpload(uploadId) {
|
|
80
|
+
const upload = this._uploads.get(uploadId);
|
|
81
|
+
if (!upload || upload.type !== UploadType.RESUMABLE) {
|
|
82
|
+
throw new errors_1.NotFoundError();
|
|
83
|
+
}
|
|
84
|
+
return upload;
|
|
85
|
+
}
|
|
86
|
+
cancelResumableUpload(uploadId) {
|
|
87
|
+
const upload = this.getResumableUpload(uploadId);
|
|
88
|
+
if (upload.status === UploadStatus.FINISHED) {
|
|
89
|
+
throw new NotCancellableError();
|
|
90
|
+
}
|
|
91
|
+
upload.status = UploadStatus.CANCELLED;
|
|
92
|
+
return upload;
|
|
93
|
+
}
|
|
94
|
+
finalizeResumableUpload(uploadId) {
|
|
95
|
+
const upload = this.getResumableUpload(uploadId);
|
|
96
|
+
if (upload.status === UploadStatus.CANCELLED) {
|
|
97
|
+
throw new UploadNotActiveError();
|
|
98
|
+
}
|
|
99
|
+
upload.status = UploadStatus.FINISHED;
|
|
100
|
+
return upload;
|
|
101
|
+
}
|
|
102
|
+
getStagingFileName(uploadId, bucketId, objectId) {
|
|
103
|
+
return encodeURIComponent(`${uploadId}_b_${bucketId}_o_${objectId}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.UploadService = UploadService;
|
|
@@ -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
|
+
while (!secretLocations.length) {
|
|
133
|
+
secretLocations = await promptSecretLocations();
|
|
134
|
+
}
|
|
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,38 @@ 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
|
+
async function promptSecretLocations() {
|
|
159
|
+
return await (0, prompt_1.promptOnce)({
|
|
160
|
+
name: "input",
|
|
161
|
+
type: "checkbox",
|
|
162
|
+
message: "Where would you like to store your secrets? You must select at least one value",
|
|
163
|
+
choices: [
|
|
164
|
+
{
|
|
165
|
+
checked: true,
|
|
166
|
+
name: "Google Cloud Secret Manager",
|
|
167
|
+
value: SecretLocation.CLOUD.toString(),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
checked: false,
|
|
171
|
+
name: "Local file (Only used by Firebase Emulator)",
|
|
172
|
+
value: SecretLocation.LOCAL.toString(),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
async function promptLocalSecret(instanceId, paramSpec) {
|
|
178
|
+
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Configure a local secret value for Extensions Emulator");
|
|
179
|
+
const value = await (0, prompt_1.promptOnce)({
|
|
180
|
+
name: paramSpec.param,
|
|
181
|
+
type: "input",
|
|
182
|
+
message: `This secret will be stored in ./extensions/${instanceId}.secret.local.\n` +
|
|
183
|
+
`Enter value for "${paramSpec.label.trim()}" to be used by Extensions Emulator:`,
|
|
184
|
+
});
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
120
187
|
async function promptReconfigureSecret(projectId, instanceId, paramSpec) {
|
|
121
188
|
const action = await (0, prompt_1.promptOnce)({
|
|
122
189
|
type: "list",
|
|
@@ -210,21 +277,3 @@ function getInquirerDefault(options, def) {
|
|
|
210
277
|
return defaultOption ? defaultOption.label || defaultOption.value : "";
|
|
211
278
|
}
|
|
212
279
|
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.getParams = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
|
|
3
|
+
exports.getParams = exports.getSecretEnvVars = exports.getNonSecretEnv = exports.getExtensionFunctionInfo = exports.buildOptions = void 0;
|
|
4
4
|
const fs = require("fs-extra");
|
|
5
5
|
const _ = require("lodash");
|
|
6
6
|
const path = require("path");
|
|
@@ -8,6 +8,7 @@ const paramHelper = require("../paramHelper");
|
|
|
8
8
|
const specHelper = require("./specHelper");
|
|
9
9
|
const localHelper = require("../localHelper");
|
|
10
10
|
const triggerHelper = require("./triggerHelper");
|
|
11
|
+
const extensionsApi_1 = require("../extensionsApi");
|
|
11
12
|
const extensionsHelper = require("../extensionsHelper");
|
|
12
13
|
const config_1 = require("../../config");
|
|
13
14
|
const error_1 = require("../../error");
|
|
@@ -35,9 +36,9 @@ async function buildOptions(options) {
|
|
|
35
36
|
return options;
|
|
36
37
|
}
|
|
37
38
|
exports.buildOptions = buildOptions;
|
|
38
|
-
async function getExtensionFunctionInfo(extensionDir, instanceId,
|
|
39
|
+
async function getExtensionFunctionInfo(extensionDir, instanceId, paramValues) {
|
|
39
40
|
const spec = await specHelper.readExtensionYaml(extensionDir);
|
|
40
|
-
const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec,
|
|
41
|
+
const functionResources = specHelper.getFunctionResourcesWithParamSubstitution(spec, paramValues);
|
|
41
42
|
const extensionTriggers = functionResources
|
|
42
43
|
.map((r) => triggerHelper.functionResourceToEmulatedTriggerDefintion(r))
|
|
43
44
|
.map((trigger) => {
|
|
@@ -45,12 +46,43 @@ async function getExtensionFunctionInfo(extensionDir, instanceId, params) {
|
|
|
45
46
|
return trigger;
|
|
46
47
|
});
|
|
47
48
|
const nodeMajorVersion = specHelper.getNodeVersion(functionResources);
|
|
49
|
+
const nonSecretEnv = getNonSecretEnv(spec.params, paramValues);
|
|
50
|
+
const secretEnvVariables = getSecretEnvVars(spec.params, paramValues);
|
|
48
51
|
return {
|
|
49
52
|
extensionTriggers,
|
|
50
53
|
nodeMajorVersion,
|
|
54
|
+
nonSecretEnv,
|
|
55
|
+
secretEnvVariables,
|
|
51
56
|
};
|
|
52
57
|
}
|
|
53
58
|
exports.getExtensionFunctionInfo = getExtensionFunctionInfo;
|
|
59
|
+
const isSecretParam = (p) => p.type === extensionsHelper.SpecParamType.SECRET || p.type === extensionsApi_1.ParamType.SECRET;
|
|
60
|
+
function getNonSecretEnv(params, paramValues) {
|
|
61
|
+
const getNonSecretEnv = Object.assign({}, paramValues);
|
|
62
|
+
const secretParams = params.filter(isSecretParam);
|
|
63
|
+
for (const p of secretParams) {
|
|
64
|
+
delete getNonSecretEnv[p.param];
|
|
65
|
+
}
|
|
66
|
+
return getNonSecretEnv;
|
|
67
|
+
}
|
|
68
|
+
exports.getNonSecretEnv = getNonSecretEnv;
|
|
69
|
+
function getSecretEnvVars(params, paramValues) {
|
|
70
|
+
const secretEnvVar = [];
|
|
71
|
+
const secretParams = params.filter(isSecretParam);
|
|
72
|
+
for (const s of secretParams) {
|
|
73
|
+
if (paramValues[s.param]) {
|
|
74
|
+
const [, projectId, , secret, , version] = paramValues[s.param].split("/");
|
|
75
|
+
secretEnvVar.push({
|
|
76
|
+
key: s.param,
|
|
77
|
+
secret,
|
|
78
|
+
projectId,
|
|
79
|
+
version,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return secretEnvVar;
|
|
84
|
+
}
|
|
85
|
+
exports.getSecretEnvVars = getSecretEnvVars;
|
|
54
86
|
function getParams(options, extensionSpec) {
|
|
55
87
|
const projectId = (0, projectUtils_1.needProjectId)(options);
|
|
56
88
|
const userParams = paramHelper.readEnvFile(options.testParams);
|
|
@@ -14,6 +14,7 @@ const api_1 = require("../api");
|
|
|
14
14
|
const archiveDirectory_1 = require("../archiveDirectory");
|
|
15
15
|
const utils_1 = require("./utils");
|
|
16
16
|
const functionsConfig_1 = require("../functionsConfig");
|
|
17
|
+
const adminSdkConfig_1 = require("../emulator/adminSdkConfig");
|
|
17
18
|
const resolveSource_1 = require("./resolveSource");
|
|
18
19
|
const error_1 = require("../error");
|
|
19
20
|
const diagnose_1 = require("./diagnose");
|
|
@@ -29,6 +30,7 @@ const logger_1 = require("../logger");
|
|
|
29
30
|
const utils_2 = require("../utils");
|
|
30
31
|
const changelog_1 = require("./changelog");
|
|
31
32
|
const getProjectNumber_1 = require("../getProjectNumber");
|
|
33
|
+
const constants_1 = require("../emulator/constants");
|
|
32
34
|
var SpecParamType;
|
|
33
35
|
(function (SpecParamType) {
|
|
34
36
|
SpecParamType["SELECT"] = "select";
|
|
@@ -76,21 +78,28 @@ function getDBInstanceFromURL(databaseUrl = "") {
|
|
|
76
78
|
return "";
|
|
77
79
|
}
|
|
78
80
|
exports.getDBInstanceFromURL = getDBInstanceFromURL;
|
|
79
|
-
async function getFirebaseProjectParams(projectId) {
|
|
80
|
-
|
|
81
|
-
const
|
|
81
|
+
async function getFirebaseProjectParams(projectId, emulatorMode = false) {
|
|
82
|
+
var _a, _b;
|
|
83
|
+
const body = emulatorMode
|
|
84
|
+
? await (0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)
|
|
85
|
+
: await (0, functionsConfig_1.getFirebaseConfig)({ project: projectId });
|
|
86
|
+
const projectNumber = emulatorMode && constants_1.Constants.isDemoProject(projectId)
|
|
87
|
+
? constants_1.Constants.FAKE_PROJECT_NUMBER
|
|
88
|
+
: await (0, getProjectNumber_1.getProjectNumber)({ projectId });
|
|
89
|
+
const databaseURL = (_a = body === null || body === void 0 ? void 0 : body.databaseURL) !== null && _a !== void 0 ? _a : `https://${projectId}.firebaseio.com`;
|
|
90
|
+
const storageBucket = (_b = body === null || body === void 0 ? void 0 : body.storageBucket) !== null && _b !== void 0 ? _b : `${projectId}.appspot.com`;
|
|
82
91
|
const FIREBASE_CONFIG = JSON.stringify({
|
|
83
|
-
projectId
|
|
84
|
-
databaseURL
|
|
85
|
-
storageBucket
|
|
92
|
+
projectId,
|
|
93
|
+
databaseURL,
|
|
94
|
+
storageBucket,
|
|
86
95
|
});
|
|
87
96
|
return {
|
|
88
|
-
PROJECT_ID:
|
|
97
|
+
PROJECT_ID: projectId,
|
|
89
98
|
PROJECT_NUMBER: projectNumber,
|
|
90
|
-
DATABASE_URL:
|
|
91
|
-
STORAGE_BUCKET:
|
|
99
|
+
DATABASE_URL: databaseURL,
|
|
100
|
+
STORAGE_BUCKET: storageBucket,
|
|
92
101
|
FIREBASE_CONFIG,
|
|
93
|
-
DATABASE_INSTANCE: getDBInstanceFromURL(
|
|
102
|
+
DATABASE_INSTANCE: getDBInstanceFromURL(databaseURL),
|
|
94
103
|
};
|
|
95
104
|
}
|
|
96
105
|
exports.getFirebaseProjectParams = getFirebaseProjectParams;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
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
|
+
const path = require("path");
|
|
5
6
|
const refs = require("./refs");
|
|
7
|
+
const config_1 = require("../config");
|
|
6
8
|
const logger_1 = require("../logger");
|
|
7
9
|
const prompt_1 = require("../prompt");
|
|
8
|
-
|
|
10
|
+
const paramHelper_1 = require("./paramHelper");
|
|
11
|
+
const error_1 = require("../error");
|
|
12
|
+
const utils = require("../utils");
|
|
13
|
+
const extensionsHelper_1 = require("./extensionsHelper");
|
|
14
|
+
const extensionsApi_1 = require("./extensionsApi");
|
|
15
|
+
exports.ENV_DIRECTORY = "extensions";
|
|
16
|
+
async function writeToManifest(specs, config, options, allowOverwrite = false) {
|
|
9
17
|
if (config.has("extensions") &&
|
|
10
18
|
Object.keys(config.get("extensions")).length &&
|
|
11
19
|
!options.nonInteractive &&
|
|
@@ -13,36 +21,156 @@ async function writeToManifest(specs, config, options) {
|
|
|
13
21
|
const currentExtensions = Object.entries(config.get("extensions"))
|
|
14
22
|
.map((i) => `${i[0]}: ${i[1]}`)
|
|
15
23
|
.join("\n\t");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
if (allowOverwrite) {
|
|
25
|
+
const overwrite = await (0, prompt_1.promptOnce)({
|
|
26
|
+
type: "list",
|
|
27
|
+
message: `firebase.json already contains extensions:\n${currentExtensions}\nWould you like to overwrite or merge?`,
|
|
28
|
+
choices: [
|
|
29
|
+
{ name: "Overwrite", value: true },
|
|
30
|
+
{ name: "Merge", value: false },
|
|
31
|
+
],
|
|
32
|
+
});
|
|
33
|
+
if (overwrite) {
|
|
34
|
+
config.set("extensions", {});
|
|
35
|
+
}
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
writeExtensionsToFirebaseJson(specs, config);
|
|
29
39
|
await writeEnvFiles(specs, config, options.force);
|
|
40
|
+
await writeLocalSecrets(specs, config, options.force);
|
|
30
41
|
}
|
|
31
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;
|
|
67
|
+
function removeFromManifest(instanceId, config) {
|
|
68
|
+
if (!instanceExists(instanceId, config)) {
|
|
69
|
+
throw new error_1.FirebaseError(`Extension instance ${instanceId} not found in firebase.json.`);
|
|
70
|
+
}
|
|
71
|
+
const extensions = config.get("extensions", {});
|
|
72
|
+
extensions[instanceId] = undefined;
|
|
73
|
+
config.set("extensions", extensions);
|
|
74
|
+
config.writeProjectFile("firebase.json", config.src);
|
|
75
|
+
logger_1.logger.info(`Removed extension instance ${instanceId} from firebase.json`);
|
|
76
|
+
config.deleteProjectFile(`extensions/${instanceId}.env`);
|
|
77
|
+
logger_1.logger.info(`Removed extension instance environment config extensions/${instanceId}.env`);
|
|
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
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.removeFromManifest = removeFromManifest;
|
|
88
|
+
function loadConfig(options) {
|
|
89
|
+
const existingConfig = config_1.Config.load(options, true);
|
|
90
|
+
if (!existingConfig) {
|
|
91
|
+
throw new error_1.FirebaseError("Not currently in a Firebase directory. Run `firebase init` to create a Firebase directory.");
|
|
92
|
+
}
|
|
93
|
+
return existingConfig;
|
|
94
|
+
}
|
|
95
|
+
exports.loadConfig = loadConfig;
|
|
96
|
+
function instanceExists(instanceId, config) {
|
|
97
|
+
return !!config.get("extensions", {})[instanceId];
|
|
98
|
+
}
|
|
99
|
+
exports.instanceExists = instanceExists;
|
|
100
|
+
function getInstanceRef(instanceId, config) {
|
|
101
|
+
if (!instanceExists(instanceId, config)) {
|
|
102
|
+
throw new error_1.FirebaseError(`Could not find extension instance ${instanceId} in firebase.json`);
|
|
103
|
+
}
|
|
104
|
+
const ref = config.get("extensions", {})[instanceId];
|
|
105
|
+
return refs.parse(ref);
|
|
106
|
+
}
|
|
107
|
+
exports.getInstanceRef = getInstanceRef;
|
|
32
108
|
function writeExtensionsToFirebaseJson(specs, config) {
|
|
33
109
|
const extensions = config.get("extensions", {});
|
|
34
110
|
for (const s of specs) {
|
|
35
111
|
extensions[s.instanceId] = refs.toExtensionVersionRef(s.ref);
|
|
36
112
|
}
|
|
37
113
|
config.set("extensions", extensions);
|
|
38
|
-
logger_1.logger.info("Adding Extensions to " + clc.bold("firebase.json") + "...");
|
|
39
114
|
config.writeProjectFile("firebase.json", config.src);
|
|
115
|
+
utils.logSuccess("Wrote extensions to " + clc.bold("firebase.json") + "...");
|
|
40
116
|
}
|
|
41
117
|
async function writeEnvFiles(specs, config, force) {
|
|
42
118
|
for (const spec of specs) {
|
|
43
119
|
const content = Object.entries(spec.params)
|
|
44
|
-
.
|
|
120
|
+
.sort((a, b) => {
|
|
121
|
+
return a[0].localeCompare(b[0]);
|
|
122
|
+
})
|
|
123
|
+
.map((r) => `${r[0]}=${r[1].baseValue}`)
|
|
45
124
|
.join("\n");
|
|
46
125
|
await config.askWriteProjectFile(`extensions/${spec.instanceId}.env`, content, force);
|
|
47
126
|
}
|
|
48
127
|
}
|
|
128
|
+
function readInstanceParam(args) {
|
|
129
|
+
var _a;
|
|
130
|
+
const aliases = (_a = args.aliases) !== null && _a !== void 0 ? _a : [];
|
|
131
|
+
const filesToCheck = [
|
|
132
|
+
`${args.instanceId}.env`,
|
|
133
|
+
...aliases.map((alias) => `${args.instanceId}.env.${alias}`),
|
|
134
|
+
...(args.projectNumber ? [`${args.instanceId}.env.${args.projectNumber}`] : []),
|
|
135
|
+
...(args.projectId ? [`${args.instanceId}.env.${args.projectId}`] : []),
|
|
136
|
+
];
|
|
137
|
+
if (args.checkLocal) {
|
|
138
|
+
filesToCheck.push(`${args.instanceId}.env.local`);
|
|
139
|
+
}
|
|
140
|
+
let noFilesFound = true;
|
|
141
|
+
const combinedParams = {};
|
|
142
|
+
for (const fileToCheck of filesToCheck) {
|
|
143
|
+
try {
|
|
144
|
+
const params = readParamsFile(args.projectDir, fileToCheck);
|
|
145
|
+
logger_1.logger.debug(`Successfully read params from ${fileToCheck}`);
|
|
146
|
+
noFilesFound = false;
|
|
147
|
+
Object.assign(combinedParams, params);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
logger_1.logger.debug(`${err}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (noFilesFound) {
|
|
154
|
+
throw new error_1.FirebaseError(`No params file found for ${args.instanceId}`);
|
|
155
|
+
}
|
|
156
|
+
return combinedParams;
|
|
157
|
+
}
|
|
158
|
+
exports.readInstanceParam = readInstanceParam;
|
|
159
|
+
function readParamsFile(projectDir, fileName) {
|
|
160
|
+
const paramPath = path.join(projectDir, exports.ENV_DIRECTORY, fileName);
|
|
161
|
+
const params = (0, paramHelper_1.readEnvFile)(paramPath);
|
|
162
|
+
return params;
|
|
163
|
+
}
|
|
164
|
+
function showDeprecationWarning() {
|
|
165
|
+
utils.logLabeledWarning(extensionsHelper_1.logPrefix, "The behavior of ext:install, ext:update, ext:configure, and ext:uninstall will change in firebase-tools@11.0.0. " +
|
|
166
|
+
"Instead of deploying extensions directly, " +
|
|
167
|
+
"changes to extension instances will be written to firebase.json and ./extensions/*.env. " +
|
|
168
|
+
`Then ${clc.bold("firebase deploy (--only extensions)")} will deploy the changes to your Firebase project. ` +
|
|
169
|
+
`To access this behavior now, pass the ${clc.bold("--local")} flag.`);
|
|
170
|
+
}
|
|
171
|
+
exports.showDeprecationWarning = showDeprecationWarning;
|
|
172
|
+
function showPreviewWarning() {
|
|
173
|
+
utils.logLabeledWarning(extensionsHelper_1.logPrefix, "These changes will be reflected in your Firebase Emulator after restart. " +
|
|
174
|
+
`Run ${clc.bold("firebase deploy (--only extensions)")} to deploy the changes to your Firebase project. `);
|
|
175
|
+
}
|
|
176
|
+
exports.showPreviewWarning = showPreviewWarning;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readEnvFile = exports.
|
|
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()]) {
|
|
@@ -19,6 +35,7 @@ function setNewDefaults(params, newDefaults) {
|
|
|
19
35
|
});
|
|
20
36
|
return params;
|
|
21
37
|
}
|
|
38
|
+
exports.setNewDefaults = setNewDefaults;
|
|
22
39
|
function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
|
|
23
40
|
const specParams = _.cloneDeep(_.get(extensionInstance, "config.source.spec.params", []));
|
|
24
41
|
const currentParams = _.cloneDeep(_.get(extensionInstance, "config.params", {}));
|
|
@@ -87,29 +104,36 @@ async function getParamsForUpdate(args) {
|
|
|
87
104
|
}
|
|
88
105
|
exports.getParamsForUpdate = getParamsForUpdate;
|
|
89
106
|
async function promptForNewParams(args) {
|
|
107
|
+
const newParamBindingOptions = buildBindingOptionsWithBaseValue(args.currentParams);
|
|
90
108
|
const firebaseProjectParams = await (0, extensionsHelper_1.getFirebaseProjectParams)(args.projectId);
|
|
91
109
|
const comparer = (param1, param2) => {
|
|
92
110
|
return param1.type === param2.type && param1.param === param2.param;
|
|
93
111
|
};
|
|
94
|
-
|
|
112
|
+
const oldParams = args.spec.params.filter((p) => Object.keys(args.currentParams).includes(p.param));
|
|
113
|
+
let paramsDiffDeletions = _.differenceWith(oldParams, args.newSpec.params, comparer);
|
|
95
114
|
paramsDiffDeletions = (0, extensionsHelper_1.substituteParams)(paramsDiffDeletions, firebaseProjectParams);
|
|
96
|
-
let paramsDiffAdditions = _.differenceWith(args.newSpec.params,
|
|
115
|
+
let paramsDiffAdditions = _.differenceWith(args.newSpec.params, oldParams, comparer);
|
|
97
116
|
paramsDiffAdditions = (0, extensionsHelper_1.substituteParams)(paramsDiffAdditions, firebaseProjectParams);
|
|
98
117
|
if (paramsDiffDeletions.length) {
|
|
99
118
|
logger_1.logger.info("The following params will no longer be used:");
|
|
100
119
|
paramsDiffDeletions.forEach((param) => {
|
|
101
120
|
logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
|
|
102
|
-
delete
|
|
121
|
+
delete newParamBindingOptions[param.param.toUpperCase()];
|
|
103
122
|
});
|
|
104
123
|
}
|
|
105
124
|
if (paramsDiffAdditions.length) {
|
|
106
125
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
|
107
126
|
for (const param of paramsDiffAdditions) {
|
|
108
|
-
const chosenValue = await askUserForParam.askForParam(
|
|
109
|
-
|
|
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;
|
|
110
134
|
}
|
|
111
135
|
}
|
|
112
|
-
return
|
|
136
|
+
return newParamBindingOptions;
|
|
113
137
|
}
|
|
114
138
|
exports.promptForNewParams = promptForNewParams;
|
|
115
139
|
function getParamsFromFile(args) {
|
|
@@ -125,9 +149,8 @@ function getParamsFromFile(args) {
|
|
|
125
149
|
const params = (0, extensionsHelper_1.populateDefaultParams)(envParams, args.paramSpecs);
|
|
126
150
|
(0, extensionsHelper_1.validateCommandLineParams)(params, args.paramSpecs);
|
|
127
151
|
logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
|
|
128
|
-
return params;
|
|
152
|
+
return buildBindingOptionsWithBaseValue(params);
|
|
129
153
|
}
|
|
130
|
-
exports.getParamsFromFile = getParamsFromFile;
|
|
131
154
|
function readEnvFile(envPath) {
|
|
132
155
|
const buf = fs.readFileSync(path.resolve(envPath), "utf8");
|
|
133
156
|
const result = env.parse(buf.toString().trim());
|