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
|
@@ -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 () {
|
package/lib/config.js
CHANGED
|
@@ -144,10 +144,10 @@ class Config {
|
|
|
144
144
|
fs.ensureFileSync(this.path(p));
|
|
145
145
|
fs.writeFileSync(this.path(p), content, "utf8");
|
|
146
146
|
}
|
|
147
|
-
askWriteProjectFile(p, content) {
|
|
147
|
+
askWriteProjectFile(p, content, force) {
|
|
148
148
|
const writeTo = this.path(p);
|
|
149
149
|
let next;
|
|
150
|
-
if (fsutils.fileExistsSync(writeTo)) {
|
|
150
|
+
if (fsutils.fileExistsSync(writeTo) && !force) {
|
|
151
151
|
next = prompt_1.promptOnce({
|
|
152
152
|
type: "confirm",
|
|
153
153
|
message: "File " + clc.underline(p) + " already exists. Overwrite?",
|
|
@@ -203,6 +203,7 @@ Config.FILENAME = "firebase.json";
|
|
|
203
203
|
Config.MATERIALIZE_TARGETS = [
|
|
204
204
|
"database",
|
|
205
205
|
"emulators",
|
|
206
|
+
"extensions",
|
|
206
207
|
"firestore",
|
|
207
208
|
"functions",
|
|
208
209
|
"hosting",
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deploy = void 0;
|
|
4
|
+
const tasks = require("./tasks");
|
|
5
|
+
const queue_1 = require("../../throttler/queue");
|
|
6
|
+
const error_1 = require("../../error");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const projectUtils_1 = require("../../projectUtils");
|
|
9
|
+
const provisioningHelper_1 = require("../../extensions/provisioningHelper");
|
|
10
|
+
const secrets_1 = require("./secrets");
|
|
11
|
+
const validate_1 = require("./validate");
|
|
12
|
+
async function deploy(context, options, payload) {
|
|
13
|
+
var _a, _b, _c, _d, _e, _f;
|
|
14
|
+
const projectId = projectUtils_1.needProjectId(options);
|
|
15
|
+
await validate_1.checkBilling(projectId, options.nonInteractive);
|
|
16
|
+
await provisioningHelper_1.bulkCheckProductsProvisioned(projectId, [
|
|
17
|
+
...((_a = payload.instancesToCreate) !== null && _a !== void 0 ? _a : []),
|
|
18
|
+
...((_b = payload.instancesToUpdate) !== null && _b !== void 0 ? _b : []),
|
|
19
|
+
...((_c = payload.instancesToConfigure) !== null && _c !== void 0 ? _c : []),
|
|
20
|
+
]);
|
|
21
|
+
await secrets_1.handleSecretParams(payload, context.have, options.nonInteractive);
|
|
22
|
+
const errorHandler = new errors_1.ErrorHandler();
|
|
23
|
+
const validationQueue = new queue_1.default({
|
|
24
|
+
retries: 5,
|
|
25
|
+
concurrency: 5,
|
|
26
|
+
handler: tasks.extensionsDeploymentHandler(errorHandler),
|
|
27
|
+
});
|
|
28
|
+
for (const create of (_d = payload.instancesToCreate) !== null && _d !== void 0 ? _d : []) {
|
|
29
|
+
const task = tasks.createExtensionInstanceTask(projectId, create, true);
|
|
30
|
+
void validationQueue.run(task);
|
|
31
|
+
}
|
|
32
|
+
for (const update of (_e = payload.instancesToUpdate) !== null && _e !== void 0 ? _e : []) {
|
|
33
|
+
const task = tasks.updateExtensionInstanceTask(projectId, update, true);
|
|
34
|
+
void validationQueue.run(task);
|
|
35
|
+
}
|
|
36
|
+
for (const configure of (_f = payload.instancesToConfigure) !== null && _f !== void 0 ? _f : []) {
|
|
37
|
+
const task = tasks.configureExtensionInstanceTask(projectId, configure, true);
|
|
38
|
+
void validationQueue.run(task);
|
|
39
|
+
}
|
|
40
|
+
const validationPromise = validationQueue.wait();
|
|
41
|
+
validationQueue.process();
|
|
42
|
+
validationQueue.close();
|
|
43
|
+
await validationPromise;
|
|
44
|
+
if (errorHandler.hasErrors()) {
|
|
45
|
+
errorHandler.print();
|
|
46
|
+
throw new error_1.FirebaseError(`Extensions deployment failed validation. No changes have been made to the Extension instances on ${projectId}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.deploy = deploy;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deletesSummary = exports.configuresSummary = exports.updatesSummary = exports.createsSummary = exports.humanReadable = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const refs = require("../../extensions/refs");
|
|
6
|
+
exports.humanReadable = (dep) => `${clc.bold(dep.instanceId)} (${dep.ref ? `${refs.toExtensionVersionRef(dep.ref)}` : `Installed from local source`})`;
|
|
7
|
+
const humanReadableUpdate = (from, to) => {
|
|
8
|
+
var _a, _b, _c, _d, _e;
|
|
9
|
+
if (((_a = from.ref) === null || _a === void 0 ? void 0 : _a.publisherId) == ((_b = to.ref) === null || _b === void 0 ? void 0 : _b.publisherId) &&
|
|
10
|
+
((_c = from.ref) === null || _c === void 0 ? void 0 : _c.extensionId) == ((_d = to.ref) === null || _d === void 0 ? void 0 : _d.extensionId)) {
|
|
11
|
+
return `\t${clc.bold(from.instanceId)} (${refs.toExtensionVersionRef(from.ref)} => ${(_e = to.ref) === null || _e === void 0 ? void 0 : _e.version})`;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
const fromRef = from.ref
|
|
15
|
+
? `${refs.toExtensionVersionRef(from.ref)}`
|
|
16
|
+
: `Installed from local source`;
|
|
17
|
+
return `\t${clc.bold(from.instanceId)} (${fromRef} => ${refs.toExtensionVersionRef(to.ref)})`;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function createsSummary(toCreate) {
|
|
21
|
+
const instancesToCreate = toCreate.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
|
|
22
|
+
return toCreate.length
|
|
23
|
+
? `The following extension instances will be created:\n${instancesToCreate}\n`
|
|
24
|
+
: "";
|
|
25
|
+
}
|
|
26
|
+
exports.createsSummary = createsSummary;
|
|
27
|
+
function updatesSummary(toUpdate, have) {
|
|
28
|
+
const instancesToUpdate = toUpdate
|
|
29
|
+
.map((to) => {
|
|
30
|
+
const from = have.find((exists) => exists.instanceId == to.instanceId);
|
|
31
|
+
return humanReadableUpdate(from, to);
|
|
32
|
+
})
|
|
33
|
+
.join("\n");
|
|
34
|
+
return toUpdate.length
|
|
35
|
+
? `The following extension instances will be updated:\n${instancesToUpdate}\n`
|
|
36
|
+
: "";
|
|
37
|
+
}
|
|
38
|
+
exports.updatesSummary = updatesSummary;
|
|
39
|
+
function configuresSummary(toConfigure) {
|
|
40
|
+
const instancesToConfigure = toConfigure.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
|
|
41
|
+
return toConfigure.length
|
|
42
|
+
? `The following extension instances will be configured:\n${instancesToConfigure}\n`
|
|
43
|
+
: "";
|
|
44
|
+
}
|
|
45
|
+
exports.configuresSummary = configuresSummary;
|
|
46
|
+
function deletesSummary(toDelete) {
|
|
47
|
+
const instancesToDelete = toDelete.map((s) => `\t${exports.humanReadable(s)}`).join("\n");
|
|
48
|
+
return toDelete.length
|
|
49
|
+
? `The following extension instances are not listed in 'firebase.json':\n${instancesToDelete}\n`
|
|
50
|
+
: "";
|
|
51
|
+
}
|
|
52
|
+
exports.deletesSummary = deletesSummary;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErrorHandler = void 0;
|
|
4
|
+
const clc = require("cli-color");
|
|
5
|
+
const logger_1 = require("../../logger");
|
|
6
|
+
class ErrorHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.errors = [];
|
|
9
|
+
}
|
|
10
|
+
record(instanceId, type, message) {
|
|
11
|
+
this.errors.push({
|
|
12
|
+
instanceId,
|
|
13
|
+
type,
|
|
14
|
+
message: message,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
print() {
|
|
18
|
+
logger_1.logger.info("");
|
|
19
|
+
logger_1.logger.info("Extensions deploy had errors:");
|
|
20
|
+
logger_1.logger.info("");
|
|
21
|
+
for (const err of this.errors) {
|
|
22
|
+
logger_1.logger.info(`- ${err.type} ${clc.bold(err.instanceId)}`);
|
|
23
|
+
logger_1.logger.info(err.message);
|
|
24
|
+
logger_1.logger.info("");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
hasErrors() {
|
|
28
|
+
return this.errors.length > 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ErrorHandler = ErrorHandler;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var prepare_1 = require("./prepare");
|
|
4
|
+
Object.defineProperty(exports, "prepare", { enumerable: true, get: function () { return prepare_1.prepare; } });
|
|
5
|
+
var deploy_1 = require("./deploy");
|
|
6
|
+
Object.defineProperty(exports, "deploy", { enumerable: true, get: function () { return deploy_1.deploy; } });
|
|
7
|
+
var release_1 = require("./release");
|
|
8
|
+
Object.defineProperty(exports, "release", { enumerable: true, get: function () { return release_1.release; } });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readParams = void 0;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const logger_1 = require("../../logger");
|
|
6
|
+
const paramHelper_1 = require("../../extensions/paramHelper");
|
|
7
|
+
const error_1 = require("../../error");
|
|
8
|
+
const ENV_DIRECTORY = "extensions";
|
|
9
|
+
function readParams(args) {
|
|
10
|
+
const filesToCheck = [
|
|
11
|
+
`${args.instanceId}.env`,
|
|
12
|
+
...args.aliases.map((alias) => `${args.instanceId}.env.${alias}`),
|
|
13
|
+
`${args.instanceId}.env.${args.projectNumber}`,
|
|
14
|
+
`${args.instanceId}.env.${args.projectId}`,
|
|
15
|
+
];
|
|
16
|
+
let noFilesFound = true;
|
|
17
|
+
const combinedParams = {};
|
|
18
|
+
for (const fileToCheck of filesToCheck) {
|
|
19
|
+
try {
|
|
20
|
+
const params = readParamsFile(args.projectDir, fileToCheck);
|
|
21
|
+
logger_1.logger.debug(`Successfully read params from ${fileToCheck}`);
|
|
22
|
+
noFilesFound = false;
|
|
23
|
+
Object.assign(combinedParams, params);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
logger_1.logger.debug(`${err}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (noFilesFound) {
|
|
30
|
+
throw new error_1.FirebaseError(`No params file found for ${args.instanceId}`);
|
|
31
|
+
}
|
|
32
|
+
return combinedParams;
|
|
33
|
+
}
|
|
34
|
+
exports.readParams = readParams;
|
|
35
|
+
function readParamsFile(projectDir, fileName) {
|
|
36
|
+
const paramPath = path.join(projectDir, ENV_DIRECTORY, fileName);
|
|
37
|
+
const params = paramHelper_1.readEnvFile(paramPath);
|
|
38
|
+
return params;
|
|
39
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveVersion = exports.want = exports.have = exports.getExtension = exports.getExtensionVersion = void 0;
|
|
4
|
+
const semver = require("semver");
|
|
5
|
+
const error_1 = require("../../error");
|
|
6
|
+
const extensionsApi = require("../../extensions/extensionsApi");
|
|
7
|
+
const extensionsHelper_1 = require("../../extensions/extensionsHelper");
|
|
8
|
+
const refs = require("../../extensions/refs");
|
|
9
|
+
const params_1 = require("./params");
|
|
10
|
+
const logger_1 = require("../../logger");
|
|
11
|
+
async function getExtensionVersion(i) {
|
|
12
|
+
if (!i.extensionVersion) {
|
|
13
|
+
if (!i.ref) {
|
|
14
|
+
throw new error_1.FirebaseError(`Can't get ExtensionVersion for ${i.instanceId} because it has no ref`);
|
|
15
|
+
}
|
|
16
|
+
i.extensionVersion = await extensionsApi.getExtensionVersion(refs.toExtensionVersionRef(i.ref));
|
|
17
|
+
}
|
|
18
|
+
return i.extensionVersion;
|
|
19
|
+
}
|
|
20
|
+
exports.getExtensionVersion = getExtensionVersion;
|
|
21
|
+
async function getExtension(i) {
|
|
22
|
+
if (!i.ref) {
|
|
23
|
+
throw new error_1.FirebaseError(`Can't get Extensionfor ${i.instanceId} because it has no ref`);
|
|
24
|
+
}
|
|
25
|
+
if (!i.extension) {
|
|
26
|
+
i.extension = await extensionsApi.getExtension(refs.toExtensionRef(i.ref));
|
|
27
|
+
}
|
|
28
|
+
return i.extension;
|
|
29
|
+
}
|
|
30
|
+
exports.getExtension = getExtension;
|
|
31
|
+
async function have(projectId) {
|
|
32
|
+
const instances = await extensionsApi.listInstances(projectId);
|
|
33
|
+
return instances.map((i) => {
|
|
34
|
+
const dep = {
|
|
35
|
+
instanceId: i.name.split("/").pop(),
|
|
36
|
+
params: i.config.params,
|
|
37
|
+
};
|
|
38
|
+
if (i.config.extensionRef) {
|
|
39
|
+
const ref = refs.parse(i.config.extensionRef);
|
|
40
|
+
dep.ref = ref;
|
|
41
|
+
dep.ref.version = i.config.extensionVersion;
|
|
42
|
+
}
|
|
43
|
+
return dep;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
exports.have = have;
|
|
47
|
+
async function want(args) {
|
|
48
|
+
const instanceSpecs = [];
|
|
49
|
+
const errors = [];
|
|
50
|
+
for (const e of Object.entries(args.extensions)) {
|
|
51
|
+
try {
|
|
52
|
+
const instanceId = e[0];
|
|
53
|
+
const ref = refs.parse(e[1]);
|
|
54
|
+
ref.version = await resolveVersion(ref);
|
|
55
|
+
const params = params_1.readParams({
|
|
56
|
+
projectDir: args.projectDir,
|
|
57
|
+
instanceId,
|
|
58
|
+
projectId: args.projectId,
|
|
59
|
+
projectNumber: args.projectNumber,
|
|
60
|
+
aliases: args.aliases,
|
|
61
|
+
});
|
|
62
|
+
const autoPopulatedParams = await extensionsHelper_1.getFirebaseProjectParams(args.projectId);
|
|
63
|
+
const subbedParams = extensionsHelper_1.substituteParams(params, autoPopulatedParams);
|
|
64
|
+
instanceSpecs.push({
|
|
65
|
+
instanceId,
|
|
66
|
+
ref,
|
|
67
|
+
params: subbedParams,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
logger_1.logger.debug(`Got error reading extensions entry ${e}: ${err}`);
|
|
72
|
+
errors.push(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (errors.length) {
|
|
76
|
+
const messages = errors.map((err) => `- ${err.message}`).join("\n");
|
|
77
|
+
throw new error_1.FirebaseError(`Errors while reading 'extensions' in 'firebase.json'\n${messages}`);
|
|
78
|
+
}
|
|
79
|
+
return instanceSpecs;
|
|
80
|
+
}
|
|
81
|
+
exports.want = want;
|
|
82
|
+
async function resolveVersion(ref) {
|
|
83
|
+
if (!ref.version || ref.version == "latest") {
|
|
84
|
+
return "latest";
|
|
85
|
+
}
|
|
86
|
+
const extensionRef = refs.toExtensionRef(ref);
|
|
87
|
+
const versions = await extensionsApi.listExtensionVersions(extensionRef);
|
|
88
|
+
const maxSatisfying = semver.maxSatisfying(versions.map((ev) => ev.spec.version), ref.version);
|
|
89
|
+
if (!maxSatisfying) {
|
|
90
|
+
throw new error_1.FirebaseError(`No version of ${extensionRef} matches requested version ${ref.version}`);
|
|
91
|
+
}
|
|
92
|
+
return maxSatisfying;
|
|
93
|
+
}
|
|
94
|
+
exports.resolveVersion = resolveVersion;
|