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
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const command_1 = require("../command");
|
|
4
|
+
const utils = require("../utils");
|
|
5
|
+
const requireAuth_1 = require("../requireAuth");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
|
+
const client_1 = require("../appdistribution/client");
|
|
8
|
+
const options_parser_util_1 = require("../appdistribution/options-parser-util");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
module.exports = new command_1.Command("appdistribution:testers:remove [emails...]")
|
|
11
|
+
.description("remove testers from a project")
|
|
12
|
+
.option("--file <file>", "a path to a file containing a list of tester emails to be removed")
|
|
13
|
+
.before(requireAuth_1.requireAuth)
|
|
14
|
+
.action(async (emails, options) => {
|
|
15
|
+
const projectName = await options_parser_util_1.getProjectName(options);
|
|
16
|
+
const appDistroClient = new client_1.AppDistributionClient();
|
|
17
|
+
const emailsArr = options_parser_util_1.getEmails(emails, options.file);
|
|
18
|
+
let deleteResponse;
|
|
19
|
+
try {
|
|
20
|
+
utils.logBullet(`Deleting ${emailsArr.length} testers from project`);
|
|
21
|
+
deleteResponse = await appDistroClient.removeTesters(projectName, emailsArr);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
throw new error_1.FirebaseError(`Failed to remove testers ${err}`);
|
|
25
|
+
}
|
|
26
|
+
if (!deleteResponse.emails) {
|
|
27
|
+
utils.logSuccess(`Testers did not exist`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
logger_1.logger.debug(`Testers: ${deleteResponse.emails}, have been successfully deleted`);
|
|
31
|
+
utils.logSuccess(`${deleteResponse.emails.length} testers have successfully been deleted`);
|
|
32
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const spawn = require("cross-spawn");
|
|
7
|
+
const uuid = require("uuid");
|
|
8
|
+
const command_1 = require("../command");
|
|
9
|
+
const downloadUtils = require("../downloadUtils");
|
|
10
|
+
const error_1 = require("../error");
|
|
11
|
+
const logger_1 = require("../logger");
|
|
12
|
+
const rimraf = require("rimraf");
|
|
13
|
+
const utils = require("../utils");
|
|
14
|
+
var SymbolGenerator;
|
|
15
|
+
(function (SymbolGenerator) {
|
|
16
|
+
SymbolGenerator["breakpad"] = "breakpad";
|
|
17
|
+
SymbolGenerator["csym"] = "csym";
|
|
18
|
+
})(SymbolGenerator || (SymbolGenerator = {}));
|
|
19
|
+
const SYMBOL_CACHE_ROOT_DIR = process.env.FIREBASE_CRASHLYTICS_CACHE_PATH || os.tmpdir();
|
|
20
|
+
const JAR_CACHE_DIR = process.env.FIREBASE_CRASHLYTICS_BUILDTOOLS_PATH ||
|
|
21
|
+
path.join(os.homedir(), ".cache", "firebase", "crashlytics", "buildtools");
|
|
22
|
+
const JAR_VERSION = "2.8.0";
|
|
23
|
+
const JAR_URL = `https://storage.googleapis.com/firebase-preview-drop/android/crashlytics-eap/crashlytics-buildtools/firebase-crashlytics-buildtools-${JAR_VERSION}-alpha-all.jar`;
|
|
24
|
+
exports.default = new command_1.Command("crashlytics:symbols:upload <symbolFiles...>")
|
|
25
|
+
.description("Upload symbols for native code, to symbolicate stack traces.")
|
|
26
|
+
.option("--app <appID>", "the app id of your Firebase app")
|
|
27
|
+
.option("--generator [breakpad|csym]", "the symbol generator being used, defaults to breakpad.")
|
|
28
|
+
.option("--dry-run", "generate symbols without uploading them")
|
|
29
|
+
.option("--debug", "print debug output and logging from the underlying uploader tool")
|
|
30
|
+
.action(async (symbolFiles, options) => {
|
|
31
|
+
const app = getGoogleAppID(options) || "";
|
|
32
|
+
const generator = getSymbolGenerator(options);
|
|
33
|
+
const dryRun = !!options.dryRun;
|
|
34
|
+
const debug = !!options.debug;
|
|
35
|
+
let jarFile = await downloadBuiltoolsJar();
|
|
36
|
+
if (process.env.LOCAL_JAR) {
|
|
37
|
+
jarFile = process.env.LOCAL_JAR;
|
|
38
|
+
}
|
|
39
|
+
const jarOptions = {
|
|
40
|
+
jarFile,
|
|
41
|
+
app,
|
|
42
|
+
generator,
|
|
43
|
+
cachePath: path.join(SYMBOL_CACHE_ROOT_DIR, `crashlytics-${uuid.v4()}`, "nativeSymbols", app, generator),
|
|
44
|
+
symbolFile: "",
|
|
45
|
+
generate: true,
|
|
46
|
+
};
|
|
47
|
+
for (const symbolFile of symbolFiles) {
|
|
48
|
+
utils.logBullet(`Generating symbols for ${symbolFile}`);
|
|
49
|
+
const generateArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { symbolFile }));
|
|
50
|
+
const output = runJar(generateArgs, debug);
|
|
51
|
+
if (output.length > 0) {
|
|
52
|
+
utils.logBullet(output);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
utils.logBullet(`Generated symbols for ${symbolFile}`);
|
|
56
|
+
utils.logBullet(`Output Path: ${jarOptions.cachePath}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (dryRun) {
|
|
60
|
+
utils.logBullet("Skipping upload because --dry-run was passed");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
utils.logBullet(`Uploading all generated symbols`);
|
|
64
|
+
const uploadArgs = buildArgs(Object.assign(Object.assign({}, jarOptions), { generate: false }));
|
|
65
|
+
const output = runJar(uploadArgs, debug);
|
|
66
|
+
if (output.length > 0) {
|
|
67
|
+
utils.logBullet(output);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
utils.logBullet("Successfully uploaded all symbols");
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
function getGoogleAppID(options) {
|
|
74
|
+
if (!options.app) {
|
|
75
|
+
throw new error_1.FirebaseError("set the --app option to a valid Firebase app id and try again");
|
|
76
|
+
}
|
|
77
|
+
return options.app;
|
|
78
|
+
}
|
|
79
|
+
function getSymbolGenerator(options) {
|
|
80
|
+
if (!options.generator) {
|
|
81
|
+
return SymbolGenerator.breakpad;
|
|
82
|
+
}
|
|
83
|
+
if (!Object.values(SymbolGenerator).includes(options.generator)) {
|
|
84
|
+
throw new error_1.FirebaseError('--symbol-generator should be set to either "breakpad" or "csym"');
|
|
85
|
+
}
|
|
86
|
+
return options.generator;
|
|
87
|
+
}
|
|
88
|
+
async function downloadBuiltoolsJar() {
|
|
89
|
+
const jarPath = path.join(JAR_CACHE_DIR, `crashlytics-buildtools-${JAR_VERSION}.jar`);
|
|
90
|
+
if (fs.existsSync(jarPath)) {
|
|
91
|
+
logger_1.logger.debug(`Buildtools Jar already downloaded at ${jarPath}`);
|
|
92
|
+
return jarPath;
|
|
93
|
+
}
|
|
94
|
+
if (fs.existsSync(JAR_CACHE_DIR)) {
|
|
95
|
+
logger_1.logger.debug(`Deleting Jar cache at ${JAR_CACHE_DIR} because the CLI was run with a newer Jar version`);
|
|
96
|
+
rimraf.sync(JAR_CACHE_DIR);
|
|
97
|
+
}
|
|
98
|
+
utils.logBullet("Downloading buildtools.jar to " + jarPath);
|
|
99
|
+
utils.logBullet("For open source licenses used by this command, look in the META-INF directory in the buildtools.jar file");
|
|
100
|
+
const tmpfile = await downloadUtils.downloadToTmp(JAR_URL);
|
|
101
|
+
fs.mkdirSync(JAR_CACHE_DIR, { recursive: true });
|
|
102
|
+
fs.copySync(tmpfile, jarPath);
|
|
103
|
+
return jarPath;
|
|
104
|
+
}
|
|
105
|
+
function buildArgs(options) {
|
|
106
|
+
const baseArgs = [
|
|
107
|
+
"-jar",
|
|
108
|
+
options.jarFile,
|
|
109
|
+
`-symbolGenerator=${options.generator}`,
|
|
110
|
+
`-symbolFileCacheDir=${options.cachePath}`,
|
|
111
|
+
"-verbose",
|
|
112
|
+
];
|
|
113
|
+
if (options.generate) {
|
|
114
|
+
return baseArgs.concat(["-generateNativeSymbols", `-unstrippedLibrary=${options.symbolFile}`]);
|
|
115
|
+
}
|
|
116
|
+
return baseArgs.concat([
|
|
117
|
+
"-uploadNativeSymbols",
|
|
118
|
+
`-googleAppId=${options.app}`,
|
|
119
|
+
]);
|
|
120
|
+
}
|
|
121
|
+
function runJar(args, debug) {
|
|
122
|
+
var _a, _b, _c;
|
|
123
|
+
const outputs = spawn.sync("java", args, {
|
|
124
|
+
stdio: debug ? "inherit" : "pipe",
|
|
125
|
+
});
|
|
126
|
+
if (outputs.status || 0 > 0) {
|
|
127
|
+
if (!debug) {
|
|
128
|
+
utils.logWarning(((_a = outputs.stdout) === null || _a === void 0 ? void 0 : _a.toString()) || "An unknown error occurred");
|
|
129
|
+
}
|
|
130
|
+
throw new error_1.FirebaseError("Failed to upload symbols");
|
|
131
|
+
}
|
|
132
|
+
if (!debug) {
|
|
133
|
+
let logRegex = /(Generated symbol file.*$)/m;
|
|
134
|
+
let matched = (((_b = outputs.stdout) === null || _b === void 0 ? void 0 : _b.toString()) || "").match(logRegex);
|
|
135
|
+
if (matched) {
|
|
136
|
+
return matched[1];
|
|
137
|
+
}
|
|
138
|
+
logRegex = /(Crashlytics symbol file uploaded successfully.*$)/m;
|
|
139
|
+
matched = (((_c = outputs.stdout) === null || _c === void 0 ? void 0 : _c.toString()) || "").match(logRegex);
|
|
140
|
+
if (matched) {
|
|
141
|
+
return matched[1];
|
|
142
|
+
}
|
|
143
|
+
return "";
|
|
144
|
+
}
|
|
145
|
+
return "";
|
|
146
|
+
}
|
|
@@ -20,6 +20,7 @@ marked.setOptions({
|
|
|
20
20
|
});
|
|
21
21
|
exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
22
22
|
.description("configure an existing extension instance")
|
|
23
|
+
.withForce()
|
|
23
24
|
.option("--params <paramsFile>", "path of params file with .env format.")
|
|
24
25
|
.before(requirePermissions_1.requirePermissions, [
|
|
25
26
|
"firebaseextensions.instances.update",
|
|
@@ -46,7 +47,14 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
46
47
|
const immutableParams = _.remove(paramSpecWithNewDefaults, (param) => {
|
|
47
48
|
return param.immutable || param.param === "LOCATION";
|
|
48
49
|
});
|
|
49
|
-
const params = await paramHelper.getParams(
|
|
50
|
+
const params = await paramHelper.getParams({
|
|
51
|
+
projectId,
|
|
52
|
+
paramSpecs: paramSpecWithNewDefaults,
|
|
53
|
+
nonInteractive: options.nonInteractive,
|
|
54
|
+
paramsEnvPath: options.params,
|
|
55
|
+
instanceId,
|
|
56
|
+
reconfiguring: true,
|
|
57
|
+
});
|
|
50
58
|
if (immutableParams.length) {
|
|
51
59
|
const plural = immutableParams.length > 1;
|
|
52
60
|
logger_1.logger.info(`The following param${plural ? "s are" : " is"} immutable:`);
|
|
@@ -5,6 +5,7 @@ const clc = require("cli-color");
|
|
|
5
5
|
const command_1 = require("../command");
|
|
6
6
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
7
7
|
const extensionsApi_1 = require("../extensions/extensionsApi");
|
|
8
|
+
const refs = require("../extensions/refs");
|
|
8
9
|
const prompt_1 = require("../prompt");
|
|
9
10
|
const requireAuth_1 = require("../requireAuth");
|
|
10
11
|
const error_1 = require("../error");
|
|
@@ -16,7 +17,7 @@ module.exports = new command_1.Command("ext:dev:delete <extensionRef>")
|
|
|
16
17
|
.before(requireAuth_1.requireAuth)
|
|
17
18
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
|
|
18
19
|
.action(async (extensionRef) => {
|
|
19
|
-
const { publisherId, extensionId, version } =
|
|
20
|
+
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
20
21
|
if (version) {
|
|
21
22
|
throw new error_1.FirebaseError(`Deleting a single version is not currently supported. You can only delete ${clc.bold("ALL versions")} of an extension. To delete all versions, please remove the version from the reference.`);
|
|
22
23
|
}
|
|
@@ -16,9 +16,14 @@ marked.setOptions({
|
|
|
16
16
|
});
|
|
17
17
|
const TEMPLATE_ROOT = path.resolve(__dirname, "../../templates/extensions/");
|
|
18
18
|
const FUNCTIONS_ROOT = path.resolve(__dirname, "../../templates/init/functions/");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
function readCommonTemplates() {
|
|
20
|
+
return {
|
|
21
|
+
extSpecTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "extension.yaml"), "utf8"),
|
|
22
|
+
preinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "PREINSTALL.md"), "utf8"),
|
|
23
|
+
postinstallTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "POSTINSTALL.md"), "utf8"),
|
|
24
|
+
changelogTemplate: fs.readFileSync(path.join(TEMPLATE_ROOT, "CHANGELOG.md"), "utf8"),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
22
27
|
async function typescriptSelected(config) {
|
|
23
28
|
const packageLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "package.lint.json"), "utf8");
|
|
24
29
|
const packageNoLintingTemplate = fs.readFileSync(path.join(TEMPLATE_ROOT, "typescript", "package.nolint.json"), "utf8");
|
|
@@ -33,9 +38,11 @@ async function typescriptSelected(config) {
|
|
|
33
38
|
message: "Do you want to use ESLint to catch probable bugs and enforce style?",
|
|
34
39
|
default: true,
|
|
35
40
|
});
|
|
36
|
-
|
|
37
|
-
await config.askWriteProjectFile("
|
|
38
|
-
await config.askWriteProjectFile("
|
|
41
|
+
const templates = readCommonTemplates();
|
|
42
|
+
await config.askWriteProjectFile("extension.yaml", templates.extSpecTemplate);
|
|
43
|
+
await config.askWriteProjectFile("PREINSTALL.md", templates.preinstallTemplate);
|
|
44
|
+
await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate);
|
|
45
|
+
await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate);
|
|
39
46
|
await config.askWriteProjectFile("functions/src/index.ts", indexTemplate);
|
|
40
47
|
if (lint) {
|
|
41
48
|
await config.askWriteProjectFile("functions/package.json", packageLintingTemplate);
|
|
@@ -62,9 +69,11 @@ async function javascriptSelected(config) {
|
|
|
62
69
|
message: "Do you want to use ESLint to catch probable bugs and enforce style?",
|
|
63
70
|
default: false,
|
|
64
71
|
});
|
|
65
|
-
|
|
66
|
-
await config.askWriteProjectFile("
|
|
67
|
-
await config.askWriteProjectFile("
|
|
72
|
+
const templates = readCommonTemplates();
|
|
73
|
+
await config.askWriteProjectFile("extension.yaml", templates.extSpecTemplate);
|
|
74
|
+
await config.askWriteProjectFile("PREINSTALL.md", templates.preinstallTemplate);
|
|
75
|
+
await config.askWriteProjectFile("POSTINSTALL.md", templates.postinstallTemplate);
|
|
76
|
+
await config.askWriteProjectFile("CHANGELOG.md", templates.changelogTemplate);
|
|
68
77
|
await config.askWriteProjectFile("functions/index.js", indexTemplate);
|
|
69
78
|
if (lint) {
|
|
70
79
|
await config.askWriteProjectFile("functions/package.json", packageLintingTemplate);
|
|
@@ -5,7 +5,7 @@ const marked = require("marked");
|
|
|
5
5
|
const TerminalRenderer = require("marked-terminal");
|
|
6
6
|
const command_1 = require("../command");
|
|
7
7
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
8
|
-
const
|
|
8
|
+
const refs = require("../extensions/refs");
|
|
9
9
|
const localHelper_1 = require("../extensions/localHelper");
|
|
10
10
|
const publishHelpers_1 = require("../extensions/publishHelpers");
|
|
11
11
|
const requireAuth_1 = require("../requireAuth");
|
|
@@ -16,12 +16,13 @@ marked.setOptions({
|
|
|
16
16
|
});
|
|
17
17
|
exports.default = new command_1.Command("ext:dev:publish <extensionRef>")
|
|
18
18
|
.description(`publish a new version of an extension`)
|
|
19
|
+
.withForce()
|
|
19
20
|
.help("if you have not previously published a version of this extension, this will " +
|
|
20
21
|
"create the extension. If you have previously published a version of this extension, this version must " +
|
|
21
22
|
"be greater than previous versions.")
|
|
22
23
|
.before(requireAuth_1.requireAuth)
|
|
23
|
-
.action(async (extensionRef) => {
|
|
24
|
-
const { publisherId, extensionId, version } =
|
|
24
|
+
.action(async (extensionRef, options) => {
|
|
25
|
+
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
25
26
|
if (version) {
|
|
26
27
|
throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should not be supplied and will be inferred directly from extension.yaml. Please increment the version in extension.yaml if you would like to bump/specify a version.`);
|
|
27
28
|
}
|
|
@@ -29,7 +30,13 @@ exports.default = new command_1.Command("ext:dev:publish <extensionRef>")
|
|
|
29
30
|
throw new error_1.FirebaseError(`Error parsing publisher ID and extension ID from extension reference '${clc.bold(extensionRef)}'. Please use the format '${clc.bold("<publisherId>/<extensionId>")}'.`);
|
|
30
31
|
}
|
|
31
32
|
const extensionYamlDirectory = localHelper_1.findExtensionYaml(process.cwd());
|
|
32
|
-
const res = await extensionsHelper_1.publishExtensionVersionFromLocalSource(
|
|
33
|
+
const res = await extensionsHelper_1.publishExtensionVersionFromLocalSource({
|
|
34
|
+
publisherId,
|
|
35
|
+
extensionId,
|
|
36
|
+
rootDirectory: extensionYamlDirectory,
|
|
37
|
+
nonInteractive: options.nonInteractive,
|
|
38
|
+
force: options.force,
|
|
39
|
+
});
|
|
33
40
|
if (res) {
|
|
34
41
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`[Install Link](${publishHelpers_1.consoleInstallLink(res.ref)})`));
|
|
35
42
|
}
|
|
@@ -4,6 +4,7 @@ const command_1 = require("../command");
|
|
|
4
4
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
5
5
|
const extensionsApi_1 = require("../extensions/extensionsApi");
|
|
6
6
|
const utils = require("../utils");
|
|
7
|
+
const refs = require("../extensions/refs");
|
|
7
8
|
const prompt_1 = require("../prompt");
|
|
8
9
|
const clc = require("cli-color");
|
|
9
10
|
const requireAuth_1 = require("../requireAuth");
|
|
@@ -16,7 +17,7 @@ module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
|
|
|
16
17
|
.before(requireAuth_1.requireAuth)
|
|
17
18
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
|
|
18
19
|
.action(async (extensionRef) => {
|
|
19
|
-
const { publisherId, extensionId, version } =
|
|
20
|
+
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
20
21
|
utils.logLabeledWarning(extensionsHelper_1.logPrefix, "If you unpublish this extension, developers won't be able to install it. For developers who currently have this extension installed, it will continue to run and will appear as unpublished when listed in the Firebase console or Firebase CLI.");
|
|
21
22
|
utils.logLabeledWarning("This is a permanent action", `Once unpublished, you may never use the extension name '${clc.bold(extensionId)}' again.`);
|
|
22
23
|
if (version) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const _ = require("lodash");
|
|
4
3
|
const clc = require("cli-color");
|
|
5
4
|
const marked = require("marked");
|
|
6
5
|
const ora = require("ora");
|
|
@@ -15,10 +14,13 @@ const command_1 = require("../command");
|
|
|
15
14
|
const error_1 = require("../error");
|
|
16
15
|
const projectUtils_1 = require("../projectUtils");
|
|
17
16
|
const extensionsApi = require("../extensions/extensionsApi");
|
|
17
|
+
const secretsUtils = require("../extensions/secretsUtils");
|
|
18
18
|
const provisioningHelper = require("../extensions/provisioningHelper");
|
|
19
|
+
const refs = require("../extensions/refs");
|
|
19
20
|
const warnings_1 = require("../extensions/warnings");
|
|
20
21
|
const paramHelper = require("../extensions/paramHelper");
|
|
21
22
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
23
|
+
const updateHelper_1 = require("../extensions/updateHelper");
|
|
22
24
|
const utils_1 = require("../extensions/utils");
|
|
23
25
|
const requirePermissions_1 = require("../requirePermissions");
|
|
24
26
|
const utils = require("../utils");
|
|
@@ -28,50 +30,120 @@ marked.setOptions({
|
|
|
28
30
|
renderer: new TerminalRenderer(),
|
|
29
31
|
});
|
|
30
32
|
async function installExtension(options) {
|
|
31
|
-
const { projectId, extensionName, source, extVersion,
|
|
33
|
+
const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force, } = options;
|
|
32
34
|
const spec = (source === null || source === void 0 ? void 0 : source.spec) || (extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec);
|
|
33
35
|
if (!spec) {
|
|
34
36
|
throw new error_1.FirebaseError(`Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`);
|
|
35
37
|
}
|
|
36
|
-
const spinner = ora.default(
|
|
38
|
+
const spinner = ora.default();
|
|
37
39
|
try {
|
|
38
40
|
await provisioningHelper.checkProductsProvisioned(projectId, spec);
|
|
39
|
-
|
|
41
|
+
const usesSecrets = secretsUtils.usesSecrets(spec);
|
|
42
|
+
if (spec.billingRequired || usesSecrets) {
|
|
40
43
|
const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
|
|
41
|
-
if (!enabled) {
|
|
44
|
+
if (!enabled && nonInteractive) {
|
|
45
|
+
throw new error_1.FirebaseError(`This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
|
|
46
|
+
marked("Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project."));
|
|
47
|
+
}
|
|
48
|
+
else if (!enabled) {
|
|
42
49
|
await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, false);
|
|
43
50
|
await checkProjectBilling_1.enableBilling(projectId, spec.displayName || spec.name);
|
|
44
51
|
}
|
|
45
52
|
else {
|
|
46
|
-
await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec,
|
|
53
|
+
await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, !nonInteractive);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const apis = spec.apis || [];
|
|
57
|
+
if (usesSecrets) {
|
|
58
|
+
apis.push({
|
|
59
|
+
apiName: "secretmanager.googleapis.com",
|
|
60
|
+
reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (apis.length) {
|
|
64
|
+
askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis);
|
|
65
|
+
const consented = await extensionsHelper_1.confirm({ nonInteractive, force, default: true });
|
|
66
|
+
if (!consented) {
|
|
67
|
+
throw new error_1.FirebaseError("Without explicit consent for the APIs listed, we cannot deploy this extension.");
|
|
47
68
|
}
|
|
48
69
|
}
|
|
70
|
+
if (usesSecrets) {
|
|
71
|
+
await secretsUtils.ensureSecretManagerApiEnabled(options);
|
|
72
|
+
}
|
|
49
73
|
const roles = spec.roles ? spec.roles.map((role) => role.role) : [];
|
|
50
|
-
|
|
74
|
+
if (roles.length) {
|
|
75
|
+
await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles);
|
|
76
|
+
const consented = await extensionsHelper_1.confirm({ nonInteractive, force, default: true });
|
|
77
|
+
if (!consented) {
|
|
78
|
+
throw new error_1.FirebaseError("Without explicit consent for the roles listed, we cannot deploy this extension.");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
51
81
|
let instanceId = spec.name;
|
|
82
|
+
let choice;
|
|
52
83
|
const anotherInstanceExists = await extensionsHelper_1.instanceIdExists(projectId, instanceId);
|
|
53
84
|
if (anotherInstanceExists) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
85
|
+
if (!nonInteractive) {
|
|
86
|
+
choice = await extensionsHelper_1.promptForRepeatInstance(projectId, spec.name);
|
|
87
|
+
}
|
|
88
|
+
else if (nonInteractive && force) {
|
|
89
|
+
choice = "updateExisting";
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new error_1.FirebaseError(`An extension with the ID '${clc.bold(extensionName)}' already exists in the project '${clc.bold(projectId)}'.` +
|
|
93
|
+
` To update or reconfigure this instance instead, rerun this command with the --force flag.`);
|
|
58
94
|
}
|
|
59
|
-
instanceId = await extensionsHelper_1.promptForValidInstanceId(`${instanceId}-${utils_1.getRandomString(4)}`);
|
|
60
|
-
}
|
|
61
|
-
const params = await paramHelper.getParams(projectId, _.get(spec, "params", []), paramFilePath);
|
|
62
|
-
spinner.start();
|
|
63
|
-
if (!source && extVersion) {
|
|
64
|
-
await extensionsApi.createInstanceFromExtensionVersion(projectId, instanceId, extVersion, params);
|
|
65
|
-
}
|
|
66
|
-
else if (source) {
|
|
67
|
-
await extensionsApi.createInstanceFromSource(projectId, instanceId, source, params);
|
|
68
95
|
}
|
|
69
96
|
else {
|
|
70
|
-
|
|
97
|
+
choice = "installNew";
|
|
98
|
+
}
|
|
99
|
+
let params;
|
|
100
|
+
switch (choice) {
|
|
101
|
+
case "installNew":
|
|
102
|
+
instanceId = await extensionsHelper_1.promptForValidInstanceId(`${instanceId}-${utils_1.getRandomString(4)}`);
|
|
103
|
+
params = await paramHelper.getParams({
|
|
104
|
+
projectId,
|
|
105
|
+
paramSpecs: spec.params,
|
|
106
|
+
nonInteractive,
|
|
107
|
+
paramsEnvPath,
|
|
108
|
+
instanceId,
|
|
109
|
+
});
|
|
110
|
+
spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
|
|
111
|
+
spinner.start();
|
|
112
|
+
await extensionsApi.createInstance({
|
|
113
|
+
projectId,
|
|
114
|
+
instanceId,
|
|
115
|
+
extensionSource: source,
|
|
116
|
+
extensionVersionRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
|
|
117
|
+
params,
|
|
118
|
+
});
|
|
119
|
+
spinner.stop();
|
|
120
|
+
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
|
|
121
|
+
`Its Instance ID is ${clc.bold(instanceId)}.`);
|
|
122
|
+
break;
|
|
123
|
+
case "updateExisting":
|
|
124
|
+
params = await paramHelper.getParams({
|
|
125
|
+
projectId,
|
|
126
|
+
paramSpecs: spec.params,
|
|
127
|
+
nonInteractive,
|
|
128
|
+
paramsEnvPath,
|
|
129
|
+
instanceId,
|
|
130
|
+
});
|
|
131
|
+
spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
|
|
132
|
+
spinner.start();
|
|
133
|
+
await updateHelper_1.update({
|
|
134
|
+
projectId,
|
|
135
|
+
instanceId,
|
|
136
|
+
source,
|
|
137
|
+
extRef: extVersion === null || extVersion === void 0 ? void 0 : extVersion.ref,
|
|
138
|
+
params,
|
|
139
|
+
});
|
|
140
|
+
spinner.stop();
|
|
141
|
+
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
|
|
142
|
+
`Its Instance ID is ${clc.bold(instanceId)}.`);
|
|
143
|
+
break;
|
|
144
|
+
case "cancel":
|
|
145
|
+
return;
|
|
71
146
|
}
|
|
72
|
-
spinner.stop();
|
|
73
|
-
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
|
|
74
|
-
`Its Instance ID is ${clc.bold(instanceId)}.`);
|
|
75
147
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked("Go to the Firebase console to view instructions for using your extension, " +
|
|
76
148
|
`which may include some required post-installation tasks: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=usage`)}`));
|
|
77
149
|
logger_1.logger.info(marked("You can run `firebase ext` to view available Firebase Extensions commands, " +
|
|
@@ -89,7 +161,7 @@ async function installExtension(options) {
|
|
|
89
161
|
});
|
|
90
162
|
}
|
|
91
163
|
}
|
|
92
|
-
async function
|
|
164
|
+
async function infoInstallBySource(projectId, extensionName) {
|
|
93
165
|
let source;
|
|
94
166
|
try {
|
|
95
167
|
source = await extensionsHelper_1.createSourceFromLocation(projectId, extensionName);
|
|
@@ -99,32 +171,21 @@ async function confirmInstallBySource(projectId, extensionName) {
|
|
|
99
171
|
`and encountered the following error when trying to create an instance of extension '${clc.bold(extensionName)}':\n ${err.message}`);
|
|
100
172
|
}
|
|
101
173
|
displayExtensionInfo_1.displayExtInfo(extensionName, "", source.spec);
|
|
102
|
-
const confirm = await extensionsHelper_1.confirmInstallInstance();
|
|
103
|
-
if (!confirm) {
|
|
104
|
-
throw new error_1.FirebaseError("Install cancelled.");
|
|
105
|
-
}
|
|
106
174
|
return source;
|
|
107
175
|
}
|
|
108
|
-
async function
|
|
176
|
+
async function infoInstallByReference(extensionName) {
|
|
109
177
|
if (extensionName.split("/").length < 2) {
|
|
110
178
|
const [extensionID, version] = extensionName.split("@");
|
|
111
179
|
extensionName = `firebase/${extensionID}@${version || "latest"}`;
|
|
112
180
|
}
|
|
113
|
-
const ref =
|
|
114
|
-
const extension = await extensionsApi.getExtension(
|
|
181
|
+
const ref = refs.parse(extensionName);
|
|
182
|
+
const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref));
|
|
115
183
|
if (!ref.version) {
|
|
116
184
|
extensionName = `${extensionName}@latest`;
|
|
117
185
|
}
|
|
118
186
|
const extVersion = await extensionsApi.getExtensionVersion(extensionName);
|
|
119
187
|
displayExtensionInfo_1.displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true);
|
|
120
|
-
|
|
121
|
-
if (!confirm) {
|
|
122
|
-
throw new error_1.FirebaseError("Install cancelled.");
|
|
123
|
-
}
|
|
124
|
-
const warningConsent = await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
125
|
-
if (!warningConsent) {
|
|
126
|
-
throw new error_1.FirebaseError("Install cancelled.");
|
|
127
|
-
}
|
|
188
|
+
await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
128
189
|
return extVersion;
|
|
129
190
|
}
|
|
130
191
|
exports.default = new command_1.Command("ext:install [extensionName]")
|
|
@@ -133,13 +194,14 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
133
194
|
? "install a local extension if [localPathOrUrl] or [url#root] is provided; install a published extension (not authored by Firebase) if [publisherId/extensionId] is provided "
|
|
134
195
|
: "") +
|
|
135
196
|
"or run with `-i` to see all available extensions.")
|
|
197
|
+
.withForce()
|
|
136
198
|
.option("--params <paramsFile>", "name of params variables file with .env format.")
|
|
137
199
|
.before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.create"])
|
|
138
200
|
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
139
201
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
140
202
|
.action(async (extensionName, options) => {
|
|
141
203
|
const projectId = projectUtils_1.needProjectId(options);
|
|
142
|
-
const
|
|
204
|
+
const paramsEnvPath = options.params;
|
|
143
205
|
let learnMore = false;
|
|
144
206
|
if (!extensionName) {
|
|
145
207
|
if (options.interactive) {
|
|
@@ -155,10 +217,17 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
155
217
|
let source;
|
|
156
218
|
let extVersion;
|
|
157
219
|
if (extensionsHelper_1.isLocalOrURLPath(extensionName)) {
|
|
158
|
-
source = await
|
|
220
|
+
source = await infoInstallBySource(projectId, extensionName);
|
|
159
221
|
}
|
|
160
222
|
else {
|
|
161
|
-
extVersion = await
|
|
223
|
+
extVersion = await infoInstallByReference(extensionName);
|
|
224
|
+
}
|
|
225
|
+
if (!(await extensionsHelper_1.confirm({
|
|
226
|
+
nonInteractive: options.nonInteractive,
|
|
227
|
+
force: options.force,
|
|
228
|
+
default: true,
|
|
229
|
+
}))) {
|
|
230
|
+
return;
|
|
162
231
|
}
|
|
163
232
|
if (!source && !extVersion) {
|
|
164
233
|
throw new error_1.FirebaseError("Could not find a source. Please specify a valid source to continue.");
|
|
@@ -171,18 +240,16 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
171
240
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `You selected: ${clc.bold(spec.displayName)}.\n` +
|
|
172
241
|
`${spec.description}\n` +
|
|
173
242
|
`View details: https://firebase.google.com/products/extensions/${spec.name}\n`);
|
|
174
|
-
const confirm = await extensionsHelper_1.confirmInstallInstance();
|
|
175
|
-
if (!confirm) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
243
|
}
|
|
179
244
|
try {
|
|
180
245
|
return installExtension({
|
|
181
|
-
|
|
246
|
+
paramsEnvPath,
|
|
182
247
|
projectId,
|
|
183
248
|
extensionName,
|
|
184
249
|
source,
|
|
185
250
|
extVersion,
|
|
251
|
+
nonInteractive: options.nonInteractive,
|
|
252
|
+
force: options.force,
|
|
186
253
|
});
|
|
187
254
|
}
|
|
188
255
|
catch (err) {
|
|
@@ -10,6 +10,7 @@ const command_1 = require("../command");
|
|
|
10
10
|
const error_1 = require("../error");
|
|
11
11
|
const projectUtils_1 = require("../projectUtils");
|
|
12
12
|
const extensionsApi = require("../extensions/extensionsApi");
|
|
13
|
+
const secretsUtils = require("../extensions/secretsUtils");
|
|
13
14
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
14
15
|
const prompt_1 = require("../prompt");
|
|
15
16
|
const requirePermissions_1 = require("../requirePermissions");
|
|
@@ -51,11 +52,16 @@ exports.default = new command_1.Command("ext:uninstall <extensionInstanceId>")
|
|
|
51
52
|
}
|
|
52
53
|
if (!options.force) {
|
|
53
54
|
const serviceAccountMessage = `Uninstalling deletes the service account used by this extension instance:\n${clc.bold(instance.serviceAccountEmail)}\n\n`;
|
|
55
|
+
const managedSecrets = await secretsUtils.getManagedSecrets(instance);
|
|
54
56
|
const resourcesMessage = _.get(instance, "config.source.spec.resources", []).length
|
|
55
57
|
? "Uninstalling deletes all extension resources created for this extension instance:\n" +
|
|
56
58
|
instance.config.source.spec.resources
|
|
57
59
|
.map((resource) => clc.bold(`- ${extensionsHelper_1.resourceTypeToNiceName[resource.type] || resource.type}: ${resource.name} \n`))
|
|
58
60
|
.join("") +
|
|
61
|
+
managedSecrets
|
|
62
|
+
.map(secretsUtils.prettySecretName)
|
|
63
|
+
.map((s) => clc.bold(`- Secret: ${s}\n`))
|
|
64
|
+
.join("") +
|
|
59
65
|
"\n"
|
|
60
66
|
: "";
|
|
61
67
|
const artifactsMessage = `The following ${clc.bold("will not")} be deleted:\n` +
|