firebase-tools 9.18.0 → 9.22.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/CHANGELOG.md +3 -6
- package/lib/api.js +3 -0
- package/lib/apiv2.js +8 -5
- package/lib/command.js +1 -1
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +9 -2
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-publish.js +10 -4
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +12 -4
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +50 -13
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +60 -18
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +47 -25
- package/lib/commands/functions-list.js +12 -12
- package/lib/commands/index.js +9 -0
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/planner.js +95 -0
- package/lib/deploy/extensions/prepare.js +103 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +93 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +82 -22
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +62 -27
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +422 -0
- package/lib/deploy/functions/release/index.js +73 -0
- package/lib/deploy/functions/release/planner.js +162 -0
- package/lib/deploy/functions/release/reporter.js +165 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +1 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1788 -403
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +439 -40
- package/lib/emulator/auth/server.js +32 -11
- package/lib/emulator/auth/state.js +205 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +120 -21
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +81 -4
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +2 -1
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/displayExtensionInfo.js +35 -33
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +149 -97
- package/lib/extensions/extensionsHelper.js +36 -32
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +73 -40
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +67 -0
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +33 -47
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +65 -35
- package/lib/gcp/cloudfunctionsv2.js +56 -43
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +132 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +6 -4
- package/schema/firebase-config.json +9 -0
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.confirm = exports.getSourceOrigin = exports.isLocalOrURLPath = exports.isLocalPath = exports.isUrlPath = exports.instanceIdExists = exports.promptForRepeatInstance = exports.promptForOfficialExtension = exports.displayReleaseNotes = exports.getExtensionSourceFromName = exports.createSourceFromLocation = exports.publishExtensionVersionFromLocalSource = exports.ensureExtensionsApiEnabled = exports.promptForValidInstanceId = exports.validateSpec = exports.validateCommandLineParams = exports.populateDefaultParams = exports.substituteParams = exports.getFirebaseProjectParams = exports.getDBInstanceFromURL = exports.resourceTypeToNiceName = exports.AUTOPOULATED_PARAM_PLACEHOLDERS = exports.EXTENSIONS_BUCKET_NAME = exports.URL_REGEX = exports.logPrefix = exports.SourceOrigin = exports.SpecParamType = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const ora = require("ora");
|
|
@@ -21,17 +21,20 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
|
21
21
|
const storage_1 = require("../gcp/storage");
|
|
22
22
|
const projectUtils_1 = require("../projectUtils");
|
|
23
23
|
const extensionsApi_1 = require("./extensionsApi");
|
|
24
|
+
const refs = require("./refs");
|
|
24
25
|
const localHelper_1 = require("./localHelper");
|
|
25
26
|
const prompt_1 = require("../prompt");
|
|
26
27
|
const logger_1 = require("../logger");
|
|
27
28
|
const utils_2 = require("../utils");
|
|
28
29
|
const changelog_1 = require("./changelog");
|
|
30
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
29
31
|
var SpecParamType;
|
|
30
32
|
(function (SpecParamType) {
|
|
31
33
|
SpecParamType["SELECT"] = "select";
|
|
32
34
|
SpecParamType["MULTISELECT"] = "multiSelect";
|
|
33
35
|
SpecParamType["STRING"] = "string";
|
|
34
36
|
SpecParamType["SELECTRESOURCE"] = "selectResource";
|
|
37
|
+
SpecParamType["SECRET"] = "secret";
|
|
35
38
|
})(SpecParamType = exports.SpecParamType || (exports.SpecParamType = {}));
|
|
36
39
|
var SourceOrigin;
|
|
37
40
|
(function (SourceOrigin) {
|
|
@@ -74,6 +77,7 @@ function getDBInstanceFromURL(databaseUrl = "") {
|
|
|
74
77
|
exports.getDBInstanceFromURL = getDBInstanceFromURL;
|
|
75
78
|
async function getFirebaseProjectParams(projectId) {
|
|
76
79
|
const body = await functionsConfig_1.getFirebaseConfig({ project: projectId });
|
|
80
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId });
|
|
77
81
|
const FIREBASE_CONFIG = JSON.stringify({
|
|
78
82
|
projectId: body.projectId,
|
|
79
83
|
databaseURL: body.databaseURL,
|
|
@@ -81,6 +85,7 @@ async function getFirebaseProjectParams(projectId) {
|
|
|
81
85
|
});
|
|
82
86
|
return {
|
|
83
87
|
PROJECT_ID: body.projectId,
|
|
88
|
+
PROJECT_NUMBER: projectNumber,
|
|
84
89
|
DATABASE_URL: body.databaseURL,
|
|
85
90
|
STORAGE_BUCKET: body.storageBucket,
|
|
86
91
|
FIREBASE_CONFIG,
|
|
@@ -106,7 +111,7 @@ function populateDefaultParams(paramVars, paramSpecs) {
|
|
|
106
111
|
const newParams = paramVars;
|
|
107
112
|
for (const param of paramSpecs) {
|
|
108
113
|
if (!paramVars[param.param]) {
|
|
109
|
-
if (param.default != undefined) {
|
|
114
|
+
if (param.default != undefined && param.required) {
|
|
110
115
|
newParams[param.param] = param.default;
|
|
111
116
|
}
|
|
112
117
|
else if (param.required) {
|
|
@@ -260,23 +265,23 @@ async function archiveAndUploadSource(extPath, bucketName) {
|
|
|
260
265
|
const res = await storage_1.uploadObject(zippedSource, bucketName);
|
|
261
266
|
return `/${res.bucket}/${res.object}`;
|
|
262
267
|
}
|
|
263
|
-
async function publishExtensionVersionFromLocalSource(
|
|
264
|
-
const extensionSpec = await localHelper_1.getLocalExtensionSpec(rootDirectory);
|
|
265
|
-
if (extensionSpec.name != extensionId) {
|
|
266
|
-
throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
|
|
268
|
+
async function publishExtensionVersionFromLocalSource(args) {
|
|
269
|
+
const extensionSpec = await localHelper_1.getLocalExtensionSpec(args.rootDirectory);
|
|
270
|
+
if (extensionSpec.name != args.extensionId) {
|
|
271
|
+
throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
|
|
267
272
|
}
|
|
268
273
|
const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
|
|
269
274
|
subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
|
|
270
275
|
validateSpec(subbedSpec);
|
|
271
276
|
let extension;
|
|
272
277
|
try {
|
|
273
|
-
extension = await extensionsApi_1.getExtension(`${publisherId}/${extensionId}`);
|
|
278
|
+
extension = await extensionsApi_1.getExtension(`${args.publisherId}/${args.extensionId}`);
|
|
274
279
|
}
|
|
275
280
|
catch (err) {
|
|
276
281
|
}
|
|
277
282
|
let notes;
|
|
278
283
|
try {
|
|
279
|
-
const changes = changelog_1.getLocalChangelog(rootDirectory);
|
|
284
|
+
const changes = changelog_1.getLocalChangelog(args.rootDirectory);
|
|
280
285
|
notes = changes[extensionSpec.version];
|
|
281
286
|
}
|
|
282
287
|
catch (err) {
|
|
@@ -289,27 +294,31 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
|
|
|
289
294
|
"Please add one so users know what has changed in this version. " +
|
|
290
295
|
marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
|
|
291
296
|
}
|
|
292
|
-
|
|
293
|
-
if (!
|
|
297
|
+
displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes);
|
|
298
|
+
if (!(await confirm({
|
|
299
|
+
nonInteractive: args.nonInteractive,
|
|
300
|
+
force: args.force,
|
|
301
|
+
default: false,
|
|
302
|
+
}))) {
|
|
294
303
|
return;
|
|
295
304
|
}
|
|
296
305
|
if (extension &&
|
|
297
306
|
extension.latestVersion &&
|
|
298
307
|
semver.lt(extensionSpec.version, extension.latestVersion)) {
|
|
299
|
-
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${publisherId}/${extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`);
|
|
308
|
+
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) is lower than the current version (${clc.bold(extension.latestVersion)}) for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please make sure this version is greater than the current version (${clc.bold(extension.latestVersion)}) inside of extension.yaml.\n`);
|
|
300
309
|
}
|
|
301
310
|
else if (extension &&
|
|
302
311
|
extension.latestVersion &&
|
|
303
312
|
semver.eq(extensionSpec.version, extension.latestVersion)) {
|
|
304
|
-
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${publisherId}/${extensionId}`)}'. Please increment the version inside of extension.yaml.\n
|
|
313
|
+
throw new error_1.FirebaseError(`The version you are trying to publish (${clc.bold(extensionSpec.version)}) already exists for the extension '${clc.bold(`${args.publisherId}/${args.extensionId}`)}'. Please increment the version inside of extension.yaml.\n`, { exit: 103 });
|
|
305
314
|
}
|
|
306
|
-
const ref = `${publisherId}/${extensionId}@${extensionSpec.version}`;
|
|
315
|
+
const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
|
|
307
316
|
let packageUri;
|
|
308
317
|
let objectPath = "";
|
|
309
318
|
const uploadSpinner = ora.default(" Archiving and uploading extension source code");
|
|
310
319
|
try {
|
|
311
320
|
uploadSpinner.start();
|
|
312
|
-
objectPath = await archiveAndUploadSource(rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
|
|
321
|
+
objectPath = await archiveAndUploadSource(args.rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
|
|
313
322
|
uploadSpinner.succeed(" Uploaded extension source code");
|
|
314
323
|
packageUri = api_1.storageOrigin + objectPath + "?alt=media";
|
|
315
324
|
}
|
|
@@ -327,7 +336,7 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
|
|
|
327
336
|
catch (err) {
|
|
328
337
|
publishSpinner.fail();
|
|
329
338
|
if (err.status == 404) {
|
|
330
|
-
throw new error_1.FirebaseError(marked(`Couldn't find publisher ID '${clc.bold(publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
|
|
339
|
+
throw new error_1.FirebaseError(marked(`Couldn't find publisher ID '${clc.bold(args.publisherId)}'. Please ensure that you have registered this ID. To register as a publisher, you can check out the [Firebase documentation](https://firebase.google.com/docs/extensions/alpha/share#register_as_an_extensions_publisher) for step-by-step instructions.`));
|
|
331
340
|
}
|
|
332
341
|
throw err;
|
|
333
342
|
}
|
|
@@ -389,20 +398,15 @@ async function getExtensionSourceFromName(extensionName) {
|
|
|
389
398
|
throw new error_1.FirebaseError(`Could not find an extension named '${extensionName}'. `);
|
|
390
399
|
}
|
|
391
400
|
exports.getExtensionSourceFromName = getExtensionSourceFromName;
|
|
392
|
-
|
|
401
|
+
function displayReleaseNotes(publisherId, extensionId, versionId, releaseNotes) {
|
|
393
402
|
const releaseNotesMessage = releaseNotes
|
|
394
403
|
? ` Release notes for this version:\n${marked(releaseNotes)}\n`
|
|
395
404
|
: "\n";
|
|
396
405
|
const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.${releaseNotesMessage}` +
|
|
397
|
-
"Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n"
|
|
398
|
-
|
|
399
|
-
return await prompt_1.promptOnce({
|
|
400
|
-
type: "confirm",
|
|
401
|
-
message,
|
|
402
|
-
default: false,
|
|
403
|
-
});
|
|
406
|
+
"Once an extension version is published, it cannot be changed. If you wish to make changes after publishing, you will need to publish a new version.\n\n";
|
|
407
|
+
logger_1.logger.info(message);
|
|
404
408
|
}
|
|
405
|
-
exports.
|
|
409
|
+
exports.displayReleaseNotes = displayReleaseNotes;
|
|
406
410
|
async function promptForOfficialExtension(message) {
|
|
407
411
|
const officialExts = await resolveSource_1.getExtensionRegistry(true);
|
|
408
412
|
return await prompt_1.promptOnce({
|
|
@@ -472,7 +476,7 @@ function getSourceOrigin(sourceOrVersion) {
|
|
|
472
476
|
if (sourceOrVersion.includes("/")) {
|
|
473
477
|
let ref;
|
|
474
478
|
try {
|
|
475
|
-
ref =
|
|
479
|
+
ref = refs.parse(sourceOrVersion);
|
|
476
480
|
}
|
|
477
481
|
catch (err) {
|
|
478
482
|
}
|
|
@@ -486,20 +490,20 @@ function getSourceOrigin(sourceOrVersion) {
|
|
|
486
490
|
throw new error_1.FirebaseError(`Could not find source '${clc.bold(sourceOrVersion)}'. Check to make sure the source is correct, and then please try again.`);
|
|
487
491
|
}
|
|
488
492
|
exports.getSourceOrigin = getSourceOrigin;
|
|
489
|
-
async function
|
|
490
|
-
if (!nonInteractive && !force) {
|
|
491
|
-
const message = `
|
|
493
|
+
async function confirm(args) {
|
|
494
|
+
if (!args.nonInteractive && !args.force) {
|
|
495
|
+
const message = `Do you wish to continue?`;
|
|
492
496
|
return await prompt_1.promptOnce({
|
|
493
497
|
type: "confirm",
|
|
494
498
|
message,
|
|
495
|
-
default:
|
|
499
|
+
default: args.default,
|
|
496
500
|
});
|
|
497
501
|
}
|
|
498
|
-
else if (nonInteractive && !force) {
|
|
499
|
-
throw new error_1.FirebaseError("Pass the --force flag to
|
|
502
|
+
else if (args.nonInteractive && !args.force) {
|
|
503
|
+
throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
|
|
500
504
|
}
|
|
501
505
|
else {
|
|
502
506
|
return true;
|
|
503
507
|
}
|
|
504
508
|
}
|
|
505
|
-
exports.
|
|
509
|
+
exports.confirm = confirm;
|
|
@@ -8,19 +8,20 @@ const extensionsApi_1 = require("./extensionsApi");
|
|
|
8
8
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
9
9
|
const utils = require("../utils");
|
|
10
10
|
const extensionsUtils = require("./utils");
|
|
11
|
-
const logger_1 = require("../logger");
|
|
12
11
|
async function listExtensions(projectId) {
|
|
13
12
|
const instances = await extensionsApi_1.listInstances(projectId);
|
|
14
13
|
if (instances.length < 1) {
|
|
15
14
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `there are no extensions installed on project ${clc.bold(projectId)}.`);
|
|
16
|
-
return
|
|
15
|
+
return [];
|
|
17
16
|
}
|
|
18
17
|
const table = new Table({
|
|
19
18
|
head: ["Extension", "Publisher", "Instance ID", "State", "Version", "Your last update"],
|
|
20
19
|
style: { head: ["yellow"] },
|
|
21
20
|
});
|
|
22
21
|
const sorted = _.sortBy(instances, "createTime", "asc").reverse();
|
|
22
|
+
const formatted = [];
|
|
23
23
|
sorted.forEach((instance) => {
|
|
24
|
+
var _a, _b, _c, _d;
|
|
24
25
|
let extension = _.get(instance, "config.extensionRef", "");
|
|
25
26
|
let publisher;
|
|
26
27
|
if (extension === "") {
|
|
@@ -30,18 +31,22 @@ async function listExtensions(projectId) {
|
|
|
30
31
|
else {
|
|
31
32
|
publisher = extension.split("/")[0];
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
+
const instanceId = (_a = _.last(instance.name.split("/"))) !== null && _a !== void 0 ? _a : "";
|
|
35
|
+
const state = instance.state +
|
|
36
|
+
(_.get(instance, "config.source.state", "ACTIVE") === "DELETED" ? " (UNPUBLISHED)" : "");
|
|
37
|
+
const version = (_d = (_c = (_b = instance === null || instance === void 0 ? void 0 : instance.config) === null || _b === void 0 ? void 0 : _b.source) === null || _c === void 0 ? void 0 : _c.spec) === null || _d === void 0 ? void 0 : _d.version;
|
|
38
|
+
const updateTime = extensionsUtils.formatTimestamp(instance.updateTime);
|
|
39
|
+
table.push([extension, publisher, instanceId, state, version, updateTime]);
|
|
40
|
+
formatted.push({
|
|
34
41
|
extension,
|
|
35
42
|
publisher,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
]);
|
|
43
|
+
instanceId,
|
|
44
|
+
state,
|
|
45
|
+
version,
|
|
46
|
+
updateTime,
|
|
47
|
+
});
|
|
42
48
|
});
|
|
43
49
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `list of extensions installed in ${clc.bold(projectId)}:`);
|
|
44
|
-
|
|
45
|
-
return { instances: sorted };
|
|
50
|
+
return formatted;
|
|
46
51
|
}
|
|
47
52
|
exports.listExtensions = listExtensions;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.readEnvFile = exports.getParamsFromFile = exports.promptForNewParams = exports.getParamsForUpdate = exports.getParams = exports.getParamsWithCurrentValuesAsDefaults = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const clc = require("cli-color");
|
|
7
|
-
const dotenv = require("dotenv");
|
|
8
7
|
const fs = require("fs-extra");
|
|
9
8
|
const error_1 = require("../error");
|
|
10
9
|
const logger_1 = require("../logger");
|
|
11
10
|
const extensionsHelper_1 = require("./extensionsHelper");
|
|
12
11
|
const askUserForParam = require("./askUserForParam");
|
|
13
12
|
const track = require("../track");
|
|
13
|
+
const env = require("../functions/env");
|
|
14
14
|
function setNewDefaults(params, newDefaults) {
|
|
15
15
|
params.forEach((param) => {
|
|
16
16
|
if (newDefaults[param.param.toUpperCase()]) {
|
|
@@ -25,9 +25,10 @@ function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
|
|
|
25
25
|
return setNewDefaults(specParams, currentParams);
|
|
26
26
|
}
|
|
27
27
|
exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
|
|
28
|
-
async function getParams(
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
async function getParams(args) {
|
|
29
|
+
let params;
|
|
30
|
+
if (args.nonInteractive && !args.paramsEnvPath) {
|
|
31
|
+
const paramsMessage = args.paramSpecs
|
|
31
32
|
.map((p) => {
|
|
32
33
|
return `\t${p.param}${p.required ? "" : " (Optional)"}`;
|
|
33
34
|
})
|
|
@@ -37,70 +38,102 @@ async function getParams(projectId, paramSpecs, noninteractive = false, envFileP
|
|
|
37
38
|
" containing values for this extension's params:\n" +
|
|
38
39
|
paramsMessage);
|
|
39
40
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
track("Extension Env File", "Invalid");
|
|
49
|
-
throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
|
|
50
|
-
}
|
|
41
|
+
else if (args.paramsEnvPath) {
|
|
42
|
+
params = getParamsFromFile({
|
|
43
|
+
projectId: args.projectId,
|
|
44
|
+
paramSpecs: args.paramSpecs,
|
|
45
|
+
paramsEnvPath: args.paramsEnvPath,
|
|
46
|
+
});
|
|
51
47
|
}
|
|
52
48
|
else {
|
|
53
|
-
|
|
49
|
+
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
50
|
+
params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
|
|
54
51
|
}
|
|
55
|
-
|
|
52
|
+
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
53
|
+
return params;
|
|
54
|
+
}
|
|
55
|
+
exports.getParams = getParams;
|
|
56
|
+
async function getParamsForUpdate(args) {
|
|
56
57
|
let params;
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if (args.nonInteractive && !args.paramsEnvPath) {
|
|
59
|
+
const paramsMessage = args.newSpec.params
|
|
60
|
+
.map((p) => {
|
|
61
|
+
return `\t${p.param}${p.required ? "" : " (Optional)"}`;
|
|
62
|
+
})
|
|
63
|
+
.join("\n");
|
|
64
|
+
throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
|
|
65
|
+
"To update this extension in non-interactive mode, set `--params` to a path to an .env file" +
|
|
66
|
+
" containing values for this extension's params:\n" +
|
|
67
|
+
paramsMessage);
|
|
68
|
+
}
|
|
69
|
+
else if (args.paramsEnvPath) {
|
|
70
|
+
params = getParamsFromFile({
|
|
71
|
+
projectId: args.projectId,
|
|
72
|
+
paramSpecs: args.newSpec.params,
|
|
73
|
+
paramsEnvPath: args.paramsEnvPath,
|
|
74
|
+
});
|
|
61
75
|
}
|
|
62
76
|
else {
|
|
63
|
-
params = await
|
|
77
|
+
params = await promptForNewParams({
|
|
78
|
+
spec: args.spec,
|
|
79
|
+
newSpec: args.newSpec,
|
|
80
|
+
currentParams: args.currentParams,
|
|
81
|
+
projectId: args.projectId,
|
|
82
|
+
instanceId: args.instanceId,
|
|
83
|
+
});
|
|
64
84
|
}
|
|
65
85
|
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
66
86
|
return params;
|
|
67
87
|
}
|
|
68
|
-
exports.
|
|
69
|
-
async function promptForNewParams(
|
|
70
|
-
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
|
|
88
|
+
exports.getParamsForUpdate = getParamsForUpdate;
|
|
89
|
+
async function promptForNewParams(args) {
|
|
90
|
+
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
71
91
|
const comparer = (param1, param2) => {
|
|
72
92
|
return param1.type === param2.type && param1.param === param2.param;
|
|
73
93
|
};
|
|
74
|
-
let paramsDiffDeletions = _.differenceWith(spec.params, _.get(newSpec, "params", []), comparer);
|
|
94
|
+
let paramsDiffDeletions = _.differenceWith(args.spec.params, _.get(args.newSpec, "params", []), comparer);
|
|
75
95
|
paramsDiffDeletions = extensionsHelper_1.substituteParams(paramsDiffDeletions, firebaseProjectParams);
|
|
76
|
-
let paramsDiffAdditions = _.differenceWith(newSpec.params, _.get(spec, "params", []), comparer);
|
|
96
|
+
let paramsDiffAdditions = _.differenceWith(args.newSpec.params, _.get(args.spec, "params", []), comparer);
|
|
77
97
|
paramsDiffAdditions = extensionsHelper_1.substituteParams(paramsDiffAdditions, firebaseProjectParams);
|
|
78
98
|
if (paramsDiffDeletions.length) {
|
|
79
99
|
logger_1.logger.info("The following params will no longer be used:");
|
|
80
100
|
paramsDiffDeletions.forEach((param) => {
|
|
81
|
-
logger_1.logger.info(clc.red(`- ${param.param}: ${currentParams[param.param.toUpperCase()]}`));
|
|
82
|
-
delete currentParams[param.param.toUpperCase()];
|
|
101
|
+
logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
|
|
102
|
+
delete args.currentParams[param.param.toUpperCase()];
|
|
83
103
|
});
|
|
84
104
|
}
|
|
85
105
|
if (paramsDiffAdditions.length) {
|
|
86
106
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
|
87
107
|
for (const param of paramsDiffAdditions) {
|
|
88
|
-
const chosenValue = await askUserForParam.askForParam(param);
|
|
89
|
-
currentParams[param.param] = chosenValue;
|
|
108
|
+
const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
|
|
109
|
+
args.currentParams[param.param] = chosenValue;
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
|
-
return currentParams;
|
|
112
|
+
return args.currentParams;
|
|
93
113
|
}
|
|
94
114
|
exports.promptForNewParams = promptForNewParams;
|
|
95
|
-
function
|
|
115
|
+
function getParamsFromFile(args) {
|
|
116
|
+
let envParams;
|
|
96
117
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
envParams = readEnvFile(args.paramsEnvPath);
|
|
119
|
+
track("Extension Env File", "Present");
|
|
99
120
|
}
|
|
100
121
|
catch (err) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
122
|
+
track("Extension Env File", "Invalid");
|
|
123
|
+
throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
|
|
124
|
+
}
|
|
125
|
+
const params = extensionsHelper_1.populateDefaultParams(envParams, args.paramSpecs);
|
|
126
|
+
extensionsHelper_1.validateCommandLineParams(params, args.paramSpecs);
|
|
127
|
+
logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
|
|
128
|
+
return params;
|
|
129
|
+
}
|
|
130
|
+
exports.getParamsFromFile = getParamsFromFile;
|
|
131
|
+
function readEnvFile(envPath) {
|
|
132
|
+
const buf = fs.readFileSync(path.resolve(envPath), "utf8");
|
|
133
|
+
const result = env.parse(buf.toString().trim());
|
|
134
|
+
if (result.errors.length) {
|
|
135
|
+
throw new error_1.FirebaseError(`Error while parsing ${envPath} - unable to parse following lines:\n${result.errors.join("\n")}`);
|
|
104
136
|
}
|
|
137
|
+
return result.envs;
|
|
105
138
|
}
|
|
106
|
-
exports.
|
|
139
|
+
exports.readEnvFile = readEnvFile;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUsedProducts = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
|
|
3
|
+
exports.getUsedProducts = exports.bulkCheckProductsProvisioned = exports.checkProductsProvisioned = exports.DeferredProduct = void 0;
|
|
4
4
|
const marked = require("marked");
|
|
5
5
|
const api = require("../api");
|
|
6
|
+
const functional_1 = require("../functional");
|
|
6
7
|
const error_1 = require("../error");
|
|
8
|
+
const planner_1 = require("../deploy/extensions/planner");
|
|
7
9
|
var DeferredProduct;
|
|
8
10
|
(function (DeferredProduct) {
|
|
9
11
|
DeferredProduct[DeferredProduct["STORAGE"] = 0] = "STORAGE";
|
|
@@ -11,6 +13,18 @@ var DeferredProduct;
|
|
|
11
13
|
})(DeferredProduct = exports.DeferredProduct || (exports.DeferredProduct = {}));
|
|
12
14
|
async function checkProductsProvisioned(projectId, spec) {
|
|
13
15
|
const usedProducts = getUsedProducts(spec);
|
|
16
|
+
await checkProducts(projectId, usedProducts);
|
|
17
|
+
}
|
|
18
|
+
exports.checkProductsProvisioned = checkProductsProvisioned;
|
|
19
|
+
async function bulkCheckProductsProvisioned(projectId, instanceSpecs) {
|
|
20
|
+
const usedProducts = await Promise.all(instanceSpecs.map(async (i) => {
|
|
21
|
+
const extensionVersion = await planner_1.getExtensionVersion(i);
|
|
22
|
+
return getUsedProducts(extensionVersion.spec);
|
|
23
|
+
}));
|
|
24
|
+
await checkProducts(projectId, [...functional_1.flattenArray(usedProducts)]);
|
|
25
|
+
}
|
|
26
|
+
exports.bulkCheckProductsProvisioned = bulkCheckProductsProvisioned;
|
|
27
|
+
async function checkProducts(projectId, usedProducts) {
|
|
14
28
|
const needProvisioning = [];
|
|
15
29
|
let isStorageProvisionedPromise;
|
|
16
30
|
let isAuthProvisionedPromise;
|
|
@@ -29,7 +43,7 @@ async function checkProductsProvisioned(projectId, spec) {
|
|
|
29
43
|
if (needProvisioning.length > 0) {
|
|
30
44
|
let errorMessage = "Some services used by this extension have not been set up on your " +
|
|
31
45
|
"Firebase project. To ensure this extension works as intended, you must enable these " +
|
|
32
|
-
"services by following the provided links, then retry
|
|
46
|
+
"services by following the provided links, then retry this command\n\n";
|
|
33
47
|
if (needProvisioning.includes(DeferredProduct.STORAGE)) {
|
|
34
48
|
errorMessage +=
|
|
35
49
|
" - Firebase Storage: store and retrieve user-generated files like images, audio, and " +
|
|
@@ -46,7 +60,6 @@ async function checkProductsProvisioned(projectId, spec) {
|
|
|
46
60
|
throw new error_1.FirebaseError(marked(errorMessage), { exit: 2 });
|
|
47
61
|
}
|
|
48
62
|
}
|
|
49
|
-
exports.checkProductsProvisioned = checkProductsProvisioned;
|
|
50
63
|
function getUsedProducts(spec) {
|
|
51
64
|
var _a, _b;
|
|
52
65
|
const usedProducts = [];
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.equal = exports.toExtensionVersionName = exports.toExtensionName = exports.toExtensionVersionRef = exports.toExtensionRef = exports.parse = void 0;
|
|
4
|
+
const semver = require("semver");
|
|
5
|
+
const error_1 = require("../error");
|
|
6
|
+
const refRegex = new RegExp(/^([^/@\n]+)\/{1}([^/@\n]+)(@{1}([^\n]+)|)$/);
|
|
7
|
+
function parse(refOrName) {
|
|
8
|
+
const ret = parseRef(refOrName) || parseName(refOrName);
|
|
9
|
+
if (!ret || !ret.publisherId || !ret.extensionId) {
|
|
10
|
+
throw new error_1.FirebaseError(`Unable to parse ${refOrName} as an extension ref`);
|
|
11
|
+
}
|
|
12
|
+
if (ret.version &&
|
|
13
|
+
!semver.valid(ret.version) &&
|
|
14
|
+
!semver.validRange(ret.version) &&
|
|
15
|
+
ret.version !== "latest") {
|
|
16
|
+
throw new error_1.FirebaseError(`Extension reference ${ret} contains an invalid version ${ret.version}.`);
|
|
17
|
+
}
|
|
18
|
+
return ret;
|
|
19
|
+
}
|
|
20
|
+
exports.parse = parse;
|
|
21
|
+
function parseRef(ref) {
|
|
22
|
+
const parts = refRegex.exec(ref);
|
|
23
|
+
if (parts && (parts.length == 5 || parts.length == 7)) {
|
|
24
|
+
const publisherId = parts[1];
|
|
25
|
+
const extensionId = parts[2];
|
|
26
|
+
const version = parts[4];
|
|
27
|
+
return { publisherId, extensionId, version };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function parseName(name) {
|
|
31
|
+
const parts = name.split("/");
|
|
32
|
+
return {
|
|
33
|
+
publisherId: parts[1],
|
|
34
|
+
extensionId: parts[3],
|
|
35
|
+
version: parts[5],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function toExtensionRef(ref) {
|
|
39
|
+
return `${ref.publisherId}/${ref.extensionId}`;
|
|
40
|
+
}
|
|
41
|
+
exports.toExtensionRef = toExtensionRef;
|
|
42
|
+
function toExtensionVersionRef(ref) {
|
|
43
|
+
if (!ref.version) {
|
|
44
|
+
throw new error_1.FirebaseError(`Ref does not have a version`);
|
|
45
|
+
}
|
|
46
|
+
return `${ref.publisherId}/${ref.extensionId}@${ref.version}`;
|
|
47
|
+
}
|
|
48
|
+
exports.toExtensionVersionRef = toExtensionVersionRef;
|
|
49
|
+
function toExtensionName(ref) {
|
|
50
|
+
return `publishers/${ref.publisherId}/extensions/${ref.extensionId}`;
|
|
51
|
+
}
|
|
52
|
+
exports.toExtensionName = toExtensionName;
|
|
53
|
+
function toExtensionVersionName(ref) {
|
|
54
|
+
if (!ref.version) {
|
|
55
|
+
throw new error_1.FirebaseError(`Ref does not have a version`);
|
|
56
|
+
}
|
|
57
|
+
return `publishers/${ref.publisherId}/extensions/${ref.extensionId}/versions/${ref.version}`;
|
|
58
|
+
}
|
|
59
|
+
exports.toExtensionVersionName = toExtensionVersionName;
|
|
60
|
+
function equal(a, b) {
|
|
61
|
+
return (!!a &&
|
|
62
|
+
!!b &&
|
|
63
|
+
a.publisherId === b.publisherId &&
|
|
64
|
+
a.extensionId === b.extensionId &&
|
|
65
|
+
a.version === b.version);
|
|
66
|
+
}
|
|
67
|
+
exports.equal = equal;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prettySecretName = exports.getSecretLabels = exports.getActiveSecrets = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = exports.SECRET_LABEL = void 0;
|
|
4
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
5
|
+
const utils = require("../utils");
|
|
6
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
7
|
+
const projectUtils_1 = require("../projectUtils");
|
|
8
|
+
const extensionsApi = require("./extensionsApi");
|
|
9
|
+
const secretManagerApi = require("../gcp/secretManager");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
exports.SECRET_LABEL = "firebase-extensions-managed";
|
|
12
|
+
async function ensureSecretManagerApiEnabled(options) {
|
|
13
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
14
|
+
return await ensureApiEnabled_1.ensure(projectId, "secretmanager.googleapis.com", "extensions", options.markdown);
|
|
15
|
+
}
|
|
16
|
+
exports.ensureSecretManagerApiEnabled = ensureSecretManagerApiEnabled;
|
|
17
|
+
function usesSecrets(spec) {
|
|
18
|
+
return spec.params && !!spec.params.find((p) => p.type == extensionsApi.ParamType.SECRET);
|
|
19
|
+
}
|
|
20
|
+
exports.usesSecrets = usesSecrets;
|
|
21
|
+
async function grantFirexServiceAgentSecretAdminRole(secret) {
|
|
22
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber({ projectId: secret.projectId });
|
|
23
|
+
const firexSaProjectId = utils.envOverride("FIREBASE_EXTENSIONS_SA_PROJECT_ID", "gcp-sa-firebasemods");
|
|
24
|
+
const saEmail = `service-${projectNumber}@${firexSaProjectId}.iam.gserviceaccount.com`;
|
|
25
|
+
return secretManagerApi.grantServiceAgentRole(secret, saEmail, "roles/secretmanager.admin");
|
|
26
|
+
}
|
|
27
|
+
exports.grantFirexServiceAgentSecretAdminRole = grantFirexServiceAgentSecretAdminRole;
|
|
28
|
+
async function getManagedSecrets(instance) {
|
|
29
|
+
return (await Promise.all(getActiveSecrets(instance.config.source.spec, instance.config.params).map(async (secretResourceName) => {
|
|
30
|
+
const secret = secretManagerApi.parseSecretResourceName(secretResourceName);
|
|
31
|
+
const labels = (await secretManagerApi.getSecret(secret.projectId, secret.name)).labels;
|
|
32
|
+
if (labels && labels[exports.SECRET_LABEL]) {
|
|
33
|
+
return secretResourceName;
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve("");
|
|
36
|
+
}))).filter((secretId) => !!secretId);
|
|
37
|
+
}
|
|
38
|
+
exports.getManagedSecrets = getManagedSecrets;
|
|
39
|
+
function getActiveSecrets(spec, params) {
|
|
40
|
+
return spec.params
|
|
41
|
+
.map((p) => (p.type == extensionsApi.ParamType.SECRET ? params[p.param] : ""))
|
|
42
|
+
.filter((pv) => !!pv);
|
|
43
|
+
}
|
|
44
|
+
exports.getActiveSecrets = getActiveSecrets;
|
|
45
|
+
function getSecretLabels(instanceId) {
|
|
46
|
+
const labels = {};
|
|
47
|
+
labels[exports.SECRET_LABEL] = instanceId;
|
|
48
|
+
return labels;
|
|
49
|
+
}
|
|
50
|
+
exports.getSecretLabels = getSecretLabels;
|
|
51
|
+
function prettySecretName(secretResourceName) {
|
|
52
|
+
const nameTokens = secretResourceName.split("/");
|
|
53
|
+
if (nameTokens.length != 4 && nameTokens.length != 6) {
|
|
54
|
+
logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
|
|
55
|
+
return secretResourceName;
|
|
56
|
+
}
|
|
57
|
+
return nameTokens.slice(0, 4).join("/");
|
|
58
|
+
}
|
|
59
|
+
exports.prettySecretName = prettySecretName;
|