firebase-tools 9.16.6 → 9.20.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 +1 -3
- package/lib/api.js +1 -0
- package/lib/apiv2.js +1 -1
- package/lib/appdistribution/client.js +84 -72
- package/lib/appdistribution/distribution.js +8 -26
- package/lib/appdistribution/options-parser-util.js +51 -0
- package/lib/command.js +8 -6
- package/lib/commands/appdistribution-distribute.js +74 -91
- package/lib/commands/appdistribution-testers-add.js +18 -0
- package/lib/commands/appdistribution-testers-remove.js +32 -0
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/ext-configure.js +9 -1
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-init.js +18 -9
- package/lib/commands/ext-dev-publish.js +11 -4
- package/lib/commands/ext-dev-unpublish.js +2 -1
- package/lib/commands/ext-install.js +115 -48
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +67 -43
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +44 -35
- package/lib/commands/functions-list.js +54 -0
- package/lib/commands/functions-log.js +5 -22
- package/lib/commands/hosting-channel-deploy.js +6 -4
- package/lib/commands/index.js +12 -0
- package/lib/deploy/functions/backend.js +47 -12
- package/lib/deploy/functions/containerCleaner.js +5 -1
- package/lib/deploy/functions/deploy.js +7 -5
- package/lib/deploy/functions/prepare.js +9 -7
- package/lib/deploy/functions/prompts.js +3 -21
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +2 -1
- package/lib/deploy/functions/runtimes/index.js +2 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +4 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +14 -9
- package/lib/deploy/functions/triggerRegionHelper.js +32 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1758 -404
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +429 -40
- package/lib/emulator/auth/server.js +18 -11
- package/lib/emulator/auth/state.js +186 -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 +102 -17
- 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/extensions/askUserForConsent.js +16 -13
- package/lib/extensions/askUserForParam.js +72 -3
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +93 -0
- package/lib/extensions/displayExtensionInfo.js +38 -38
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/extensionsApi.js +69 -95
- package/lib/extensions/extensionsHelper.js +75 -50
- package/lib/extensions/paramHelper.js +79 -36
- package/lib/extensions/refs.js +59 -0
- package/lib/extensions/resolveSource.js +2 -20
- package/lib/extensions/secretsUtils.js +58 -0
- package/lib/extensions/updateHelper.js +39 -105
- package/lib/extensions/warnings.js +1 -7
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/functionslog.js +40 -0
- package/lib/functions/listFunctions.js +10 -0
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/cloudfunctions.js +84 -9
- package/lib/gcp/cloudfunctionsv2.js +99 -7
- package/lib/gcp/cloudlogging.js +27 -21
- package/lib/gcp/secretManager.js +111 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/package.json +5 -4
- package/schema/firebase-config.json +2 -1
- package/templates/extensions/CHANGELOG.md +7 -0
- package/templates/init/hosting/index.html +10 -10
|
@@ -1,11 +1,15 @@
|
|
|
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");
|
|
7
7
|
const semver = require("semver");
|
|
8
8
|
const marked = require("marked");
|
|
9
|
+
const TerminalRenderer = require("marked-terminal");
|
|
10
|
+
marked.setOptions({
|
|
11
|
+
renderer: new TerminalRenderer(),
|
|
12
|
+
});
|
|
9
13
|
const api_1 = require("../api");
|
|
10
14
|
const archiveDirectory_1 = require("../archiveDirectory");
|
|
11
15
|
const utils_1 = require("./utils");
|
|
@@ -17,16 +21,19 @@ const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
|
17
21
|
const storage_1 = require("../gcp/storage");
|
|
18
22
|
const projectUtils_1 = require("../projectUtils");
|
|
19
23
|
const extensionsApi_1 = require("./extensionsApi");
|
|
24
|
+
const refs = require("./refs");
|
|
20
25
|
const localHelper_1 = require("./localHelper");
|
|
21
26
|
const prompt_1 = require("../prompt");
|
|
22
27
|
const logger_1 = require("../logger");
|
|
23
28
|
const utils_2 = require("../utils");
|
|
29
|
+
const changelog_1 = require("./changelog");
|
|
24
30
|
var SpecParamType;
|
|
25
31
|
(function (SpecParamType) {
|
|
26
32
|
SpecParamType["SELECT"] = "select";
|
|
27
33
|
SpecParamType["MULTISELECT"] = "multiSelect";
|
|
28
34
|
SpecParamType["STRING"] = "string";
|
|
29
35
|
SpecParamType["SELECTRESOURCE"] = "selectResource";
|
|
36
|
+
SpecParamType["SECRET"] = "secret";
|
|
30
37
|
})(SpecParamType = exports.SpecParamType || (exports.SpecParamType = {}));
|
|
31
38
|
var SourceOrigin;
|
|
32
39
|
(function (SourceOrigin) {
|
|
@@ -255,41 +262,60 @@ async function archiveAndUploadSource(extPath, bucketName) {
|
|
|
255
262
|
const res = await storage_1.uploadObject(zippedSource, bucketName);
|
|
256
263
|
return `/${res.bucket}/${res.object}`;
|
|
257
264
|
}
|
|
258
|
-
async function publishExtensionVersionFromLocalSource(
|
|
259
|
-
const extensionSpec = await localHelper_1.getLocalExtensionSpec(rootDirectory);
|
|
260
|
-
if (extensionSpec.name != extensionId) {
|
|
261
|
-
throw new error_1.FirebaseError(`Extension ID '${clc.bold(extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
|
|
265
|
+
async function publishExtensionVersionFromLocalSource(args) {
|
|
266
|
+
const extensionSpec = await localHelper_1.getLocalExtensionSpec(args.rootDirectory);
|
|
267
|
+
if (extensionSpec.name != args.extensionId) {
|
|
268
|
+
throw new error_1.FirebaseError(`Extension ID '${clc.bold(args.extensionId)}' does not match the name in extension.yaml '${clc.bold(extensionSpec.name)}'.`);
|
|
262
269
|
}
|
|
263
270
|
const subbedSpec = JSON.parse(JSON.stringify(extensionSpec));
|
|
264
271
|
subbedSpec.params = substituteParams(extensionSpec.params || [], exports.AUTOPOULATED_PARAM_PLACEHOLDERS);
|
|
265
272
|
validateSpec(subbedSpec);
|
|
266
|
-
const consent = await confirmExtensionVersion(publisherId, extensionId, extensionSpec.version);
|
|
267
|
-
if (!consent) {
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
273
|
let extension;
|
|
271
274
|
try {
|
|
272
|
-
extension = await extensionsApi_1.getExtension(`${publisherId}/${extensionId}`);
|
|
275
|
+
extension = await extensionsApi_1.getExtension(`${args.publisherId}/${args.extensionId}`);
|
|
273
276
|
}
|
|
274
277
|
catch (err) {
|
|
275
278
|
}
|
|
279
|
+
let notes;
|
|
280
|
+
try {
|
|
281
|
+
const changes = changelog_1.getLocalChangelog(args.rootDirectory);
|
|
282
|
+
notes = changes[extensionSpec.version];
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
throw new error_1.FirebaseError("No CHANGELOG.md file found. " +
|
|
286
|
+
"Please create one and add an entry for this version. " +
|
|
287
|
+
marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
|
|
288
|
+
}
|
|
289
|
+
if (!notes && extension) {
|
|
290
|
+
throw new error_1.FirebaseError(`No entry for version ${extensionSpec.version} found in CHANGELOG.md. ` +
|
|
291
|
+
"Please add one so users know what has changed in this version. " +
|
|
292
|
+
marked("See https://firebase.google.com/docs/extensions/alpha/create-user-docs#writing-changelog for more details."));
|
|
293
|
+
}
|
|
294
|
+
displayReleaseNotes(args.publisherId, args.extensionId, extensionSpec.version, notes);
|
|
295
|
+
if (!(await confirm({
|
|
296
|
+
nonInteractive: args.nonInteractive,
|
|
297
|
+
force: args.force,
|
|
298
|
+
default: false,
|
|
299
|
+
}))) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
276
302
|
if (extension &&
|
|
277
303
|
extension.latestVersion &&
|
|
278
304
|
semver.lt(extensionSpec.version, extension.latestVersion)) {
|
|
279
|
-
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`);
|
|
305
|
+
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`);
|
|
280
306
|
}
|
|
281
307
|
else if (extension &&
|
|
282
308
|
extension.latestVersion &&
|
|
283
309
|
semver.eq(extensionSpec.version, extension.latestVersion)) {
|
|
284
|
-
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`);
|
|
310
|
+
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`);
|
|
285
311
|
}
|
|
286
|
-
const ref = `${publisherId}/${extensionId}@${extensionSpec.version}`;
|
|
312
|
+
const ref = `${args.publisherId}/${args.extensionId}@${extensionSpec.version}`;
|
|
287
313
|
let packageUri;
|
|
288
314
|
let objectPath = "";
|
|
289
315
|
const uploadSpinner = ora.default(" Archiving and uploading extension source code");
|
|
290
316
|
try {
|
|
291
317
|
uploadSpinner.start();
|
|
292
|
-
objectPath = await archiveAndUploadSource(rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
|
|
318
|
+
objectPath = await archiveAndUploadSource(args.rootDirectory, exports.EXTENSIONS_BUCKET_NAME);
|
|
293
319
|
uploadSpinner.succeed(" Uploaded extension source code");
|
|
294
320
|
packageUri = api_1.storageOrigin + objectPath + "?alt=media";
|
|
295
321
|
}
|
|
@@ -307,7 +333,7 @@ async function publishExtensionVersionFromLocalSource(publisherId, extensionId,
|
|
|
307
333
|
catch (err) {
|
|
308
334
|
publishSpinner.fail();
|
|
309
335
|
if (err.status == 404) {
|
|
310
|
-
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.`));
|
|
336
|
+
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.`));
|
|
311
337
|
}
|
|
312
338
|
throw err;
|
|
313
339
|
}
|
|
@@ -369,17 +395,15 @@ async function getExtensionSourceFromName(extensionName) {
|
|
|
369
395
|
throw new error_1.FirebaseError(`Could not find an extension named '${extensionName}'. `);
|
|
370
396
|
}
|
|
371
397
|
exports.getExtensionSourceFromName = getExtensionSourceFromName;
|
|
372
|
-
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
default: false,
|
|
380
|
-
});
|
|
398
|
+
function displayReleaseNotes(publisherId, extensionId, versionId, releaseNotes) {
|
|
399
|
+
const releaseNotesMessage = releaseNotes
|
|
400
|
+
? ` Release notes for this version:\n${marked(releaseNotes)}\n`
|
|
401
|
+
: "\n";
|
|
402
|
+
const message = `You are about to publish version ${clc.green(versionId)} of ${clc.green(`${publisherId}/${extensionId}`)} to Firebase's registry of extensions.${releaseNotesMessage}` +
|
|
403
|
+
"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";
|
|
404
|
+
logger_1.logger.info(message);
|
|
381
405
|
}
|
|
382
|
-
exports.
|
|
406
|
+
exports.displayReleaseNotes = displayReleaseNotes;
|
|
383
407
|
async function promptForOfficialExtension(message) {
|
|
384
408
|
const officialExts = await resolveSource_1.getExtensionRegistry(true);
|
|
385
409
|
return await prompt_1.promptOnce({
|
|
@@ -392,11 +416,16 @@ async function promptForOfficialExtension(message) {
|
|
|
392
416
|
}
|
|
393
417
|
exports.promptForOfficialExtension = promptForOfficialExtension;
|
|
394
418
|
async function promptForRepeatInstance(projectName, extensionName) {
|
|
395
|
-
const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'
|
|
396
|
-
|
|
419
|
+
const message = `An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectName)}'. What would you like to do?`;
|
|
420
|
+
const choices = [
|
|
421
|
+
{ name: "Update or reconfigure the existing instance", value: "updateExisting" },
|
|
422
|
+
{ name: "Install a new instance with a different ID", value: "installNew" },
|
|
423
|
+
{ name: "Cancel extension installation", value: "cancel" },
|
|
424
|
+
];
|
|
397
425
|
return await prompt_1.promptOnce({
|
|
398
|
-
type: "
|
|
426
|
+
type: "list",
|
|
399
427
|
message,
|
|
428
|
+
choices,
|
|
400
429
|
});
|
|
401
430
|
}
|
|
402
431
|
exports.promptForRepeatInstance = promptForRepeatInstance;
|
|
@@ -434,29 +463,17 @@ function isLocalOrURLPath(extInstallPath) {
|
|
|
434
463
|
return isLocalPath(extInstallPath) || isUrlPath(extInstallPath);
|
|
435
464
|
}
|
|
436
465
|
exports.isLocalOrURLPath = isLocalOrURLPath;
|
|
437
|
-
|
|
438
|
-
if (!sourceOrVersion) {
|
|
439
|
-
return SourceOrigin.OFFICIAL_EXTENSION;
|
|
440
|
-
}
|
|
441
|
-
if (semver.valid(sourceOrVersion)) {
|
|
442
|
-
return SourceOrigin.OFFICIAL_EXTENSION_VERSION;
|
|
443
|
-
}
|
|
466
|
+
function getSourceOrigin(sourceOrVersion) {
|
|
444
467
|
if (isLocalPath(sourceOrVersion)) {
|
|
445
468
|
return SourceOrigin.LOCAL;
|
|
446
469
|
}
|
|
447
470
|
if (isUrlPath(sourceOrVersion)) {
|
|
448
471
|
return SourceOrigin.URL;
|
|
449
472
|
}
|
|
450
|
-
try {
|
|
451
|
-
await resolveSource_1.resolveRegistryEntry(sourceOrVersion);
|
|
452
|
-
return SourceOrigin.OFFICIAL_EXTENSION;
|
|
453
|
-
}
|
|
454
|
-
catch (_a) {
|
|
455
|
-
}
|
|
456
473
|
if (sourceOrVersion.includes("/")) {
|
|
457
474
|
let ref;
|
|
458
475
|
try {
|
|
459
|
-
ref =
|
|
476
|
+
ref = refs.parse(sourceOrVersion);
|
|
460
477
|
}
|
|
461
478
|
catch (err) {
|
|
462
479
|
}
|
|
@@ -470,12 +487,20 @@ async function getSourceOrigin(sourceOrVersion) {
|
|
|
470
487
|
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.`);
|
|
471
488
|
}
|
|
472
489
|
exports.getSourceOrigin = getSourceOrigin;
|
|
473
|
-
async function
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
490
|
+
async function confirm(args) {
|
|
491
|
+
if (!args.nonInteractive && !args.force) {
|
|
492
|
+
const message = `Do you wish to continue?`;
|
|
493
|
+
return await prompt_1.promptOnce({
|
|
494
|
+
type: "confirm",
|
|
495
|
+
message,
|
|
496
|
+
default: args.default,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
else if (args.nonInteractive && !args.force) {
|
|
500
|
+
throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
480
505
|
}
|
|
481
|
-
exports.
|
|
506
|
+
exports.confirm = confirm;
|
|
@@ -1,6 +1,6 @@
|
|
|
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");
|
|
@@ -25,70 +25,113 @@ function getParamsWithCurrentValuesAsDefaults(extensionInstance) {
|
|
|
25
25
|
return setNewDefaults(specParams, currentParams);
|
|
26
26
|
}
|
|
27
27
|
exports.getParamsWithCurrentValuesAsDefaults = getParamsWithCurrentValuesAsDefaults;
|
|
28
|
-
async function getParams(
|
|
29
|
-
let
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
async function getParams(args) {
|
|
29
|
+
let params;
|
|
30
|
+
if (args.nonInteractive && !args.paramsEnvPath) {
|
|
31
|
+
const paramsMessage = args.paramSpecs
|
|
32
|
+
.map((p) => {
|
|
33
|
+
return `\t${p.param}${p.required ? "" : " (Optional)"}`;
|
|
34
|
+
})
|
|
35
|
+
.join("\n");
|
|
36
|
+
throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
|
|
37
|
+
"To install this extension in non-interactive mode, set `--params` to a path to an .env file" +
|
|
38
|
+
" containing values for this extension's params:\n" +
|
|
39
|
+
paramsMessage);
|
|
40
|
+
}
|
|
41
|
+
else if (args.paramsEnvPath) {
|
|
42
|
+
params = getParamsFromFile({
|
|
43
|
+
projectId: args.projectId,
|
|
44
|
+
paramSpecs: args.paramSpecs,
|
|
45
|
+
noninteractive: args.nonInteractive,
|
|
46
|
+
paramsEnvPath: args.paramsEnvPath,
|
|
47
|
+
});
|
|
40
48
|
}
|
|
41
49
|
else {
|
|
42
|
-
|
|
50
|
+
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
51
|
+
params = await askUserForParam.ask(args.projectId, args.instanceId, args.paramSpecs, firebaseProjectParams, !!args.reconfiguring);
|
|
43
52
|
}
|
|
44
|
-
|
|
53
|
+
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
54
|
+
return params;
|
|
55
|
+
}
|
|
56
|
+
exports.getParams = getParams;
|
|
57
|
+
async function getParamsForUpdate(args) {
|
|
45
58
|
let params;
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
59
|
+
if (args.nonInteractive && !args.paramsEnvPath) {
|
|
60
|
+
const paramsMessage = args.newSpec.params
|
|
61
|
+
.map((p) => {
|
|
62
|
+
return `\t${p.param}${p.required ? "" : " (Optional)"}`;
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
throw new error_1.FirebaseError("In non-interactive mode but no `--params` flag found. " +
|
|
66
|
+
"To update this extension in non-interactive mode, set `--params` to a path to an .env file" +
|
|
67
|
+
" containing values for this extension's params:\n" +
|
|
68
|
+
paramsMessage);
|
|
69
|
+
}
|
|
70
|
+
else if (args.paramsEnvPath) {
|
|
71
|
+
params = getParamsFromFile({
|
|
72
|
+
projectId: args.projectId,
|
|
73
|
+
paramSpecs: args.newSpec.params,
|
|
74
|
+
noninteractive: args.nonInteractive,
|
|
75
|
+
paramsEnvPath: args.paramsEnvPath,
|
|
76
|
+
});
|
|
49
77
|
}
|
|
50
78
|
else {
|
|
51
|
-
params = await
|
|
79
|
+
params = await promptForNewParams({
|
|
80
|
+
spec: args.spec,
|
|
81
|
+
newSpec: args.newSpec,
|
|
82
|
+
currentParams: args.currentParams,
|
|
83
|
+
projectId: args.projectId,
|
|
84
|
+
instanceId: args.instanceId,
|
|
85
|
+
});
|
|
52
86
|
}
|
|
53
87
|
track("Extension Params", _.isEmpty(params) ? "Not Present" : "Present", _.size(params));
|
|
54
88
|
return params;
|
|
55
89
|
}
|
|
56
|
-
exports.
|
|
57
|
-
async function promptForNewParams(
|
|
58
|
-
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(projectId);
|
|
90
|
+
exports.getParamsForUpdate = getParamsForUpdate;
|
|
91
|
+
async function promptForNewParams(args) {
|
|
92
|
+
const firebaseProjectParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
59
93
|
const comparer = (param1, param2) => {
|
|
60
94
|
return param1.type === param2.type && param1.param === param2.param;
|
|
61
95
|
};
|
|
62
|
-
let paramsDiffDeletions = _.differenceWith(spec.params, _.get(newSpec, "params", []), comparer);
|
|
96
|
+
let paramsDiffDeletions = _.differenceWith(args.spec.params, _.get(args.newSpec, "params", []), comparer);
|
|
63
97
|
paramsDiffDeletions = extensionsHelper_1.substituteParams(paramsDiffDeletions, firebaseProjectParams);
|
|
64
|
-
let paramsDiffAdditions = _.differenceWith(newSpec.params, _.get(spec, "params", []), comparer);
|
|
98
|
+
let paramsDiffAdditions = _.differenceWith(args.newSpec.params, _.get(args.spec, "params", []), comparer);
|
|
65
99
|
paramsDiffAdditions = extensionsHelper_1.substituteParams(paramsDiffAdditions, firebaseProjectParams);
|
|
66
100
|
if (paramsDiffDeletions.length) {
|
|
67
101
|
logger_1.logger.info("The following params will no longer be used:");
|
|
68
102
|
paramsDiffDeletions.forEach((param) => {
|
|
69
|
-
logger_1.logger.info(clc.red(`- ${param.param}: ${currentParams[param.param.toUpperCase()]}`));
|
|
70
|
-
delete currentParams[param.param.toUpperCase()];
|
|
103
|
+
logger_1.logger.info(clc.red(`- ${param.param}: ${args.currentParams[param.param.toUpperCase()]}`));
|
|
104
|
+
delete args.currentParams[param.param.toUpperCase()];
|
|
71
105
|
});
|
|
72
106
|
}
|
|
73
107
|
if (paramsDiffAdditions.length) {
|
|
74
108
|
logger_1.logger.info("To update this instance, configure the following new parameters:");
|
|
75
109
|
for (const param of paramsDiffAdditions) {
|
|
76
|
-
const chosenValue = await askUserForParam.askForParam(param);
|
|
77
|
-
currentParams[param.param] = chosenValue;
|
|
110
|
+
const chosenValue = await askUserForParam.askForParam(args.projectId, args.instanceId, param, false);
|
|
111
|
+
args.currentParams[param.param] = chosenValue;
|
|
78
112
|
}
|
|
79
113
|
}
|
|
80
|
-
return currentParams;
|
|
114
|
+
return args.currentParams;
|
|
81
115
|
}
|
|
82
116
|
exports.promptForNewParams = promptForNewParams;
|
|
83
|
-
function
|
|
117
|
+
function getParamsFromFile(args) {
|
|
118
|
+
let envParams;
|
|
84
119
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
120
|
+
envParams = readEnvFile(args.paramsEnvPath);
|
|
121
|
+
track("Extension Env File", "Present");
|
|
87
122
|
}
|
|
88
123
|
catch (err) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
});
|
|
124
|
+
track("Extension Env File", "Invalid");
|
|
125
|
+
throw new error_1.FirebaseError(`Error reading env file: ${err.message}\n`, { original: err });
|
|
92
126
|
}
|
|
127
|
+
const params = extensionsHelper_1.populateDefaultParams(envParams, args.paramSpecs);
|
|
128
|
+
extensionsHelper_1.validateCommandLineParams(params, args.paramSpecs);
|
|
129
|
+
logger_1.logger.info(`Using param values from ${args.paramsEnvPath}`);
|
|
130
|
+
return params;
|
|
131
|
+
}
|
|
132
|
+
exports.getParamsFromFile = getParamsFromFile;
|
|
133
|
+
function readEnvFile(envPath) {
|
|
134
|
+
const buf = fs.readFileSync(path.resolve(envPath), "utf8");
|
|
135
|
+
return dotenv.parse(buf.toString().trim(), { debug: true });
|
|
93
136
|
}
|
|
94
|
-
exports.
|
|
137
|
+
exports.readEnvFile = readEnvFile;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
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;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTrustedPublishers = exports.getExtensionRegistry = exports.
|
|
3
|
+
exports.getTrustedPublishers = exports.getExtensionRegistry = exports.getMinRequiredVersion = exports.getTargetVersion = exports.resolveRegistryEntry = exports.isOfficialSource = exports.resolveSourceUrl = exports.confirmUpdateWarning = void 0;
|
|
4
4
|
const _ = require("lodash");
|
|
5
5
|
const clc = require("cli-color");
|
|
6
6
|
const marked = require("marked");
|
|
7
|
-
const semver = require("semver");
|
|
8
7
|
const api = require("../api");
|
|
9
8
|
const error_1 = require("../error");
|
|
10
9
|
const logger_1 = require("../logger");
|
|
@@ -58,23 +57,6 @@ function getMinRequiredVersion(registryEntry) {
|
|
|
58
57
|
return _.get(registryEntry, ["labels", "minRequired"]);
|
|
59
58
|
}
|
|
60
59
|
exports.getMinRequiredVersion = getMinRequiredVersion;
|
|
61
|
-
async function promptForUpdateWarnings(registryEntry, startVersion, endVersion) {
|
|
62
|
-
if (registryEntry.updateWarnings) {
|
|
63
|
-
for (const targetRange in registryEntry.updateWarnings) {
|
|
64
|
-
if (semver.satisfies(endVersion, targetRange)) {
|
|
65
|
-
const updateWarnings = registryEntry.updateWarnings[targetRange];
|
|
66
|
-
for (const updateWarning of updateWarnings) {
|
|
67
|
-
if (semver.satisfies(startVersion, updateWarning.from)) {
|
|
68
|
-
await module.exports.confirmUpdateWarning(updateWarning);
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
break;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
exports.promptForUpdateWarnings = promptForUpdateWarnings;
|
|
78
60
|
async function getExtensionRegistry(onlyFeatured) {
|
|
79
61
|
const res = await api.request("GET", EXTENSIONS_REGISTRY_ENDPOINT, {
|
|
80
62
|
origin: api.firebaseExtensionsRegistryOrigin,
|
|
@@ -96,7 +78,7 @@ async function getTrustedPublishers() {
|
|
|
96
78
|
}
|
|
97
79
|
catch (err) {
|
|
98
80
|
logger_1.logger.debug("Couldn't get extensions registry, assuming no trusted publishers except Firebase.");
|
|
99
|
-
return ["firebase
|
|
81
|
+
return ["firebase"];
|
|
100
82
|
}
|
|
101
83
|
const publisherIds = new Set();
|
|
102
84
|
for (const entry in registry) {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prettySecretName = exports.getSecretLabels = exports.getManagedSecrets = exports.grantFirexServiceAgentSecretAdminRole = exports.usesSecrets = exports.ensureSecretManagerApiEnabled = 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
|
+
const 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).map(async (secretResourceName) => {
|
|
30
|
+
const secret = secretManagerApi.parseSecretResourceName(secretResourceName);
|
|
31
|
+
const labels = await secretManagerApi.getSecretLabels(secret.projectId, secret.name);
|
|
32
|
+
if (labels && labels[SECRET_LABEL]) {
|
|
33
|
+
return secretResourceName;
|
|
34
|
+
}
|
|
35
|
+
return Promise.resolve("");
|
|
36
|
+
}))).filter((secretId) => !!secretId);
|
|
37
|
+
}
|
|
38
|
+
exports.getManagedSecrets = getManagedSecrets;
|
|
39
|
+
function getActiveSecrets(instance) {
|
|
40
|
+
return instance.config.source.spec.params
|
|
41
|
+
.map((p) => p.type == extensionsApi.ParamType.SECRET && instance.config.params[p.param])
|
|
42
|
+
.filter((pv) => !!pv);
|
|
43
|
+
}
|
|
44
|
+
function getSecretLabels(instanceId) {
|
|
45
|
+
const labels = {};
|
|
46
|
+
labels[SECRET_LABEL] = instanceId;
|
|
47
|
+
return labels;
|
|
48
|
+
}
|
|
49
|
+
exports.getSecretLabels = getSecretLabels;
|
|
50
|
+
function prettySecretName(secretResourceName) {
|
|
51
|
+
const nameTokens = secretResourceName.split("/");
|
|
52
|
+
if (nameTokens.length != 4 && nameTokens.length != 6) {
|
|
53
|
+
logger_1.logger.debug(`unable to parse secret secretResourceName: ${secretResourceName}`);
|
|
54
|
+
return secretResourceName;
|
|
55
|
+
}
|
|
56
|
+
return nameTokens.slice(0, 4).join("/");
|
|
57
|
+
}
|
|
58
|
+
exports.prettySecretName = prettySecretName;
|