firebase-tools 14.6.0 → 14.8.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/commands/functions-delete.js +0 -2
- package/lib/commands/functions-list.js +23 -32
- package/lib/commands/init.js +14 -1
- package/lib/crashlytics/listTopIssues.js +2 -1
- package/lib/deploy/functions/checkIam.js +2 -2
- package/lib/emulator/commandUtils.js +2 -1
- package/lib/emulator/controller.js +1 -1
- package/lib/emulator/dataconnect/pgliteServer.js +124 -102
- package/lib/emulator/downloadableEmulatorInfo.json +18 -18
- package/lib/ensureApiEnabled.js +22 -0
- package/lib/fsutils.js +16 -1
- package/lib/gcp/serviceusage.js +2 -2
- package/lib/gcp/storage.js +8 -4
- package/lib/mcp/index.js +15 -1
- package/lib/mcp/tools/crashlytics/list_top_issues.js +7 -2
- package/lib/mcp/tools/dataconnect/emulator.js +3 -19
- package/lib/mcp/tools/dataconnect/execute_graphql.js +1 -1
- package/lib/mcp/tools/dataconnect/execute_graphql_read.js +1 -1
- package/lib/mcp/tools/dataconnect/execute_mutation.js +1 -1
- package/lib/mcp/tools/dataconnect/execute_query.js +1 -1
- package/lib/mcp/tools/firestore/delete_document.js +2 -2
- package/lib/mcp/tools/firestore/get_documents.js +2 -2
- package/lib/mcp/tools/firestore/list_collections.js +2 -2
- package/lib/mcp/tools/firestore/query_collection.js +2 -2
- package/lib/mcp/tools/storage/get_download_url.js +8 -2
- package/lib/operation-poller.js +3 -1
- package/lib/track.js +4 -0
- package/package.json +4 -2
- package/templates/init/functions/javascript/index.js +14 -1
- package/templates/init/functions/python/main.py +8 -0
- package/templates/init/functions/typescript/index.ts +14 -1
- package/lib/mcp/tools/firestore/emulator.js +0 -16
|
@@ -16,7 +16,6 @@ const planner = require("../deploy/functions/release/planner");
|
|
|
16
16
|
const fabricator = require("../deploy/functions/release/fabricator");
|
|
17
17
|
const executor = require("../deploy/functions/release/executor");
|
|
18
18
|
const reporter = require("../deploy/functions/release/reporter");
|
|
19
|
-
const containerCleaner = require("../deploy/functions/containerCleaner");
|
|
20
19
|
const getProjectNumber_1 = require("../getProjectNumber");
|
|
21
20
|
exports.command = new command_1.Command("functions:delete [filters...]")
|
|
22
21
|
.description("delete one or more Cloud Functions by name or group name.")
|
|
@@ -91,5 +90,4 @@ exports.command = new command_1.Command("functions:delete [filters...]")
|
|
|
91
90
|
exit: 1,
|
|
92
91
|
});
|
|
93
92
|
}
|
|
94
|
-
await containerCleaner.cleanupBuildImages([], allEpToDelete);
|
|
95
93
|
});
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.command = void 0;
|
|
4
4
|
const command_1 = require("../command");
|
|
5
|
-
const error_1 = require("../error");
|
|
6
5
|
const projectUtils_1 = require("../projectUtils");
|
|
7
6
|
const requirePermissions_1 = require("../requirePermissions");
|
|
8
7
|
const backend = require("../deploy/functions/backend");
|
|
@@ -12,36 +11,28 @@ exports.command = new command_1.Command("functions:list")
|
|
|
12
11
|
.description("list all deployed functions in your Firebase project")
|
|
13
12
|
.before(requirePermissions_1.requirePermissions, ["cloudfunctions.functions.list"])
|
|
14
13
|
.action(async (options) => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
table.push(entry);
|
|
37
|
-
}
|
|
38
|
-
logger_1.logger.info(table.toString());
|
|
39
|
-
return endpointsList;
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
throw new error_1.FirebaseError("Failed to list functions", {
|
|
43
|
-
exit: 1,
|
|
44
|
-
original: err,
|
|
45
|
-
});
|
|
14
|
+
const context = {
|
|
15
|
+
projectId: (0, projectUtils_1.needProjectId)(options),
|
|
16
|
+
};
|
|
17
|
+
const existing = await backend.existingBackend(context);
|
|
18
|
+
const endpointsList = backend.allEndpoints(existing).sort(backend.compareFunctions);
|
|
19
|
+
const table = new Table({
|
|
20
|
+
head: ["Function", "Version", "Trigger", "Location", "Memory", "Runtime"],
|
|
21
|
+
style: { head: ["yellow"] },
|
|
22
|
+
});
|
|
23
|
+
for (const endpoint of endpointsList) {
|
|
24
|
+
const trigger = backend.endpointTriggerType(endpoint);
|
|
25
|
+
const availableMemoryMb = endpoint.availableMemoryMb || "---";
|
|
26
|
+
const entry = [
|
|
27
|
+
endpoint.id,
|
|
28
|
+
endpoint.platform === "gcfv2" ? "v2" : "v1",
|
|
29
|
+
trigger,
|
|
30
|
+
endpoint.region,
|
|
31
|
+
availableMemoryMb,
|
|
32
|
+
endpoint.runtime,
|
|
33
|
+
];
|
|
34
|
+
table.push(entry);
|
|
46
35
|
}
|
|
36
|
+
logger_1.logger.info(table.toString());
|
|
37
|
+
return endpointsList;
|
|
47
38
|
});
|
package/lib/commands/init.js
CHANGED
|
@@ -16,6 +16,7 @@ const utils = require("../utils");
|
|
|
16
16
|
const experiments_1 = require("../experiments");
|
|
17
17
|
const templates_1 = require("../templates");
|
|
18
18
|
const error_1 = require("../error");
|
|
19
|
+
const track_1 = require("../track");
|
|
19
20
|
const homeDir = os.homedir();
|
|
20
21
|
const BANNER_TEXT = (0, templates_1.readTemplateSync)("banner.txt");
|
|
21
22
|
const GITIGNORE_TEMPLATE = (0, templates_1.readTemplateSync)("_gitignore");
|
|
@@ -114,13 +115,14 @@ exports.command = new command_1.Command("init [feature]")
|
|
|
114
115
|
.before(requireAuth_1.requireAuth)
|
|
115
116
|
.action(initAction);
|
|
116
117
|
async function initAction(feature, options) {
|
|
117
|
-
var _a;
|
|
118
|
+
var _a, _b;
|
|
118
119
|
if (feature && !featureNames.includes(feature)) {
|
|
119
120
|
return utils.reject(clc.bold(feature) +
|
|
120
121
|
" is not a supported feature; must be one of " +
|
|
121
122
|
featureNames.join(", ") +
|
|
122
123
|
".");
|
|
123
124
|
}
|
|
125
|
+
const start = process.uptime();
|
|
124
126
|
const cwd = options.cwd || process.cwd();
|
|
125
127
|
const warnings = [];
|
|
126
128
|
let warningText = "";
|
|
@@ -168,6 +170,15 @@ async function initAction(feature, options) {
|
|
|
168
170
|
message: "Which Firebase features do you want to set up for this directory? " +
|
|
169
171
|
"Press Space to select features, then Enter to confirm your choices.",
|
|
170
172
|
choices: choices.filter((c) => !c.hidden),
|
|
173
|
+
validate: (choices) => {
|
|
174
|
+
if (choices.length === 0) {
|
|
175
|
+
return ("Must select at least one feature. Use " +
|
|
176
|
+
clc.bold(clc.underline("SPACEBAR")) +
|
|
177
|
+
" to select features, or specify a feature by running " +
|
|
178
|
+
clc.bold("firebase init [feature_name]"));
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
},
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
184
|
if (!setup.features || ((_a = setup.features) === null || _a === void 0 ? void 0 : _a.length) === 0) {
|
|
@@ -191,6 +202,8 @@ async function initAction(feature, options) {
|
|
|
191
202
|
if (!fsutils.fileExistsSync(config.path(".gitignore"))) {
|
|
192
203
|
config.writeProjectFile(".gitignore", GITIGNORE_TEMPLATE);
|
|
193
204
|
}
|
|
205
|
+
const duration = Math.floor((process.uptime() - start) * 1000);
|
|
206
|
+
await (0, track_1.trackGA4)("product_init", { products_initialized: (_b = setup.features) === null || _b === void 0 ? void 0 : _b.join(",") }, duration);
|
|
194
207
|
logger_1.logger.info();
|
|
195
208
|
utils.logSuccess("Firebase initialization complete!");
|
|
196
209
|
}
|
|
@@ -10,10 +10,11 @@ const apiClient = new apiv2_1.Client({
|
|
|
10
10
|
urlPrefix: (0, api_1.crashlyticsApiOrigin)(),
|
|
11
11
|
apiVersion: "v1alpha",
|
|
12
12
|
});
|
|
13
|
-
async function listTopIssues(projectId, appId, issueCount) {
|
|
13
|
+
async function listTopIssues(projectId, appId, issueType, issueCount) {
|
|
14
14
|
try {
|
|
15
15
|
const queryParams = new URLSearchParams();
|
|
16
16
|
queryParams.set("page_size", `${issueCount}`);
|
|
17
|
+
queryParams.set("filter.issue.error_types", `${issueType}`);
|
|
17
18
|
const requestProjectId = parseProjectId(appId);
|
|
18
19
|
if (requestProjectId === undefined) {
|
|
19
20
|
throw new error_1.FirebaseError("Unable to get the projectId from the AppId.");
|
|
@@ -120,10 +120,10 @@ async function ensureGenkitMonitoringRoles(projectId, projectNumber, want, have,
|
|
|
120
120
|
.map((endpoint) => endpoint.serviceAccount || "")
|
|
121
121
|
.filter((value, index, self) => self.indexOf(value) === index);
|
|
122
122
|
const defaultServiceAccountIndex = serviceAccounts.indexOf("");
|
|
123
|
-
if (defaultServiceAccountIndex) {
|
|
123
|
+
if (defaultServiceAccountIndex !== -1) {
|
|
124
124
|
serviceAccounts[defaultServiceAccountIndex] = await gce.getDefaultServiceAccount(projectNumber);
|
|
125
125
|
}
|
|
126
|
-
const members = serviceAccounts.map((sa) => `serviceAccount:${sa}`);
|
|
126
|
+
const members = serviceAccounts.filter((sa) => !!sa).map((sa) => `serviceAccount:${sa}`);
|
|
127
127
|
const requiredBindings = [];
|
|
128
128
|
for (const monitoringRole of exports.GENKIT_MONITORING_ROLES) {
|
|
129
129
|
requiredBindings.push({
|
|
@@ -92,7 +92,8 @@ async function beforeEmulatorCommand(options) {
|
|
|
92
92
|
const canStartWithoutConfig = options.only &&
|
|
93
93
|
!controller.shouldStart(optionsWithConfig, types_1.Emulators.FUNCTIONS) &&
|
|
94
94
|
!controller.shouldStart(optionsWithConfig, types_1.Emulators.HOSTING);
|
|
95
|
-
if (!constants_1.Constants.isDemoProject(options.project)
|
|
95
|
+
if (!constants_1.Constants.isDemoProject(options.project) ||
|
|
96
|
+
controller.shouldStart(optionsWithConfig, types_1.Emulators.EXTENSIONS)) {
|
|
96
97
|
try {
|
|
97
98
|
await (0, requireAuth_1.requireAuth)(options);
|
|
98
99
|
}
|
|
@@ -740,7 +740,7 @@ async function exportEmulatorData(exportPath, options, initiatedBy) {
|
|
|
740
740
|
const exportAbsPath = path.resolve(exportPath);
|
|
741
741
|
if (!fs.existsSync(exportAbsPath)) {
|
|
742
742
|
utils.logBullet(`Creating export directory ${exportAbsPath}`);
|
|
743
|
-
fs.mkdirSync(exportAbsPath);
|
|
743
|
+
fs.mkdirSync(exportAbsPath, { recursive: true });
|
|
744
744
|
}
|
|
745
745
|
const existingMetadata = hubExport_1.HubExport.readMetadata(exportAbsPath);
|
|
746
746
|
const isExportDirEmpty = fs.readdirSync(exportAbsPath).length === 0;
|
|
@@ -19,16 +19,17 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
|
19
19
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.fromNodeSocket = exports.
|
|
22
|
+
exports.fromNodeSocket = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
|
|
23
23
|
const pglite_1 = require("@electric-sql/pglite");
|
|
24
24
|
const { dynamicImport } = require(true && "../../dynamicImport");
|
|
25
25
|
const net = require("node:net");
|
|
26
26
|
const node_stream_1 = require("node:stream");
|
|
27
27
|
const fs = require("fs");
|
|
28
|
+
const path = require("node:path");
|
|
28
29
|
const pg_gateway_1 = require("pg-gateway");
|
|
29
30
|
const logger_1 = require("../../logger");
|
|
30
31
|
const error_1 = require("../../error");
|
|
31
|
-
const
|
|
32
|
+
const fsutils_1 = require("../../fsutils");
|
|
32
33
|
exports.TRUNCATE_TABLES_SQL = `
|
|
33
34
|
DO $do$
|
|
34
35
|
DECLARE _clear text;
|
|
@@ -45,22 +46,43 @@ class PostgresServer {
|
|
|
45
46
|
async createPGServer(host = "127.0.0.1", port) {
|
|
46
47
|
const getDb = this.getDb.bind(this);
|
|
47
48
|
const server = net.createServer(async (socket) => {
|
|
48
|
-
|
|
49
|
-
serverVersion: "
|
|
49
|
+
await fromNodeSocket(socket, {
|
|
50
|
+
serverVersion: "17.4 (PGlite 0.3.3)",
|
|
50
51
|
auth: { method: "trust" },
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
onMessage(data, { isAuthenticated }) {
|
|
53
|
+
return __asyncGenerator(this, arguments, function* onMessage_1() {
|
|
54
|
+
var _a, e_1, _b, _c;
|
|
55
|
+
if (!isAuthenticated) {
|
|
56
|
+
return yield __await(void 0);
|
|
57
|
+
}
|
|
58
|
+
const db = yield __await(getDb());
|
|
59
|
+
if (data[0] === pg_gateway_1.FrontendMessageCode.Terminate) {
|
|
60
|
+
yield __await(db.query("DEALLOCATE ALL"));
|
|
61
|
+
}
|
|
62
|
+
const response = yield __await(db.execProtocolRaw(data));
|
|
63
|
+
try {
|
|
64
|
+
for (var _d = true, _e = __asyncValues((0, pg_gateway_1.getMessages)(response)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a;) {
|
|
65
|
+
_c = _f.value;
|
|
66
|
+
_d = false;
|
|
67
|
+
try {
|
|
68
|
+
const message = _c;
|
|
69
|
+
yield yield __await(message);
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
_d = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
77
|
+
finally {
|
|
78
|
+
try {
|
|
79
|
+
if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
|
|
80
|
+
}
|
|
81
|
+
finally { if (e_1) throw e_1.error; }
|
|
82
|
+
}
|
|
83
|
+
});
|
|
61
84
|
},
|
|
62
85
|
});
|
|
63
|
-
const extendedQueryPatch = new PGliteExtendedQueryPatch(connection);
|
|
64
86
|
socket.on("end", () => {
|
|
65
87
|
logger_1.logger.debug("Postgres client disconnected");
|
|
66
88
|
});
|
|
@@ -79,30 +101,15 @@ class PostgresServer {
|
|
|
79
101
|
}
|
|
80
102
|
async getDb() {
|
|
81
103
|
if (!this.db) {
|
|
82
|
-
|
|
83
|
-
fs.mkdirSync(this.dataDirectory, { recursive: true });
|
|
84
|
-
}
|
|
85
|
-
const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector;
|
|
86
|
-
const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp;
|
|
87
|
-
const pgliteArgs = {
|
|
88
|
-
debug: this.debug,
|
|
89
|
-
extensions: {
|
|
90
|
-
vector,
|
|
91
|
-
uuidOssp,
|
|
92
|
-
},
|
|
93
|
-
dataDir: this.dataDirectory,
|
|
94
|
-
};
|
|
95
|
-
if (this.importPath) {
|
|
96
|
-
logger_1.logger.debug(`Importing from ${this.importPath}`);
|
|
97
|
-
const rf = fs.readFileSync(this.importPath);
|
|
98
|
-
const file = new File([rf], this.importPath);
|
|
99
|
-
pgliteArgs.loadDataDir = file;
|
|
100
|
-
}
|
|
101
|
-
this.db = await this.forceCreateDB(pgliteArgs);
|
|
102
|
-
await this.db.waitReady;
|
|
104
|
+
this.db = await this.forceCreateDB();
|
|
103
105
|
}
|
|
104
106
|
return this.db;
|
|
105
107
|
}
|
|
108
|
+
async getExtensions() {
|
|
109
|
+
const vector = (await dynamicImport("@electric-sql/pglite/vector")).vector;
|
|
110
|
+
const uuidOssp = (await dynamicImport("@electric-sql/pglite/contrib/uuid_ossp")).uuid_ossp;
|
|
111
|
+
return { vector, uuidOssp };
|
|
112
|
+
}
|
|
106
113
|
async clearDb() {
|
|
107
114
|
const db = await this.getDb();
|
|
108
115
|
await db.query(exports.TRUNCATE_TABLES_SQL);
|
|
@@ -113,18 +120,92 @@ class PostgresServer {
|
|
|
113
120
|
const arrayBuff = await dump.arrayBuffer();
|
|
114
121
|
fs.writeFileSync(exportPath, new Uint8Array(arrayBuff));
|
|
115
122
|
}
|
|
116
|
-
async
|
|
123
|
+
async migrateDb(pgliteArgs) {
|
|
124
|
+
if (!this.baseDataDirectory) {
|
|
125
|
+
throw new error_1.FirebaseError("Cannot migrate database without a data directory.");
|
|
126
|
+
}
|
|
127
|
+
const { PGlite: PGlite02 } = await dynamicImport("pglite-2");
|
|
128
|
+
const pgDump = (await dynamicImport("@electric-sql/pglite-tools/pg_dump")).pgDump;
|
|
129
|
+
logger_1.logger.info("Opening database with Postgres 16...");
|
|
130
|
+
const extensions = await this.getExtensions();
|
|
131
|
+
const dataDir = this.baseDataDirectory;
|
|
132
|
+
const oldDb = new PGlite02(Object.assign(Object.assign({}, pgliteArgs), { dataDir }));
|
|
133
|
+
await oldDb.waitReady;
|
|
134
|
+
const oldVersion = await oldDb.query("SELECT version();");
|
|
135
|
+
logger_1.logger.debug(`Old database version: ${oldVersion.rows[0].version}`);
|
|
136
|
+
if (!oldVersion.rows[0].version.includes("PostgreSQL 16")) {
|
|
137
|
+
await oldDb.close();
|
|
138
|
+
throw new error_1.FirebaseError("Migration started, but DB version is not PostgreSQL 16.");
|
|
139
|
+
}
|
|
140
|
+
logger_1.logger.info("Dumping data from old database...");
|
|
141
|
+
const dumpDir = await oldDb.dumpDataDir("none");
|
|
142
|
+
const tempOldDb = await PGlite02.create({
|
|
143
|
+
loadDataDir: dumpDir,
|
|
144
|
+
extensions,
|
|
145
|
+
});
|
|
146
|
+
const dumpResult = await pgDump({ pg: tempOldDb, args: ["--verbose", "--verbose"] });
|
|
147
|
+
await tempOldDb.close();
|
|
148
|
+
await oldDb.close();
|
|
149
|
+
logger_1.logger.info(`Moving old database directory to ${this.baseDataDirectory}/pg16...`);
|
|
150
|
+
const pg16Dir = this.getVersionedDataDir(16);
|
|
151
|
+
(0, fsutils_1.moveAll)(this.baseDataDirectory, pg16Dir);
|
|
152
|
+
logger_1.logger.info("If you need to use an older version of the Firebase CLI, you can restore from that directory.");
|
|
153
|
+
logger_1.logger.info("Creating new database with Postgres 17...");
|
|
154
|
+
const pg17Dir = this.getVersionedDataDir(17);
|
|
155
|
+
const newDb = new pglite_1.PGlite(Object.assign(Object.assign({}, pgliteArgs), { dataDir: pg17Dir }));
|
|
156
|
+
await newDb.waitReady;
|
|
157
|
+
logger_1.logger.info("Importing data into new database...");
|
|
158
|
+
const dumpText = await dumpResult.text();
|
|
159
|
+
await newDb.exec(dumpText);
|
|
160
|
+
await newDb.exec("SET SEARCH_PATH = public;");
|
|
161
|
+
logger_1.logger.info("Postgres database migration successful.");
|
|
162
|
+
return newDb;
|
|
163
|
+
}
|
|
164
|
+
getVersionedDataDir(version) {
|
|
165
|
+
if (!this.baseDataDirectory) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
return path.join(this.baseDataDirectory, `pg${version}`);
|
|
169
|
+
}
|
|
170
|
+
async forceCreateDB() {
|
|
171
|
+
const baseArgs = {
|
|
172
|
+
debug: this.debug,
|
|
173
|
+
extensions: await this.getExtensions(),
|
|
174
|
+
};
|
|
175
|
+
const pg17Dir = this.getVersionedDataDir(17);
|
|
176
|
+
if (pg17Dir && !fs.existsSync(pg17Dir)) {
|
|
177
|
+
fs.mkdirSync(pg17Dir, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
if (this.importPath) {
|
|
180
|
+
logger_1.logger.debug(`Importing from ${this.importPath}`);
|
|
181
|
+
const rf = fs.readFileSync(this.importPath);
|
|
182
|
+
const file = new File([rf], this.importPath);
|
|
183
|
+
baseArgs.loadDataDir = file;
|
|
184
|
+
}
|
|
185
|
+
if (this.baseDataDirectory && fs.existsSync(this.baseDataDirectory)) {
|
|
186
|
+
const versionFilePath = path.join(this.baseDataDirectory, "PG_VERSION");
|
|
187
|
+
if (fs.existsSync(versionFilePath)) {
|
|
188
|
+
const version = fs.readFileSync(versionFilePath, "utf-8").trim();
|
|
189
|
+
logger_1.logger.debug(`Found Postgres version file with version: ${version}`);
|
|
190
|
+
if (version === "16") {
|
|
191
|
+
logger_1.logger.info("Detected a Postgres 16 data directory from an older version of firebase-tools. Migrating to Postgres 17...");
|
|
192
|
+
return this.migrateDb(baseArgs);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
117
196
|
try {
|
|
118
|
-
const db =
|
|
197
|
+
const db = new pglite_1.PGlite(Object.assign(Object.assign({}, baseArgs), { dataDir: pg17Dir }));
|
|
198
|
+
await db.waitReady;
|
|
119
199
|
return db;
|
|
120
200
|
}
|
|
121
201
|
catch (err) {
|
|
122
|
-
if (
|
|
123
|
-
fs.rmSync(
|
|
124
|
-
const db =
|
|
202
|
+
if (pg17Dir && (0, error_1.hasMessage)(err) && /Database already exists/.test(err.message)) {
|
|
203
|
+
fs.rmSync(pg17Dir, { force: true, recursive: true });
|
|
204
|
+
const db = new pglite_1.PGlite(Object.assign(Object.assign({}, baseArgs), { dataDir: pg17Dir }));
|
|
205
|
+
await db.waitReady;
|
|
125
206
|
return db;
|
|
126
207
|
}
|
|
127
|
-
logger_1.logger.
|
|
208
|
+
logger_1.logger.warn(`Error from pglite: ${err}`);
|
|
128
209
|
throw new error_1.FirebaseError("Unexpected error starting up Postgres.");
|
|
129
210
|
}
|
|
130
211
|
}
|
|
@@ -140,71 +221,12 @@ class PostgresServer {
|
|
|
140
221
|
constructor(args) {
|
|
141
222
|
this.db = undefined;
|
|
142
223
|
this.server = undefined;
|
|
143
|
-
this.
|
|
224
|
+
this.baseDataDirectory = args.dataDirectory;
|
|
144
225
|
this.importPath = args.importPath;
|
|
145
226
|
this.debug = args.debug ? 5 : 0;
|
|
146
227
|
}
|
|
147
228
|
}
|
|
148
229
|
exports.PostgresServer = PostgresServer;
|
|
149
|
-
class PGliteExtendedQueryPatch {
|
|
150
|
-
constructor(connection) {
|
|
151
|
-
this.connection = connection;
|
|
152
|
-
this.isExtendedQuery = false;
|
|
153
|
-
this.eqpErrored = false;
|
|
154
|
-
}
|
|
155
|
-
filterResponse(message, response) {
|
|
156
|
-
return __asyncGenerator(this, arguments, function* filterResponse_1() {
|
|
157
|
-
var _a, e_1, _b, _c;
|
|
158
|
-
const pipelineStartMessages = [
|
|
159
|
-
pg_gateway_1.FrontendMessageCode.Parse,
|
|
160
|
-
pg_gateway_1.FrontendMessageCode.Bind,
|
|
161
|
-
pg_gateway_1.FrontendMessageCode.Close,
|
|
162
|
-
];
|
|
163
|
-
const decoder = new node_string_decoder_1.StringDecoder();
|
|
164
|
-
const decoded = decoder.write(message);
|
|
165
|
-
logger_1.logger.debug(decoded);
|
|
166
|
-
if (pipelineStartMessages.includes(message[0])) {
|
|
167
|
-
this.isExtendedQuery = true;
|
|
168
|
-
}
|
|
169
|
-
if (message[0] === pg_gateway_1.FrontendMessageCode.Sync) {
|
|
170
|
-
this.isExtendedQuery = false;
|
|
171
|
-
this.eqpErrored = false;
|
|
172
|
-
return yield __await(this.connection.createReadyForQuery());
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
for (var _d = true, _e = __asyncValues((0, pg_gateway_1.getMessages)(response)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a;) {
|
|
176
|
-
_c = _f.value;
|
|
177
|
-
_d = false;
|
|
178
|
-
try {
|
|
179
|
-
const message = _c;
|
|
180
|
-
if (this.eqpErrored) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
if (this.isExtendedQuery && message[0] === pg_gateway_1.BackendMessageCode.ErrorMessage) {
|
|
184
|
-
this.eqpErrored = true;
|
|
185
|
-
}
|
|
186
|
-
if (this.isExtendedQuery && message[0] === pg_gateway_1.BackendMessageCode.ReadyForQuery) {
|
|
187
|
-
logger_1.logger.debug("Filtered out a ReadyForQuery.");
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
yield yield __await(message);
|
|
191
|
-
}
|
|
192
|
-
finally {
|
|
193
|
-
_d = true;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
198
|
-
finally {
|
|
199
|
-
try {
|
|
200
|
-
if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
|
|
201
|
-
}
|
|
202
|
-
finally { if (e_1) throw e_1.error; }
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
exports.PGliteExtendedQueryPatch = PGliteExtendedQueryPatch;
|
|
208
230
|
async function fromNodeSocket(socket, options) {
|
|
209
231
|
const rs = node_stream_1.Readable.toWeb(socket);
|
|
210
232
|
const ws = node_stream_1.Writable.toWeb(socket);
|
|
@@ -54,28 +54,28 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "2.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
57
|
+
"version": "2.7.1",
|
|
58
|
+
"expectedSize": 27607808,
|
|
59
|
+
"expectedChecksum": "3cf0b4dcb96639f1b7ddf3e2052ea28f",
|
|
60
|
+
"expectedChecksumSHA256": "76b6db1f87e14e3c39035301ae199bec0458a2e7534a1db962276a92fa99c215",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-v2.7.1",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.7.1"
|
|
63
63
|
},
|
|
64
64
|
"win32": {
|
|
65
|
-
"version": "2.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
65
|
+
"version": "2.7.1",
|
|
66
|
+
"expectedSize": 28067328,
|
|
67
|
+
"expectedChecksum": "8dacedaecac242199dfcf3f6c023f89a",
|
|
68
|
+
"expectedChecksumSHA256": "fc098efa9aed9b1ac7d35b90da47608190f3a5562f37444012785a9793d743a0",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-v2.7.1",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.7.1.exe"
|
|
71
71
|
},
|
|
72
72
|
"linux": {
|
|
73
|
-
"version": "2.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.
|
|
73
|
+
"version": "2.7.1",
|
|
74
|
+
"expectedSize": 27521176,
|
|
75
|
+
"expectedChecksum": "3c03d85c2d1de80a2f2ba13fe3b5a3ff",
|
|
76
|
+
"expectedChecksumSHA256": "94c35ab64bd8d206e7843513848e7375cd8f9b6a06d013b1ec541ac831cda8a8",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-v2.7.1",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-2.7.1"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
}
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -8,6 +8,7 @@ const apiv2_1 = require("./apiv2");
|
|
|
8
8
|
const utils = require("./utils");
|
|
9
9
|
const error_1 = require("./error");
|
|
10
10
|
const logger_1 = require("./logger");
|
|
11
|
+
const configstore_1 = require("./configstore");
|
|
11
12
|
exports.POLL_SETTINGS = {
|
|
12
13
|
pollInterval: 10000,
|
|
13
14
|
pollsBeforeRetry: 12,
|
|
@@ -18,6 +19,9 @@ const apiClient = new apiv2_1.Client({
|
|
|
18
19
|
});
|
|
19
20
|
async function check(projectId, apiUri, prefix, silent = false) {
|
|
20
21
|
const apiName = apiUri.startsWith("http") ? new URL(apiUri).hostname : apiUri;
|
|
22
|
+
if (checkAPIEnablementCache(projectId, apiName)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
21
25
|
const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
|
|
22
26
|
headers: { "x-goog-quota-user": `projects/${projectId}` },
|
|
23
27
|
skipLog: { resBody: true },
|
|
@@ -26,6 +30,9 @@ async function check(projectId, apiUri, prefix, silent = false) {
|
|
|
26
30
|
if (isEnabled && !silent) {
|
|
27
31
|
utils.logLabeledSuccess(prefix, `required API ${(0, colorette_1.bold)(apiName)} is enabled`);
|
|
28
32
|
}
|
|
33
|
+
if (isEnabled) {
|
|
34
|
+
cacheEnabledAPI(projectId, apiName);
|
|
35
|
+
}
|
|
29
36
|
return isEnabled;
|
|
30
37
|
}
|
|
31
38
|
exports.check = check;
|
|
@@ -39,6 +46,7 @@ async function enable(projectId, apiName) {
|
|
|
39
46
|
headers: { "x-goog-quota-user": `projects/${projectId}` },
|
|
40
47
|
skipLog: { resBody: true },
|
|
41
48
|
});
|
|
49
|
+
cacheEnabledAPI(projectId, apiName);
|
|
42
50
|
}
|
|
43
51
|
catch (err) {
|
|
44
52
|
if ((0, error_1.isBillingError)(err)) {
|
|
@@ -120,3 +128,17 @@ function enableApiURI(projectId, apiName) {
|
|
|
120
128
|
return `https://console.cloud.google.com/apis/library/${apiName}?project=${projectId}`;
|
|
121
129
|
}
|
|
122
130
|
exports.enableApiURI = enableApiURI;
|
|
131
|
+
const API_ENABLEMENT_CACHE_KEY = "apiEnablementCache";
|
|
132
|
+
function checkAPIEnablementCache(projectId, apiName) {
|
|
133
|
+
var _a;
|
|
134
|
+
const cache = configstore_1.configstore.get(API_ENABLEMENT_CACHE_KEY);
|
|
135
|
+
return !!((_a = cache === null || cache === void 0 ? void 0 : cache[projectId]) === null || _a === void 0 ? void 0 : _a[apiName]);
|
|
136
|
+
}
|
|
137
|
+
function cacheEnabledAPI(projectId, apiName) {
|
|
138
|
+
const cache = (configstore_1.configstore.get(API_ENABLEMENT_CACHE_KEY) || {});
|
|
139
|
+
if (!cache[projectId]) {
|
|
140
|
+
cache[projectId] = {};
|
|
141
|
+
}
|
|
142
|
+
cache[projectId][apiName] = true;
|
|
143
|
+
configstore_1.configstore.set(API_ENABLEMENT_CACHE_KEY, cache);
|
|
144
|
+
}
|
package/lib/fsutils.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.listFiles = exports.readFile = exports.dirExistsSync = exports.fileExistsSync = void 0;
|
|
3
|
+
exports.moveAll = exports.listFiles = exports.readFile = exports.dirExistsSync = exports.fileExistsSync = void 0;
|
|
4
4
|
const fs_1 = require("fs");
|
|
5
|
+
const path = require("path");
|
|
5
6
|
const error_1 = require("./error");
|
|
7
|
+
const fs_extra_1 = require("fs-extra");
|
|
6
8
|
function fileExistsSync(path) {
|
|
7
9
|
try {
|
|
8
10
|
return (0, fs_1.statSync)(path).isFile();
|
|
@@ -45,3 +47,16 @@ function listFiles(path) {
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
exports.listFiles = listFiles;
|
|
50
|
+
function moveAll(srcDir, destDir) {
|
|
51
|
+
if (!(0, fs_1.existsSync)(destDir)) {
|
|
52
|
+
(0, fs_1.mkdirSync)(destDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
const files = listFiles(srcDir);
|
|
55
|
+
for (const f of files) {
|
|
56
|
+
const srcPath = path.join(srcDir, f);
|
|
57
|
+
if (srcPath === destDir)
|
|
58
|
+
continue;
|
|
59
|
+
(0, fs_extra_1.moveSync)(srcPath, path.join(destDir, f));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.moveAll = moveAll;
|
package/lib/gcp/serviceusage.js
CHANGED
|
@@ -20,7 +20,7 @@ const serviceUsagePollerOptions = {
|
|
|
20
20
|
async function generateServiceIdentity(projectNumber, service, prefix) {
|
|
21
21
|
utils.logLabeledBullet(prefix, `generating the service identity for ${(0, colorette_1.bold)(service)}...`);
|
|
22
22
|
try {
|
|
23
|
-
const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`);
|
|
23
|
+
const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`, {}, { headers: { "x-goog-quota-user": `projects/${projectNumber}` } });
|
|
24
24
|
return res.body;
|
|
25
25
|
}
|
|
26
26
|
catch (err) {
|
|
@@ -35,6 +35,6 @@ async function generateServiceIdentityAndPoll(projectNumber, service, prefix) {
|
|
|
35
35
|
if (op.done) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
-
await poller.pollOperation(Object.assign(Object.assign({}, serviceUsagePollerOptions), { operationResourceName: op.name }));
|
|
38
|
+
await poller.pollOperation(Object.assign(Object.assign({}, serviceUsagePollerOptions), { operationResourceName: op.name, headers: { "x-goog-quota-user": `projects/${projectNumber}` } }));
|
|
39
39
|
}
|
|
40
40
|
exports.generateServiceIdentityAndPoll = generateServiceIdentityAndPoll;
|
package/lib/gcp/storage.js
CHANGED
|
@@ -143,15 +143,19 @@ async function getServiceAccount(projectId) {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
exports.getServiceAccount = getServiceAccount;
|
|
146
|
-
async function getDownloadUrl(bucketName, objectPath) {
|
|
146
|
+
async function getDownloadUrl(bucketName, objectPath, emulatorUrl) {
|
|
147
147
|
try {
|
|
148
|
-
const
|
|
148
|
+
const origin = emulatorUrl || (0, api_1.firebaseStorageOrigin)();
|
|
149
|
+
const localAPIClient = new apiv2_1.Client({ urlPrefix: origin });
|
|
149
150
|
const response = await localAPIClient.get(`/v0/b/${bucketName}/o/${encodeURIComponent(objectPath)}`);
|
|
151
|
+
if (emulatorUrl) {
|
|
152
|
+
return `${origin}/v0/b/${bucketName}/o/${encodeURIComponent(objectPath)}?alt=media`;
|
|
153
|
+
}
|
|
150
154
|
if (!response.body.downloadTokens) {
|
|
151
|
-
throw new Error(
|
|
155
|
+
throw new Error(`no download tokens exist for ${objectPath}, please visit the Firebase console to make one`);
|
|
152
156
|
}
|
|
153
157
|
const [token] = response.body.downloadTokens.split(",");
|
|
154
|
-
return `${
|
|
158
|
+
return `${origin}/v0/b/${bucketName}/o/${encodeURIComponent(objectPath)}?alt=media&token=${token}`;
|
|
155
159
|
}
|
|
156
160
|
catch (err) {
|
|
157
161
|
logger_1.logger.error(err);
|
package/lib/mcp/index.js
CHANGED
|
@@ -102,6 +102,19 @@ class FirebaseMcpServer {
|
|
|
102
102
|
this.emulatorHubClient = new hubClient_js_1.EmulatorHubClient(projectId);
|
|
103
103
|
return this.emulatorHubClient;
|
|
104
104
|
}
|
|
105
|
+
async getEmulatorUrl(emulatorType) {
|
|
106
|
+
const hubClient = await this.getEmulatorHubClient();
|
|
107
|
+
if (!hubClient) {
|
|
108
|
+
throw Error("Emulator Hub not found or is not running. You can start the emulator by running `firebase emulators:start` in your firebase project directory.");
|
|
109
|
+
}
|
|
110
|
+
const emulators = await hubClient.getEmulators();
|
|
111
|
+
const emulatorInfo = emulators[emulatorType];
|
|
112
|
+
if (!emulatorInfo) {
|
|
113
|
+
throw Error("No Firestore Emulator found running. Make sure your project firebase.json file includes firestore and then rerun emulator using `firebase emulators:start` from your project directory.");
|
|
114
|
+
}
|
|
115
|
+
const host = emulatorInfo.host.includes(":") ? `[${emulatorInfo.host}]` : emulatorInfo.host;
|
|
116
|
+
return `http://${host}:${emulatorInfo.port}`;
|
|
117
|
+
}
|
|
105
118
|
get availableTools() {
|
|
106
119
|
var _a;
|
|
107
120
|
return (0, index_js_2.availableTools)(((_a = this.activeFeatures) === null || _a === void 0 ? void 0 : _a.length) ? this.activeFeatures : this.detectedFeatures);
|
|
@@ -125,7 +138,8 @@ class FirebaseMcpServer {
|
|
|
125
138
|
}
|
|
126
139
|
async getAuthenticatedUser() {
|
|
127
140
|
try {
|
|
128
|
-
|
|
141
|
+
const email = await (0, requireAuth_js_1.requireAuth)(await this.resolveOptions());
|
|
142
|
+
return email !== null && email !== void 0 ? email : "Application Default Credentials";
|
|
129
143
|
}
|
|
130
144
|
catch (e) {
|
|
131
145
|
return null;
|
|
@@ -17,6 +17,10 @@ exports.list_top_issues = (0, tool_js_1.tool)({
|
|
|
17
17
|
.number()
|
|
18
18
|
.optional()
|
|
19
19
|
.describe("Number of issues that needs to be fetched. Defaults to 10 if unspecified."),
|
|
20
|
+
issue_type: zod_1.z
|
|
21
|
+
.enum(["FATAL", "NON-FATAL", "ANR"])
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Types of issues that can be fetched comma-separated. Defaults to `FATAL` (Crashes). Other values include NON-FATAL (Non-fatal issues), ANR (Application not responding)."),
|
|
20
24
|
}),
|
|
21
25
|
annotations: {
|
|
22
26
|
title: "List Top Crashlytics Issues.",
|
|
@@ -26,9 +30,10 @@ exports.list_top_issues = (0, tool_js_1.tool)({
|
|
|
26
30
|
requiresAuth: true,
|
|
27
31
|
requiresProject: true,
|
|
28
32
|
},
|
|
29
|
-
}, async ({ app_id, issue_count }, { projectId }) => {
|
|
33
|
+
}, async ({ app_id, issue_type, issue_count }, { projectId }) => {
|
|
30
34
|
if (!app_id)
|
|
31
35
|
return (0, util_js_1.mcpError)(`Must specify 'app_id' parameter.`);
|
|
36
|
+
issue_type !== null && issue_type !== void 0 ? issue_type : (issue_type = "FATAL");
|
|
32
37
|
issue_count !== null && issue_count !== void 0 ? issue_count : (issue_count = 10);
|
|
33
|
-
return (0, util_js_1.toContent)(await (0, listTopIssues_js_1.listTopIssues)(projectId, app_id, issue_count));
|
|
38
|
+
return (0, util_js_1.toContent)(await (0, listTopIssues_js_1.listTopIssues)(projectId, app_id, issue_type, issue_count));
|
|
34
39
|
});
|
|
@@ -4,26 +4,10 @@ exports.getDataConnectEmulatorClient = void 0;
|
|
|
4
4
|
const types_js_1 = require("../../../emulator/types.js");
|
|
5
5
|
const apiv2_js_1 = require("../../../apiv2.js");
|
|
6
6
|
const dataplaneClient_js_1 = require("../../../dataconnect/dataplaneClient.js");
|
|
7
|
-
function
|
|
8
|
-
const
|
|
9
|
-
return {
|
|
10
|
-
host: emulatorInfo.host,
|
|
11
|
-
port: emulatorInfo.port,
|
|
12
|
-
url: `http://${host}:${emulatorInfo.port}`,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
async function getDataConnectEmulatorClient(hubClient) {
|
|
16
|
-
if (!hubClient) {
|
|
17
|
-
throw Error("Emulator Hub not found or is not running. Please ensure the emulator is started, you can start the Data Connect emualtor by running `firebase emulators:start --only dataconnect`.");
|
|
18
|
-
}
|
|
19
|
-
const emulators = await hubClient.getEmulators();
|
|
20
|
-
const dcEmulatorInfo = emulators[types_js_1.Emulators.DATACONNECT];
|
|
21
|
-
if (!dcEmulatorInfo) {
|
|
22
|
-
throw Error("No Data Connect Emulator found running, you can start the emualtor by running `firebase emulators:start --only dataconnect`.");
|
|
23
|
-
}
|
|
24
|
-
const emulatorDetails = formatEndpoint(dcEmulatorInfo);
|
|
7
|
+
async function getDataConnectEmulatorClient(host) {
|
|
8
|
+
const emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.DATACONNECT);
|
|
25
9
|
const apiClient = new apiv2_js_1.Client({
|
|
26
|
-
urlPrefix:
|
|
10
|
+
urlPrefix: emulatorUrl,
|
|
27
11
|
apiVersion: dataplaneClient_js_1.DATACONNECT_API_VERSION,
|
|
28
12
|
auth: false,
|
|
29
13
|
});
|
|
@@ -34,7 +34,7 @@ exports.execute_graphql = (0, tool_js_1.tool)({
|
|
|
34
34
|
const serviceInfo = await (0, fileUtils_js_1.pickService)(projectId, config, service_id || undefined);
|
|
35
35
|
let apiClient;
|
|
36
36
|
if (use_emulator) {
|
|
37
|
-
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(
|
|
37
|
+
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(host);
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
apiClient = dataplane.dataconnectDataplaneClient();
|
|
@@ -34,7 +34,7 @@ exports.execute_graphql_read = (0, tool_js_1.tool)({
|
|
|
34
34
|
const serviceInfo = await (0, fileUtils_js_1.pickService)(projectId, config, service_id || undefined);
|
|
35
35
|
let apiClient;
|
|
36
36
|
if (use_emulator) {
|
|
37
|
-
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(
|
|
37
|
+
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(host);
|
|
38
38
|
}
|
|
39
39
|
else {
|
|
40
40
|
apiClient = dataplane.dataconnectDataplaneClient();
|
|
@@ -49,7 +49,7 @@ exports.execute_mutation = (0, tool_js_1.tool)({
|
|
|
49
49
|
}
|
|
50
50
|
const connectorPath = `${serviceInfo.serviceName}/connectors/${connector_id}`;
|
|
51
51
|
if (use_emulator) {
|
|
52
|
-
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(
|
|
52
|
+
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(host);
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
apiClient = dataplane.dataconnectDataplaneClient();
|
|
@@ -49,7 +49,7 @@ exports.execute_query = (0, tool_js_1.tool)({
|
|
|
49
49
|
}
|
|
50
50
|
const connectorPath = `${serviceInfo.serviceName}/connectors/${connector_id}`;
|
|
51
51
|
if (use_emulator) {
|
|
52
|
-
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(
|
|
52
|
+
apiClient = await (0, emulator_js_1.getDataConnectEmulatorClient)(host);
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
apiClient = dataplane.dataconnectDataplaneClient();
|
|
@@ -6,7 +6,7 @@ const tool_js_1 = require("../../tool.js");
|
|
|
6
6
|
const util_js_1 = require("../../util.js");
|
|
7
7
|
const firestore_js_1 = require("../../../gcp/firestore.js");
|
|
8
8
|
const delete_js_1 = require("../../../firestore/delete.js");
|
|
9
|
-
const
|
|
9
|
+
const types_js_1 = require("../../../emulator/types.js");
|
|
10
10
|
exports.delete_document = (0, tool_js_1.tool)({
|
|
11
11
|
name: "delete_document",
|
|
12
12
|
description: "Deletes a Firestore documents from a database in the current project by full document paths. Use this if you know the exact path of a document.",
|
|
@@ -31,7 +31,7 @@ exports.delete_document = (0, tool_js_1.tool)({
|
|
|
31
31
|
}, async ({ path, database, use_emulator }, { projectId, host }) => {
|
|
32
32
|
let emulatorUrl;
|
|
33
33
|
if (use_emulator) {
|
|
34
|
-
emulatorUrl = await
|
|
34
|
+
emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.FIRESTORE);
|
|
35
35
|
}
|
|
36
36
|
const { documents, missing } = await (0, firestore_js_1.getDocuments)(projectId, [path], database, emulatorUrl);
|
|
37
37
|
if (missing.length > 0 && documents && documents.length === 0) {
|
|
@@ -6,7 +6,7 @@ const tool_js_1 = require("../../tool.js");
|
|
|
6
6
|
const util_js_1 = require("../../util.js");
|
|
7
7
|
const firestore_js_1 = require("../../../gcp/firestore.js");
|
|
8
8
|
const converter_js_1 = require("./converter.js");
|
|
9
|
-
const
|
|
9
|
+
const types_js_1 = require("../../../emulator/types.js");
|
|
10
10
|
exports.get_documents = (0, tool_js_1.tool)({
|
|
11
11
|
name: "get_documents",
|
|
12
12
|
description: "Retrieves one or more Firestore documents from a database in the current project by full document paths. Use this if you know the exact path of a document.",
|
|
@@ -33,7 +33,7 @@ exports.get_documents = (0, tool_js_1.tool)({
|
|
|
33
33
|
return (0, util_js_1.mcpError)("Must supply at least one document path.");
|
|
34
34
|
let emulatorUrl;
|
|
35
35
|
if (use_emulator) {
|
|
36
|
-
emulatorUrl = await
|
|
36
|
+
emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.FIRESTORE);
|
|
37
37
|
}
|
|
38
38
|
const { documents, missing } = await (0, firestore_js_1.getDocuments)(projectId, paths, database, emulatorUrl);
|
|
39
39
|
if (missing.length > 0 && documents && documents.length === 0) {
|
|
@@ -6,7 +6,7 @@ const tool_js_1 = require("../../tool.js");
|
|
|
6
6
|
const util_js_1 = require("../../util.js");
|
|
7
7
|
const firestore_js_1 = require("../../../gcp/firestore.js");
|
|
8
8
|
const errors_js_1 = require("../../errors.js");
|
|
9
|
-
const
|
|
9
|
+
const types_js_1 = require("../../../emulator/types.js");
|
|
10
10
|
exports.list_collections = (0, tool_js_1.tool)({
|
|
11
11
|
name: "list_collections",
|
|
12
12
|
description: "Retrieves a list of collections from a Firestore database in the current project.",
|
|
@@ -28,7 +28,7 @@ exports.list_collections = (0, tool_js_1.tool)({
|
|
|
28
28
|
}, async ({ database, use_emulator }, { projectId, host }) => {
|
|
29
29
|
let emulatorUrl;
|
|
30
30
|
if (use_emulator) {
|
|
31
|
-
emulatorUrl = await
|
|
31
|
+
emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.FIRESTORE);
|
|
32
32
|
}
|
|
33
33
|
if (!projectId)
|
|
34
34
|
return errors_js_1.NO_PROJECT_ERROR;
|
|
@@ -6,7 +6,7 @@ const tool_js_1 = require("../../tool.js");
|
|
|
6
6
|
const util_js_1 = require("../../util.js");
|
|
7
7
|
const firestore_js_1 = require("../../../gcp/firestore.js");
|
|
8
8
|
const converter_js_1 = require("./converter.js");
|
|
9
|
-
const
|
|
9
|
+
const types_js_1 = require("../../../emulator/types.js");
|
|
10
10
|
exports.query_collection = (0, tool_js_1.tool)({
|
|
11
11
|
name: "query_collection",
|
|
12
12
|
description: "Retrieves one or more Firestore documents from a collection is a database in the current project by a collection with a full document path. Use this if you know the exact path of a collection and the filtering clause you would like for the document.",
|
|
@@ -123,7 +123,7 @@ exports.query_collection = (0, tool_js_1.tool)({
|
|
|
123
123
|
structuredQuery.limit = limit ? limit : 10;
|
|
124
124
|
let emulatorUrl;
|
|
125
125
|
if (use_emulator) {
|
|
126
|
-
emulatorUrl = await
|
|
126
|
+
emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.FIRESTORE);
|
|
127
127
|
}
|
|
128
128
|
const { documents } = await (0, firestore_js_1.queryCollection)(projectId, structuredQuery, database, emulatorUrl);
|
|
129
129
|
const docs = documents.map(converter_js_1.firestoreDocumentToJson);
|
|
@@ -5,6 +5,7 @@ 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 storage_js_1 = require("../../../gcp/storage.js");
|
|
8
|
+
const types_js_1 = require("../../../emulator/types.js");
|
|
8
9
|
exports.get_object_download_url = (0, tool_js_1.tool)({
|
|
9
10
|
name: "get_object_download_url",
|
|
10
11
|
description: "Retrieves the download URL for an object in Firebase Storage.",
|
|
@@ -16,6 +17,7 @@ exports.get_object_download_url = (0, tool_js_1.tool)({
|
|
|
16
17
|
object_path: zod_1.z
|
|
17
18
|
.string()
|
|
18
19
|
.describe("The path to the object in Firebase storage without the bucket name attached"),
|
|
20
|
+
use_emulator: zod_1.z.boolean().default(false).describe("Target the Storage emulator if true."),
|
|
19
21
|
}),
|
|
20
22
|
annotations: {
|
|
21
23
|
title: "Get Storage Object Download URL",
|
|
@@ -25,10 +27,14 @@ exports.get_object_download_url = (0, tool_js_1.tool)({
|
|
|
25
27
|
requiresProject: true,
|
|
26
28
|
requiresAuth: true,
|
|
27
29
|
},
|
|
28
|
-
}, async ({ bucket, object_path }, { projectId }) => {
|
|
30
|
+
}, async ({ bucket, object_path, use_emulator }, { projectId, host }) => {
|
|
29
31
|
if (!bucket) {
|
|
30
32
|
bucket = `${projectId}.firebasestorage.app`;
|
|
31
33
|
}
|
|
32
|
-
|
|
34
|
+
let emulatorUrl;
|
|
35
|
+
if (use_emulator) {
|
|
36
|
+
emulatorUrl = await host.getEmulatorUrl(types_js_1.Emulators.STORAGE);
|
|
37
|
+
}
|
|
38
|
+
const downloadUrl = await (0, storage_js_1.getDownloadUrl)(bucket, object_path, emulatorUrl);
|
|
33
39
|
return (0, util_js_1.toContent)(downloadUrl);
|
|
34
40
|
});
|
package/lib/operation-poller.js
CHANGED
|
@@ -34,7 +34,9 @@ class OperationPoller {
|
|
|
34
34
|
return async () => {
|
|
35
35
|
let res;
|
|
36
36
|
try {
|
|
37
|
-
res = await apiClient.get(options.operationResourceName
|
|
37
|
+
res = await apiClient.get(options.operationResourceName, {
|
|
38
|
+
headers: options.headers,
|
|
39
|
+
});
|
|
38
40
|
}
|
|
39
41
|
catch (err) {
|
|
40
42
|
if (err.status === 500 || err.status === 503) {
|
package/lib/track.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var _a;
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.cliSession = exports.vscodeSession = exports.emulatorSession = exports.trackVSCode = exports.trackEmulator = exports.trackGA4 = exports.usageEnabled = exports.GA4_PROPERTIES = void 0;
|
|
4
5
|
const node_fetch_1 = require("node-fetch");
|
|
@@ -41,6 +42,9 @@ const GA4_USER_PROPS = {
|
|
|
41
42
|
firepit_version: {
|
|
42
43
|
value: process.env.FIREPIT_VERSION || "none",
|
|
43
44
|
},
|
|
45
|
+
is_firebase_studio: {
|
|
46
|
+
value: (_a = process.env.MONOSPACE_ENV) !== null && _a !== void 0 ? _a : "false",
|
|
47
|
+
},
|
|
44
48
|
};
|
|
45
49
|
async function trackGA4(eventName, params, duration = 1) {
|
|
46
50
|
const session = cliSession();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firebase-tools",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.8.0",
|
|
4
4
|
"description": "Command-Line Interface for Firebase",
|
|
5
5
|
"main": "./lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
]
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@electric-sql/pglite": "^0.
|
|
63
|
+
"@electric-sql/pglite": "^0.3.3",
|
|
64
|
+
"@electric-sql/pglite-tools": "^0.2.8",
|
|
64
65
|
"@google-cloud/cloud-sql-connector": "^1.3.3",
|
|
65
66
|
"@google-cloud/pubsub": "^4.5.0",
|
|
66
67
|
"@inquirer/prompts": "^7.4.0",
|
|
@@ -110,6 +111,7 @@
|
|
|
110
111
|
"p-limit": "^3.0.1",
|
|
111
112
|
"pg": "^8.11.3",
|
|
112
113
|
"pg-gateway": "^0.3.0-beta.4",
|
|
114
|
+
"pglite-2": "npm:@electric-sql/pglite@0.2.17",
|
|
113
115
|
"portfinder": "^1.0.32",
|
|
114
116
|
"progress": "^2.0.3",
|
|
115
117
|
"proxy-agent": "^6.3.0",
|
|
@@ -7,9 +7,22 @@
|
|
|
7
7
|
* See a full list of supported triggers at https://firebase.google.com/docs/functions
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
const {
|
|
10
|
+
const {setGlobalOptions} = require("firebase-functions");
|
|
11
|
+
const {onRequest} = require("firebase-functions/https");
|
|
11
12
|
const logger = require("firebase-functions/logger");
|
|
12
13
|
|
|
14
|
+
// For cost control, you can set the maximum number of containers that can be
|
|
15
|
+
// running at the same time. This helps mitigate the impact of unexpected
|
|
16
|
+
// traffic spikes by instead downgrading performance. This limit is a
|
|
17
|
+
// per-function limit. You can override the limit for each function using the
|
|
18
|
+
// `maxInstances` option in the function's options, e.g.
|
|
19
|
+
// `onRequest({ maxInstances: 5 }, (req, res) => { ... })`.
|
|
20
|
+
// NOTE: setGlobalOptions does not apply to functions using the v1 API. V1
|
|
21
|
+
// functions should each use functions.runWith({ maxInstances: 10 }) instead.
|
|
22
|
+
// In the v1 API, each function can only serve one request per container, so
|
|
23
|
+
// this will be the maximum concurrent request count.
|
|
24
|
+
setGlobalOptions({ maxInstances: 10 });
|
|
25
|
+
|
|
13
26
|
// Create and deploy your first functions
|
|
14
27
|
// https://firebase.google.com/docs/functions/get-started
|
|
15
28
|
|
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
# Deploy with `firebase deploy`
|
|
4
4
|
|
|
5
5
|
from firebase_functions import https_fn
|
|
6
|
+
from firebase_functions.options import set_global_options
|
|
6
7
|
from firebase_admin import initialize_app
|
|
7
8
|
|
|
9
|
+
# For cost control, you can set the maximum number of containers that can be
|
|
10
|
+
# running at the same time. This helps mitigate the impact of unexpected
|
|
11
|
+
# traffic spikes by instead downgrading performance. This limit is a per-function
|
|
12
|
+
# limit. You can override the limit for each function using the max_instances
|
|
13
|
+
# parameter in the decorator, e.g. @https_fn.on_request(max_instances=5).
|
|
14
|
+
set_global_options(max_instances=10)
|
|
15
|
+
|
|
8
16
|
# initialize_app()
|
|
9
17
|
#
|
|
10
18
|
#
|
|
@@ -7,12 +7,25 @@
|
|
|
7
7
|
* See a full list of supported triggers at https://firebase.google.com/docs/functions
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {setGlobalOptions} from "firebase-functions";
|
|
11
|
+
import {onRequest} from "firebase-functions/https";
|
|
11
12
|
import * as logger from "firebase-functions/logger";
|
|
12
13
|
|
|
13
14
|
// Start writing functions
|
|
14
15
|
// https://firebase.google.com/docs/functions/typescript
|
|
15
16
|
|
|
17
|
+
// For cost control, you can set the maximum number of containers that can be
|
|
18
|
+
// running at the same time. This helps mitigate the impact of unexpected
|
|
19
|
+
// traffic spikes by instead downgrading performance. This limit is a
|
|
20
|
+
// per-function limit. You can override the limit for each function using the
|
|
21
|
+
// `maxInstances` option in the function's options, e.g.
|
|
22
|
+
// `onRequest({ maxInstances: 5 }, (req, res) => { ... })`.
|
|
23
|
+
// NOTE: setGlobalOptions does not apply to functions using the v1 API. V1
|
|
24
|
+
// functions should each use functions.runWith({ maxInstances: 10 }) instead.
|
|
25
|
+
// In the v1 API, each function can only serve one request per container, so
|
|
26
|
+
// this will be the maximum concurrent request count.
|
|
27
|
+
setGlobalOptions({ maxInstances: 10 });
|
|
28
|
+
|
|
16
29
|
// export const helloWorld = onRequest((request, response) => {
|
|
17
30
|
// logger.info("Hello logs!", {structuredData: true});
|
|
18
31
|
// response.send("Hello from Firebase!");
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getFirestoreEmulatorUrl = void 0;
|
|
4
|
-
const types_js_1 = require("../../../emulator/types.js");
|
|
5
|
-
async function getFirestoreEmulatorUrl(hubClient) {
|
|
6
|
-
if (!hubClient) {
|
|
7
|
-
throw Error("Emulator Hub not found or is not running. You can start the emulator by running `firebase emulators:start` in your firebase project directory.");
|
|
8
|
-
}
|
|
9
|
-
const emulators = await hubClient.getEmulators();
|
|
10
|
-
const firestoreEmulatorInfo = emulators[types_js_1.Emulators.FIRESTORE];
|
|
11
|
-
if (!firestoreEmulatorInfo) {
|
|
12
|
-
throw Error("No Firestore Emulator found running. Make sure your project firebase.json file includes firestore and then rerun emulator using `firebase emulators:start` from your project directory.");
|
|
13
|
-
}
|
|
14
|
-
return `http://${firestoreEmulatorInfo.host}:${firestoreEmulatorInfo.port}`;
|
|
15
|
-
}
|
|
16
|
-
exports.getFirestoreEmulatorUrl = getFirestoreEmulatorUrl;
|