firebase-tools 9.18.0 → 9.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -6
- package/lib/api.js +3 -0
- package/lib/apiv2.js +8 -5
- package/lib/command.js +1 -1
- package/lib/commands/crashlytics-symbols-upload.js +146 -0
- package/lib/commands/deploy.js +9 -1
- package/lib/commands/ext-configure.js +9 -2
- package/lib/commands/ext-dev-deprecate.js +63 -0
- package/lib/commands/ext-dev-extension-delete.js +2 -1
- package/lib/commands/ext-dev-publish.js +10 -4
- package/lib/commands/ext-dev-undeprecate.js +56 -0
- package/lib/commands/ext-dev-unpublish.js +12 -4
- package/lib/commands/ext-export.js +44 -0
- package/lib/commands/ext-install.js +50 -13
- package/lib/commands/ext-uninstall.js +6 -0
- package/lib/commands/ext-update.js +60 -18
- package/lib/commands/functions-config-export.js +115 -0
- package/lib/commands/functions-delete.js +47 -25
- package/lib/commands/functions-list.js +12 -12
- package/lib/commands/index.js +9 -0
- package/lib/commands/init.js +3 -0
- package/lib/config.js +3 -2
- package/lib/deploy/extensions/args.js +2 -0
- package/lib/deploy/extensions/deploy.js +49 -0
- package/lib/deploy/extensions/deploymentSummary.js +52 -0
- package/lib/deploy/extensions/errors.js +31 -0
- package/lib/deploy/extensions/index.js +8 -0
- package/lib/deploy/extensions/planner.js +95 -0
- package/lib/deploy/extensions/prepare.js +103 -0
- package/lib/deploy/extensions/release.js +43 -0
- package/lib/deploy/extensions/secrets.js +150 -0
- package/lib/deploy/extensions/tasks.js +98 -0
- package/lib/deploy/extensions/validate.js +17 -0
- package/lib/deploy/functions/backend.js +93 -115
- package/lib/deploy/functions/checkIam.js +8 -8
- package/lib/deploy/functions/containerCleaner.js +82 -22
- package/lib/deploy/functions/deploy.js +4 -10
- package/lib/deploy/functions/functionsDeployHelper.js +3 -68
- package/lib/deploy/functions/prepare.js +62 -27
- package/lib/deploy/functions/pricing.js +17 -17
- package/lib/deploy/functions/prompts.js +22 -21
- package/lib/deploy/functions/release/executor.js +39 -0
- package/lib/deploy/functions/release/fabricator.js +422 -0
- package/lib/deploy/functions/release/index.js +73 -0
- package/lib/deploy/functions/release/planner.js +162 -0
- package/lib/deploy/functions/release/reporter.js +165 -0
- package/lib/deploy/functions/release/sourceTokenScraper.js +28 -0
- package/lib/deploy/functions/release/timer.js +14 -0
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +129 -126
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +41 -45
- package/lib/deploy/functions/triggerRegionHelper.js +40 -0
- package/lib/deploy/functions/validate.js +1 -24
- package/lib/deploy/index.js +1 -0
- package/lib/downloadUtils.js +37 -0
- package/lib/emulator/auth/apiSpec.js +1788 -403
- package/lib/emulator/auth/handlers.js +6 -5
- package/lib/emulator/auth/operations.js +439 -40
- package/lib/emulator/auth/server.js +32 -11
- package/lib/emulator/auth/state.js +205 -5
- package/lib/emulator/auth/widget_ui.js +2 -2
- package/lib/emulator/download.js +2 -31
- package/lib/emulator/downloadableEmulators.js +7 -7
- package/lib/emulator/emulatorLogger.js +0 -3
- package/lib/emulator/events/types.js +16 -0
- package/lib/emulator/functionsEmulator.js +120 -21
- package/lib/emulator/functionsEmulatorRuntime.js +46 -121
- package/lib/emulator/functionsEmulatorShared.js +51 -7
- package/lib/emulator/functionsEmulatorShell.js +1 -1
- package/lib/emulator/pubsubEmulator.js +61 -40
- package/lib/emulator/storage/cloudFunctions.js +37 -7
- package/lib/extensions/askUserForConsent.js +14 -1
- package/lib/extensions/askUserForParam.js +81 -4
- package/lib/extensions/billingMigrationHelper.js +1 -11
- package/lib/extensions/changelog.js +2 -1
- package/lib/extensions/checkProjectBilling.js +7 -7
- package/lib/extensions/displayExtensionInfo.js +35 -33
- package/lib/extensions/emulator/optionsHelper.js +3 -3
- package/lib/extensions/emulator/triggerHelper.js +2 -32
- package/lib/extensions/export.js +107 -0
- package/lib/extensions/extensionsApi.js +149 -97
- package/lib/extensions/extensionsHelper.js +36 -32
- package/lib/extensions/listExtensions.js +16 -11
- package/lib/extensions/paramHelper.js +73 -40
- package/lib/extensions/provisioningHelper.js +16 -3
- package/lib/extensions/refs.js +67 -0
- package/lib/extensions/secretsUtils.js +59 -0
- package/lib/extensions/updateHelper.js +33 -47
- package/lib/extensions/versionHelper.js +14 -0
- package/lib/extensions/warnings.js +33 -1
- package/lib/functional.js +64 -0
- package/lib/functions/env.js +26 -13
- package/lib/functions/runtimeConfigExport.js +137 -0
- package/lib/gcp/artifactregistry.js +16 -0
- package/lib/gcp/cloudfunctions.js +65 -35
- package/lib/gcp/cloudfunctionsv2.js +56 -43
- package/lib/gcp/cloudscheduler.js +22 -16
- package/lib/gcp/cloudtasks.js +143 -0
- package/lib/gcp/docker.js +7 -1
- package/lib/gcp/proto.js +2 -2
- package/lib/gcp/pubsub.js +1 -9
- package/lib/gcp/secretManager.js +132 -0
- package/lib/gcp/storage.js +16 -0
- package/lib/previews.js +1 -1
- package/lib/requireInteractive.js +12 -0
- package/lib/utils.js +30 -1
- package/package.json +6 -4
- package/schema/firebase-config.json +9 -0
- package/lib/deploy/functions/deploymentPlanner.js +0 -113
- package/lib/deploy/functions/deploymentTimer.js +0 -23
- package/lib/deploy/functions/errorHandler.js +0 -75
- package/lib/deploy/functions/release.js +0 -116
- package/lib/deploy/functions/tasks.js +0 -324
- package/lib/functions/listFunctions.js +0 -10
- package/lib/functionsDelete.js +0 -60
|
@@ -1,6 +1,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,7 +14,9 @@ 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");
|
|
@@ -29,7 +30,7 @@ marked.setOptions({
|
|
|
29
30
|
renderer: new TerminalRenderer(),
|
|
30
31
|
});
|
|
31
32
|
async function installExtension(options) {
|
|
32
|
-
const { projectId, extensionName, source, extVersion,
|
|
33
|
+
const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force, } = options;
|
|
33
34
|
const spec = (source === null || source === void 0 ? void 0 : source.spec) || (extVersion === null || extVersion === void 0 ? void 0 : extVersion.spec);
|
|
34
35
|
if (!spec) {
|
|
35
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.`);
|
|
@@ -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,16 +47,33 @@ 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);
|
|
57
|
-
const consented = await extensionsHelper_1.
|
|
76
|
+
const consented = await extensionsHelper_1.confirm({ nonInteractive, force, default: true });
|
|
58
77
|
if (!consented) {
|
|
59
78
|
throw new error_1.FirebaseError("Without explicit consent for the roles listed, we cannot deploy this extension.");
|
|
60
79
|
}
|
|
@@ -81,7 +100,13 @@ async function installExtension(options) {
|
|
|
81
100
|
switch (choice) {
|
|
82
101
|
case "installNew":
|
|
83
102
|
instanceId = await extensionsHelper_1.promptForValidInstanceId(`${instanceId}-${utils_1.getRandomString(4)}`);
|
|
84
|
-
params = await paramHelper.getParams(
|
|
103
|
+
params = await paramHelper.getParams({
|
|
104
|
+
projectId,
|
|
105
|
+
paramSpecs: spec.params,
|
|
106
|
+
nonInteractive,
|
|
107
|
+
paramsEnvPath,
|
|
108
|
+
instanceId,
|
|
109
|
+
});
|
|
85
110
|
spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
|
|
86
111
|
spinner.start();
|
|
87
112
|
await extensionsApi.createInstance({
|
|
@@ -96,7 +121,13 @@ async function installExtension(options) {
|
|
|
96
121
|
`Its Instance ID is ${clc.bold(instanceId)}.`);
|
|
97
122
|
break;
|
|
98
123
|
case "updateExisting":
|
|
99
|
-
params = await paramHelper.getParams(
|
|
124
|
+
params = await paramHelper.getParams({
|
|
125
|
+
projectId,
|
|
126
|
+
paramSpecs: spec.params,
|
|
127
|
+
nonInteractive,
|
|
128
|
+
paramsEnvPath,
|
|
129
|
+
instanceId,
|
|
130
|
+
});
|
|
100
131
|
spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
|
|
101
132
|
spinner.start();
|
|
102
133
|
await updateHelper_1.update({
|
|
@@ -147,14 +178,14 @@ async function infoInstallByReference(extensionName) {
|
|
|
147
178
|
const [extensionID, version] = extensionName.split("@");
|
|
148
179
|
extensionName = `firebase/${extensionID}@${version || "latest"}`;
|
|
149
180
|
}
|
|
150
|
-
const ref =
|
|
151
|
-
const extension = await extensionsApi.getExtension(
|
|
181
|
+
const ref = refs.parse(extensionName);
|
|
182
|
+
const extension = await extensionsApi.getExtension(refs.toExtensionRef(ref));
|
|
152
183
|
if (!ref.version) {
|
|
153
184
|
extensionName = `${extensionName}@latest`;
|
|
154
185
|
}
|
|
155
186
|
const extVersion = await extensionsApi.getExtensionVersion(extensionName);
|
|
156
187
|
displayExtensionInfo_1.displayExtInfo(extensionName, ref.publisherId, extVersion.spec, true);
|
|
157
|
-
warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
188
|
+
await warnings_1.displayWarningPrompts(ref.publisherId, extension.registryLaunchStage, extVersion);
|
|
158
189
|
return extVersion;
|
|
159
190
|
}
|
|
160
191
|
exports.default = new command_1.Command("ext:install [extensionName]")
|
|
@@ -170,7 +201,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
170
201
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
171
202
|
.action(async (extensionName, options) => {
|
|
172
203
|
const projectId = projectUtils_1.needProjectId(options);
|
|
173
|
-
const
|
|
204
|
+
const paramsEnvPath = options.params;
|
|
174
205
|
let learnMore = false;
|
|
175
206
|
if (!extensionName) {
|
|
176
207
|
if (options.interactive) {
|
|
@@ -191,7 +222,13 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
191
222
|
else {
|
|
192
223
|
extVersion = await infoInstallByReference(extensionName);
|
|
193
224
|
}
|
|
194
|
-
|
|
225
|
+
if (!(await extensionsHelper_1.confirm({
|
|
226
|
+
nonInteractive: options.nonInteractive,
|
|
227
|
+
force: options.force,
|
|
228
|
+
default: true,
|
|
229
|
+
}))) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
195
232
|
if (!source && !extVersion) {
|
|
196
233
|
throw new error_1.FirebaseError("Could not find a source. Please specify a valid source to continue.");
|
|
197
234
|
}
|
|
@@ -206,7 +243,7 @@ exports.default = new command_1.Command("ext:install [extensionName]")
|
|
|
206
243
|
}
|
|
207
244
|
try {
|
|
208
245
|
return installExtension({
|
|
209
|
-
|
|
246
|
+
paramsEnvPath,
|
|
210
247
|
projectId,
|
|
211
248
|
extensionName,
|
|
212
249
|
source,
|
|
@@ -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,10 +12,12 @@ 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");
|
|
18
19
|
const updateHelper_1 = require("../extensions/updateHelper");
|
|
20
|
+
const refs = require("../extensions/refs");
|
|
19
21
|
const projectUtils_1 = require("../projectUtils");
|
|
20
22
|
const requirePermissions_1 = require("../requirePermissions");
|
|
21
23
|
const utils = require("../utils");
|
|
@@ -43,6 +45,7 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
43
45
|
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
44
46
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
45
47
|
.withForce()
|
|
48
|
+
.option("--params <paramsFile>", "name of params variables file with .env format.")
|
|
46
49
|
.action(async (instanceId, updateSource, options) => {
|
|
47
50
|
const spinner = ora.default(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
|
|
48
51
|
try {
|
|
@@ -57,12 +60,12 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
57
60
|
}
|
|
58
61
|
throw err;
|
|
59
62
|
}
|
|
60
|
-
const existingSpec =
|
|
63
|
+
const existingSpec = existingInstance.config.source.spec;
|
|
61
64
|
if (existingInstance.config.source.state === "DELETED") {
|
|
62
65
|
throw new error_1.FirebaseError(`Instance '${clc.bold(instanceId)}' cannot be updated anymore because the underlying extension was unpublished from Firebase's registry of extensions. Going forward, you will only be able to re-configure or uninstall this instance.`);
|
|
63
66
|
}
|
|
64
|
-
const existingParams =
|
|
65
|
-
const existingSource =
|
|
67
|
+
const existingParams = existingInstance.config.params;
|
|
68
|
+
const existingSource = existingInstance.config.source.name;
|
|
66
69
|
if (existingInstance.config.extensionRef) {
|
|
67
70
|
updateSource = updateHelper_1.inferUpdateSource(updateSource, existingInstance.config.extensionRef);
|
|
68
71
|
}
|
|
@@ -76,60 +79,99 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
76
79
|
switch (newSourceOrigin) {
|
|
77
80
|
case extensionsHelper_1.SourceOrigin.LOCAL:
|
|
78
81
|
if (previews_1.previews.extdev) {
|
|
79
|
-
newSourceName = await updateHelper_1.updateFromLocalSource(projectId, instanceId, updateSource, existingSpec
|
|
82
|
+
newSourceName = await updateHelper_1.updateFromLocalSource(projectId, instanceId, updateSource, existingSpec);
|
|
80
83
|
break;
|
|
81
84
|
}
|
|
82
85
|
case extensionsHelper_1.SourceOrigin.URL:
|
|
83
86
|
if (previews_1.previews.extdev) {
|
|
84
|
-
newSourceName = await updateHelper_1.updateFromUrlSource(projectId, instanceId, updateSource, existingSpec
|
|
87
|
+
newSourceName = await updateHelper_1.updateFromUrlSource(projectId, instanceId, updateSource, existingSpec);
|
|
85
88
|
break;
|
|
86
89
|
}
|
|
87
90
|
case extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION_VERSION:
|
|
88
|
-
newSourceName = await updateHelper_1.updateToVersionFromPublisherSource(projectId, instanceId, updateSource, existingSpec
|
|
91
|
+
newSourceName = await updateHelper_1.updateToVersionFromPublisherSource(projectId, instanceId, updateSource, existingSpec);
|
|
89
92
|
break;
|
|
90
93
|
case extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION:
|
|
91
|
-
newSourceName = await updateHelper_1.updateFromPublisherSource(projectId, instanceId, updateSource, existingSpec
|
|
94
|
+
newSourceName = await updateHelper_1.updateFromPublisherSource(projectId, instanceId, updateSource, existingSpec);
|
|
92
95
|
break;
|
|
93
96
|
default:
|
|
94
97
|
throw new error_1.FirebaseError(`Unknown source '${clc.bold(updateSource)}.'`);
|
|
95
98
|
}
|
|
99
|
+
if (!(await extensionsHelper_1.confirm({
|
|
100
|
+
nonInteractive: options.nonInteractive,
|
|
101
|
+
force: options.force,
|
|
102
|
+
default: true,
|
|
103
|
+
}))) {
|
|
104
|
+
throw new error_1.FirebaseError(`Update cancelled.`);
|
|
105
|
+
}
|
|
96
106
|
const newSource = await extensionsApi.getSource(newSourceName);
|
|
97
107
|
const newSpec = newSource.spec;
|
|
98
108
|
if (![extensionsHelper_1.SourceOrigin.LOCAL, extensionsHelper_1.SourceOrigin.URL].includes(newSourceOrigin) &&
|
|
99
109
|
existingSpec.version === newSpec.version) {
|
|
100
110
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold(existingSpec.version)}.`);
|
|
101
|
-
const retry = await
|
|
111
|
+
const retry = await extensionsHelper_1.confirm({
|
|
112
|
+
nonInteractive: options.nonInteractive,
|
|
113
|
+
force: options.force,
|
|
114
|
+
default: false,
|
|
115
|
+
});
|
|
102
116
|
if (!retry) {
|
|
103
117
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Update aborted.");
|
|
104
118
|
return;
|
|
105
119
|
}
|
|
106
120
|
}
|
|
107
|
-
await updateHelper_1.displayChanges(
|
|
121
|
+
await updateHelper_1.displayChanges({
|
|
122
|
+
spec: existingSpec,
|
|
123
|
+
newSpec: newSpec,
|
|
124
|
+
nonInteractive: options.nonInteractive,
|
|
125
|
+
force: options.force,
|
|
126
|
+
});
|
|
108
127
|
await provisioningHelper.checkProductsProvisioned(projectId, newSpec);
|
|
109
|
-
|
|
128
|
+
const usesSecrets = secretsUtils.usesSecrets(newSpec);
|
|
129
|
+
if (newSpec.billingRequired || usesSecrets) {
|
|
110
130
|
const enabled = await cloudbilling_1.checkBillingEnabled(projectId);
|
|
131
|
+
billingMigrationHelper_1.displayNode10UpdateBillingNotice(existingSpec, newSpec);
|
|
132
|
+
if (!(await extensionsHelper_1.confirm({
|
|
133
|
+
nonInteractive: options.nonInteractive,
|
|
134
|
+
force: options.force,
|
|
135
|
+
default: true,
|
|
136
|
+
}))) {
|
|
137
|
+
throw new error_1.FirebaseError("Update cancelled.");
|
|
138
|
+
}
|
|
111
139
|
if (!enabled) {
|
|
112
|
-
|
|
113
|
-
|
|
140
|
+
if (!options.nonInteractive) {
|
|
141
|
+
await checkProjectBilling_1.enableBilling(projectId);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new error_1.FirebaseError("The extension requires your project to be upgraded to the Blaze plan. " +
|
|
145
|
+
"To run this command in non-interactive mode, first upgrade your project: " +
|
|
146
|
+
marked(`https://console.cloud.google.com/billing/linkedaccount?project=${projectId}`));
|
|
147
|
+
}
|
|
114
148
|
}
|
|
115
|
-
|
|
116
|
-
await
|
|
149
|
+
if (usesSecrets) {
|
|
150
|
+
await secretsUtils.ensureSecretManagerApiEnabled(options);
|
|
117
151
|
}
|
|
118
152
|
}
|
|
119
|
-
const
|
|
153
|
+
const oldParamValues = Object.assign({}, existingParams);
|
|
154
|
+
const newParams = await paramHelper.getParamsForUpdate({
|
|
155
|
+
spec: existingSpec,
|
|
156
|
+
newSpec,
|
|
157
|
+
currentParams: existingParams,
|
|
158
|
+
projectId,
|
|
159
|
+
paramsEnvPath: options.params,
|
|
160
|
+
nonInteractive: options.nonInteractive,
|
|
161
|
+
instanceId,
|
|
162
|
+
});
|
|
120
163
|
spinner.start();
|
|
121
164
|
const updateOptions = {
|
|
122
165
|
projectId,
|
|
123
166
|
instanceId,
|
|
124
167
|
};
|
|
125
168
|
if (newSourceName.includes("publisher")) {
|
|
126
|
-
|
|
127
|
-
updateOptions.extRef = `${publisherId}/${extensionId}@${version}`;
|
|
169
|
+
updateOptions.extRef = refs.toExtensionVersionRef(refs.parse(newSourceName));
|
|
128
170
|
}
|
|
129
171
|
else {
|
|
130
172
|
updateOptions.source = newSource;
|
|
131
173
|
}
|
|
132
|
-
if (!_.isEqual(newParams,
|
|
174
|
+
if (!_.isEqual(newParams, oldParamValues)) {
|
|
133
175
|
updateOptions.params = newParams;
|
|
134
176
|
}
|
|
135
177
|
await updateHelper_1.update(updateOptions);
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const requireInteractive_1 = require("../requireInteractive");
|
|
6
|
+
const command_1 = require("../command");
|
|
7
|
+
const error_1 = require("../error");
|
|
8
|
+
const iam_1 = require("../gcp/iam");
|
|
9
|
+
const logger_1 = require("../logger");
|
|
10
|
+
const prompt_1 = require("../prompt");
|
|
11
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
12
|
+
const utils_1 = require("../utils");
|
|
13
|
+
const functional_1 = require("../functional");
|
|
14
|
+
const configExport = require("../functions/runtimeConfigExport");
|
|
15
|
+
const requireConfig = require("../requireConfig");
|
|
16
|
+
const REQUIRED_PERMISSIONS = [
|
|
17
|
+
"runtimeconfig.configs.list",
|
|
18
|
+
"runtimeconfig.configs.get",
|
|
19
|
+
"runtimeconfig.variables.list",
|
|
20
|
+
"runtimeconfig.variables.get",
|
|
21
|
+
];
|
|
22
|
+
const RESERVED_PROJECT_ALIAS = ["local"];
|
|
23
|
+
const MAX_ATTEMPTS = 3;
|
|
24
|
+
function checkReservedAliases(pInfos) {
|
|
25
|
+
for (const pInfo of pInfos) {
|
|
26
|
+
if (pInfo.alias && RESERVED_PROJECT_ALIAS.includes(pInfo.alias)) {
|
|
27
|
+
utils_1.logWarning(`Project alias (${clc.bold(pInfo.alias)}) is reserved for internal use. ` +
|
|
28
|
+
`Saving exported config in .env.${pInfo.projectId} instead.`);
|
|
29
|
+
delete pInfo.alias;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function checkRequiredPermission(pInfos) {
|
|
34
|
+
pInfos = pInfos.filter((pInfo) => !pInfo.config);
|
|
35
|
+
const testPermissions = pInfos.map((pInfo) => iam_1.testIamPermissions(pInfo.projectId, REQUIRED_PERMISSIONS));
|
|
36
|
+
const results = await Promise.all(testPermissions);
|
|
37
|
+
for (const [pInfo, result] of functional_1.zip(pInfos, results)) {
|
|
38
|
+
if (result.passed) {
|
|
39
|
+
throw new error_1.FirebaseError(`Unexpectedly failed to fetch runtime config for project ${pInfo.projectId}`);
|
|
40
|
+
}
|
|
41
|
+
utils_1.logWarning("You are missing the following permissions to read functions config on project " +
|
|
42
|
+
`${clc.bold(pInfo.projectId)}:\n\t${result.missing.join("\n\t")}`);
|
|
43
|
+
const confirm = await prompt_1.promptOnce({
|
|
44
|
+
type: "confirm",
|
|
45
|
+
name: "skip",
|
|
46
|
+
default: true,
|
|
47
|
+
message: `Continue without importing configs from project ${pInfo.projectId}?`,
|
|
48
|
+
});
|
|
49
|
+
if (!confirm) {
|
|
50
|
+
throw new error_1.FirebaseError("Command aborted!");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function promptForPrefix(errMsg) {
|
|
55
|
+
utils_1.logWarning("The following configs keys could not be exported as environment variables:\n");
|
|
56
|
+
utils_1.logWarning(errMsg);
|
|
57
|
+
return await prompt_1.promptOnce({
|
|
58
|
+
type: "input",
|
|
59
|
+
name: "prefix",
|
|
60
|
+
default: "CONFIG_",
|
|
61
|
+
message: "Enter a PREFIX to rename invalid environment variable keys:",
|
|
62
|
+
}, {});
|
|
63
|
+
}
|
|
64
|
+
function fromEntries(itr) {
|
|
65
|
+
const obj = {};
|
|
66
|
+
for (const [k, v] of itr) {
|
|
67
|
+
obj[k] = v;
|
|
68
|
+
}
|
|
69
|
+
return obj;
|
|
70
|
+
}
|
|
71
|
+
exports.default = new command_1.Command("functions:config:export")
|
|
72
|
+
.description("Export environment config as environment variables in dotenv format")
|
|
73
|
+
.before(requirePermissions_1.requirePermissions, [
|
|
74
|
+
"runtimeconfig.configs.list",
|
|
75
|
+
"runtimeconfig.configs.get",
|
|
76
|
+
"runtimeconfig.variables.list",
|
|
77
|
+
"runtimeconfig.variables.get",
|
|
78
|
+
])
|
|
79
|
+
.before(requireConfig)
|
|
80
|
+
.before(requireInteractive_1.default)
|
|
81
|
+
.action(async (options) => {
|
|
82
|
+
let pInfos = configExport.getProjectInfos(options);
|
|
83
|
+
checkReservedAliases(pInfos);
|
|
84
|
+
utils_1.logBullet("Importing functions configs from projects [" +
|
|
85
|
+
pInfos.map(({ projectId }) => `${clc.bold(projectId)}`).join(", ") +
|
|
86
|
+
"]");
|
|
87
|
+
await configExport.hydrateConfigs(pInfos);
|
|
88
|
+
await checkRequiredPermission(pInfos);
|
|
89
|
+
pInfos = pInfos.filter((pInfo) => pInfo.config);
|
|
90
|
+
logger_1.logger.debug(`Loaded function configs: ${JSON.stringify(pInfos)}`);
|
|
91
|
+
utils_1.logBullet(`Importing configs from projects: [${pInfos.map((p) => p.projectId).join(", ")}]`);
|
|
92
|
+
let attempts = 0;
|
|
93
|
+
let prefix = "";
|
|
94
|
+
while (true) {
|
|
95
|
+
if (attempts >= MAX_ATTEMPTS) {
|
|
96
|
+
throw new error_1.FirebaseError("Exceeded max attempts to fix invalid config keys.");
|
|
97
|
+
}
|
|
98
|
+
const errMsg = configExport.hydrateEnvs(pInfos, prefix);
|
|
99
|
+
if (errMsg.length == 0) {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
prefix = await promptForPrefix(errMsg);
|
|
103
|
+
attempts += 1;
|
|
104
|
+
}
|
|
105
|
+
const header = `# Exported firebase functions:config:export command on ${new Date().toLocaleDateString()}`;
|
|
106
|
+
const dotEnvs = pInfos.map((pInfo) => configExport.toDotenvFormat(pInfo.envs, header));
|
|
107
|
+
const filenames = pInfos.map(configExport.generateDotenvFilename);
|
|
108
|
+
const filesToWrite = fromEntries(functional_1.zip(filenames, dotEnvs));
|
|
109
|
+
filesToWrite[".env.local"] = `${header}\n# .env.local file contains environment variables for the Functions Emulator.\n`;
|
|
110
|
+
filesToWrite[".env"] = `${header}# .env file contains environment variables that applies to all projects.\n`;
|
|
111
|
+
const functionsDir = options.config.get("functions.source", ".");
|
|
112
|
+
for (const [filename, content] of Object.entries(filesToWrite)) {
|
|
113
|
+
await options.config.askWriteProjectFile(path.join(functionsDir, filename), content);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const command_1 = require("../command");
|
|
4
3
|
const clc = require("cli-color");
|
|
5
4
|
const functionsConfig = require("../functionsConfig");
|
|
6
|
-
const
|
|
5
|
+
const command_1 = require("../command");
|
|
6
|
+
const error_1 = require("../error");
|
|
7
7
|
const projectUtils_1 = require("../projectUtils");
|
|
8
8
|
const prompt_1 = require("../prompt");
|
|
9
|
-
const
|
|
9
|
+
const functional_1 = require("../functional");
|
|
10
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
11
|
+
const helper = require("../deploy/functions/functionsDeployHelper");
|
|
11
12
|
const utils = require("../utils");
|
|
12
13
|
const backend = require("../deploy/functions/backend");
|
|
14
|
+
const planner = require("../deploy/functions/release/planner");
|
|
15
|
+
const fabricator = require("../deploy/functions/release/fabricator");
|
|
16
|
+
const executor = require("../deploy/functions/release/executor");
|
|
17
|
+
const reporter = require("../deploy/functions/release/reporter");
|
|
18
|
+
const containerCleaner = require("../deploy/functions/containerCleaner");
|
|
13
19
|
exports.default = new command_1.Command("functions:delete [filters...]")
|
|
14
20
|
.description("delete one or more Cloud Functions by name or group name.")
|
|
15
21
|
.option("--region <region>", "Specify region of the function to be deleted. " +
|
|
@@ -22,35 +28,29 @@ exports.default = new command_1.Command("functions:delete [filters...]")
|
|
|
22
28
|
}
|
|
23
29
|
const context = {
|
|
24
30
|
projectId: projectUtils_1.needProjectId(options),
|
|
31
|
+
filters: filters.map((f) => f.split(".")),
|
|
25
32
|
};
|
|
26
|
-
const filterChunks = filters.map((filter) => {
|
|
27
|
-
return filter.split(".");
|
|
28
|
-
});
|
|
29
33
|
const [config, existingBackend] = await Promise.all([
|
|
30
34
|
functionsConfig.getFirebaseConfig(options),
|
|
31
35
|
backend.existingBackend(context),
|
|
32
36
|
]);
|
|
33
37
|
await backend.checkAvailability(context, backend.empty());
|
|
34
38
|
const appEngineLocation = functionsConfig.getAppEngineLocation(config);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const nameMatches = helper.functionMatchesAnyGroup(fn, filterChunks);
|
|
38
|
-
return regionMatches && nameMatches;
|
|
39
|
-
});
|
|
40
|
-
if (functionsToDelete.length === 0) {
|
|
41
|
-
return utils.reject(`The specified filters do not match any existing functions in project ${clc.bold(context.projectId)}.`, { exit: 1 });
|
|
39
|
+
if (options.region) {
|
|
40
|
+
existingBackend.endpoints = { [options.region]: existingBackend.endpoints[options.region] };
|
|
42
41
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
42
|
+
const plan = planner.createDeploymentPlan(backend.empty(), existingBackend, {
|
|
43
|
+
filters: context.filters,
|
|
44
|
+
deleteAll: true,
|
|
45
45
|
});
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
46
|
+
const allEpToDelete = Object.values(plan)
|
|
47
|
+
.map((changes) => changes.endpointsToDelete)
|
|
48
|
+
.reduce(functional_1.reduceFlat, [])
|
|
49
|
+
.sort(backend.compareFunctions);
|
|
50
|
+
if (allEpToDelete.length === 0) {
|
|
51
|
+
throw new error_1.FirebaseError(`The specified filters do not match any existing functions in project ${clc.bold(context.projectId)}.`);
|
|
52
|
+
}
|
|
53
|
+
const deleteList = allEpToDelete.map((func) => `\t${helper.getFunctionLabel(func)}`).join("\n");
|
|
54
54
|
const confirmDeletion = await prompt_1.promptOnce({
|
|
55
55
|
type: "confirm",
|
|
56
56
|
name: "force",
|
|
@@ -60,7 +60,29 @@ exports.default = new command_1.Command("functions:delete [filters...]")
|
|
|
60
60
|
"\n Are you sure?",
|
|
61
61
|
}, options);
|
|
62
62
|
if (!confirmDeletion) {
|
|
63
|
-
|
|
63
|
+
throw new error_1.FirebaseError("Command aborted.");
|
|
64
|
+
}
|
|
65
|
+
const functionExecutor = new executor.QueueExecutor({
|
|
66
|
+
retries: 30,
|
|
67
|
+
backoff: 20000,
|
|
68
|
+
concurrency: 40,
|
|
69
|
+
maxBackoff: 40000,
|
|
70
|
+
});
|
|
71
|
+
try {
|
|
72
|
+
const fab = new fabricator.Fabricator({
|
|
73
|
+
functionExecutor,
|
|
74
|
+
executor: new executor.QueueExecutor({}),
|
|
75
|
+
appEngineLocation,
|
|
76
|
+
});
|
|
77
|
+
const summary = await fab.applyPlan(plan);
|
|
78
|
+
await reporter.logAndTrackDeployStats(summary);
|
|
79
|
+
reporter.printErrors(summary);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
throw new error_1.FirebaseError("Failed to delete functions", {
|
|
83
|
+
original: err,
|
|
84
|
+
exit: 1,
|
|
85
|
+
});
|
|
64
86
|
}
|
|
65
|
-
|
|
87
|
+
await containerCleaner.cleanupBuildImages([], allEpToDelete);
|
|
66
88
|
});
|
|
@@ -5,7 +5,6 @@ const error_1 = require("../error");
|
|
|
5
5
|
const projectUtils_1 = require("../projectUtils");
|
|
6
6
|
const requirePermissions_1 = require("../requirePermissions");
|
|
7
7
|
const backend = require("../deploy/functions/backend");
|
|
8
|
-
const listFunctions_1 = require("../functions/listFunctions");
|
|
9
8
|
const previews_1 = require("../previews");
|
|
10
9
|
const logger_1 = require("../logger");
|
|
11
10
|
const Table = require("cli-table");
|
|
@@ -17,7 +16,8 @@ exports.default = new command_1.Command("functions:list")
|
|
|
17
16
|
const context = {
|
|
18
17
|
projectId: projectUtils_1.needProjectId(options),
|
|
19
18
|
};
|
|
20
|
-
const
|
|
19
|
+
const existing = await backend.existingBackend(context);
|
|
20
|
+
const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions);
|
|
21
21
|
const table = previews_1.previews.functionsv2
|
|
22
22
|
? new Table({
|
|
23
23
|
head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
|
|
@@ -27,26 +27,26 @@ exports.default = new command_1.Command("functions:list")
|
|
|
27
27
|
head: ["Function", "Trigger", "Location", "Memory", "Runtime"],
|
|
28
28
|
style: { head: ["yellow"] },
|
|
29
29
|
});
|
|
30
|
-
for (const
|
|
31
|
-
const trigger = backend.
|
|
32
|
-
const availableMemoryMb =
|
|
30
|
+
for (const endpoint of endpointsList) {
|
|
31
|
+
const trigger = backend.endpointTriggerType(endpoint);
|
|
32
|
+
const availableMemoryMb = endpoint.availableMemoryMb || "---";
|
|
33
33
|
const entry = previews_1.previews.functionsv2
|
|
34
34
|
? [
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
endpoint.id,
|
|
36
|
+
endpoint.platform === "gcfv2" ? "v2" : "v1",
|
|
37
37
|
trigger,
|
|
38
|
-
|
|
38
|
+
endpoint.region,
|
|
39
39
|
availableMemoryMb,
|
|
40
|
-
|
|
40
|
+
endpoint.runtime,
|
|
41
41
|
]
|
|
42
|
-
: [
|
|
42
|
+
: [endpoint.id, trigger, endpoint.region, availableMemoryMb, endpoint.runtime];
|
|
43
43
|
table.push(entry);
|
|
44
44
|
}
|
|
45
45
|
logger_1.logger.info(table.toString());
|
|
46
|
-
return
|
|
46
|
+
return endpointsList;
|
|
47
47
|
}
|
|
48
48
|
catch (err) {
|
|
49
|
-
throw new error_1.FirebaseError(
|
|
49
|
+
throw new error_1.FirebaseError("Failed to list functions", {
|
|
50
50
|
exit: 1,
|
|
51
51
|
original: err,
|
|
52
52
|
});
|
package/lib/commands/index.js
CHANGED
|
@@ -26,6 +26,9 @@ module.exports = function (client) {
|
|
|
26
26
|
client.auth = {};
|
|
27
27
|
client.auth.export = loadCommand("auth-export");
|
|
28
28
|
client.auth.upload = loadCommand("auth-import");
|
|
29
|
+
client.crashlytics = {};
|
|
30
|
+
client.crashlytics.symbols = {};
|
|
31
|
+
client.crashlytics.symbols.upload = loadCommand("crashlytics-symbols-upload");
|
|
29
32
|
client.database = {};
|
|
30
33
|
client.database.get = loadCommand("database-get");
|
|
31
34
|
client.database.instances = {};
|
|
@@ -58,6 +61,7 @@ module.exports = function (client) {
|
|
|
58
61
|
client.ext = loadCommand("ext");
|
|
59
62
|
client.ext.configure = loadCommand("ext-configure");
|
|
60
63
|
client.ext.info = loadCommand("ext-info");
|
|
64
|
+
client.ext.export = loadCommand("ext-export");
|
|
61
65
|
client.ext.install = loadCommand("ext-install");
|
|
62
66
|
client.ext.list = loadCommand("ext-list");
|
|
63
67
|
client.ext.uninstall = loadCommand("ext-uninstall");
|
|
@@ -74,6 +78,8 @@ module.exports = function (client) {
|
|
|
74
78
|
client.ext.dev.emulators = {};
|
|
75
79
|
client.ext.dev.emulators.start = loadCommand("ext-dev-emulators-start");
|
|
76
80
|
client.ext.dev.emulators.exec = loadCommand("ext-dev-emulators-exec");
|
|
81
|
+
client.ext.dev.deprecate = loadCommand("ext-dev-deprecate");
|
|
82
|
+
client.ext.dev.undeprecate = loadCommand("ext-dev-undeprecate");
|
|
77
83
|
client.ext.dev.unpublish = loadCommand("ext-dev-unpublish");
|
|
78
84
|
client.ext.dev.publish = loadCommand("ext-dev-publish");
|
|
79
85
|
client.ext.dev.delete = loadCommand("ext-dev-extension-delete");
|
|
@@ -84,6 +90,9 @@ module.exports = function (client) {
|
|
|
84
90
|
client.functions = {};
|
|
85
91
|
client.functions.config = {};
|
|
86
92
|
client.functions.config.clone = loadCommand("functions-config-clone");
|
|
93
|
+
if (previews.dotenv) {
|
|
94
|
+
client.functions.config.export = loadCommand("functions-config-export");
|
|
95
|
+
}
|
|
87
96
|
client.functions.config.get = loadCommand("functions-config-get");
|
|
88
97
|
client.functions.config.set = loadCommand("functions-config-set");
|
|
89
98
|
client.functions.config.unset = loadCommand("functions-config-unset");
|
package/lib/commands/init.js
CHANGED
|
@@ -155,6 +155,9 @@ module.exports = new Command("init [feature]")
|
|
|
155
155
|
if (allAccounts.length > 1) {
|
|
156
156
|
setup.features.unshift("account");
|
|
157
157
|
}
|
|
158
|
+
if (setup.features.includes("hosting") && setup.features.includes("hosting:github")) {
|
|
159
|
+
setup.features = setup.features.filter((f) => f != "hosting:github");
|
|
160
|
+
}
|
|
158
161
|
return init(setup, config, options);
|
|
159
162
|
})
|
|
160
163
|
.then(function () {
|