firebase-tools 14.3.0 → 14.4.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/lib/api.js +10 -2
- package/lib/apphosting/backend.js +72 -18
- package/lib/apphosting/githubConnections.js +13 -19
- package/lib/apphosting/rollout.js +2 -2
- package/lib/apphosting/secrets/dialogs.js +4 -4
- package/lib/apphosting/secrets/index.js +2 -2
- package/lib/auth.js +22 -2
- package/lib/bin/cli.js +1 -33
- package/lib/bin/mcp.js +12 -2
- package/lib/checkValidTargetFilters.js +1 -0
- package/lib/command.js +3 -2
- package/lib/commands/apphosting-secrets-grantaccess.js +1 -1
- package/lib/commands/deploy.js +1 -0
- package/lib/commands/init.js +1 -1
- package/lib/commands/login-use.js +2 -11
- package/lib/commands/use.js +9 -8
- package/lib/config.js +1 -0
- package/lib/crashlytics/listTopIssues.js +44 -0
- package/lib/dataconnect/cloudAICompanionClient.js +67 -0
- package/lib/dataconnect/dataplaneClient.js +16 -1
- package/lib/dataconnect/types.js +5 -1
- package/lib/deploy/apphosting/args.js +2 -0
- package/lib/deploy/apphosting/deploy.js +74 -0
- package/lib/deploy/apphosting/index.js +9 -0
- package/lib/deploy/apphosting/prepare.js +141 -0
- package/lib/deploy/apphosting/release.js +53 -0
- package/lib/deploy/apphosting/util.js +65 -0
- package/lib/deploy/extensions/v2FunctionHelper.js +2 -1
- package/lib/deploy/functions/checkIam.js +3 -3
- package/lib/deploy/functions/ensure.js +2 -1
- package/lib/deploy/functions/prepare.js +23 -16
- package/lib/deploy/functions/release/fabricator.js +4 -4
- package/lib/deploy/functions/release/index.js +1 -1
- package/lib/deploy/functions/runtimes/python/index.js +3 -0
- package/lib/deploy/functions/runtimes/supported/types.js +17 -11
- package/lib/deploy/index.js +2 -0
- package/lib/emulator/apphosting/index.js +1 -0
- package/lib/emulator/apphosting/serve.js +77 -3
- package/lib/emulator/auth/widget_ui.js +2 -1
- package/lib/emulator/controller.js +18 -4
- package/lib/emulator/dataconnectEmulator.js +9 -2
- package/lib/emulator/downloadableEmulatorInfo.json +60 -0
- package/lib/emulator/downloadableEmulators.js +25 -61
- package/lib/ensureApiEnabled.js +11 -1
- package/lib/experiments.js +1 -1
- package/lib/extensions/manifest.js +2 -2
- package/lib/fsAsync.js +9 -2
- package/lib/gcp/auth.js +32 -2
- package/lib/gcp/cloudbilling.js +12 -1
- package/lib/gcp/cloudfunctions.js +1 -2
- package/lib/gcp/cloudfunctionsv2.js +1 -2
- package/lib/gcp/cloudscheduler.js +2 -2
- package/lib/gcp/computeEngine.js +19 -2
- package/lib/gcp/devConnect.js +6 -1
- package/lib/gcp/firestore.js +24 -1
- package/lib/gcp/iam.js +1 -5
- package/lib/gcp/storage.js +43 -1
- package/lib/index.js +1 -2
- package/lib/init/features/apphosting.js +84 -6
- package/lib/init/features/database.js +64 -45
- package/lib/init/features/dataconnect/index.js +51 -53
- package/lib/init/features/emulators.js +9 -5
- package/lib/init/features/firestore/index.js +54 -23
- package/lib/init/features/firestore/indexes.js +23 -23
- package/lib/init/features/firestore/rules.js +35 -40
- package/lib/init/features/functions/index.js +2 -0
- package/lib/init/features/functions/javascript.js +3 -2
- package/lib/init/features/functions/typescript.js +3 -2
- package/lib/init/features/genkit/index.js +2 -1
- package/lib/init/features/hosting/github.js +3 -2
- package/lib/init/features/index.js +8 -4
- package/lib/init/features/remoteconfig.js +3 -2
- package/lib/init/index.js +76 -24
- package/lib/logger.js +71 -7
- package/lib/management/projects.js +25 -2
- package/lib/mcp/errors.js +1 -1
- package/lib/mcp/index.js +134 -51
- package/lib/mcp/tools/auth/{disable_auth_user.js → disable_user.js} +3 -3
- package/lib/mcp/tools/auth/get_user.js +38 -0
- package/lib/mcp/tools/auth/index.js +8 -6
- package/lib/mcp/tools/auth/list_users.js +47 -0
- package/lib/mcp/tools/auth/set_claims.js +43 -0
- package/lib/mcp/tools/auth/set_sms_region_policy.js +1 -1
- package/lib/mcp/tools/core/{consult_firebase_assistant.js → consult_assistant.js} +4 -4
- package/lib/mcp/tools/core/create_android_sha.js +40 -0
- package/lib/mcp/tools/core/create_app.js +90 -0
- package/lib/mcp/tools/core/create_project.js +68 -0
- package/lib/mcp/tools/core/get_admin_sdk_config.js +26 -0
- package/lib/mcp/tools/core/get_environment.js +51 -0
- package/lib/mcp/tools/{project → core}/get_sdk_config.js +6 -3
- package/lib/mcp/tools/core/index.js +20 -6
- package/lib/mcp/tools/core/init.js +129 -0
- package/lib/mcp/tools/core/update_environment.js +55 -0
- package/lib/mcp/tools/crashlytics/index.js +5 -0
- package/lib/mcp/tools/crashlytics/list_top_issues.js +34 -0
- package/lib/mcp/tools/dataconnect/converter.js +30 -1
- package/lib/mcp/tools/dataconnect/emulator.js +32 -0
- package/lib/mcp/tools/dataconnect/execute_graphql.js +48 -0
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +48 -0
- package/lib/mcp/tools/dataconnect/execute_mutation.js +62 -0
- package/lib/mcp/tools/dataconnect/execute_query.js +62 -0
- package/lib/mcp/tools/dataconnect/{generate_dataconnect_operation.js → generate_operation.js} +9 -9
- package/lib/mcp/tools/dataconnect/{generate_dataconnect_schema.js → generate_schema.js} +4 -4
- package/lib/mcp/tools/dataconnect/{get_dataconnect_connector.js → get_connector.js} +9 -9
- package/lib/mcp/tools/dataconnect/{get_dataconnect_schema.js → get_schema.js} +10 -10
- package/lib/mcp/tools/dataconnect/index.js +14 -10
- package/lib/mcp/tools/dataconnect/{list_dataconnect_services.js → list_services.js} +5 -5
- package/lib/mcp/tools/firestore/converter.js +47 -1
- package/lib/mcp/tools/firestore/delete_document.js +37 -0
- package/lib/mcp/tools/firestore/{get_firestore_documents.js → get_documents.js} +3 -3
- package/lib/mcp/tools/firestore/index.js +12 -6
- package/lib/mcp/tools/firestore/{list_firestore_collections.js → list_collections.js} +4 -9
- package/lib/mcp/tools/firestore/query_collection.js +116 -0
- package/lib/mcp/tools/index.js +44 -8
- package/lib/mcp/tools/messaging/index.js +5 -0
- package/lib/mcp/tools/messaging/send_message.js +42 -0
- package/lib/mcp/tools/remoteconfig/get_template.js +27 -0
- package/lib/mcp/tools/remoteconfig/index.js +7 -0
- package/lib/mcp/tools/remoteconfig/publish_template.js +34 -0
- package/lib/mcp/tools/remoteconfig/rollback_template.js +29 -0
- package/lib/mcp/tools/rules/get_rules.js +29 -0
- package/lib/mcp/tools/rules/validate_rules.js +98 -0
- package/lib/mcp/tools/storage/get_download_url.js +34 -0
- package/lib/mcp/tools/storage/{get_storage_rules.js → get_rules.js} +6 -6
- package/lib/mcp/tools/storage/index.js +8 -2
- package/lib/mcp/types.js +9 -1
- package/lib/mcp/util.js +29 -2
- package/lib/messaging/interfaces.js +2 -0
- package/lib/messaging/sendMessage.js +48 -0
- package/lib/remoteconfig/publish.js +39 -0
- package/lib/requireAuth.js +2 -2
- package/lib/utils.js +2 -37
- package/package.json +2 -1
- package/schema/firebase-config.json +62 -10
- package/templates/init/functions/javascript/package.lint.json +1 -1
- package/templates/init/functions/javascript/package.nolint.json +1 -1
- package/templates/init/functions/typescript/package.lint.json +1 -1
- package/templates/init/functions/typescript/package.nolint.json +2 -2
- package/lib/mcp/tools/auth/get_auth_user.js +0 -29
- package/lib/mcp/tools/auth/set_auth_claims.js +0 -34
- package/lib/mcp/tools/core/get_firebase_directory.js +0 -20
- package/lib/mcp/tools/core/set_firebase_directory.js +0 -33
- package/lib/mcp/tools/firestore/get_firestore_rules.js +0 -26
- package/lib/mcp/tools/project/index.js +0 -7
- /package/lib/mcp/tools/{project → core}/get_project.js +0 -0
- /package/lib/mcp/tools/{project → core}/list_apps.js +0 -0
|
@@ -5,7 +5,6 @@ const prompt_1 = require("../../prompt");
|
|
|
5
5
|
const fsutils = require("../../fsutils");
|
|
6
6
|
const clc = require("colorette");
|
|
7
7
|
async function doSetup(setup, config) {
|
|
8
|
-
setup.config.remoteconfig = {};
|
|
9
8
|
const jsonFilePath = await (0, prompt_1.input)({
|
|
10
9
|
message: "What file should be used for your Remote Config template?",
|
|
11
10
|
default: "remoteconfig.template.json",
|
|
@@ -20,7 +19,9 @@ async function doSetup(setup, config) {
|
|
|
20
19
|
return;
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
|
-
setup.config.remoteconfig
|
|
22
|
+
setup.config.remoteconfig = {
|
|
23
|
+
template: jsonFilePath,
|
|
24
|
+
};
|
|
24
25
|
config.writeProjectFile(setup.config.remoteconfig.template, "{}");
|
|
25
26
|
}
|
|
26
27
|
exports.doSetup = doSetup;
|
package/lib/init/index.js
CHANGED
|
@@ -1,45 +1,97 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.init = void 0;
|
|
3
|
+
exports.actuate = exports.init = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
5
|
const clc = require("colorette");
|
|
6
6
|
const error_1 = require("../error");
|
|
7
7
|
const logger_1 = require("../logger");
|
|
8
8
|
const features = require("./features");
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
9
|
+
const featuresList = [
|
|
10
|
+
{ name: "account", doSetup: features.account },
|
|
11
|
+
{
|
|
12
|
+
name: "database",
|
|
13
|
+
askQuestions: features.databaseAskQuestions,
|
|
14
|
+
actuate: features.databaseActuate,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "firestore",
|
|
18
|
+
askQuestions: features.firestoreAskQuestions,
|
|
19
|
+
actuate: features.firestoreActuate,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "dataconnect",
|
|
23
|
+
askQuestions: features.dataconnectAskQuestions,
|
|
24
|
+
actuate: features.dataconnectActuate,
|
|
25
|
+
postSetup: features.dataconnectPostSetup,
|
|
26
|
+
},
|
|
27
|
+
{ name: "dataconnect:sdk", doSetup: features.dataconnectSdk },
|
|
28
|
+
{ name: "functions", doSetup: features.functions },
|
|
29
|
+
{ name: "hosting", doSetup: features.hosting },
|
|
30
|
+
{ name: "storage", doSetup: features.storage },
|
|
31
|
+
{ name: "emulators", doSetup: features.emulators },
|
|
32
|
+
{ name: "extensions", doSetup: features.extensions },
|
|
33
|
+
{ name: "project", doSetup: features.project },
|
|
34
|
+
{ name: "remoteconfig", doSetup: features.remoteconfig },
|
|
35
|
+
{ name: "hosting:github", doSetup: features.hostingGithub },
|
|
36
|
+
{ name: "genkit", doSetup: features.genkit },
|
|
37
|
+
{ name: "apphosting", displayName: "App Hosting", doSetup: features.apphosting },
|
|
38
|
+
];
|
|
39
|
+
const featureMap = new Map(featuresList.map((feature) => [feature.name, feature]));
|
|
26
40
|
async function init(setup, config, options) {
|
|
27
41
|
var _a;
|
|
28
42
|
const nextFeature = (_a = setup.features) === null || _a === void 0 ? void 0 : _a.shift();
|
|
29
43
|
if (nextFeature) {
|
|
30
|
-
|
|
44
|
+
const f = featureMap.get(nextFeature);
|
|
45
|
+
if (!f) {
|
|
31
46
|
const availableFeatures = Object.keys(features)
|
|
32
47
|
.filter((f) => f !== "project")
|
|
33
48
|
.join(", ");
|
|
34
49
|
throw new error_1.FirebaseError(`${clc.bold(nextFeature)} is not a valid feature. Must be one of ${availableFeatures}`);
|
|
35
50
|
}
|
|
36
|
-
logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${(0, lodash_1.capitalize)(nextFeature)} Setup`));
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${f.displayName || (0, lodash_1.capitalize)(nextFeature)} Setup`));
|
|
52
|
+
if (f.doSetup) {
|
|
53
|
+
await f.doSetup(setup, config, options);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
if (f.askQuestions) {
|
|
57
|
+
await f.askQuestions(setup, config, options);
|
|
58
|
+
}
|
|
59
|
+
if (f.actuate) {
|
|
60
|
+
await f.actuate(setup, config, options);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (f.postSetup) {
|
|
64
|
+
await f.postSetup(setup, config, options);
|
|
40
65
|
}
|
|
41
|
-
await fn(setup, config, options);
|
|
42
66
|
return init(setup, config, options);
|
|
43
67
|
}
|
|
44
68
|
}
|
|
45
69
|
exports.init = init;
|
|
70
|
+
async function actuate(setup, config, options) {
|
|
71
|
+
var _a;
|
|
72
|
+
const nextFeature = (_a = setup.features) === null || _a === void 0 ? void 0 : _a.shift();
|
|
73
|
+
if (nextFeature) {
|
|
74
|
+
const f = lookupFeature(nextFeature);
|
|
75
|
+
logger_1.logger.info(clc.bold(`\n${clc.white("===")} ${(0, lodash_1.capitalize)(nextFeature)} Setup Actuation`));
|
|
76
|
+
if (f.doSetup) {
|
|
77
|
+
throw new error_1.FirebaseError(`The feature ${nextFeature} does not support actuate yet. Please run ${clc.bold("firebase init " + nextFeature)} instead.`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (f.actuate) {
|
|
81
|
+
await f.actuate(setup, config, options);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return actuate(setup, config, options);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.actuate = actuate;
|
|
88
|
+
function lookupFeature(feature) {
|
|
89
|
+
const f = featureMap.get(feature);
|
|
90
|
+
if (!f) {
|
|
91
|
+
const availableFeatures = Object.keys(features)
|
|
92
|
+
.filter((f) => f !== "project")
|
|
93
|
+
.join(", ");
|
|
94
|
+
throw new error_1.FirebaseError(`${clc.bold(feature)} is not a valid feature. Must be one of ${availableFeatures}`);
|
|
95
|
+
}
|
|
96
|
+
return f;
|
|
97
|
+
}
|
package/lib/logger.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.useConsoleLoggers = exports.useFileLogger = exports.logger = exports.tryStringify = exports.findAvailableLogFile = exports.vsceLogEmitter = void 0;
|
|
4
4
|
const winston = require("winston");
|
|
5
|
-
const vsCodeUtils_1 = require("./vsCodeUtils");
|
|
6
5
|
const events_1 = require("events");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const triple_beam_1 = require("triple-beam");
|
|
9
|
+
const util_1 = require("util");
|
|
10
|
+
const vsCodeUtils_1 = require("./vsCodeUtils");
|
|
7
11
|
exports.vsceLogEmitter = new events_1.EventEmitter();
|
|
8
12
|
function expandErrors(logger) {
|
|
9
13
|
const oldLogFunc = logger.log.bind(logger);
|
|
@@ -45,6 +49,39 @@ function maybeUseVSCodeLogger(logger) {
|
|
|
45
49
|
logger.log = vsceLogger;
|
|
46
50
|
return logger;
|
|
47
51
|
}
|
|
52
|
+
function findAvailableLogFile() {
|
|
53
|
+
const candidates = ["firebase-debug.log"];
|
|
54
|
+
for (let i = 1; i < 10; i++) {
|
|
55
|
+
candidates.push(`firebase-debug.${i}.log`);
|
|
56
|
+
}
|
|
57
|
+
for (const c of candidates) {
|
|
58
|
+
const logFilename = path.join(process.cwd(), c);
|
|
59
|
+
try {
|
|
60
|
+
const fd = fs.openSync(logFilename, "r+");
|
|
61
|
+
fs.closeSync(fd);
|
|
62
|
+
return logFilename;
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
if (e.code === "ENOENT") {
|
|
66
|
+
return logFilename;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error("Unable to obtain permissions for firebase-debug.log");
|
|
71
|
+
}
|
|
72
|
+
exports.findAvailableLogFile = findAvailableLogFile;
|
|
73
|
+
function tryStringify(value) {
|
|
74
|
+
if (typeof value === "string") {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
return JSON.stringify(value);
|
|
79
|
+
}
|
|
80
|
+
catch (_a) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.tryStringify = tryStringify;
|
|
48
85
|
const rawLogger = winston.createLogger();
|
|
49
86
|
rawLogger.add(new winston.transports.Console({
|
|
50
87
|
silent: true,
|
|
@@ -52,9 +89,36 @@ rawLogger.add(new winston.transports.Console({
|
|
|
52
89
|
}));
|
|
53
90
|
rawLogger.exitOnError = false;
|
|
54
91
|
exports.logger = maybeUseVSCodeLogger(annotateDebugLines(expandErrors(rawLogger)));
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
92
|
+
function useFileLogger(logFile) {
|
|
93
|
+
const logFileName = logFile !== null && logFile !== void 0 ? logFile : findAvailableLogFile();
|
|
94
|
+
exports.logger.add(new winston.transports.File({
|
|
95
|
+
level: "debug",
|
|
96
|
+
filename: logFileName,
|
|
97
|
+
format: winston.format.printf((info) => {
|
|
98
|
+
const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(tryStringify);
|
|
99
|
+
return `[${info.level}] ${(0, util_1.stripVTControlCharacters)(segments.join(" "))}`;
|
|
100
|
+
}),
|
|
101
|
+
}));
|
|
102
|
+
return logFileName;
|
|
103
|
+
}
|
|
104
|
+
exports.useFileLogger = useFileLogger;
|
|
105
|
+
function useConsoleLoggers() {
|
|
106
|
+
if (process.env.DEBUG) {
|
|
107
|
+
exports.logger.add(new winston.transports.Console({
|
|
108
|
+
level: "debug",
|
|
109
|
+
format: winston.format.printf((info) => {
|
|
110
|
+
const segments = [info.message, ...(info[triple_beam_1.SPLAT] || [])].map(tryStringify);
|
|
111
|
+
return `${(0, util_1.stripVTControlCharacters)(segments.join(" "))}`;
|
|
112
|
+
}),
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
else if (process.env.IS_FIREBASE_CLI) {
|
|
116
|
+
exports.logger.add(new winston.transports.Console({
|
|
117
|
+
level: "info",
|
|
118
|
+
format: winston.format.printf((info) => [info.message, ...(info[triple_beam_1.SPLAT] || [])]
|
|
119
|
+
.filter((chunk) => typeof chunk === "string")
|
|
120
|
+
.join(" ")),
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
59
123
|
}
|
|
60
|
-
exports.
|
|
124
|
+
exports.useConsoleLoggers = useConsoleLoggers;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getProject = exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
|
|
3
|
+
exports.checkFirebaseEnabledForCloudProject = exports.getProject = exports.getFirebaseProject = exports.listFirebaseProjects = exports.getAvailableCloudProjectPage = exports.getFirebaseProjectPage = exports.addFirebaseToCloudProject = exports.createCloudProject = exports.promptAvailableProjectId = exports.getOrPromptProject = exports.addFirebaseToCloudProjectAndLog = exports.createFirebaseProjectAndLog = exports.promptProjectCreation = exports.ProjectParentResourceType = void 0;
|
|
4
4
|
const clc = require("colorette");
|
|
5
5
|
const ora = require("ora");
|
|
6
6
|
const apiv2_1 = require("../apiv2");
|
|
@@ -336,8 +336,31 @@ async function getFirebaseProject(projectId) {
|
|
|
336
336
|
}
|
|
337
337
|
exports.getFirebaseProject = getFirebaseProject;
|
|
338
338
|
async function getProject(projectId) {
|
|
339
|
-
await (0, ensureApiEnabled_1.
|
|
339
|
+
await (0, ensureApiEnabled_1.bestEffortEnsure)(projectId, api.resourceManagerOrigin(), "firebase", true);
|
|
340
340
|
const response = await resourceManagerClient.get(`/projects/${projectId}`);
|
|
341
341
|
return response.body;
|
|
342
342
|
}
|
|
343
343
|
exports.getProject = getProject;
|
|
344
|
+
async function checkFirebaseEnabledForCloudProject(projectId) {
|
|
345
|
+
try {
|
|
346
|
+
const res = await firebaseAPIClient.request({
|
|
347
|
+
method: "GET",
|
|
348
|
+
path: `/projects/${projectId}`,
|
|
349
|
+
timeout: TIMEOUT_MILLIS,
|
|
350
|
+
});
|
|
351
|
+
return res.body;
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
if ((0, error_1.getErrStatus)(err) === 404) {
|
|
355
|
+
return undefined;
|
|
356
|
+
}
|
|
357
|
+
let message = err.message;
|
|
358
|
+
if (err.original) {
|
|
359
|
+
message += ` (original: ${err.original.message})`;
|
|
360
|
+
}
|
|
361
|
+
logger_1.logger.debug(message);
|
|
362
|
+
throw new error_1.FirebaseError(`Failed to check if Firebase is enabled for project ${projectId}. ` +
|
|
363
|
+
"Please make sure the project exists and your account has permission to access it.", { exit: 2, original: err });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
exports.checkFirebaseEnabledForCloudProject = checkFirebaseEnabledForCloudProject;
|
package/lib/mcp/errors.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mcpAuthError = exports.NO_PROJECT_ERROR = void 0;
|
|
4
4
|
const util_1 = require("./util");
|
|
5
|
-
exports.NO_PROJECT_ERROR = (0, util_1.mcpError)('No active project was found. Use the `
|
|
5
|
+
exports.NO_PROJECT_ERROR = (0, util_1.mcpError)('No active project was found. Use the `firebase_update_environment` tool to set the project directory to an absolute folder location containing a firebase.json config file. Alternatively, change the MCP server config to add [...,"--dir","/absolute/path/to/project/directory"] in its command-line arguments.', "PRECONDITION_FAILED");
|
|
6
6
|
function mcpAuthError() {
|
|
7
7
|
const cmd = (0, util_1.commandExistsSync)("firebase") ? "firebase" : "npx -y firebase-tools";
|
|
8
8
|
return (0, util_1.mcpError)(`The user is not currently logged into the Firebase CLI, which is required to use this tool. Please instruct the user to execute this shell command to sign in or to configure [Application Default Credentials][ADC] on their machine.
|
package/lib/mcp/index.js
CHANGED
|
@@ -5,110 +5,193 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
|
5
5
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
6
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
7
|
const util_js_1 = require("./util.js");
|
|
8
|
+
const types_js_2 = require("./types.js");
|
|
8
9
|
const index_js_2 = require("./tools/index.js");
|
|
9
10
|
const configstore_js_1 = require("../configstore.js");
|
|
10
|
-
const index_js_3 = require("./tools/core/index.js");
|
|
11
11
|
const command_js_1 = require("../command.js");
|
|
12
12
|
const requireAuth_js_1 = require("../requireAuth.js");
|
|
13
13
|
const projectUtils_js_1 = require("../projectUtils.js");
|
|
14
14
|
const errors_js_1 = require("./errors.js");
|
|
15
15
|
const track_js_1 = require("../track.js");
|
|
16
16
|
const config_js_1 = require("../config.js");
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const rc_js_1 = require("../rc.js");
|
|
18
|
+
const hubClient_js_1 = require("../emulator/hubClient.js");
|
|
19
|
+
const node_fs_1 = require("node:fs");
|
|
20
|
+
const SERVER_VERSION = "0.1.0";
|
|
19
21
|
const cmd = new command_js_1.Command("experimental:mcp").before(requireAuth_js_1.requireAuth);
|
|
20
22
|
class FirebaseMcpServer {
|
|
21
23
|
constructor(options) {
|
|
22
|
-
|
|
24
|
+
this._ready = false;
|
|
25
|
+
this._readyPromises = [];
|
|
23
26
|
this.activeFeatures = options.activeFeatures;
|
|
27
|
+
this.startupRoot = options.projectRoot || process.env.PROJECT_ROOT;
|
|
24
28
|
this.server = new index_js_1.Server({ name: "firebase", version: SERVER_VERSION });
|
|
25
29
|
this.server.registerCapabilities({ tools: { listChanged: true } });
|
|
26
30
|
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, this.mcpListTools.bind(this));
|
|
27
31
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, this.mcpCallTool.bind(this));
|
|
28
|
-
this.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this.
|
|
32
|
+
this.server.oninitialized = async () => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const clientInfo = this.server.getClientVersion();
|
|
35
|
+
this.clientInfo = clientInfo;
|
|
36
|
+
if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.name) {
|
|
37
|
+
(0, track_js_1.trackGA4)("mcp_client_connected", {
|
|
38
|
+
mcp_client_name: clientInfo.name,
|
|
39
|
+
mcp_client_version: clientInfo.version,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (!((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name))
|
|
43
|
+
this.clientInfo = { name: "<unknown-client>" };
|
|
44
|
+
this._ready = true;
|
|
45
|
+
while (this._readyPromises.length) {
|
|
46
|
+
(_b = this._readyPromises.pop()) === null || _b === void 0 ? void 0 : _b.resolve();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
this.detectProjectRoot();
|
|
50
|
+
this.detectActiveFeatures();
|
|
32
51
|
}
|
|
33
|
-
|
|
52
|
+
ready() {
|
|
53
|
+
if (this._ready)
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this._readyPromises.push({ resolve: resolve, reject });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
get clientConfigKey() {
|
|
34
60
|
var _a;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
return `mcp.clientConfigs.${((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>"}:${this.startupRoot || process.cwd()}`;
|
|
62
|
+
}
|
|
63
|
+
getStoredClientConfig() {
|
|
64
|
+
return configstore_js_1.configstore.get(this.clientConfigKey) || {};
|
|
65
|
+
}
|
|
66
|
+
updateStoredClientConfig(update) {
|
|
67
|
+
const config = configstore_js_1.configstore.get(this.clientConfigKey) || {};
|
|
68
|
+
const newConfig = Object.assign(Object.assign({}, config), update);
|
|
69
|
+
configstore_js_1.configstore.set(this.clientConfigKey, newConfig);
|
|
70
|
+
return newConfig;
|
|
71
|
+
}
|
|
72
|
+
async detectProjectRoot() {
|
|
73
|
+
await this.ready();
|
|
74
|
+
if (this.cachedProjectRoot)
|
|
75
|
+
return this.cachedProjectRoot;
|
|
76
|
+
const storedRoot = this.getStoredClientConfig().projectRoot;
|
|
77
|
+
this.cachedProjectRoot = storedRoot || this.startupRoot || process.cwd();
|
|
78
|
+
return this.cachedProjectRoot;
|
|
79
|
+
}
|
|
80
|
+
async detectActiveFeatures() {
|
|
81
|
+
var _a;
|
|
82
|
+
if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
|
|
83
|
+
return this.detectedFeatures;
|
|
84
|
+
const options = await this.resolveOptions();
|
|
85
|
+
const projectId = await this.getProjectId();
|
|
86
|
+
const detected = await Promise.all(types_js_2.SERVER_FEATURES.map(async (f) => {
|
|
87
|
+
if (await (0, util_js_1.checkFeatureActive)(f, projectId, options))
|
|
88
|
+
return f;
|
|
89
|
+
return null;
|
|
90
|
+
}));
|
|
91
|
+
this.detectedFeatures = detected.filter((f) => !!f);
|
|
92
|
+
return this.detectedFeatures;
|
|
93
|
+
}
|
|
94
|
+
async getEmulatorHubClient() {
|
|
95
|
+
if (this.emulatorHubClient) {
|
|
96
|
+
return this.emulatorHubClient;
|
|
41
97
|
}
|
|
42
|
-
|
|
98
|
+
const projectId = await this.getProjectId();
|
|
99
|
+
if (!projectId) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.emulatorHubClient = new hubClient_js_1.EmulatorHubClient(projectId);
|
|
103
|
+
return this.emulatorHubClient;
|
|
104
|
+
}
|
|
105
|
+
get availableTools() {
|
|
106
|
+
var _a;
|
|
107
|
+
return (0, index_js_2.availableTools)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
|
|
43
108
|
}
|
|
44
109
|
getTool(name) {
|
|
45
110
|
return this.availableTools.find((t) => t.mcp.name === name) || null;
|
|
46
111
|
}
|
|
47
|
-
async mcpListTools() {
|
|
48
|
-
const hasActiveProject = !!(await this.getProjectId());
|
|
49
|
-
await (0, track_js_1.trackGA4)("mcp_list_tools", {});
|
|
50
|
-
return {
|
|
51
|
-
tools: this.availableTools.map((t) => t.mcp),
|
|
52
|
-
_meta: {
|
|
53
|
-
projectRoot: this.projectRoot,
|
|
54
|
-
projectDetected: hasActiveProject,
|
|
55
|
-
authenticated: await this.getAuthenticated(),
|
|
56
|
-
activeFeatures: this.activeFeatures,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
112
|
setProjectRoot(newRoot) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
void this.server.sendToolListChanged();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
configstore_js_1.configstore.set(PROJECT_ROOT_KEY, newRoot);
|
|
68
|
-
this.projectRoot = newRoot;
|
|
113
|
+
this.updateStoredClientConfig({ projectRoot: newRoot });
|
|
114
|
+
this.cachedProjectRoot = newRoot || undefined;
|
|
115
|
+
this.detectedFeatures = undefined;
|
|
69
116
|
void this.server.sendToolListChanged();
|
|
70
117
|
}
|
|
71
118
|
async resolveOptions() {
|
|
72
|
-
const options = { cwd: this.
|
|
119
|
+
const options = { cwd: this.cachedProjectRoot, isMCP: true };
|
|
73
120
|
await cmd.prepare(options);
|
|
74
121
|
return options;
|
|
75
122
|
}
|
|
76
123
|
async getProjectId() {
|
|
77
124
|
return (0, projectUtils_js_1.getProjectId)(await this.resolveOptions());
|
|
78
125
|
}
|
|
79
|
-
async
|
|
126
|
+
async getAuthenticatedUser() {
|
|
80
127
|
try {
|
|
81
|
-
await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
|
|
82
|
-
return true;
|
|
128
|
+
return await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
|
|
83
129
|
}
|
|
84
130
|
catch (e) {
|
|
85
|
-
return
|
|
131
|
+
return null;
|
|
86
132
|
}
|
|
87
133
|
}
|
|
88
|
-
async
|
|
134
|
+
async mcpListTools() {
|
|
89
135
|
var _a, _b;
|
|
136
|
+
await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
|
|
137
|
+
const hasActiveProject = !!(await this.getProjectId());
|
|
138
|
+
await (0, track_js_1.trackGA4)("mcp_list_tools", {
|
|
139
|
+
mcp_client_name: (_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name,
|
|
140
|
+
mcp_client_version: (_b = this.clientInfo) === null || _b === void 0 ? void 0 : _b.version,
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
tools: this.availableTools.map((t) => t.mcp),
|
|
144
|
+
_meta: {
|
|
145
|
+
projectRoot: this.cachedProjectRoot,
|
|
146
|
+
projectDetected: hasActiveProject,
|
|
147
|
+
authenticatedUser: await this.getAuthenticatedUser(),
|
|
148
|
+
activeFeatures: this.activeFeatures,
|
|
149
|
+
detectedFeatures: this.detectedFeatures,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async mcpCallTool(request) {
|
|
154
|
+
var _a, _b, _c, _d, _e, _f;
|
|
155
|
+
await this.detectProjectRoot();
|
|
90
156
|
const toolName = request.params.name;
|
|
91
157
|
const toolArgs = request.params.arguments;
|
|
92
158
|
const tool = this.getTool(toolName);
|
|
93
159
|
if (!tool)
|
|
94
160
|
throw new Error(`Tool '${toolName}' could not be found.`);
|
|
95
161
|
const projectId = await this.getProjectId();
|
|
96
|
-
|
|
162
|
+
const accountEmail = await this.getAuthenticatedUser();
|
|
163
|
+
if (tool.mcp.name !== "firebase_update_environment" &&
|
|
164
|
+
(!this.cachedProjectRoot || !(0, node_fs_1.existsSync)(this.cachedProjectRoot)))
|
|
165
|
+
return (0, util_js_1.mcpError)(`The current project directory '${this.cachedProjectRoot || "<NO PROJECT DIRECTORY FOUND>"}' does not exist. Please use the 'update_firebase_environment' tool to target a different project directory.`);
|
|
166
|
+
if (((_a = tool.mcp._meta) === null || _a === void 0 ? void 0 : _a.requiresAuth) && !accountEmail)
|
|
97
167
|
return (0, errors_js_1.mcpAuthError)();
|
|
98
168
|
if (((_b = tool.mcp._meta) === null || _b === void 0 ? void 0 : _b.requiresProject) && !projectId)
|
|
99
169
|
return errors_js_1.NO_PROJECT_ERROR;
|
|
170
|
+
const options = { projectDir: this.cachedProjectRoot, cwd: this.cachedProjectRoot };
|
|
171
|
+
const toolsCtx = {
|
|
172
|
+
projectId: projectId,
|
|
173
|
+
host: this,
|
|
174
|
+
config: config_js_1.Config.load(options, true) || new config_js_1.Config({}, options),
|
|
175
|
+
rc: (0, rc_js_1.loadRC)(options),
|
|
176
|
+
accountEmail,
|
|
177
|
+
};
|
|
100
178
|
try {
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
179
|
+
const res = await tool.fn(toolArgs, toolsCtx);
|
|
180
|
+
await (0, track_js_1.trackGA4)("mcp_tool_call", {
|
|
181
|
+
tool_name: toolName,
|
|
182
|
+
error: res.isError ? 1 : 0,
|
|
183
|
+
mcp_client_name: (_c = this.clientInfo) === null || _c === void 0 ? void 0 : _c.name,
|
|
184
|
+
mcp_client_version: (_d = this.clientInfo) === null || _d === void 0 ? void 0 : _d.version,
|
|
106
185
|
});
|
|
107
|
-
await (0, track_js_1.trackGA4)("mcp_tool_call", { tool_name: toolName, error: res.isError ? 1 : 0 });
|
|
108
186
|
return res;
|
|
109
187
|
}
|
|
110
188
|
catch (err) {
|
|
111
|
-
await (0, track_js_1.trackGA4)("mcp_tool_call", {
|
|
189
|
+
await (0, track_js_1.trackGA4)("mcp_tool_call", {
|
|
190
|
+
tool_name: toolName,
|
|
191
|
+
error: 1,
|
|
192
|
+
mcp_client_name: (_e = this.clientInfo) === null || _e === void 0 ? void 0 : _e.name,
|
|
193
|
+
mcp_client_version: (_f = this.clientInfo) === null || _f === void 0 ? void 0 : _f.version,
|
|
194
|
+
});
|
|
112
195
|
return (0, util_js_1.mcpError)(err);
|
|
113
196
|
}
|
|
114
197
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.disable_user = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const tool_js_1 = require("../../tool.js");
|
|
6
6
|
const util_js_1 = require("../../util.js");
|
|
7
7
|
const auth_js_1 = require("../../../gcp/auth.js");
|
|
8
|
-
exports.
|
|
9
|
-
name: "
|
|
8
|
+
exports.disable_user = (0, tool_js_1.tool)({
|
|
9
|
+
name: "disable_user",
|
|
10
10
|
description: "Disables or enables a user based on a UID.",
|
|
11
11
|
inputSchema: zod_1.z.object({
|
|
12
12
|
uid: zod_1.z.string().describe("The localId or UID of the user to disable or enable"),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.get_user = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const tool_js_1 = require("../../tool.js");
|
|
6
|
+
const util_js_1 = require("../../util.js");
|
|
7
|
+
const auth_js_1 = require("../../../gcp/auth.js");
|
|
8
|
+
exports.get_user = (0, tool_js_1.tool)({
|
|
9
|
+
name: "get_user",
|
|
10
|
+
description: "Retrieves a user based on an email address, phone number, or UID.",
|
|
11
|
+
inputSchema: zod_1.z.object({
|
|
12
|
+
email: zod_1.z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("The user's email address. At least one of email, phone_number, or uid must be provided."),
|
|
16
|
+
phone_number: zod_1.z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("The user's phone number. At least one of email, phone_number, or uid must be provided."),
|
|
20
|
+
uid: zod_1.z
|
|
21
|
+
.string()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("The user's UID. At least one of email, phone_number, or uid must be provided."),
|
|
24
|
+
}),
|
|
25
|
+
annotations: {
|
|
26
|
+
title: "Get Firebase Auth User",
|
|
27
|
+
readOnlyHint: true,
|
|
28
|
+
},
|
|
29
|
+
_meta: {
|
|
30
|
+
requiresAuth: true,
|
|
31
|
+
requiresProject: true,
|
|
32
|
+
},
|
|
33
|
+
}, async ({ email, phone_number, uid }, { projectId }) => {
|
|
34
|
+
if (email === undefined && phone_number === undefined && uid === undefined) {
|
|
35
|
+
return (0, util_js_1.mcpError)(`No user identifier supplied in auth_get_user tool`);
|
|
36
|
+
}
|
|
37
|
+
return (0, util_js_1.toContent)(await (0, auth_js_1.findUser)(projectId, email, phone_number, uid));
|
|
38
|
+
});
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.authTools = void 0;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
const get_user_js_1 = require("./get_user.js");
|
|
5
|
+
const disable_user_js_1 = require("./disable_user.js");
|
|
6
|
+
const set_claims_js_1 = require("./set_claims.js");
|
|
7
7
|
const set_sms_region_policy_js_1 = require("./set_sms_region_policy.js");
|
|
8
|
+
const list_users_js_1 = require("./list_users.js");
|
|
8
9
|
exports.authTools = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
get_user_js_1.get_user,
|
|
11
|
+
disable_user_js_1.disable_user,
|
|
12
|
+
list_users_js_1.list_users,
|
|
13
|
+
set_claims_js_1.set_claim,
|
|
12
14
|
set_sms_region_policy_js_1.set_sms_region_policy,
|
|
13
15
|
];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.list_users = void 0;
|
|
15
|
+
const zod_1 = require("zod");
|
|
16
|
+
const tool_js_1 = require("../../tool.js");
|
|
17
|
+
const util_js_1 = require("../../util.js");
|
|
18
|
+
const auth_js_1 = require("../../../gcp/auth.js");
|
|
19
|
+
exports.list_users = (0, tool_js_1.tool)({
|
|
20
|
+
name: "list_users",
|
|
21
|
+
description: "Retrieves all users in the project up to the specified limit.",
|
|
22
|
+
inputSchema: zod_1.z.object({
|
|
23
|
+
limit: zod_1.z
|
|
24
|
+
.number()
|
|
25
|
+
.optional()
|
|
26
|
+
.default(100)
|
|
27
|
+
.describe("The number of users to return. Defaults to 100 if not supplied."),
|
|
28
|
+
}),
|
|
29
|
+
annotations: {
|
|
30
|
+
title: "List Firebase Users",
|
|
31
|
+
readOnlyHint: true,
|
|
32
|
+
},
|
|
33
|
+
_meta: {
|
|
34
|
+
requiresAuth: true,
|
|
35
|
+
requiresProject: true,
|
|
36
|
+
},
|
|
37
|
+
}, async ({ limit } = {}, { projectId }) => {
|
|
38
|
+
if (!limit) {
|
|
39
|
+
limit = 100;
|
|
40
|
+
}
|
|
41
|
+
const users = await (0, auth_js_1.listUsers)(projectId, limit);
|
|
42
|
+
const usersPruned = users.map((user) => {
|
|
43
|
+
const { passwordHash, salt } = user, prunedUser = __rest(user, ["passwordHash", "salt"]);
|
|
44
|
+
return prunedUser;
|
|
45
|
+
});
|
|
46
|
+
return (0, util_js_1.toContent)(usersPruned);
|
|
47
|
+
});
|