firebase-tools 9.19.0 → 9.23.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 +3 -0
- package/lib/apiv2.js +7 -4
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +3 -1
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +10 -3
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +24 -3
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +10 -3
- 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/params.js +39 -0
- package/lib/deploy/extensions/planner.js +94 -0
- package/lib/deploy/extensions/prepare.js +111 -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 +71 -14
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +63 -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 +10 -1
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +549 -6
- package/lib/emulator/auth/handlers.js +4 -3
- package/lib/emulator/auth/operations.js +154 -14
- package/lib/emulator/auth/server.js +26 -15
- package/lib/emulator/auth/state.js +151 -13
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/functionsEmulator.js +18 -4
- package/lib/emulator/functionsEmulatorRuntime.js +29 -7
- 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/checkProjectBilling.js +7 -7
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +104 -21
- package/lib/extensions/extensionsHelper.js +6 -2
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +9 -6
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +9 -1
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +12 -2
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +8 -1
- package/lib/functions/env.js +10 -4
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +20 -74
- package/lib/gcp/cloudfunctionsv2.js +12 -90
- 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/projectUtils.js +10 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +5 -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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1 @@
|
|
|
1
|
-
- `
|
|
2
|
-
- Fixes issue where account specified by `login:use` was not being correctly loaded (#3759).
|
|
3
|
-
- Fixes minor layout issues in Auth Emulator IDP sign-in page (#3774).
|
|
1
|
+
- `firebase deploy --only extensions` now supports project specifc .env files. When deploying to multiple projects, param values that are different between projects can be put in `extensions/${extensionInstanceId}.env.${projectIdOrAlias}` and common param values can be put in `extensions/${extensionInstanceId}.env`.
|
package/lib/api.js
CHANGED
|
@@ -72,6 +72,7 @@ var api = {
|
|
|
72
72
|
cloudbillingOrigin: utils.envOverride("FIREBASE_CLOUDBILLING_URL", "https://cloudbilling.googleapis.com"),
|
|
73
73
|
cloudloggingOrigin: utils.envOverride("FIREBASE_CLOUDLOGGING_URL", "https://logging.googleapis.com"),
|
|
74
74
|
containerRegistryDomain: utils.envOverride("CONTAINER_REGISTRY_DOMAIN", "gcr.io"),
|
|
75
|
+
artifactRegistryDomain: utils.envOverride("ARTIFACT_REGISTRY_DOMAIN", "https://artifactregistry.googleapis.com"),
|
|
75
76
|
appDistributionOrigin: utils.envOverride("FIREBASE_APP_DISTRIBUTION_URL", "https://firebaseappdistribution.googleapis.com"),
|
|
76
77
|
appengineOrigin: utils.envOverride("FIREBASE_APPENGINE_URL", "https://appengine.googleapis.com"),
|
|
77
78
|
authOrigin: utils.envOverride("FIREBASE_AUTH_URL", "https://accounts.google.com"),
|
|
@@ -95,6 +96,7 @@ var api = {
|
|
|
95
96
|
functionsUploadRegion: utils.envOverride("FIREBASE_FUNCTIONS_UPLOAD_REGION", "us-central1"),
|
|
96
97
|
functionsDefaultRegion: utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1"),
|
|
97
98
|
cloudschedulerOrigin: utils.envOverride("FIREBASE_CLOUDSCHEDULER_URL", "https://cloudscheduler.googleapis.com"),
|
|
99
|
+
cloudTasksOrigin: utils.envOverride("FIREBASE_CLOUD_TAKS_URL", "https://cloudtasks.googleapis.com"),
|
|
98
100
|
pubsubOrigin: utils.envOverride("FIREBASE_PUBSUB_URL", "https://pubsub.googleapis.com"),
|
|
99
101
|
googleOrigin: utils.envOverride("FIREBASE_TOKEN_URL", utils.envOverride("FIREBASE_GOOGLE_URL", "https://www.googleapis.com")),
|
|
100
102
|
hostingOrigin: utils.envOverride("FIREBASE_HOSTING_URL", "https://web.app"),
|
|
@@ -115,6 +117,7 @@ var api = {
|
|
|
115
117
|
serviceUsageOrigin: utils.envOverride("FIREBASE_SERVICE_USAGE_URL", "https://serviceusage.googleapis.com"),
|
|
116
118
|
githubOrigin: utils.envOverride("GITHUB_URL", "https://github.com"),
|
|
117
119
|
githubApiOrigin: utils.envOverride("GITHUB_API_URL", "https://api.github.com"),
|
|
120
|
+
secretManagerOrigin: utils.envOverride("CLOUD_SECRET_MANAGER_URL", "https://secretmanager.googleapis.com"),
|
|
118
121
|
githubClientId: utils.envOverride("GITHUB_CLIENT_ID", "89cf50f02ac6aaed3484"),
|
|
119
122
|
githubClientSecret: utils.envOverride("GITHUB_CLIENT_SECRET", "3330d14abc895d9a74d5f17cd7a00711fa2c5bf0"),
|
|
120
123
|
setRefreshToken: function (token) {
|
package/lib/apiv2.js
CHANGED
|
@@ -118,8 +118,10 @@ class Client {
|
|
|
118
118
|
reqOptions.headers.set("User-Agent", `FirebaseCLI/${CLI_VERSION}`);
|
|
119
119
|
}
|
|
120
120
|
reqOptions.headers.set("X-Client-Version", `FirebaseCLI/${CLI_VERSION}`);
|
|
121
|
-
if (reqOptions.
|
|
122
|
-
reqOptions.
|
|
121
|
+
if (!reqOptions.headers.has("Content-Type")) {
|
|
122
|
+
if (reqOptions.responseType === "json") {
|
|
123
|
+
reqOptions.headers.set("Content-Type", "application/json");
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
return reqOptions;
|
|
125
127
|
}
|
|
@@ -217,11 +219,12 @@ class Client {
|
|
|
217
219
|
}
|
|
218
220
|
let body;
|
|
219
221
|
if (options.responseType === "json") {
|
|
220
|
-
|
|
222
|
+
const text = await res.text();
|
|
223
|
+
if (!text.length) {
|
|
221
224
|
body = undefined;
|
|
222
225
|
}
|
|
223
226
|
else {
|
|
224
|
-
body =
|
|
227
|
+
body = JSON.parse(text);
|
|
225
228
|
}
|
|
226
229
|
}
|
|
227
230
|
else if (options.responseType === "stream") {
|
|
@@ -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://dl.google.com/android/maven2/com/google/firebase/firebase-crashlytics-buildtools/${JAR_VERSION}/firebase-crashlytics-buildtools-${JAR_VERSION}.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.replace(/:/g, "-"), 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
|
+
}
|
package/lib/commands/deploy.js
CHANGED
|
@@ -9,7 +9,15 @@ const deploy = require("../deploy");
|
|
|
9
9
|
const requireConfig = require("../requireConfig");
|
|
10
10
|
const { filterTargets } = require("../filterTargets");
|
|
11
11
|
const { requireHostingSite } = require("../requireHostingSite");
|
|
12
|
-
const VALID_TARGETS = [
|
|
12
|
+
const VALID_TARGETS = [
|
|
13
|
+
"database",
|
|
14
|
+
"storage",
|
|
15
|
+
"firestore",
|
|
16
|
+
"functions",
|
|
17
|
+
"hosting",
|
|
18
|
+
"remoteconfig",
|
|
19
|
+
"extensions",
|
|
20
|
+
];
|
|
13
21
|
const TARGET_PERMISSIONS = {
|
|
14
22
|
database: ["firebasedatabase.instances.update"],
|
|
15
23
|
hosting: ["firebasehosting.sites.update"],
|
|
@@ -52,6 +52,8 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
52
52
|
paramSpecs: paramSpecWithNewDefaults,
|
|
53
53
|
nonInteractive: options.nonInteractive,
|
|
54
54
|
paramsEnvPath: options.params,
|
|
55
|
+
instanceId,
|
|
56
|
+
reconfiguring: true,
|
|
55
57
|
});
|
|
56
58
|
if (immutableParams.length) {
|
|
57
59
|
const plural = immutableParams.length > 1;
|
|
@@ -67,7 +69,7 @@ exports.default = new command_1.Command("ext:configure <extensionInstanceId>")
|
|
|
67
69
|
", uninstall the extension, then install a new instance of this extension.");
|
|
68
70
|
}
|
|
69
71
|
spinner.start();
|
|
70
|
-
const res = await extensionsApi.configureInstance(projectId, instanceId, params);
|
|
72
|
+
const res = await extensionsApi.configureInstance({ projectId, instanceId, params });
|
|
71
73
|
spinner.stop();
|
|
72
74
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, `successfully configured ${clc.bold(instanceId)}.`);
|
|
73
75
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, marked(`You can view your reconfigured instance in the Firebase console: ${utils.consoleUrl(projectId, `/extensions/instances/${instanceId}?tab=config`)}`));
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const clc = require("cli-color");
|
|
4
|
+
const semver = require("semver");
|
|
5
|
+
const refs = require("../extensions/refs");
|
|
6
|
+
const utils = require("../utils");
|
|
7
|
+
const command_1 = require("../command");
|
|
8
|
+
const prompt_1 = require("../prompt");
|
|
9
|
+
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
10
|
+
const extensionsApi_1 = require("../extensions/extensionsApi");
|
|
11
|
+
const versionHelper_1 = require("../extensions/versionHelper");
|
|
12
|
+
const requireAuth_1 = require("../requireAuth");
|
|
13
|
+
const error_1 = require("../error");
|
|
14
|
+
exports.default = new command_1.Command("ext:dev:deprecate <extensionRef> <versionPredicate>")
|
|
15
|
+
.description("deprecate extension versions that match the version predicate")
|
|
16
|
+
.option("-m, --message <deprecationMessage>", "deprecation message")
|
|
17
|
+
.option("-f, --force", "override deprecation message for existing deprecated extension versions that match")
|
|
18
|
+
.before(requireAuth_1.requireAuth)
|
|
19
|
+
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
20
|
+
.action(async (extensionRef, versionPredicate, options) => {
|
|
21
|
+
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
22
|
+
if (version) {
|
|
23
|
+
throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
|
|
24
|
+
}
|
|
25
|
+
if (!publisherId || !extensionId) {
|
|
26
|
+
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>")}'.`);
|
|
27
|
+
}
|
|
28
|
+
const { comparator, targetSemVer } = versionHelper_1.parseVersionPredicate(versionPredicate);
|
|
29
|
+
const filter = `id${comparator}"${targetSemVer}"`;
|
|
30
|
+
const extensionVersions = await extensionsApi_1.listExtensionVersions(extensionRef, filter);
|
|
31
|
+
const filteredExtensionVersions = extensionVersions
|
|
32
|
+
.sort((ev1, ev2) => {
|
|
33
|
+
return -semver.compare(ev1.spec.version, ev2.spec.version);
|
|
34
|
+
})
|
|
35
|
+
.filter((extensionVersion) => {
|
|
36
|
+
if (extensionVersion.state === "DEPRECATED" && !options.force) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const message = extensionVersion.state === "DEPRECATED" ? ", will overwrite deprecation message" : "";
|
|
40
|
+
utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state + message);
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
if (filteredExtensionVersions.length > 0) {
|
|
44
|
+
if (!options.force) {
|
|
45
|
+
const confirmMessage = "You are about to deprecate these extension version(s). Do you wish to continue?";
|
|
46
|
+
const consent = await prompt_1.promptOnce({
|
|
47
|
+
type: "confirm",
|
|
48
|
+
message: confirmMessage,
|
|
49
|
+
default: false,
|
|
50
|
+
});
|
|
51
|
+
if (!consent) {
|
|
52
|
+
throw new error_1.FirebaseError("Deprecation canceled.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw new error_1.FirebaseError("No extension versions matched the version predicate.");
|
|
58
|
+
}
|
|
59
|
+
await utils.allSettled(filteredExtensionVersions.map(async (extensionVersion) => {
|
|
60
|
+
await extensionsApi_1.deprecateExtensionVersion(extensionVersion.ref, options.deprecationMessage);
|
|
61
|
+
}));
|
|
62
|
+
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully deprecated extension version(s).");
|
|
63
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const clc = require("cli-color");
|
|
4
|
+
const semver = require("semver");
|
|
5
|
+
const refs = require("../extensions/refs");
|
|
6
|
+
const utils = require("../utils");
|
|
7
|
+
const command_1 = require("../command");
|
|
8
|
+
const prompt_1 = require("../prompt");
|
|
9
|
+
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
10
|
+
const extensionsApi_1 = require("../extensions/extensionsApi");
|
|
11
|
+
const versionHelper_1 = require("../extensions/versionHelper");
|
|
12
|
+
const requireAuth_1 = require("../requireAuth");
|
|
13
|
+
const error_1 = require("../error");
|
|
14
|
+
exports.default = new command_1.Command("ext:dev:undeprecate <extensionRef> <versionPredicate>")
|
|
15
|
+
.description("undeprecate extension versions that match the version predicate")
|
|
16
|
+
.before(requireAuth_1.requireAuth)
|
|
17
|
+
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
18
|
+
.action(async (extensionRef, versionPredicate, options) => {
|
|
19
|
+
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
20
|
+
if (version) {
|
|
21
|
+
throw new error_1.FirebaseError(`The input extension reference must be of the format ${clc.bold("<publisherId>/<extensionId>")}. Version should be supplied in the version predicate argument.`);
|
|
22
|
+
}
|
|
23
|
+
if (!publisherId || !extensionId) {
|
|
24
|
+
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>")}'.`);
|
|
25
|
+
}
|
|
26
|
+
const { comparator, targetSemVer } = versionHelper_1.parseVersionPredicate(versionPredicate);
|
|
27
|
+
const filter = `id${comparator}"${targetSemVer}"`;
|
|
28
|
+
const extensionVersions = await extensionsApi_1.listExtensionVersions(extensionRef, filter);
|
|
29
|
+
extensionVersions
|
|
30
|
+
.sort((ev1, ev2) => {
|
|
31
|
+
return -semver.compare(ev1.spec.version, ev2.spec.version);
|
|
32
|
+
})
|
|
33
|
+
.forEach((extensionVersion) => {
|
|
34
|
+
utils.logLabeledBullet(extensionVersion.ref, extensionVersion.state);
|
|
35
|
+
});
|
|
36
|
+
if (extensionVersions.length > 0) {
|
|
37
|
+
if (!options.force) {
|
|
38
|
+
const confirmMessage = "You are about to undeprecate these extension version(s). Do you wish to continue?";
|
|
39
|
+
const consent = await prompt_1.promptOnce({
|
|
40
|
+
type: "confirm",
|
|
41
|
+
message: confirmMessage,
|
|
42
|
+
default: false,
|
|
43
|
+
});
|
|
44
|
+
if (!consent) {
|
|
45
|
+
throw new error_1.FirebaseError("Undeprecation canceled.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new error_1.FirebaseError("No extension versions matched the version predicate.");
|
|
51
|
+
}
|
|
52
|
+
await utils.allSettled(extensionVersions.map(async (extensionVersion) => {
|
|
53
|
+
await extensionsApi_1.undeprecateExtensionVersion(extensionVersion.ref);
|
|
54
|
+
}));
|
|
55
|
+
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully undeprecated extension version(s).");
|
|
56
|
+
});
|
|
@@ -12,11 +12,12 @@ const error_1 = require("../error");
|
|
|
12
12
|
const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
|
|
13
13
|
module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
|
|
14
14
|
.description("unpublish an extension")
|
|
15
|
+
.withForce()
|
|
15
16
|
.help("use this command to unpublish an extension, and make it unavailable for developers to install or reconfigure. " +
|
|
16
17
|
"Specify the extension you want to unpublish using the format '<publisherId>/<extensionId>.")
|
|
17
18
|
.before(requireAuth_1.requireAuth)
|
|
18
19
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extDevMinVersion")
|
|
19
|
-
.action(async (extensionRef) => {
|
|
20
|
+
.action(async (extensionRef, options) => {
|
|
20
21
|
const { publisherId, extensionId, version } = refs.parse(extensionRef);
|
|
21
22
|
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.");
|
|
22
23
|
utils.logLabeledWarning("This is a permanent action", `Once unpublished, you may never use the extension name '${clc.bold(extensionId)}' again.`);
|
|
@@ -24,14 +25,20 @@ module.exports = new command_1.Command("ext:dev:unpublish <extensionRef>")
|
|
|
24
25
|
throw new error_1.FirebaseError(`Unpublishing a single version is not currently supported. You can only unpublish ${clc.bold("ALL versions")} of an extension. To unpublish all versions, please remove the version from the reference.`);
|
|
25
26
|
}
|
|
26
27
|
await extensionsApi_1.getExtension(extensionRef);
|
|
27
|
-
const consent = await comfirmUnpublish(publisherId, extensionId);
|
|
28
|
+
const consent = await comfirmUnpublish(publisherId, extensionId, options);
|
|
28
29
|
if (!consent) {
|
|
29
30
|
throw new error_1.FirebaseError("unpublishing cancelled.");
|
|
30
31
|
}
|
|
31
32
|
await extensionsApi_1.unpublishExtension(extensionRef);
|
|
32
33
|
utils.logLabeledSuccess(extensionsHelper_1.logPrefix, "successfully unpublished all versions of this extension.");
|
|
33
34
|
});
|
|
34
|
-
async function comfirmUnpublish(publisherId, extensionId) {
|
|
35
|
+
async function comfirmUnpublish(publisherId, extensionId, options) {
|
|
36
|
+
if (options.nonInteractive && !options.force) {
|
|
37
|
+
throw new error_1.FirebaseError("Pass the --force flag to use this command in non-interactive mode");
|
|
38
|
+
}
|
|
39
|
+
if (options.nonInteractive && options.force) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
35
42
|
const message = `You are about to unpublish ALL versions of ${clc.green(`${publisherId}/${extensionId}`)}.\nDo you wish to continue? `;
|
|
36
43
|
return prompt_1.promptOnce({
|
|
37
44
|
type: "confirm",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion");
|
|
4
|
+
const command_1 = require("../command");
|
|
5
|
+
const planner = require("../deploy/extensions/planner");
|
|
6
|
+
const export_1 = require("../extensions/export");
|
|
7
|
+
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
8
|
+
const functional_1 = require("../functional");
|
|
9
|
+
const getProjectNumber_1 = require("../getProjectNumber");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const projectUtils_1 = require("../projectUtils");
|
|
12
|
+
const prompt_1 = require("../prompt");
|
|
13
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
14
|
+
module.exports = new command_1.Command("ext:export")
|
|
15
|
+
.description("export all Extension instances installed on a project to a local Firebase directory")
|
|
16
|
+
.before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.list"])
|
|
17
|
+
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
18
|
+
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
19
|
+
.withForce()
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
22
|
+
const projectNumber = await getProjectNumber_1.getProjectNumber(options);
|
|
23
|
+
const have = await Promise.all((await planner.have(projectId)).map(async (i) => {
|
|
24
|
+
const subbed = await export_1.setSecretParamsToLatest(i);
|
|
25
|
+
return export_1.parameterizeProject(projectId, projectNumber, subbed);
|
|
26
|
+
}));
|
|
27
|
+
if (have.length == 0) {
|
|
28
|
+
logger_1.logger.info(`No extension instances installed on ${projectId}, so there is nothing to export.`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const [withRef, withoutRef] = functional_1.partition(have, (s) => !!s.ref);
|
|
32
|
+
export_1.displayExportInfo(withRef, withoutRef);
|
|
33
|
+
if (!options.nonInteractive &&
|
|
34
|
+
!options.force &&
|
|
35
|
+
!(await prompt_1.promptOnce({
|
|
36
|
+
message: "Do you wish to add these Extension instances to firebase.json?",
|
|
37
|
+
type: "confirm",
|
|
38
|
+
default: true,
|
|
39
|
+
}))) {
|
|
40
|
+
logger_1.logger.info("Exiting. No changes made.");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await export_1.writeFiles(withRef, options);
|
|
44
|
+
});
|
|
@@ -14,6 +14,7 @@ const command_1 = require("../command");
|
|
|
14
14
|
const error_1 = require("../error");
|
|
15
15
|
const projectUtils_1 = require("../projectUtils");
|
|
16
16
|
const extensionsApi = require("../extensions/extensionsApi");
|
|
17
|
+
const secretsUtils = require("../extensions/secretsUtils");
|
|
17
18
|
const provisioningHelper = require("../extensions/provisioningHelper");
|
|
18
19
|
const refs = require("../extensions/refs");
|
|
19
20
|
const warnings_1 = require("../extensions/warnings");
|
|
@@ -37,7 +38,8 @@ async function installExtension(options) {
|
|
|
37
38
|
const spinner = ora.default();
|
|
38
39
|
try {
|
|
39
40
|
await provisioningHelper.checkProductsProvisioned(projectId, spec);
|
|
40
|
-
|
|
41
|
+
const usesSecrets = secretsUtils.usesSecrets(spec);
|
|
42
|
+
if (spec.billingRequired || usesSecrets) {
|
|
41
43
|
const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
|
|
42
44
|
if (!enabled && nonInteractive) {
|
|
43
45
|
throw new error_1.FirebaseError(`This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
|
|
@@ -45,12 +47,29 @@ async function installExtension(options) {
|
|
|
45
47
|
}
|
|
46
48
|
else if (!enabled) {
|
|
47
49
|
await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, false);
|
|
48
|
-
await checkProjectBilling_1.enableBilling(projectId
|
|
50
|
+
await checkProjectBilling_1.enableBilling(projectId);
|
|
49
51
|
}
|
|
50
52
|
else {
|
|
51
53
|
await billingMigrationHelper_1.displayNode10CreateBillingNotice(spec, !nonInteractive);
|
|
52
54
|
}
|
|
53
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.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (usesSecrets) {
|
|
71
|
+
await secretsUtils.ensureSecretManagerApiEnabled(options);
|
|
72
|
+
}
|
|
54
73
|
const roles = spec.roles ? spec.roles.map((role) => role.role) : [];
|
|
55
74
|
if (roles.length) {
|
|
56
75
|
await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles);
|
|
@@ -86,6 +105,7 @@ async function installExtension(options) {
|
|
|
86
105
|
paramSpecs: spec.params,
|
|
87
106
|
nonInteractive,
|
|
88
107
|
paramsEnvPath,
|
|
108
|
+
instanceId,
|
|
89
109
|
});
|
|
90
110
|
spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
|
|
91
111
|
spinner.start();
|
|
@@ -106,6 +126,7 @@ async function installExtension(options) {
|
|
|
106
126
|
paramSpecs: spec.params,
|
|
107
127
|
nonInteractive,
|
|
108
128
|
paramsEnvPath,
|
|
129
|
+
instanceId,
|
|
109
130
|
});
|
|
110
131
|
spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
|
|
111
132
|
spinner.start();
|
|
@@ -164,7 +185,7 @@ async function infoInstallByReference(extensionName) {
|
|
|
164
185
|
}
|
|
165
186
|
const extVersion = await extensionsApi.getExtensionVersion(extensionName);
|
|
166
187
|
displayExtensionInfo_1.displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true);
|
|
167
|
-
warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
188
|
+
await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
168
189
|
return extVersion;
|
|
169
190
|
}
|
|
170
191
|
exports.default = new command_1.Command("ext:install [extensionName]")
|
|
@@ -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` +
|
|
@@ -12,6 +12,7 @@ const billingMigrationHelper_1 = require("../extensions/billingMigrationHelper")
|
|
|
12
12
|
const checkProjectBilling_1 = require("../extensions/checkProjectBilling");
|
|
13
13
|
const cloudbilling_1 = require("../gcp/cloudbilling");
|
|
14
14
|
const extensionsApi = require("../extensions/extensionsApi");
|
|
15
|
+
const secretsUtils = require("../extensions/secretsUtils");
|
|
15
16
|
const provisioningHelper = require("../extensions/provisioningHelper");
|
|
16
17
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
17
18
|
const paramHelper = require("../extensions/paramHelper");
|
|
@@ -124,7 +125,8 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
124
125
|
force: options.force,
|
|
125
126
|
});
|
|
126
127
|
await provisioningHelper.checkProductsProvisioned(projectId, newSpec);
|
|
127
|
-
|
|
128
|
+
const usesSecrets = secretsUtils.usesSecrets(newSpec);
|
|
129
|
+
if (newSpec.billingRequired || usesSecrets) {
|
|
128
130
|
const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
|
|
129
131
|
billingMigrationHelper_1.displayNode10UpdateBillingNotice(existingSpec, newSpec);
|
|
130
132
|
if (!(await extensionsHelper_1.confirm({
|
|
@@ -136,7 +138,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
136
138
|
}
|
|
137
139
|
if (!enabled) {
|
|
138
140
|
if (!options.nonInteractive) {
|
|
139
|
-
await checkProjectBilling_1.enableBilling(projectId
|
|
141
|
+
await checkProjectBilling_1.enableBilling(projectId);
|
|
140
142
|
}
|
|
141
143
|
else {
|
|
142
144
|
throw new error_1.FirebaseError("The extension requires your project to be upgraded to the Blaze plan. " +
|
|
@@ -144,7 +146,11 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
144
146
|
marked(`https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`));
|
|
145
147
|
}
|
|
146
148
|
}
|
|
149
|
+
if (usesSecrets) {
|
|
150
|
+
await secretsUtils.ensureSecretManagerApiEnabled(options);
|
|
151
|
+
}
|
|
147
152
|
}
|
|
153
|
+
const oldParamValues = Object.assign({}, existingParams);
|
|
148
154
|
const newParams = await paramHelper.getParamsForUpdate({
|
|
149
155
|
spec: existingSpec,
|
|
150
156
|
newSpec,
|
|
@@ -152,6 +158,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
152
158
|
projectId,
|
|
153
159
|
paramsEnvPath: options.params,
|
|
154
160
|
nonInteractive: options.nonInteractive,
|
|
161
|
+
instanceId,
|
|
155
162
|
});
|
|
156
163
|
spinner.start();
|
|
157
164
|
const updateOptions = {
|
|
@@ -164,7 +171,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
164
171
|
else {
|
|
165
172
|
updateOptions.source = newSource;
|
|
166
173
|
}
|
|
167
|
-
if (!_.isEqual(newParams,
|
|
174
|
+
if (!_.isEqual(newParams, oldParamValues)) {
|
|
168
175
|
updateOptions.params = newParams;
|
|
169
176
|
}
|
|
170
177
|
await updateHelper_1.update(updateOptions);
|