firebase-tools 14.11.0 → 14.11.1
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/apptesting/ensureProjectConfigured.js +55 -0
- package/lib/commands/functions-config-clone.js +2 -0
- package/lib/commands/functions-config-get.js +2 -0
- package/lib/commands/functions-config-set.js +2 -0
- package/lib/commands/functions-config-unset.js +2 -0
- package/lib/commands/init.js +1 -1
- package/lib/deploy/functions/prepare.js +3 -1
- package/lib/deploy/functions/prepareFunctionsUpload.js +2 -0
- package/lib/deploy/functions/release/reporter.js +1 -0
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/env.js +18 -0
- package/lib/experiments.js +7 -0
- package/lib/firestore/api.js +15 -1
- package/lib/functions/deprecationWarnings.js +21 -0
- package/lib/init/features/aitools/promptUpdater.js +4 -1
- package/lib/init/features/apptesting/index.js +4 -0
- package/lib/mcp/index.js +44 -27
- package/lib/mcp/logging-transport.js +23 -0
- package/lib/mcp/util.js +2 -1
- package/lib/requireAuth.js +9 -3
- package/lib/timeout.js +21 -0
- package/lib/track.js +2 -2
- package/lib/utils.js +7 -2
- package/package.json +1 -1
- package/lib/emulator/adminSdkConfig.test.js +0 -16
- package/lib/firestore/api-sort.test.js +0 -74
- package/lib/firestore/backupUtils.test.js +0 -18
- package/lib/firestore/pretty-print.test.js +0 -61
- package/lib/firestore/util.test.js +0 -42
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureProjectConfigured = void 0;
|
|
4
|
+
const resourceManager_1 = require("../gcp/resourceManager");
|
|
5
|
+
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
6
|
+
const api_1 = require("../api");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const error_1 = require("../error");
|
|
9
|
+
const iam = require("../gcp/iam");
|
|
10
|
+
const prompt_1 = require("../prompt");
|
|
11
|
+
const TEST_RUNNER_ROLE = "roles/firebaseapptesting.testRunner";
|
|
12
|
+
const TEST_RUNNER_SERVICE_ACCOUNT_NAME = "firebaseapptesting-test-runner";
|
|
13
|
+
async function ensureProjectConfigured(projectId) {
|
|
14
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "storage", false);
|
|
15
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "run", false);
|
|
16
|
+
await (0, ensureApiEnabled_1.ensure)(projectId, (0, api_1.appTestingOrigin)(), "artifactregistry", false);
|
|
17
|
+
const serviceAccount = runnerServiceAccount(projectId);
|
|
18
|
+
const serviceAccountExistsAndIsRunner = await (0, resourceManager_1.serviceAccountHasRoles)(projectId, serviceAccount, [TEST_RUNNER_ROLE], true);
|
|
19
|
+
if (!serviceAccountExistsAndIsRunner) {
|
|
20
|
+
const grant = await (0, prompt_1.confirm)(`Firebase App Testing runs tests in Cloud Run using a service account, provision an account, "${serviceAccount}", with the role "${TEST_RUNNER_ROLE}"?`);
|
|
21
|
+
if (!grant) {
|
|
22
|
+
(0, utils_1.logBullet)("You, or your project administrator, should run the following command to grant the required role:\n\n" +
|
|
23
|
+
`\tgcloud projects add-iam-policy-binding ${projectId} \\\n` +
|
|
24
|
+
`\t --member="serviceAccount:${serviceAccount}" \\\n` +
|
|
25
|
+
`\t --role="${TEST_RUNNER_ROLE}"\n`);
|
|
26
|
+
throw new error_1.FirebaseError(`Firebase App Testing requires a service account named "${serviceAccount}" with the "${TEST_RUNNER_ROLE}" role to execute tests using Cloud Run`);
|
|
27
|
+
}
|
|
28
|
+
await provisionServiceAccount(projectId, serviceAccount);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.ensureProjectConfigured = ensureProjectConfigured;
|
|
32
|
+
async function provisionServiceAccount(projectId, serviceAccount) {
|
|
33
|
+
try {
|
|
34
|
+
await iam.createServiceAccount(projectId, TEST_RUNNER_SERVICE_ACCOUNT_NAME, "Service Account used in Cloud Run, responsible for running tests", "Firebase App Testing Test Runner");
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
if ((0, error_1.getErrStatus)(err) !== 409) {
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
await (0, resourceManager_1.addServiceAccountToRoles)(projectId, serviceAccount, [TEST_RUNNER_ROLE], true);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if ((0, error_1.getErrStatus)(err) === 400) {
|
|
46
|
+
(0, utils_1.logWarning)(`Your App Testing runner service account, "${serviceAccount}", is still being provisioned in the background. If you encounter an error, please try again after a few moments.`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runnerServiceAccount(projectId) {
|
|
54
|
+
return `${TEST_RUNNER_SERVICE_ACCOUNT_NAME}@${projectId}.iam.gserviceaccount.com`;
|
|
55
|
+
}
|
|
@@ -10,6 +10,7 @@ const requirePermissions_1 = require("../requirePermissions");
|
|
|
10
10
|
const functionsConfig = require("../functionsConfig");
|
|
11
11
|
const functionsConfigClone_1 = require("../functionsConfigClone");
|
|
12
12
|
const utils = require("../utils");
|
|
13
|
+
const deprecationWarnings_1 = require("../functions/deprecationWarnings");
|
|
13
14
|
exports.command = new command_1.Command("functions:config:clone")
|
|
14
15
|
.description("clone environment config from another project")
|
|
15
16
|
.option("--from <projectId>", "the project from which to clone configuration")
|
|
@@ -50,4 +51,5 @@ exports.command = new command_1.Command("functions:config:clone")
|
|
|
50
51
|
await (0, functionsConfigClone_1.functionsConfigClone)(options.from, projectId, only, except);
|
|
51
52
|
utils.logSuccess(`Cloned functions config from ${clc.bold(options.from)} into ${clc.bold(projectId)}`);
|
|
52
53
|
logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
|
|
54
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
53
55
|
});
|
|
@@ -8,6 +8,7 @@ const logger_1 = require("../logger");
|
|
|
8
8
|
const projectUtils_1 = require("../projectUtils");
|
|
9
9
|
const requirePermissions_1 = require("../requirePermissions");
|
|
10
10
|
const functionsConfig = require("../functionsConfig");
|
|
11
|
+
const deprecationWarnings_1 = require("../functions/deprecationWarnings");
|
|
11
12
|
async function materialize(projectId, path) {
|
|
12
13
|
if (path === undefined) {
|
|
13
14
|
return functionsConfig.materializeAll(projectId);
|
|
@@ -31,5 +32,6 @@ exports.command = new command_1.Command("functions:config:get [path]")
|
|
|
31
32
|
.action(async (path, options) => {
|
|
32
33
|
const result = await materialize((0, projectUtils_1.needProjectId)(options), path);
|
|
33
34
|
logger_1.logger.info(JSON.stringify(result, null, 2));
|
|
35
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
34
36
|
return result;
|
|
35
37
|
});
|
|
@@ -9,6 +9,7 @@ const projectUtils_1 = require("../projectUtils");
|
|
|
9
9
|
const requirePermissions_1 = require("../requirePermissions");
|
|
10
10
|
const functionsConfig = require("../functionsConfig");
|
|
11
11
|
const utils = require("../utils");
|
|
12
|
+
const deprecationWarnings_1 = require("../functions/deprecationWarnings");
|
|
12
13
|
exports.command = new command_1.Command("functions:config:set [values...]")
|
|
13
14
|
.description("set environment config with key=value syntax")
|
|
14
15
|
.before(requirePermissions_1.requirePermissions, [
|
|
@@ -40,4 +41,5 @@ exports.command = new command_1.Command("functions:config:set [values...]")
|
|
|
40
41
|
await Promise.all(promises);
|
|
41
42
|
utils.logSuccess("Functions config updated.");
|
|
42
43
|
logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
|
|
44
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
43
45
|
});
|
|
@@ -10,6 +10,7 @@ const functionsConfig = require("../functionsConfig");
|
|
|
10
10
|
const runtimeconfig = require("../gcp/runtimeconfig");
|
|
11
11
|
const utils = require("../utils");
|
|
12
12
|
const error_1 = require("../error");
|
|
13
|
+
const deprecationWarnings_1 = require("../functions/deprecationWarnings");
|
|
13
14
|
exports.command = new command_1.Command("functions:config:unset [keys...]")
|
|
14
15
|
.description("unset environment config at the specified path(s)")
|
|
15
16
|
.before(requirePermissions_1.requirePermissions, [
|
|
@@ -39,4 +40,5 @@ exports.command = new command_1.Command("functions:config:unset [keys...]")
|
|
|
39
40
|
}));
|
|
40
41
|
utils.logSuccess("Environment updated.");
|
|
41
42
|
logger_1.logger.info(`\nPlease deploy your functions for the change to take effect by running ${clc.bold("firebase deploy --only functions")}\n`);
|
|
43
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
42
44
|
});
|
package/lib/commands/init.js
CHANGED
|
@@ -102,7 +102,7 @@ if ((0, experiments_1.isEnabled)("genkit")) {
|
|
|
102
102
|
if ((0, experiments_1.isEnabled)("apptesting")) {
|
|
103
103
|
choices.push({
|
|
104
104
|
value: "apptesting",
|
|
105
|
-
name: "App Testing: create a smoke test",
|
|
105
|
+
name: "App Testing: create a smoke test, enable Cloud APIs (storage, run, & artifactregistry), and add a service account.",
|
|
106
106
|
checked: false,
|
|
107
107
|
});
|
|
108
108
|
}
|
|
@@ -53,7 +53,9 @@ async function prepare(context, options, payload) {
|
|
|
53
53
|
context.firebaseConfig = firebaseConfig;
|
|
54
54
|
let runtimeConfig = { firebase: firebaseConfig };
|
|
55
55
|
if (checkAPIsEnabled[1]) {
|
|
56
|
-
|
|
56
|
+
const config = await (0, prepareFunctionsUpload_1.getFunctionsConfig)(projectId);
|
|
57
|
+
runtimeConfig = Object.assign(Object.assign({}, runtimeConfig), config);
|
|
58
|
+
context.hasRuntimeConfig = Object.keys(config).length > 0;
|
|
57
59
|
}
|
|
58
60
|
context.codebaseDeployEvents = {};
|
|
59
61
|
const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
|
|
@@ -13,6 +13,7 @@ const hash_1 = require("./cache/hash");
|
|
|
13
13
|
const functionsConfig = require("../../functionsConfig");
|
|
14
14
|
const utils = require("../../utils");
|
|
15
15
|
const fsAsync = require("../../fsAsync");
|
|
16
|
+
const deprecationWarnings_1 = require("../../functions/deprecationWarnings");
|
|
16
17
|
const CONFIG_DEST_FILE = ".runtimeconfig.json";
|
|
17
18
|
async function getFunctionsConfig(projectId) {
|
|
18
19
|
var _a, _b;
|
|
@@ -73,6 +74,7 @@ async function packageSource(sourceDir, config, runtimeConfig) {
|
|
|
73
74
|
name: CONFIG_DEST_FILE,
|
|
74
75
|
mode: 420,
|
|
75
76
|
});
|
|
77
|
+
(0, deprecationWarnings_1.logFunctionsConfigDeprecationWarning)();
|
|
76
78
|
}
|
|
77
79
|
await pipeAsync(archive, fileStream);
|
|
78
80
|
}
|
|
@@ -81,6 +81,7 @@ async function logAndTrackDeployStats(summary, context) {
|
|
|
81
81
|
fn_deploy_num_successes: totalSuccesses,
|
|
82
82
|
fn_deploy_num_canceled: totalAborts,
|
|
83
83
|
fn_deploy_num_failures: totalErrors,
|
|
84
|
+
has_runtime_config: String(!!(context === null || context === void 0 ? void 0 : context.hasRuntimeConfig)),
|
|
84
85
|
};
|
|
85
86
|
reports.push((0, track_1.trackGA4)("function_deploy_group", fnDeployGroupEvent));
|
|
86
87
|
const avgTime = totalTime / (totalSuccesses + totalErrors);
|
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.10.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.10.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.
|
|
57
|
+
"version": "2.10.1",
|
|
58
|
+
"expectedSize": 29336416,
|
|
59
|
+
"expectedChecksum": "328ae0a354505430f1c162edf37da776",
|
|
60
|
+
"expectedChecksumSHA256": "ede5dc299045151f228b2adc346f47ad5d3ee10c3c9d7528df5fc0f0c6d70808",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.10.1",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1"
|
|
63
63
|
},
|
|
64
64
|
"win32": {
|
|
65
|
-
"version": "2.10.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.10.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.
|
|
65
|
+
"version": "2.10.1",
|
|
66
|
+
"expectedSize": 29829632,
|
|
67
|
+
"expectedChecksum": "82e82e35c1728a214db37967dba751e7",
|
|
68
|
+
"expectedChecksumSHA256": "ba509e921c692ac60c1138b5b9c83a23b4e11feb339f08df5f1cfced44cffc91",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.10.1",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1.exe"
|
|
71
71
|
},
|
|
72
72
|
"linux": {
|
|
73
|
-
"version": "2.10.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.10.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.
|
|
73
|
+
"version": "2.10.1",
|
|
74
|
+
"expectedSize": 29266104,
|
|
75
|
+
"expectedChecksum": "d183c2335e16f8c568bdc9e782dcd240",
|
|
76
|
+
"expectedChecksumSHA256": "338e0cce561d29993737b91abfe7fc12c7d82d014ef114348ccdafeb3b7be3ae",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.10.1",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.10.1"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
package/lib/env.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isFirebaseMcp = exports.isFirebaseStudio = void 0;
|
|
4
|
+
const fsutils_1 = require("./fsutils");
|
|
5
|
+
let googleIdxFolderExists;
|
|
6
|
+
function isFirebaseStudio() {
|
|
7
|
+
if (googleIdxFolderExists === true || process.env.MONOSPACE_ENV)
|
|
8
|
+
return true;
|
|
9
|
+
if (googleIdxFolderExists === false)
|
|
10
|
+
return false;
|
|
11
|
+
googleIdxFolderExists = (0, fsutils_1.dirExistsSync)("/google/idx");
|
|
12
|
+
return googleIdxFolderExists;
|
|
13
|
+
}
|
|
14
|
+
exports.isFirebaseStudio = isFirebaseStudio;
|
|
15
|
+
function isFirebaseMcp() {
|
|
16
|
+
return !!process.env.IS_FIREBASE_MCP;
|
|
17
|
+
}
|
|
18
|
+
exports.isFirebaseMcp = isFirebaseMcp;
|
package/lib/experiments.js
CHANGED
|
@@ -41,6 +41,13 @@ exports.ALL_EXPERIMENTS = experiments({
|
|
|
41
41
|
"of how that image was created.",
|
|
42
42
|
public: true,
|
|
43
43
|
},
|
|
44
|
+
dangerouslyAllowFunctionsConfig: {
|
|
45
|
+
shortDescription: "Allows the use of deprecated functions.config() API",
|
|
46
|
+
fullDescription: "The functions.config() API is deprecated and will be removed on December 31, 2025. " +
|
|
47
|
+
"This experiment allows continued use of the API during the migration period.",
|
|
48
|
+
default: true,
|
|
49
|
+
public: true,
|
|
50
|
+
},
|
|
44
51
|
emulatoruisnapshot: {
|
|
45
52
|
shortDescription: "Load pre-release versions of the emulator UI",
|
|
46
53
|
},
|
package/lib/firestore/api.js
CHANGED
|
@@ -18,6 +18,20 @@ class FirestoreApi {
|
|
|
18
18
|
this.apiClient = new apiv2_1.Client({ urlPrefix: (0, api_1.firestoreOrigin)(), apiVersion: "v1" });
|
|
19
19
|
this.printer = new pretty_print_1.PrettyPrint();
|
|
20
20
|
}
|
|
21
|
+
static processIndexes(indexes) {
|
|
22
|
+
return indexes.map((index) => {
|
|
23
|
+
var _a, _b, _c;
|
|
24
|
+
let fields = index.fields;
|
|
25
|
+
const lastField = (_a = index.fields) === null || _a === void 0 ? void 0 : _a[index.fields.length - 1];
|
|
26
|
+
if ((lastField === null || lastField === void 0 ? void 0 : lastField.fieldPath) === "__name__") {
|
|
27
|
+
const defaultDirection = (_c = (_b = index.fields) === null || _b === void 0 ? void 0 : _b[index.fields.length - 2]) === null || _c === void 0 ? void 0 : _c.order;
|
|
28
|
+
if ((lastField === null || lastField === void 0 ? void 0 : lastField.order) === defaultDirection) {
|
|
29
|
+
fields = fields.slice(0, -1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return Object.assign(Object.assign({}, index), { fields });
|
|
33
|
+
});
|
|
34
|
+
}
|
|
21
35
|
async deploy(options, indexes, fieldOverrides, databaseId = "(default)") {
|
|
22
36
|
const spec = this.upgradeOldSpec({
|
|
23
37
|
indexes,
|
|
@@ -126,7 +140,7 @@ class FirestoreApi {
|
|
|
126
140
|
if (!indexes) {
|
|
127
141
|
return [];
|
|
128
142
|
}
|
|
129
|
-
return indexes;
|
|
143
|
+
return FirestoreApi.processIndexes(indexes);
|
|
130
144
|
}
|
|
131
145
|
async listFieldOverrides(project, databaseId = "(default)") {
|
|
132
146
|
const parent = `projects/${project}/databases/${databaseId}/collectionGroups/-`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logFunctionsConfigDeprecationWarning = void 0;
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const FUNCTIONS_CONFIG_DEPRECATION_MESSAGE = `DEPRECATION NOTICE: Action required to deploy after Dec 31, 2025
|
|
6
|
+
|
|
7
|
+
functions.config() API is deprecated.
|
|
8
|
+
Cloud Runtime Configuration API, the Google Cloud service used to store function configuration data, will be shut down on December 31, 2025. As a result, you must migrate away from using functions.config() to continue deploying your functions after December 31, 2025.
|
|
9
|
+
|
|
10
|
+
What this means for you:
|
|
11
|
+
|
|
12
|
+
- The Firebase CLI commands for managing this configuration (functions:config:set, get, unset, clone, and export) are deprecated. These commands no longer work after December 31, 2025.
|
|
13
|
+
- firebase deploy command will fail for functions that use the legacy functions.config() API after December 31, 2025.
|
|
14
|
+
|
|
15
|
+
Existing deployments will continue to work with their current configuration.
|
|
16
|
+
|
|
17
|
+
See your migration options at: https://firebase.google.com/docs/functions/config-env#migrate-to-dotenv`;
|
|
18
|
+
function logFunctionsConfigDeprecationWarning() {
|
|
19
|
+
(0, utils_1.logWarningToStderr)(FUNCTIONS_CONFIG_DEPRECATION_MESSAGE);
|
|
20
|
+
}
|
|
21
|
+
exports.logFunctionsConfigDeprecationWarning = logFunctionsConfigDeprecationWarning;
|
|
@@ -7,7 +7,9 @@ const path = require("path");
|
|
|
7
7
|
const prompt_1 = require("../../../prompt");
|
|
8
8
|
const utils = require("../../../utils");
|
|
9
9
|
const logger_1 = require("../../../logger");
|
|
10
|
-
const
|
|
10
|
+
const vsCodeUtils_1 = require("../../../vsCodeUtils");
|
|
11
|
+
const CLI_PROMPTS_DIR = path.join(__dirname, "../../../../prompts");
|
|
12
|
+
const VSCODE_PROMPTS_DIR = path.join(__dirname, "./prompts");
|
|
11
13
|
const FIREBASE_TAG_REGEX = /<firebase_prompts(?:\s+hash="([^"]+)")?>([\s\S]*?)<\/firebase_prompts>/;
|
|
12
14
|
const PROMPT_FILES = {
|
|
13
15
|
base: "FIREBASE.md",
|
|
@@ -92,6 +94,7 @@ function getFeatureContent(feature) {
|
|
|
92
94
|
const filename = PROMPT_FILES[feature];
|
|
93
95
|
if (!filename)
|
|
94
96
|
return "";
|
|
97
|
+
const PROMPTS_DIR = (0, vsCodeUtils_1.isVSCodeExtension)() ? VSCODE_PROMPTS_DIR : CLI_PROMPTS_DIR;
|
|
95
98
|
const content = fs.readFileSync(path.join(PROMPTS_DIR, filename), "utf8");
|
|
96
99
|
return content;
|
|
97
100
|
}
|
|
@@ -4,6 +4,7 @@ exports.actuate = exports.askQuestions = void 0;
|
|
|
4
4
|
const path_1 = require("path");
|
|
5
5
|
const prompt_1 = require("../../../prompt");
|
|
6
6
|
const templates_1 = require("../../../templates");
|
|
7
|
+
const ensureProjectConfigured_1 = require("../../../apptesting/ensureProjectConfigured");
|
|
7
8
|
const SMOKE_TEST_YAML_TEMPLATE = (0, templates_1.readTemplateSync)("init/apptesting/smoke_test.yaml");
|
|
8
9
|
async function askQuestions(setup) {
|
|
9
10
|
var _a, _b;
|
|
@@ -14,6 +15,9 @@ async function askQuestions(setup) {
|
|
|
14
15
|
default: "tests",
|
|
15
16
|
})),
|
|
16
17
|
} });
|
|
18
|
+
if (setup.projectId) {
|
|
19
|
+
await (0, ensureProjectConfigured_1.ensureProjectConfigured)(setup.projectId);
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
22
|
exports.askQuestions = askQuestions;
|
|
19
23
|
async function actuate(setup, config) {
|
package/lib/mcp/index.js
CHANGED
|
@@ -19,8 +19,11 @@ const hubClient_js_1 = require("../emulator/hubClient.js");
|
|
|
19
19
|
const node_fs_1 = require("node:fs");
|
|
20
20
|
const ensureApiEnabled_js_1 = require("../ensureApiEnabled.js");
|
|
21
21
|
const api = require("../api.js");
|
|
22
|
-
const
|
|
23
|
-
const
|
|
22
|
+
const logging_transport_js_1 = require("./logging-transport.js");
|
|
23
|
+
const env_js_1 = require("../env.js");
|
|
24
|
+
const timeout_js_1 = require("../timeout.js");
|
|
25
|
+
const SERVER_VERSION = "0.2.0";
|
|
26
|
+
const cmd = new command_js_1.Command("experimental:mcp");
|
|
24
27
|
const orderedLogLevels = [
|
|
25
28
|
"debug",
|
|
26
29
|
"info",
|
|
@@ -32,9 +35,20 @@ const orderedLogLevels = [
|
|
|
32
35
|
"emergency",
|
|
33
36
|
];
|
|
34
37
|
class FirebaseMcpServer {
|
|
38
|
+
async trackGA4(event, params = {}) {
|
|
39
|
+
var _a, _b;
|
|
40
|
+
if (!this.clientInfo)
|
|
41
|
+
await (0, timeout_js_1.timeoutFallback)(this.ready(), null, 2000);
|
|
42
|
+
const clientInfoParams = {
|
|
43
|
+
mcp_client_name: ((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>",
|
|
44
|
+
mcp_client_version: ((_b = this.clientInfo) === null || _b === void 0 ? void 0 : _b.version) || "<unknown-version>",
|
|
45
|
+
};
|
|
46
|
+
(0, track_js_1.trackGA4)(event, Object.assign(Object.assign({}, params), clientInfoParams));
|
|
47
|
+
}
|
|
35
48
|
constructor(options) {
|
|
36
49
|
this._ready = false;
|
|
37
50
|
this._readyPromises = [];
|
|
51
|
+
this.currentLogLevel = process.env.FIREBASE_MCP_DEBUG_LOG ? "debug" : undefined;
|
|
38
52
|
this.logger = Object.fromEntries(orderedLogLevels.map((logLevel) => [
|
|
39
53
|
logLevel,
|
|
40
54
|
(message) => this.log(logLevel, message),
|
|
@@ -50,10 +64,7 @@ class FirebaseMcpServer {
|
|
|
50
64
|
const clientInfo = this.server.getClientVersion();
|
|
51
65
|
this.clientInfo = clientInfo;
|
|
52
66
|
if (clientInfo === null || clientInfo === void 0 ? void 0 : clientInfo.name) {
|
|
53
|
-
|
|
54
|
-
mcp_client_name: clientInfo.name,
|
|
55
|
-
mcp_client_version: clientInfo.version,
|
|
56
|
-
});
|
|
67
|
+
this.trackGA4("mcp_client_connected");
|
|
57
68
|
}
|
|
58
69
|
if (!((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name))
|
|
59
70
|
this.clientInfo = { name: "<unknown-client>" };
|
|
@@ -76,9 +87,12 @@ class FirebaseMcpServer {
|
|
|
76
87
|
this._readyPromises.push({ resolve: resolve, reject });
|
|
77
88
|
});
|
|
78
89
|
}
|
|
90
|
+
get clientName() {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
return (_b = (_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : ((0, env_js_1.isFirebaseStudio)() ? "Firebase Studio" : "<unknown-client>");
|
|
93
|
+
}
|
|
79
94
|
get clientConfigKey() {
|
|
80
|
-
|
|
81
|
-
return `mcp.clientConfigs.${((_a = this.clientInfo) === null || _a === void 0 ? void 0 : _a.name) || "<unknown-client>"}:${this.startupRoot || process.cwd()}`;
|
|
95
|
+
return `mcp.clientConfigs.${this.clientName}:${this.startupRoot || process.cwd()}`;
|
|
82
96
|
}
|
|
83
97
|
getStoredClientConfig() {
|
|
84
98
|
return configstore_js_1.configstore.get(this.clientConfigKey) || {};
|
|
@@ -90,17 +104,19 @@ class FirebaseMcpServer {
|
|
|
90
104
|
return newConfig;
|
|
91
105
|
}
|
|
92
106
|
async detectProjectRoot() {
|
|
93
|
-
await this.ready();
|
|
107
|
+
await (0, timeout_js_1.timeoutFallback)(this.ready(), null, 2000);
|
|
94
108
|
if (this.cachedProjectRoot)
|
|
95
109
|
return this.cachedProjectRoot;
|
|
96
110
|
const storedRoot = this.getStoredClientConfig().projectRoot;
|
|
97
111
|
this.cachedProjectRoot = storedRoot || this.startupRoot || process.cwd();
|
|
112
|
+
this.log("debug", "detected and cached project root: " + this.cachedProjectRoot);
|
|
98
113
|
return this.cachedProjectRoot;
|
|
99
114
|
}
|
|
100
115
|
async detectActiveFeatures() {
|
|
101
116
|
var _a;
|
|
102
117
|
if ((_a = this.detectedFeatures) === null || _a === void 0 ? void 0 : _a.length)
|
|
103
118
|
return this.detectedFeatures;
|
|
119
|
+
this.log("debug", "detecting active features of Firebase MCP server...");
|
|
104
120
|
const options = await this.resolveOptions();
|
|
105
121
|
const projectId = await this.getProjectId();
|
|
106
122
|
const detected = await Promise.all(types_js_2.SERVER_FEATURES.map(async (f) => {
|
|
@@ -109,6 +125,7 @@ class FirebaseMcpServer {
|
|
|
109
125
|
return null;
|
|
110
126
|
}));
|
|
111
127
|
this.detectedFeatures = detected.filter((f) => !!f);
|
|
128
|
+
this.log("debug", "detected features of Firebase MCP server: " + (this.detectedFeatures.join(", ") || "<none>"));
|
|
112
129
|
return this.detectedFeatures;
|
|
113
130
|
}
|
|
114
131
|
async getEmulatorHubClient() {
|
|
@@ -156,36 +173,37 @@ class FirebaseMcpServer {
|
|
|
156
173
|
async getProjectId() {
|
|
157
174
|
return (0, projectUtils_js_1.getProjectId)(await this.resolveOptions());
|
|
158
175
|
}
|
|
159
|
-
async getAuthenticatedUser() {
|
|
176
|
+
async getAuthenticatedUser(skipAutoAuth = false) {
|
|
160
177
|
try {
|
|
161
|
-
|
|
162
|
-
|
|
178
|
+
this.log("debug", `calling requireAuth`);
|
|
179
|
+
const email = await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions(), skipAutoAuth);
|
|
180
|
+
this.log("debug", `detected authenticated account: ${email || "<none>"}`);
|
|
181
|
+
return (email !== null && email !== void 0 ? email : skipAutoAuth) ? null : "Application Default Credentials";
|
|
163
182
|
}
|
|
164
183
|
catch (e) {
|
|
184
|
+
this.log("debug", `error in requireAuth: ${e}`);
|
|
165
185
|
return null;
|
|
166
186
|
}
|
|
167
187
|
}
|
|
168
188
|
async mcpListTools() {
|
|
169
|
-
var _a, _b;
|
|
170
189
|
await Promise.all([this.detectActiveFeatures(), this.detectProjectRoot()]);
|
|
171
190
|
const hasActiveProject = !!(await this.getProjectId());
|
|
172
|
-
await
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
191
|
+
await this.trackGA4("mcp_list_tools");
|
|
192
|
+
const skipAutoAuthForStudio = (0, env_js_1.isFirebaseStudio)();
|
|
193
|
+
this.log("debug", `skip auto-auth in studio environment: ${skipAutoAuthForStudio}`);
|
|
176
194
|
return {
|
|
177
195
|
tools: this.availableTools.map((t) => t.mcp),
|
|
178
196
|
_meta: {
|
|
179
197
|
projectRoot: this.cachedProjectRoot,
|
|
180
198
|
projectDetected: hasActiveProject,
|
|
181
|
-
authenticatedUser: await this.getAuthenticatedUser(),
|
|
199
|
+
authenticatedUser: await this.getAuthenticatedUser(skipAutoAuthForStudio),
|
|
182
200
|
activeFeatures: this.activeFeatures,
|
|
183
201
|
detectedFeatures: this.detectedFeatures,
|
|
184
202
|
},
|
|
185
203
|
};
|
|
186
204
|
}
|
|
187
205
|
async mcpCallTool(request) {
|
|
188
|
-
var _a, _b, _c
|
|
206
|
+
var _a, _b, _c;
|
|
189
207
|
await this.detectProjectRoot();
|
|
190
208
|
const toolName = request.params.name;
|
|
191
209
|
const toolArgs = request.params.arguments;
|
|
@@ -225,26 +243,24 @@ class FirebaseMcpServer {
|
|
|
225
243
|
};
|
|
226
244
|
try {
|
|
227
245
|
const res = await tool.fn(toolArgs, toolsCtx);
|
|
228
|
-
await
|
|
246
|
+
await this.trackGA4("mcp_tool_call", {
|
|
229
247
|
tool_name: toolName,
|
|
230
248
|
error: res.isError ? 1 : 0,
|
|
231
|
-
mcp_client_name: (_d = this.clientInfo) === null || _d === void 0 ? void 0 : _d.name,
|
|
232
|
-
mcp_client_version: (_e = this.clientInfo) === null || _e === void 0 ? void 0 : _e.version,
|
|
233
249
|
});
|
|
234
250
|
return res;
|
|
235
251
|
}
|
|
236
252
|
catch (err) {
|
|
237
|
-
await
|
|
253
|
+
await this.trackGA4("mcp_tool_call", {
|
|
238
254
|
tool_name: toolName,
|
|
239
255
|
error: 1,
|
|
240
|
-
mcp_client_name: (_f = this.clientInfo) === null || _f === void 0 ? void 0 : _f.name,
|
|
241
|
-
mcp_client_version: (_g = this.clientInfo) === null || _g === void 0 ? void 0 : _g.version,
|
|
242
256
|
});
|
|
243
257
|
return (0, util_js_1.mcpError)(err);
|
|
244
258
|
}
|
|
245
259
|
}
|
|
246
260
|
async start() {
|
|
247
|
-
const transport =
|
|
261
|
+
const transport = process.env.FIREBASE_MCP_DEBUG_LOG
|
|
262
|
+
? new logging_transport_js_1.LoggingStdioServerTransport(process.env.FIREBASE_MCP_DEBUG_LOG)
|
|
263
|
+
: new stdio_js_1.StdioServerTransport();
|
|
248
264
|
await this.server.connect(transport);
|
|
249
265
|
}
|
|
250
266
|
async log(level, message) {
|
|
@@ -258,7 +274,8 @@ class FirebaseMcpServer {
|
|
|
258
274
|
if (orderedLogLevels.indexOf(this.currentLogLevel) > orderedLogLevels.indexOf(level)) {
|
|
259
275
|
return;
|
|
260
276
|
}
|
|
261
|
-
|
|
277
|
+
if (this._ready)
|
|
278
|
+
await this.server.sendLoggingMessage({ level, data });
|
|
262
279
|
}
|
|
263
280
|
}
|
|
264
281
|
exports.FirebaseMcpServer = FirebaseMcpServer;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LoggingStdioServerTransport = void 0;
|
|
4
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const promises_1 = require("fs/promises");
|
|
7
|
+
class LoggingStdioServerTransport extends stdio_js_1.StdioServerTransport {
|
|
8
|
+
constructor(path) {
|
|
9
|
+
super();
|
|
10
|
+
this.path = path;
|
|
11
|
+
(0, fs_1.appendFileSync)(path, "--- new process start ---\n");
|
|
12
|
+
const origOnData = this._ondata;
|
|
13
|
+
this._ondata = (chunk) => {
|
|
14
|
+
origOnData(chunk);
|
|
15
|
+
(0, fs_1.appendFileSync)(path, chunk.toString(), { encoding: "utf8" });
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
async send(message) {
|
|
19
|
+
await super.send(message);
|
|
20
|
+
await (0, promises_1.appendFile)(this.path, JSON.stringify(message) + "\n");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.LoggingStdioServerTransport = LoggingStdioServerTransport;
|
package/lib/mcp/util.js
CHANGED
|
@@ -6,6 +6,7 @@ const js_yaml_1 = require("js-yaml");
|
|
|
6
6
|
const os_1 = require("os");
|
|
7
7
|
const api_1 = require("../api");
|
|
8
8
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
9
|
+
const timeout_1 = require("../timeout");
|
|
9
10
|
function toContent(data, options) {
|
|
10
11
|
if (typeof data === "string")
|
|
11
12
|
return { content: [{ type: "text", text: data }] };
|
|
@@ -70,7 +71,7 @@ async function checkFeatureActive(feature, projectId, options) {
|
|
|
70
71
|
return true;
|
|
71
72
|
try {
|
|
72
73
|
if (projectId)
|
|
73
|
-
return await (0, ensureApiEnabled_1.check)(projectId, SERVER_FEATURE_APIS[feature], "", true);
|
|
74
|
+
return await (0, timeout_1.timeoutFallback)((0, ensureApiEnabled_1.check)(projectId, SERVER_FEATURE_APIS[feature], "", true), true, 3000);
|
|
74
75
|
}
|
|
75
76
|
catch (e) {
|
|
76
77
|
return true;
|
package/lib/requireAuth.js
CHANGED
|
@@ -10,6 +10,8 @@ const logger_1 = require("./logger");
|
|
|
10
10
|
const utils = require("./utils");
|
|
11
11
|
const scopes = require("./scopes");
|
|
12
12
|
const auth_1 = require("./auth");
|
|
13
|
+
const env_1 = require("./env");
|
|
14
|
+
const timeout_1 = require("./timeout");
|
|
13
15
|
const AUTH_ERROR_MESSAGE = `Command requires authentication, please run ${clc.bold("firebase login")}`;
|
|
14
16
|
let authClient;
|
|
15
17
|
let lastOptions;
|
|
@@ -28,13 +30,14 @@ async function autoAuth(options, authScopes) {
|
|
|
28
30
|
logger_1.logger.debug(`Running auto auth`);
|
|
29
31
|
let clientEmail;
|
|
30
32
|
try {
|
|
31
|
-
const
|
|
33
|
+
const timeoutMillis = (0, env_1.isFirebaseMcp)() ? 5000 : 15000;
|
|
34
|
+
const credentials = await (0, timeout_1.timeoutError)(client.getCredentials(), new error_1.FirebaseError(`Authenticating with default credentials timed out after ${timeoutMillis / 1000} seconds. Please try running \`firebase login\` instead.`), timeoutMillis);
|
|
32
35
|
clientEmail = credentials.client_email;
|
|
33
36
|
}
|
|
34
37
|
catch (e) {
|
|
35
38
|
logger_1.logger.debug(`Error getting account credentials.`);
|
|
36
39
|
}
|
|
37
|
-
if (
|
|
40
|
+
if ((0, env_1.isFirebaseStudio)() && token && clientEmail) {
|
|
38
41
|
const activeAccount = {
|
|
39
42
|
user: { email: clientEmail },
|
|
40
43
|
tokens: {
|
|
@@ -56,7 +59,7 @@ async function refreshAuth() {
|
|
|
56
59
|
return lastOptions.tokens;
|
|
57
60
|
}
|
|
58
61
|
exports.refreshAuth = refreshAuth;
|
|
59
|
-
async function requireAuth(options) {
|
|
62
|
+
async function requireAuth(options, skipAutoAuth = false) {
|
|
60
63
|
lastOptions = options;
|
|
61
64
|
api.setScopes([scopes.CLOUD_PLATFORM, scopes.FIREBASE_PLATFORM]);
|
|
62
65
|
options.authScopes = api.getScopes();
|
|
@@ -76,6 +79,9 @@ async function requireAuth(options) {
|
|
|
76
79
|
else if (user && (!(0, auth_1.isExpired)(tokens) || (tokens === null || tokens === void 0 ? void 0 : tokens.refresh_token))) {
|
|
77
80
|
logger_1.logger.debug(`> authorizing via signed-in user (${user.email})`);
|
|
78
81
|
}
|
|
82
|
+
else if (skipAutoAuth) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
79
85
|
else {
|
|
80
86
|
try {
|
|
81
87
|
return await autoAuth(options, options.authScopes);
|
package/lib/timeout.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.timeoutError = exports.timeoutFallback = void 0;
|
|
4
|
+
async function timeoutFallback(promise, value, timeoutMillis = 2000) {
|
|
5
|
+
return Promise.race([
|
|
6
|
+
promise,
|
|
7
|
+
new Promise((resolve) => setTimeout(() => resolve(value), timeoutMillis)),
|
|
8
|
+
]);
|
|
9
|
+
}
|
|
10
|
+
exports.timeoutFallback = timeoutFallback;
|
|
11
|
+
async function timeoutError(promise, error, timeoutMillis = 5000) {
|
|
12
|
+
if (typeof error === "string")
|
|
13
|
+
error = new Error(error);
|
|
14
|
+
return Promise.race([
|
|
15
|
+
promise,
|
|
16
|
+
new Promise((resolve, reject) => {
|
|
17
|
+
setTimeout(() => reject(error || new Error("Operation timed out.")), timeoutMillis);
|
|
18
|
+
}),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
exports.timeoutError = timeoutError;
|
package/lib/track.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a;
|
|
3
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
3
|
exports.cliSession = exports.vscodeSession = exports.emulatorSession = exports.trackVSCode = exports.trackEmulator = exports.trackGA4 = exports.usageEnabled = exports.GA4_PROPERTIES = void 0;
|
|
5
4
|
const node_fetch_1 = require("node-fetch");
|
|
@@ -7,6 +6,7 @@ const uuid_1 = require("uuid");
|
|
|
7
6
|
const auth_1 = require("./auth");
|
|
8
7
|
const configstore_1 = require("./configstore");
|
|
9
8
|
const logger_1 = require("./logger");
|
|
9
|
+
const env_1 = require("./env");
|
|
10
10
|
const pkg = require("../package.json");
|
|
11
11
|
exports.GA4_PROPERTIES = {
|
|
12
12
|
cli: {
|
|
@@ -43,7 +43,7 @@ const GA4_USER_PROPS = {
|
|
|
43
43
|
value: process.env.FIREPIT_VERSION || "none",
|
|
44
44
|
},
|
|
45
45
|
is_firebase_studio: {
|
|
46
|
-
value: (
|
|
46
|
+
value: (0, env_1.isFirebaseStudio)().toString(),
|
|
47
47
|
},
|
|
48
48
|
};
|
|
49
49
|
async function trackGA4(eventName, params, duration = 1) {
|
package/lib/utils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = void 0;
|
|
3
|
+
exports.openInBrowserPopup = exports.openInBrowser = exports.connectableHostname = exports.randomInt = exports.debounce = exports.last = exports.cloneDeep = exports.groupBy = exports.assertIsStringOrUndefined = exports.assertIsNumber = exports.assertIsString = exports.thirtyDaysFromNow = exports.isRunningInWSL = exports.isCloudEnvironment = exports.datetimeString = exports.createDestroyer = exports.sleep = exports.promiseWithSpinner = exports.tryParse = exports.promiseProps = exports.withTimeout = exports.promiseWhile = exports.promiseAllSettled = exports.getFunctionsEventProvider = exports.endpoint = exports.makeActiveProject = exports.streamToString = exports.stringToStream = exports.explainStdin = exports.allSettled = exports.reject = exports.logLabeledError = exports.logLabeledWarning = exports.logWarningToStderr = exports.logWarning = exports.logLabeledBullet = exports.logBullet = exports.logLabeledSuccess = exports.logSuccess = exports.addSubdomain = exports.addDatabaseNamespace = exports.getDatabaseViewDataUrl = exports.getDatabaseUrl = exports.envOverride = exports.setVSCodeEnvVars = exports.getInheritedOption = exports.consoleUrl = exports.vscodeEnvVars = exports.envOverrides = exports.IS_WINDOWS = void 0;
|
|
4
|
+
exports.deepEqual = exports.promptForDirectory = exports.updateOrCreateGitignore = exports.readSecretValue = exports.generateId = exports.wrappedSafeLoad = exports.readFileFromDirectory = exports.getHostnameFromUrl = void 0;
|
|
5
5
|
const fs = require("fs-extra");
|
|
6
6
|
const tty = require("tty");
|
|
7
7
|
const path = require("node:path");
|
|
@@ -115,6 +115,11 @@ function logWarning(message, type = "warn", data = undefined) {
|
|
|
115
115
|
logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} `)), message, data);
|
|
116
116
|
}
|
|
117
117
|
exports.logWarning = logWarning;
|
|
118
|
+
function logWarningToStderr(message) {
|
|
119
|
+
const prefix = clc.bold(`${WARNING_CHAR} `);
|
|
120
|
+
process.stderr.write(clc.yellow(prefix + message) + "\n");
|
|
121
|
+
}
|
|
122
|
+
exports.logWarningToStderr = logWarningToStderr;
|
|
118
123
|
function logLabeledWarning(label, message, type = "warn", data = undefined) {
|
|
119
124
|
logger_1.logger[type](clc.yellow(clc.bold(`${WARNING_CHAR} ${label}:`)), message, data);
|
|
120
125
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const adminSdkConfig_1 = require("./adminSdkConfig");
|
|
5
|
-
describe("adminSdkConfig", () => {
|
|
6
|
-
describe("getProjectAdminSdkConfigOrCached", () => {
|
|
7
|
-
it("should return a fake config for a demo project id", async () => {
|
|
8
|
-
const projectId = "demo-project-1234";
|
|
9
|
-
await (0, chai_1.expect)((0, adminSdkConfig_1.getProjectAdminSdkConfigOrCached)(projectId)).to.eventually.deep.equal({
|
|
10
|
-
projectId: "demo-project-1234",
|
|
11
|
-
databaseURL: "https://demo-project-1234.firebaseio.com",
|
|
12
|
-
storageBucket: "demo-project-1234.appspot.com",
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const firestore_1 = require("../gcp/firestore");
|
|
5
|
-
const proto_1 = require("../gcp/proto");
|
|
6
|
-
const sort = require("./api-sort");
|
|
7
|
-
describe("compareApiBackup", () => {
|
|
8
|
-
it("should compare backups by location", () => {
|
|
9
|
-
const nam5Backup = {
|
|
10
|
-
name: "projects/example/locations/nam5/backups/backupid",
|
|
11
|
-
};
|
|
12
|
-
const usWest1Backup = {
|
|
13
|
-
name: "projects/example/locations/us-west1/backups/backupid",
|
|
14
|
-
};
|
|
15
|
-
(0, chai_1.expect)(sort.compareApiBackup(usWest1Backup, nam5Backup)).to.greaterThanOrEqual(1);
|
|
16
|
-
(0, chai_1.expect)(sort.compareApiBackup(nam5Backup, usWest1Backup)).to.lessThanOrEqual(-1);
|
|
17
|
-
});
|
|
18
|
-
it("should compare backups by snapshotTime (descending) if location is the same", () => {
|
|
19
|
-
const earlierBackup = {
|
|
20
|
-
name: "projects/example/locations/nam5/backups/backupid",
|
|
21
|
-
snapshotTime: "2024-01-01T00:00:00.000000Z",
|
|
22
|
-
};
|
|
23
|
-
const laterBackup = {
|
|
24
|
-
name: "projects/example/locations/nam5/backups/backupid",
|
|
25
|
-
snapshotTime: "2024-02-02T00:00:00.000000Z",
|
|
26
|
-
};
|
|
27
|
-
(0, chai_1.expect)(sort.compareApiBackup(earlierBackup, laterBackup)).to.greaterThanOrEqual(1);
|
|
28
|
-
(0, chai_1.expect)(sort.compareApiBackup(laterBackup, earlierBackup)).to.lessThanOrEqual(-1);
|
|
29
|
-
});
|
|
30
|
-
it("should compare backups by full name if location and snapshotTime are the same", () => {
|
|
31
|
-
const nam5Backup1 = {
|
|
32
|
-
name: "projects/example/locations/nam5/backups/earlier-backupid",
|
|
33
|
-
snapshotTime: "2024-01-01T00:00:00.000000Z",
|
|
34
|
-
};
|
|
35
|
-
const nam5Backup2 = {
|
|
36
|
-
name: "projects/example/locations/nam5/backups/later-backupid",
|
|
37
|
-
snapshotTime: "2024-01-01T00:00:00.000000Z",
|
|
38
|
-
};
|
|
39
|
-
(0, chai_1.expect)(sort.compareApiBackup(nam5Backup2, nam5Backup1)).to.greaterThanOrEqual(1);
|
|
40
|
-
(0, chai_1.expect)(sort.compareApiBackup(nam5Backup1, nam5Backup2)).to.lessThanOrEqual(-1);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe("compareApiBackupSchedule", () => {
|
|
44
|
-
it("daily schedules should precede weekly ones", () => {
|
|
45
|
-
const dailySchedule = {
|
|
46
|
-
name: "projects/example/databases/mydatabase/backupSchedules/schedule",
|
|
47
|
-
dailyRecurrence: {},
|
|
48
|
-
retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
|
|
49
|
-
};
|
|
50
|
-
const weeklySchedule = {
|
|
51
|
-
name: "projects/example/databases/mydatabase/backupSchedules/schedule",
|
|
52
|
-
weeklyRecurrence: {
|
|
53
|
-
day: firestore_1.DayOfWeek.FRIDAY,
|
|
54
|
-
},
|
|
55
|
-
retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24 * 7),
|
|
56
|
-
};
|
|
57
|
-
(0, chai_1.expect)(sort.compareApiBackupSchedule(weeklySchedule, dailySchedule)).to.greaterThanOrEqual(1);
|
|
58
|
-
(0, chai_1.expect)(sort.compareApiBackup(dailySchedule, weeklySchedule)).to.lessThanOrEqual(-1);
|
|
59
|
-
});
|
|
60
|
-
it("should compare schedules with the same recurrence by name", () => {
|
|
61
|
-
const dailySchedule1 = {
|
|
62
|
-
name: "projects/example/databases/mydatabase/backupSchedules/schedule1",
|
|
63
|
-
dailyRecurrence: {},
|
|
64
|
-
retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
|
|
65
|
-
};
|
|
66
|
-
const dailySchedule2 = {
|
|
67
|
-
name: "projects/example/databases/mydatabase/backupSchedules/schedule2",
|
|
68
|
-
dailyRecurrence: {},
|
|
69
|
-
retention: (0, proto_1.durationFromSeconds)(60 * 60 * 24),
|
|
70
|
-
};
|
|
71
|
-
(0, chai_1.expect)(sort.compareApiBackupSchedule(dailySchedule1, dailySchedule2)).to.lessThanOrEqual(-1);
|
|
72
|
-
(0, chai_1.expect)(sort.compareApiBackup(dailySchedule2, dailySchedule1)).to.greaterThanOrEqual(1);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const backupUtils_1 = require("./backupUtils");
|
|
5
|
-
describe("calculateRetention", () => {
|
|
6
|
-
it("should accept minutes", () => {
|
|
7
|
-
(0, chai_1.expect)((0, backupUtils_1.calculateRetention)("5m")).to.eq(300);
|
|
8
|
-
});
|
|
9
|
-
it("should accept hours", () => {
|
|
10
|
-
(0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3h")).to.eq(10800);
|
|
11
|
-
});
|
|
12
|
-
it("should accept days", () => {
|
|
13
|
-
(0, chai_1.expect)((0, backupUtils_1.calculateRetention)("2d")).to.eq(172800);
|
|
14
|
-
});
|
|
15
|
-
it("should accept weeks", () => {
|
|
16
|
-
(0, chai_1.expect)((0, backupUtils_1.calculateRetention)("3w")).to.eq(1814400);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const API = require("./api-types");
|
|
5
|
-
const pretty_print_1 = require("./pretty-print");
|
|
6
|
-
const printer = new pretty_print_1.PrettyPrint();
|
|
7
|
-
describe("prettyIndexString", () => {
|
|
8
|
-
it("should correctly print an order type Index", () => {
|
|
9
|
-
(0, chai_1.expect)(printer.prettyIndexString({
|
|
10
|
-
name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
|
|
11
|
-
queryScope: API.QueryScope.COLLECTION,
|
|
12
|
-
fields: [
|
|
13
|
-
{ fieldPath: "foo", order: API.Order.ASCENDING },
|
|
14
|
-
{ fieldPath: "bar", order: API.Order.DESCENDING },
|
|
15
|
-
],
|
|
16
|
-
}, false)).to.contain("(foo,ASCENDING) (bar,DESCENDING) ");
|
|
17
|
-
});
|
|
18
|
-
it("should correctly print a contains type Index", () => {
|
|
19
|
-
(0, chai_1.expect)(printer.prettyIndexString({
|
|
20
|
-
name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
|
|
21
|
-
queryScope: API.QueryScope.COLLECTION,
|
|
22
|
-
fields: [
|
|
23
|
-
{ fieldPath: "foo", order: API.Order.ASCENDING },
|
|
24
|
-
{ fieldPath: "baz", arrayConfig: API.ArrayConfig.CONTAINS },
|
|
25
|
-
],
|
|
26
|
-
}, false)).to.contain("(foo,ASCENDING) (baz,CONTAINS) ");
|
|
27
|
-
});
|
|
28
|
-
it("should correctly print a vector type Index", () => {
|
|
29
|
-
(0, chai_1.expect)(printer.prettyIndexString({
|
|
30
|
-
name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
|
|
31
|
-
queryScope: API.QueryScope.COLLECTION,
|
|
32
|
-
fields: [{ fieldPath: "foo", vectorConfig: { dimension: 100, flat: {} } }],
|
|
33
|
-
}, false)).to.contain("(foo,VECTOR<100>) ");
|
|
34
|
-
});
|
|
35
|
-
it("should correctly print a vector type Index with other fields", () => {
|
|
36
|
-
(0, chai_1.expect)(printer.prettyIndexString({
|
|
37
|
-
name: "/projects/project/databases/(default)/collectionGroups/collectionB/indexes/a",
|
|
38
|
-
queryScope: API.QueryScope.COLLECTION,
|
|
39
|
-
fields: [
|
|
40
|
-
{ fieldPath: "foo", order: API.Order.ASCENDING },
|
|
41
|
-
{ fieldPath: "bar", vectorConfig: { dimension: 200, flat: {} } },
|
|
42
|
-
],
|
|
43
|
-
}, false)).to.contain("(foo,ASCENDING) (bar,VECTOR<200>) ");
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
describe("firebaseConsoleDatabaseUrl", () => {
|
|
47
|
-
it("should provide a console link", () => {
|
|
48
|
-
(0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "example-db")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/example-db/data");
|
|
49
|
-
});
|
|
50
|
-
it("should convert (default) to -default-", () => {
|
|
51
|
-
(0, chai_1.expect)(printer.firebaseConsoleDatabaseUrl("example-project", "(default)")).to.equal("https://console.firebase.google.com/project/example-project/firestore/databases/-default-/data");
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
describe("prettyStringArray", () => {
|
|
55
|
-
it("should correctly print an array of strings", () => {
|
|
56
|
-
(0, chai_1.expect)(printer.prettyStringArray(["kms-key-1", "kms-key-2"])).to.equal("kms-key-1\nkms-key-2\n");
|
|
57
|
-
});
|
|
58
|
-
it("should print nothing if the array is empty", () => {
|
|
59
|
-
(0, chai_1.expect)(printer.prettyStringArray([])).to.equal("");
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const chai_1 = require("chai");
|
|
4
|
-
const util = require("./util");
|
|
5
|
-
describe("IndexNameParsing", () => {
|
|
6
|
-
it("should parse an index name correctly", () => {
|
|
7
|
-
const name = "/projects/myproject/databases/(default)/collectionGroups/collection/indexes/abc123/";
|
|
8
|
-
(0, chai_1.expect)(util.parseIndexName(name)).to.eql({
|
|
9
|
-
projectId: "myproject",
|
|
10
|
-
databaseId: "(default)",
|
|
11
|
-
collectionGroupId: "collection",
|
|
12
|
-
indexId: "abc123",
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
it("should parse a field name correctly", () => {
|
|
16
|
-
const name = "/projects/myproject/databases/(default)/collectionGroups/collection/fields/abc123/";
|
|
17
|
-
(0, chai_1.expect)(util.parseFieldName(name)).to.eql({
|
|
18
|
-
projectId: "myproject",
|
|
19
|
-
databaseId: "(default)",
|
|
20
|
-
collectionGroupId: "collection",
|
|
21
|
-
fieldPath: "abc123",
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
it("should parse an index name from a named database correctly", () => {
|
|
25
|
-
const name = "/projects/myproject/databases/named-db/collectionGroups/collection/indexes/abc123/";
|
|
26
|
-
(0, chai_1.expect)(util.parseIndexName(name)).to.eql({
|
|
27
|
-
projectId: "myproject",
|
|
28
|
-
databaseId: "named-db",
|
|
29
|
-
collectionGroupId: "collection",
|
|
30
|
-
indexId: "abc123",
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
it("should parse a field name from a named database correctly", () => {
|
|
34
|
-
const name = "/projects/myproject/databases/named-db/collectionGroups/collection/fields/abc123/";
|
|
35
|
-
(0, chai_1.expect)(util.parseFieldName(name)).to.eql({
|
|
36
|
-
projectId: "myproject",
|
|
37
|
-
databaseId: "named-db",
|
|
38
|
-
collectionGroupId: "collection",
|
|
39
|
-
fieldPath: "abc123",
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|