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
|
@@ -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");
|
|
@@ -24,24 +26,13 @@ marked.setOptions({
|
|
|
24
26
|
renderer: new TerminalRenderer(),
|
|
25
27
|
});
|
|
26
28
|
function isValidUpdate(existingSourceOrigin, newSourceOrigin) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if ([extensionsHelper_1.SourceOrigin.OFFICIAL_EXTENSION, extensionsHelper_1.SourceOrigin.OFFICIAL_EXTENSION_VERSION].includes(newSourceOrigin)) {
|
|
30
|
-
validUpdate = true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else if (existingSourceOrigin === extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION) {
|
|
34
|
-
if ([extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION, extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes(newSourceOrigin)) {
|
|
35
|
-
validUpdate = true;
|
|
36
|
-
}
|
|
29
|
+
if (existingSourceOrigin === extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION) {
|
|
30
|
+
return [extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION, extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION_VERSION].includes(newSourceOrigin);
|
|
37
31
|
}
|
|
38
|
-
else if (existingSourceOrigin === extensionsHelper_1.SourceOrigin.LOCAL
|
|
39
|
-
|
|
40
|
-
if ([extensionsHelper_1.SourceOrigin.LOCAL, extensionsHelper_1.SourceOrigin.URL].includes(newSourceOrigin)) {
|
|
41
|
-
validUpdate = true;
|
|
42
|
-
}
|
|
32
|
+
else if (existingSourceOrigin === extensionsHelper_1.SourceOrigin.LOCAL) {
|
|
33
|
+
return [extensionsHelper_1.SourceOrigin.LOCAL, extensionsHelper_1.SourceOrigin.URL].includes(newSourceOrigin);
|
|
43
34
|
}
|
|
44
|
-
return
|
|
35
|
+
return false;
|
|
45
36
|
}
|
|
46
37
|
exports.default = new command_1.Command("ext:update <extensionInstanceId> [updateSource]")
|
|
47
38
|
.description(previews_1.previews.extdev
|
|
@@ -53,6 +44,8 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
53
44
|
])
|
|
54
45
|
.before(extensionsHelper_1.ensureExtensionsApiEnabled)
|
|
55
46
|
.before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion")
|
|
47
|
+
.withForce()
|
|
48
|
+
.option("--params <paramsFile>", "name of params variables file with .env format.")
|
|
56
49
|
.action(async (instanceId, updateSource, options) => {
|
|
57
50
|
const spinner = ora.default(`Updating ${clc.bold(instanceId)}. This usually takes 3 to 5 minutes...`);
|
|
58
51
|
try {
|
|
@@ -67,18 +60,18 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
67
60
|
}
|
|
68
61
|
throw err;
|
|
69
62
|
}
|
|
70
|
-
const existingSpec =
|
|
63
|
+
const existingSpec = existingInstance.config.source.spec;
|
|
71
64
|
if (existingInstance.config.source.state === "DELETED") {
|
|
72
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.`);
|
|
73
66
|
}
|
|
74
|
-
const existingParams =
|
|
75
|
-
const existingSource =
|
|
67
|
+
const existingParams = existingInstance.config.params;
|
|
68
|
+
const existingSource = existingInstance.config.source.name;
|
|
76
69
|
if (existingInstance.config.extensionRef) {
|
|
77
70
|
updateSource = updateHelper_1.inferUpdateSource(updateSource, existingInstance.config.extensionRef);
|
|
78
71
|
}
|
|
79
72
|
let newSourceName;
|
|
80
73
|
const existingSourceOrigin = await updateHelper_1.getExistingSourceOrigin(projectId, instanceId, existingSpec.name, existingSource);
|
|
81
|
-
const newSourceOrigin =
|
|
74
|
+
const newSourceOrigin = extensionsHelper_1.getSourceOrigin(updateSource);
|
|
82
75
|
const validUpdate = isValidUpdate(existingSourceOrigin, newSourceOrigin);
|
|
83
76
|
if (!validUpdate) {
|
|
84
77
|
throw new error_1.FirebaseError(`Cannot update from a(n) ${existingSourceOrigin} to a(n) ${newSourceOrigin}. Please provide a new source that is a(n) ${existingSourceOrigin} and try again.`);
|
|
@@ -86,68 +79,99 @@ exports.default = new command_1.Command("ext:update <extensionInstanceId> [updat
|
|
|
86
79
|
switch (newSourceOrigin) {
|
|
87
80
|
case extensionsHelper_1.SourceOrigin.LOCAL:
|
|
88
81
|
if (previews_1.previews.extdev) {
|
|
89
|
-
newSourceName = await updateHelper_1.updateFromLocalSource(projectId, instanceId, updateSource, existingSpec
|
|
82
|
+
newSourceName = await updateHelper_1.updateFromLocalSource(projectId, instanceId, updateSource, existingSpec);
|
|
90
83
|
break;
|
|
91
84
|
}
|
|
92
85
|
case extensionsHelper_1.SourceOrigin.URL:
|
|
93
86
|
if (previews_1.previews.extdev) {
|
|
94
|
-
newSourceName = await updateHelper_1.updateFromUrlSource(projectId, instanceId, updateSource, existingSpec
|
|
87
|
+
newSourceName = await updateHelper_1.updateFromUrlSource(projectId, instanceId, updateSource, existingSpec);
|
|
95
88
|
break;
|
|
96
89
|
}
|
|
97
|
-
case extensionsHelper_1.SourceOrigin.OFFICIAL_EXTENSION_VERSION:
|
|
98
|
-
newSourceName = await updateHelper_1.updateToVersionFromRegistryFile(projectId, instanceId, existingSpec, existingSource, updateSource);
|
|
99
|
-
break;
|
|
100
|
-
case extensionsHelper_1.SourceOrigin.OFFICIAL_EXTENSION:
|
|
101
|
-
newSourceName = await updateHelper_1.updateFromRegistryFile(projectId, instanceId, existingSpec, existingSource);
|
|
102
|
-
break;
|
|
103
90
|
case extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION_VERSION:
|
|
104
|
-
newSourceName = await updateHelper_1.updateToVersionFromPublisherSource(projectId, instanceId, updateSource, existingSpec
|
|
91
|
+
newSourceName = await updateHelper_1.updateToVersionFromPublisherSource(projectId, instanceId, updateSource, existingSpec);
|
|
105
92
|
break;
|
|
106
93
|
case extensionsHelper_1.SourceOrigin.PUBLISHED_EXTENSION:
|
|
107
|
-
newSourceName = await updateHelper_1.updateFromPublisherSource(projectId, instanceId, updateSource, existingSpec
|
|
94
|
+
newSourceName = await updateHelper_1.updateFromPublisherSource(projectId, instanceId, updateSource, existingSpec);
|
|
108
95
|
break;
|
|
109
96
|
default:
|
|
110
97
|
throw new error_1.FirebaseError(`Unknown source '${clc.bold(updateSource)}.'`);
|
|
111
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
|
+
}
|
|
112
106
|
const newSource = await extensionsApi.getSource(newSourceName);
|
|
113
107
|
const newSpec = newSource.spec;
|
|
114
108
|
if (![extensionsHelper_1.SourceOrigin.LOCAL, extensionsHelper_1.SourceOrigin.URL].includes(newSourceOrigin) &&
|
|
115
109
|
existingSpec.version === newSpec.version) {
|
|
116
110
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, `${clc.bold(instanceId)} is already up to date. Its version is ${clc.bold(existingSpec.version)}.`);
|
|
117
|
-
const retry = await
|
|
111
|
+
const retry = await extensionsHelper_1.confirm({
|
|
112
|
+
nonInteractive: options.nonInteractive,
|
|
113
|
+
force: options.force,
|
|
114
|
+
default: false,
|
|
115
|
+
});
|
|
118
116
|
if (!retry) {
|
|
119
117
|
utils.logLabeledBullet(extensionsHelper_1.logPrefix, "Update aborted.");
|
|
120
118
|
return;
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
await updateHelper_1.displayChanges({
|
|
122
|
+
spec: existingSpec,
|
|
123
|
+
newSpec: newSpec,
|
|
124
|
+
nonInteractive: options.nonInteractive,
|
|
125
|
+
force: options.force,
|
|
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);
|
|
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
|
+
}
|
|
129
139
|
if (!enabled) {
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
if (!options.nonInteractive) {
|
|
141
|
+
await checkProjectBilling_1.enableBilling(projectId, instanceId);
|
|
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
|
+
}
|
|
132
148
|
}
|
|
133
|
-
|
|
134
|
-
await
|
|
149
|
+
if (usesSecrets) {
|
|
150
|
+
await secretsUtils.ensureSecretManagerApiEnabled(options);
|
|
135
151
|
}
|
|
136
152
|
}
|
|
137
|
-
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
|
+
});
|
|
138
163
|
spinner.start();
|
|
139
164
|
const updateOptions = {
|
|
140
165
|
projectId,
|
|
141
166
|
instanceId,
|
|
142
167
|
};
|
|
143
168
|
if (newSourceName.includes("publisher")) {
|
|
144
|
-
|
|
145
|
-
updateOptions.extRef = `${publisherId}/${extensionId}@${version}`;
|
|
169
|
+
updateOptions.extRef = refs.toExtensionVersionRef(refs.parse(newSourceName));
|
|
146
170
|
}
|
|
147
171
|
else {
|
|
148
172
|
updateOptions.source = newSource;
|
|
149
173
|
}
|
|
150
|
-
if (!_.isEqual(newParams,
|
|
174
|
+
if (!_.isEqual(newParams, oldParamValues)) {
|
|
151
175
|
updateOptions.params = newParams;
|
|
152
176
|
}
|
|
153
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
|
+
});
|
|
@@ -10,6 +10,7 @@ const helper = require("../deploy/functions/functionsDeployHelper");
|
|
|
10
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
11
11
|
const utils = require("../utils");
|
|
12
12
|
const backend = require("../deploy/functions/backend");
|
|
13
|
+
const error_1 = require("../error");
|
|
13
14
|
exports.default = new command_1.Command("functions:delete [filters...]")
|
|
14
15
|
.description("delete one or more Cloud Functions by name or group name.")
|
|
15
16
|
.option("--region <region>", "Specify region of the function to be deleted. " +
|
|
@@ -26,41 +27,49 @@ exports.default = new command_1.Command("functions:delete [filters...]")
|
|
|
26
27
|
const filterChunks = filters.map((filter) => {
|
|
27
28
|
return filter.split(".");
|
|
28
29
|
});
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
try {
|
|
31
|
+
const [config, existingBackend] = await Promise.all([
|
|
32
|
+
functionsConfig.getFirebaseConfig(options),
|
|
33
|
+
backend.existingBackend(context),
|
|
34
|
+
]);
|
|
35
|
+
await backend.checkAvailability(context, backend.empty());
|
|
36
|
+
const appEngineLocation = functionsConfig.getAppEngineLocation(config);
|
|
37
|
+
const functionsToDelete = existingBackend.cloudFunctions.filter((fn) => {
|
|
38
|
+
const regionMatches = options.region ? fn.region === options.region : true;
|
|
39
|
+
const nameMatches = helper.functionMatchesAnyGroup(fn, filterChunks);
|
|
40
|
+
return regionMatches && nameMatches;
|
|
41
|
+
});
|
|
42
|
+
if (functionsToDelete.length === 0) {
|
|
43
|
+
throw new Error(`The specified filters do not match any existing functions in project ${clc.bold(context.projectId)}.`);
|
|
44
|
+
}
|
|
45
|
+
const schedulesToDelete = existingBackend.schedules.filter((schedule) => {
|
|
46
|
+
functionsToDelete.some(backend.sameFunctionName(schedule.targetService));
|
|
47
|
+
});
|
|
48
|
+
const topicsToDelete = existingBackend.topics.filter((topic) => {
|
|
49
|
+
functionsToDelete.some(backend.sameFunctionName(topic.targetService));
|
|
50
|
+
});
|
|
51
|
+
const deleteList = functionsToDelete
|
|
52
|
+
.map((func) => {
|
|
53
|
+
return "\t" + helper.getFunctionLabel(func);
|
|
54
|
+
})
|
|
55
|
+
.join("\n");
|
|
56
|
+
const confirmDeletion = await prompt_1.promptOnce({
|
|
57
|
+
type: "confirm",
|
|
58
|
+
name: "force",
|
|
59
|
+
default: false,
|
|
60
|
+
message: "You are about to delete the following Cloud Functions:\n" +
|
|
61
|
+
deleteList +
|
|
62
|
+
"\n Are you sure?",
|
|
63
|
+
}, options);
|
|
64
|
+
if (!confirmDeletion) {
|
|
65
|
+
throw new Error("Command aborted.");
|
|
66
|
+
}
|
|
67
|
+
return await functionsDelete_1.deleteFunctions(functionsToDelete, schedulesToDelete, topicsToDelete, appEngineLocation);
|
|
42
68
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
49
|
-
const deleteList = functionsToDelete
|
|
50
|
-
.map((func) => {
|
|
51
|
-
return "\t" + helper.getFunctionLabel(func);
|
|
52
|
-
})
|
|
53
|
-
.join("\n");
|
|
54
|
-
const confirmDeletion = await prompt_1.promptOnce({
|
|
55
|
-
type: "confirm",
|
|
56
|
-
name: "force",
|
|
57
|
-
default: false,
|
|
58
|
-
message: "You are about to delete the following Cloud Functions:\n" +
|
|
59
|
-
deleteList +
|
|
60
|
-
"\n Are you sure?",
|
|
61
|
-
}, options);
|
|
62
|
-
if (!confirmDeletion) {
|
|
63
|
-
return utils.reject("Command aborted.", { exit: 1 });
|
|
69
|
+
catch (err) {
|
|
70
|
+
throw new error_1.FirebaseError("Failed to delete functions", {
|
|
71
|
+
original: err,
|
|
72
|
+
exit: 1,
|
|
73
|
+
});
|
|
64
74
|
}
|
|
65
|
-
return await functionsDelete_1.deleteFunctions(functionsToDelete, schedulesToDelete, topicsToDelete, appEngineLocation);
|
|
66
75
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const command_1 = require("../command");
|
|
4
|
+
const error_1 = require("../error");
|
|
5
|
+
const projectUtils_1 = require("../projectUtils");
|
|
6
|
+
const requirePermissions_1 = require("../requirePermissions");
|
|
7
|
+
const backend = require("../deploy/functions/backend");
|
|
8
|
+
const listFunctions_1 = require("../functions/listFunctions");
|
|
9
|
+
const previews_1 = require("../previews");
|
|
10
|
+
const logger_1 = require("../logger");
|
|
11
|
+
const Table = require("cli-table");
|
|
12
|
+
exports.default = new command_1.Command("functions:list")
|
|
13
|
+
.description("list all deployed functions in your Firebase project")
|
|
14
|
+
.before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list"])
|
|
15
|
+
.action(async (options) => {
|
|
16
|
+
try {
|
|
17
|
+
const context = {
|
|
18
|
+
projectId: projectUtils_1.needProjectId(options),
|
|
19
|
+
};
|
|
20
|
+
const functionList = await listFunctions_1.listFunctions(context);
|
|
21
|
+
const table = previews_1.previews.functionsv2
|
|
22
|
+
? new Table({
|
|
23
|
+
head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
|
|
24
|
+
style: { head: ["yellow"] },
|
|
25
|
+
})
|
|
26
|
+
: new Table({
|
|
27
|
+
head: ["Function", "Trigger", "Location", "Memory", "Runtime"],
|
|
28
|
+
style: { head: ["yellow"] },
|
|
29
|
+
});
|
|
30
|
+
for (const fnSpec of functionList.functions) {
|
|
31
|
+
const trigger = backend.isEventTrigger(fnSpec.trigger) ? fnSpec.trigger.eventType : "https";
|
|
32
|
+
const availableMemoryMb = fnSpec.availableMemoryMb || "---";
|
|
33
|
+
const entry = previews_1.previews.functionsv2
|
|
34
|
+
? [
|
|
35
|
+
fnSpec.entryPoint,
|
|
36
|
+
fnSpec.platform === "gcfv2" ? "v2" : "v1",
|
|
37
|
+
trigger,
|
|
38
|
+
fnSpec.region,
|
|
39
|
+
availableMemoryMb,
|
|
40
|
+
fnSpec.runtime,
|
|
41
|
+
]
|
|
42
|
+
: [fnSpec.entryPoint, trigger, fnSpec.region, availableMemoryMb, fnSpec.runtime];
|
|
43
|
+
table.push(entry);
|
|
44
|
+
}
|
|
45
|
+
logger_1.logger.info(table.toString());
|
|
46
|
+
return functionList;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
throw new error_1.FirebaseError("Failed to list functions", {
|
|
50
|
+
exit: 1,
|
|
51
|
+
original: err,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const _ = require("lodash");
|
|
4
3
|
const opn = require("open");
|
|
5
4
|
const qs = require("querystring");
|
|
6
5
|
const command_1 = require("../command");
|
|
7
6
|
const error_1 = require("../error");
|
|
8
|
-
const
|
|
7
|
+
const cloudlogging = require("../gcp/cloudlogging");
|
|
8
|
+
const functionsLog = require("../functions/functionslog");
|
|
9
9
|
const projectUtils_1 = require("../projectUtils");
|
|
10
|
-
const logger_1 = require("../logger");
|
|
11
10
|
const requirePermissions_1 = require("../requirePermissions");
|
|
12
11
|
module.exports = new command_1.Command("functions:log")
|
|
13
12
|
.description("read logs from deployed functions")
|
|
@@ -18,30 +17,14 @@ module.exports = new command_1.Command("functions:log")
|
|
|
18
17
|
.action(async (options) => {
|
|
19
18
|
try {
|
|
20
19
|
const projectId = projectUtils_1.needProjectId(options);
|
|
21
|
-
|
|
22
|
-
if (options.only) {
|
|
23
|
-
const funcNames = options.only.split(",");
|
|
24
|
-
const apiFuncFilters = _.map(funcNames, (funcName) => {
|
|
25
|
-
return `resource.labels.function_name="${funcName}"`;
|
|
26
|
-
});
|
|
27
|
-
apiFilter += `\n(${apiFuncFilters.join(" OR ")})`;
|
|
28
|
-
}
|
|
20
|
+
const apiFilter = functionsLog.getApiFilter(options.only);
|
|
29
21
|
if (options.open) {
|
|
30
22
|
const url = `https://console.developers.google.com/logs/viewer?advancedFilter=${qs.escape(apiFilter)}&project=${projectId}`;
|
|
31
23
|
opn(url);
|
|
32
24
|
return;
|
|
33
25
|
}
|
|
34
|
-
const entries = await
|
|
35
|
-
|
|
36
|
-
const entry = entries[i];
|
|
37
|
-
logger_1.logger.info(entry.timestamp, _.get(entry, "severity", "?").substring(0, 1), _.get(entry, "resource.labels.function_name") + ":", entry.textPayload ||
|
|
38
|
-
JSON.stringify(entry.jsonPayload) ||
|
|
39
|
-
JSON.stringify(entry.protoPayload) ||
|
|
40
|
-
"");
|
|
41
|
-
}
|
|
42
|
-
if (_.isEmpty(entries)) {
|
|
43
|
-
logger_1.logger.info("No log entries found.");
|
|
44
|
-
}
|
|
26
|
+
const entries = await cloudlogging.listEntries(projectId, apiFilter, options.lines || 35, "desc");
|
|
27
|
+
functionsLog.logEntries(entries);
|
|
45
28
|
return entries;
|
|
46
29
|
}
|
|
47
30
|
catch (err) {
|
|
@@ -29,9 +29,6 @@ exports.default = new command_1.Command("hosting:channel:deploy [channelId]")
|
|
|
29
29
|
if (options.open) {
|
|
30
30
|
throw new error_1.FirebaseError("open is not yet implemented");
|
|
31
31
|
}
|
|
32
|
-
if (options["no-authorized-domains"]) {
|
|
33
|
-
throw new error_1.FirebaseError("no-authorized-domains is not yet implemented");
|
|
34
|
-
}
|
|
35
32
|
let expireTTL = expireUtils_1.DEFAULT_DURATION;
|
|
36
33
|
if (options.expires) {
|
|
37
34
|
expireTTL = expireUtils_1.calculateChannelExpireTTL(options.expires);
|
|
@@ -95,8 +92,13 @@ exports.default = new command_1.Command("hosting:channel:deploy [channelId]")
|
|
|
95
92
|
versionNames.push(version);
|
|
96
93
|
});
|
|
97
94
|
}
|
|
95
|
+
if (options.authorizedDomains) {
|
|
96
|
+
await syncAuthState(projectId, sites);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
logger_1.logger.debug(`skipping syncAuthState since authorizedDomains is ${options.authorizedDomains}`);
|
|
100
|
+
}
|
|
98
101
|
logger_1.logger.info();
|
|
99
|
-
await syncAuthState(projectId, sites);
|
|
100
102
|
const deploys = {};
|
|
101
103
|
sites.forEach((d) => {
|
|
102
104
|
deploys[d.target || d.site] = d;
|
package/lib/commands/index.js
CHANGED
|
@@ -11,6 +11,9 @@ module.exports = function (client) {
|
|
|
11
11
|
};
|
|
12
12
|
client.appdistribution = {};
|
|
13
13
|
client.appdistribution.distribute = loadCommand("appdistribution-distribute");
|
|
14
|
+
client.appdistribution.testers = {};
|
|
15
|
+
client.appdistribution.testers.add = loadCommand("appdistribution-testers-add");
|
|
16
|
+
client.appdistribution.testers.delete = loadCommand("appdistribution-testers-remove");
|
|
14
17
|
client.apps = {};
|
|
15
18
|
client.apps.create = loadCommand("apps-create");
|
|
16
19
|
client.apps.list = loadCommand("apps-list");
|
|
@@ -23,6 +26,11 @@ module.exports = function (client) {
|
|
|
23
26
|
client.auth = {};
|
|
24
27
|
client.auth.export = loadCommand("auth-export");
|
|
25
28
|
client.auth.upload = loadCommand("auth-import");
|
|
29
|
+
if (previews.crashlyticsSymbolsUpload) {
|
|
30
|
+
client.crashlytics = {};
|
|
31
|
+
client.crashlytics.symbols = {};
|
|
32
|
+
client.crashlytics.symbols.upload = loadCommand("crashlytics-symbols-upload");
|
|
33
|
+
}
|
|
26
34
|
client.database = {};
|
|
27
35
|
client.database.get = loadCommand("database-get");
|
|
28
36
|
client.database.instances = {};
|
|
@@ -81,12 +89,16 @@ module.exports = function (client) {
|
|
|
81
89
|
client.functions = {};
|
|
82
90
|
client.functions.config = {};
|
|
83
91
|
client.functions.config.clone = loadCommand("functions-config-clone");
|
|
92
|
+
if (previews.dotenv) {
|
|
93
|
+
client.functions.config.export = loadCommand("functions-config-export");
|
|
94
|
+
}
|
|
84
95
|
client.functions.config.get = loadCommand("functions-config-get");
|
|
85
96
|
client.functions.config.set = loadCommand("functions-config-set");
|
|
86
97
|
client.functions.config.unset = loadCommand("functions-config-unset");
|
|
87
98
|
client.functions.delete = loadCommand("functions-delete");
|
|
88
99
|
client.functions.log = loadCommand("functions-log");
|
|
89
100
|
client.functions.shell = loadCommand("functions-shell");
|
|
101
|
+
client.functions.list = loadCommand("functions-list");
|
|
90
102
|
if (previews.deletegcfartifacts) {
|
|
91
103
|
client.functions.deletegcfartifacts = loadCommand("functions-deletegcfartifacts");
|
|
92
104
|
}
|