firebase-tools 15.3.1 → 15.5.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/apiv2.js +13 -3
- package/lib/appdistribution/client.js +2 -1
- package/lib/apphosting/rollout.js +1 -1
- package/lib/apptesting/parseTestFiles.js +91 -43
- package/lib/bin/cli.js +2 -2
- package/lib/command.js +3 -0
- package/lib/commands/apptesting.js +75 -0
- package/lib/commands/dataconnect-compile.js +46 -0
- package/lib/commands/dataconnect-execute.js +14 -12
- package/lib/commands/firestore-databases-create.js +69 -12
- package/lib/commands/firestore-databases-update.js +6 -14
- package/lib/commands/index.js +2 -0
- package/lib/dataconnect/client.js +2 -1
- package/lib/dataconnect/types.js +8 -1
- package/lib/deploy/functions/prepare.js +1 -0
- package/lib/deploy/functions/validate.js +25 -0
- package/lib/emulator/auth/operations.js +2 -2
- package/lib/emulator/dataconnect/pgliteServer.js +26 -33
- package/lib/emulator/dataconnectEmulator.js +0 -1
- package/lib/emulator/downloadableEmulatorInfo.json +24 -24
- package/lib/ensureApiEnabled.js +2 -2
- package/lib/env.js +18 -0
- package/lib/firestore/api-types.js +26 -11
- package/lib/firestore/api.js +3 -0
- package/lib/gcp/iam.js +1 -1
- package/lib/gcp/rules.js +5 -2
- package/lib/gcp/serviceusage.js +2 -2
- package/lib/init/features/firestore/indexes.js +1 -1
- package/lib/init/features/{storage.js → storage/index.js} +12 -4
- package/lib/init/features/storage/rules.js +20 -0
- package/lib/mcp/tools/core/get_security_rules.js +1 -1
- package/lib/mcp/tools/crashlytics/reports.js +1 -1
- package/lib/mcp/tools/firestore/delete_document.js +1 -1
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/track.js +1 -16
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/templates/init/apptesting/smoke_test.yaml +1 -1
package/lib/dataconnect/types.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.isGraphQLResponseError = exports.isGraphQLResponse = exports.MAIN_SCHEMA
|
|
|
4
4
|
exports.requiresVector = requiresVector;
|
|
5
5
|
exports.toDatasource = toDatasource;
|
|
6
6
|
exports.mainSchemaYaml = mainSchemaYaml;
|
|
7
|
+
exports.secondarySchemaYamls = secondarySchemaYamls;
|
|
7
8
|
exports.mainSchema = mainSchema;
|
|
8
9
|
exports.isMainSchema = isMainSchema;
|
|
9
10
|
exports.MAIN_SCHEMA_ID = "main";
|
|
@@ -42,6 +43,12 @@ function mainSchemaYaml(dataconnectYaml) {
|
|
|
42
43
|
}
|
|
43
44
|
return mainSch;
|
|
44
45
|
}
|
|
46
|
+
function secondarySchemaYamls(dataconnectYaml) {
|
|
47
|
+
if (dataconnectYaml.schema) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return (dataconnectYaml.schemas || []).filter((s) => s.id && s.id !== exports.MAIN_SCHEMA_ID);
|
|
51
|
+
}
|
|
45
52
|
function mainSchema(schemas) {
|
|
46
53
|
const mainSch = schemas.find((s) => isMainSchema(s));
|
|
47
54
|
if (!mainSch) {
|
|
@@ -54,5 +61,5 @@ function isMainSchema(schema) {
|
|
|
54
61
|
}
|
|
55
62
|
const isGraphQLResponse = (g) => !!g.data || !!g.errors;
|
|
56
63
|
exports.isGraphQLResponse = isGraphQLResponse;
|
|
57
|
-
const isGraphQLResponseError = (g) => !!g.error;
|
|
64
|
+
const isGraphQLResponseError = (g) => !!g.error?.message || !!g.message;
|
|
58
65
|
exports.isGraphQLResponseError = isGraphQLResponseError;
|
|
@@ -205,6 +205,7 @@ async function prepare(context, options, payload) {
|
|
|
205
205
|
await (0, checkIam_1.ensureGenkitMonitoringRoles)(projectId, projectNumber, matchingBackend, haveBackend, options.dryRun);
|
|
206
206
|
await ensure.secretAccess(projectId, matchingBackend, haveBackend, options.dryRun);
|
|
207
207
|
updateEndpointTargetedStatus(wantBackends, context.filters || []);
|
|
208
|
+
validate.checkFiltersIntegrity(wantBackends, context.filters);
|
|
208
209
|
(0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
|
|
209
210
|
}
|
|
210
211
|
function inferDetailsFromExisting(want, have, usedDotenv) {
|
|
@@ -8,6 +8,7 @@ exports.endpointsAreUnique = endpointsAreUnique;
|
|
|
8
8
|
exports.functionsDirectoryExists = functionsDirectoryExists;
|
|
9
9
|
exports.functionIdsAreValid = functionIdsAreValid;
|
|
10
10
|
exports.secretsAreValid = secretsAreValid;
|
|
11
|
+
exports.checkFiltersIntegrity = checkFiltersIntegrity;
|
|
11
12
|
const path = require("path");
|
|
12
13
|
const clc = require("colorette");
|
|
13
14
|
const error_1 = require("../../error");
|
|
@@ -257,3 +258,27 @@ async function validateSecretVersions(projectId, endpoints) {
|
|
|
257
258
|
}
|
|
258
259
|
}
|
|
259
260
|
}
|
|
261
|
+
function checkFiltersIntegrity(wantBackends, filters) {
|
|
262
|
+
if (!filters) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
for (const filter of filters) {
|
|
266
|
+
let matched = false;
|
|
267
|
+
for (const b of Object.values(wantBackends)) {
|
|
268
|
+
if (backend.someEndpoint(b, (e) => (0, functionsDeployHelper_1.endpointMatchesFilter)(e, filter))) {
|
|
269
|
+
matched = true;
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!matched) {
|
|
274
|
+
const parts = [];
|
|
275
|
+
if (filter.codebase) {
|
|
276
|
+
parts.push(filter.codebase);
|
|
277
|
+
}
|
|
278
|
+
if (filter.idChunks) {
|
|
279
|
+
parts.push(filter.idChunks.join("-"));
|
|
280
|
+
}
|
|
281
|
+
throw new error_1.FirebaseError(`No function matches the filter: ${parts.join(":")}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -2243,8 +2243,8 @@ function generateBlockingFunctionJwt(state, event, url, timeoutMs, user, options
|
|
|
2243
2243
|
}
|
|
2244
2244
|
if (user.lastLoginAt || user.createdAt) {
|
|
2245
2245
|
jwt.user_record.metadata = {
|
|
2246
|
-
last_sign_in_time: user.lastLoginAt,
|
|
2247
|
-
creation_time: user.createdAt,
|
|
2246
|
+
last_sign_in_time: user.lastLoginAt ? parseInt(user.lastLoginAt) : undefined,
|
|
2247
|
+
creation_time: user.createdAt ? parseInt(user.createdAt) : undefined,
|
|
2248
2248
|
};
|
|
2249
2249
|
}
|
|
2250
2250
|
if (state.shouldForwardCredentialToBlockingFunction("accessToken")) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.PGliteLoggerPatch = exports.PostgresServer = exports.TRUNCATE_TABLES_SQL = void 0;
|
|
4
4
|
exports.fromNodeSocket = fromNodeSocket;
|
|
5
5
|
const pglite_1 = require("@electric-sql/pglite");
|
|
6
6
|
const { dynamicImport } = require(true && "../../dynamicImport");
|
|
@@ -42,12 +42,12 @@ class PostgresServer {
|
|
|
42
42
|
await db.query("DEALLOCATE ALL");
|
|
43
43
|
}
|
|
44
44
|
const response = await db.execProtocolRaw(data);
|
|
45
|
-
for await (const message of extendedQueryPatch.
|
|
45
|
+
for await (const message of extendedQueryPatch.getMessages(data, response)) {
|
|
46
46
|
yield message;
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
});
|
|
50
|
-
const extendedQueryPatch = new
|
|
50
|
+
const extendedQueryPatch = new PGliteLoggerPatch(connection);
|
|
51
51
|
socket.on("end", () => {
|
|
52
52
|
logger_1.logger.debug("Postgres client disconnected");
|
|
53
53
|
});
|
|
@@ -202,42 +202,35 @@ async function fromNodeSocket(socket, options) {
|
|
|
202
202
|
: undefined;
|
|
203
203
|
return new pg_gateway_1.PostgresConnection({ readable: rs, writable: ws }, opts);
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
const CODE_TO_FRONTEND_MESSAGE = {};
|
|
206
|
+
for (const key in pg_gateway_1.FrontendMessageCode) {
|
|
207
|
+
if (Object.prototype.hasOwnProperty.call(pg_gateway_1.FrontendMessageCode, key)) {
|
|
208
|
+
CODE_TO_FRONTEND_MESSAGE[pg_gateway_1.FrontendMessageCode[key]] = key;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const CODE_TO_BACKEND_MESSAGE = {};
|
|
212
|
+
for (const key in pg_gateway_1.BackendMessageCode) {
|
|
213
|
+
if (Object.prototype.hasOwnProperty.call(pg_gateway_1.BackendMessageCode, key)) {
|
|
214
|
+
CODE_TO_BACKEND_MESSAGE[pg_gateway_1.BackendMessageCode[key]] = key;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function codeToFrontendMessageName(code) {
|
|
218
|
+
return CODE_TO_FRONTEND_MESSAGE[code] || `UNKNOWN_FRONTEND_CODE_${code}`;
|
|
219
|
+
}
|
|
220
|
+
function codeTogBackendMessageName(code) {
|
|
221
|
+
return CODE_TO_BACKEND_MESSAGE[code] || `UNKNOWN_BACKEND_CODE_${code}`;
|
|
222
|
+
}
|
|
223
|
+
class PGliteLoggerPatch {
|
|
206
224
|
constructor(connection) {
|
|
207
225
|
this.connection = connection;
|
|
208
|
-
this.isExtendedQuery = false;
|
|
209
|
-
this.eqpErrored = false;
|
|
210
226
|
this.pgliteDebugLog = fs.createWriteStream("pglite-debug.log");
|
|
211
227
|
}
|
|
212
|
-
async *
|
|
213
|
-
|
|
214
|
-
pg_gateway_1.FrontendMessageCode.Parse,
|
|
215
|
-
pg_gateway_1.FrontendMessageCode.Bind,
|
|
216
|
-
pg_gateway_1.FrontendMessageCode.Close,
|
|
217
|
-
];
|
|
218
|
-
const decoded = decoder.write(message);
|
|
219
|
-
this.pgliteDebugLog.write("Front: " + decoded);
|
|
220
|
-
if (pipelineStartMessages.includes(message[0])) {
|
|
221
|
-
this.isExtendedQuery = true;
|
|
222
|
-
}
|
|
223
|
-
if (message[0] === pg_gateway_1.FrontendMessageCode.Sync) {
|
|
224
|
-
this.isExtendedQuery = false;
|
|
225
|
-
this.eqpErrored = false;
|
|
226
|
-
}
|
|
228
|
+
async *getMessages(request, response) {
|
|
229
|
+
this.pgliteDebugLog.write(`\n[-> ${codeToFrontendMessageName(request[0])}] ` + decoder.write(request));
|
|
227
230
|
for await (const bm of (0, pg_gateway_1.getMessages)(response)) {
|
|
228
|
-
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
if (this.isExtendedQuery && bm[0] === pg_gateway_1.BackendMessageCode.ErrorMessage) {
|
|
232
|
-
this.eqpErrored = true;
|
|
233
|
-
}
|
|
234
|
-
if (this.isExtendedQuery && bm[0] === pg_gateway_1.BackendMessageCode.ReadyForQuery) {
|
|
235
|
-
this.pgliteDebugLog.write("Filtered: " + decoder.write(bm));
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
this.pgliteDebugLog.write("Sent: " + decoder.write(bm));
|
|
231
|
+
this.pgliteDebugLog.write(`\n[<- ${codeTogBackendMessageName(bm[0])}] ${decoder.write(bm)}`);
|
|
239
232
|
yield bm;
|
|
240
233
|
}
|
|
241
234
|
}
|
|
242
235
|
}
|
|
243
|
-
exports.
|
|
236
|
+
exports.PGliteLoggerPatch = PGliteLoggerPatch;
|
|
@@ -231,7 +231,6 @@ class DataConnectEmulator {
|
|
|
231
231
|
connectionString: connectionString.toString(),
|
|
232
232
|
database,
|
|
233
233
|
serviceId,
|
|
234
|
-
maxOpenConnections: 1,
|
|
235
234
|
});
|
|
236
235
|
this.logger.logLabeled("DEBUG", "Data Connect", `Successfully connected to ${connectionString}}`);
|
|
237
236
|
return true;
|
|
@@ -54,36 +54,36 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "3.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.
|
|
57
|
+
"version": "3.1.1",
|
|
58
|
+
"expectedSize": 30450528,
|
|
59
|
+
"expectedChecksum": "06e49453e3c9f876d445972a3588965f",
|
|
60
|
+
"expectedChecksumSHA256": "07dbbb5d1356597acc478d3c58dbf48e716b3795f4da0e7e174b0fd472b004d2",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.1.1",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
|
|
63
63
|
},
|
|
64
64
|
"darwin_arm64": {
|
|
65
|
-
"version": "3.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.
|
|
65
|
+
"version": "3.1.1",
|
|
66
|
+
"expectedSize": 29916818,
|
|
67
|
+
"expectedChecksum": "477dd3da175dba6d7545ade62a8274f6",
|
|
68
|
+
"expectedChecksumSHA256": "472fe448f25fa8546aa383406db78eaf4eec4d8b700bb325dbc65899aba1bd20",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.1.1",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
|
|
71
71
|
},
|
|
72
72
|
"win32": {
|
|
73
|
-
"version": "3.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.
|
|
73
|
+
"version": "3.1.1",
|
|
74
|
+
"expectedSize": 30951424,
|
|
75
|
+
"expectedChecksum": "c51ec99bffd2f37fa2bb793f7cc74ea7",
|
|
76
|
+
"expectedChecksumSHA256": "e03d0dae5f040b03d211d6741540ed11206b03548b230eb8e36338b5b57545ee",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.1.1",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1.exe"
|
|
79
79
|
},
|
|
80
80
|
"linux": {
|
|
81
|
-
"version": "3.
|
|
82
|
-
"expectedSize":
|
|
83
|
-
"expectedChecksum": "
|
|
84
|
-
"expectedChecksumSHA256": "
|
|
85
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.
|
|
86
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.
|
|
81
|
+
"version": "3.1.1",
|
|
82
|
+
"expectedSize": 30372024,
|
|
83
|
+
"expectedChecksum": "8a3977f07facaacaa9be3f9ec6bd90b7",
|
|
84
|
+
"expectedChecksumSHA256": "5c36d16d4ded51dd887c939a82e8cdc7e84cb40a2c5d00cb02bff90ca65c2472",
|
|
85
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.1.1",
|
|
86
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.1.1"
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
package/lib/ensureApiEnabled.js
CHANGED
|
@@ -27,7 +27,7 @@ async function check(projectId, apiUri, prefix, silent = false) {
|
|
|
27
27
|
return true;
|
|
28
28
|
}
|
|
29
29
|
const res = await apiClient.get(`/projects/${projectId}/services/${apiName}`, {
|
|
30
|
-
headers: { "x-goog-
|
|
30
|
+
headers: { "x-goog-user-project": `projects/${projectId}` },
|
|
31
31
|
skipLog: { resBody: true },
|
|
32
32
|
});
|
|
33
33
|
const isEnabled = res.body.state === "ENABLED";
|
|
@@ -45,7 +45,7 @@ function isPermissionError(e) {
|
|
|
45
45
|
async function enable(projectId, apiName) {
|
|
46
46
|
try {
|
|
47
47
|
await apiClient.post(`/projects/${projectId}/services/${apiName}:enable`, undefined, {
|
|
48
|
-
headers: { "x-goog-
|
|
48
|
+
headers: { "x-goog-user-project": `projects/${projectId}` },
|
|
49
49
|
skipLog: { resBody: true },
|
|
50
50
|
});
|
|
51
51
|
cacheEnabledAPI(projectId, apiName);
|
package/lib/env.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isFirebaseStudio = isFirebaseStudio;
|
|
4
4
|
exports.isFirebaseMcp = isFirebaseMcp;
|
|
5
|
+
exports.detectAIAgent = detectAIAgent;
|
|
5
6
|
const fsutils_1 = require("./fsutils");
|
|
6
7
|
let googleIdxFolderExists;
|
|
7
8
|
function isFirebaseStudio() {
|
|
@@ -15,3 +16,20 @@ function isFirebaseStudio() {
|
|
|
15
16
|
function isFirebaseMcp() {
|
|
16
17
|
return !!process.env.IS_FIREBASE_MCP;
|
|
17
18
|
}
|
|
19
|
+
function detectAIAgent() {
|
|
20
|
+
if (process.env.ANTIGRAVITY_CLI_ALIAS)
|
|
21
|
+
return "antigravity";
|
|
22
|
+
if (process.env.CLAUDECODE)
|
|
23
|
+
return "claude_code";
|
|
24
|
+
if (process.env.CLINE_ACTIVE)
|
|
25
|
+
return "cline";
|
|
26
|
+
if (process.env.CODEX_SANDBOX)
|
|
27
|
+
return "codex_cli";
|
|
28
|
+
if (process.env.CURSOR_AGENT)
|
|
29
|
+
return "cursor";
|
|
30
|
+
if (process.env.GEMINI_CLI)
|
|
31
|
+
return "gemini_cli";
|
|
32
|
+
if (process.env.OPENCODE)
|
|
33
|
+
return "open_code";
|
|
34
|
+
return "unknown";
|
|
35
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RecurrenceType = exports.DatabaseEdition = exports.
|
|
3
|
+
exports.RecurrenceType = exports.DatabaseEdition = exports.DataAccessMode = exports.RealtimeUpdatesMode = exports.PointInTimeRecoveryEnablement = exports.DatabaseDeleteProtectionState = exports.EnablementOption = exports.DatabaseType = exports.StateTtl = exports.State = exports.ArrayConfig = exports.Order = exports.Density = exports.ApiScope = exports.QueryScope = exports.Mode = void 0;
|
|
4
|
+
exports.validateEnablementOption = validateEnablementOption;
|
|
5
|
+
const error_1 = require("../error");
|
|
4
6
|
var Mode;
|
|
5
7
|
(function (Mode) {
|
|
6
8
|
Mode["ASCENDING"] = "ASCENDING";
|
|
@@ -51,26 +53,32 @@ var DatabaseType;
|
|
|
51
53
|
DatabaseType["DATASTORE_MODE"] = "DATASTORE_MODE";
|
|
52
54
|
DatabaseType["FIRESTORE_NATIVE"] = "FIRESTORE_NATIVE";
|
|
53
55
|
})(DatabaseType || (exports.DatabaseType = DatabaseType = {}));
|
|
54
|
-
var
|
|
55
|
-
(function (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
})(
|
|
56
|
+
var EnablementOption;
|
|
57
|
+
(function (EnablementOption) {
|
|
58
|
+
EnablementOption["ENABLED"] = "ENABLED";
|
|
59
|
+
EnablementOption["DISABLED"] = "DISABLED";
|
|
60
|
+
})(EnablementOption || (exports.EnablementOption = EnablementOption = {}));
|
|
59
61
|
var DatabaseDeleteProtectionState;
|
|
60
62
|
(function (DatabaseDeleteProtectionState) {
|
|
61
63
|
DatabaseDeleteProtectionState["ENABLED"] = "DELETE_PROTECTION_ENABLED";
|
|
62
64
|
DatabaseDeleteProtectionState["DISABLED"] = "DELETE_PROTECTION_DISABLED";
|
|
63
65
|
})(DatabaseDeleteProtectionState || (exports.DatabaseDeleteProtectionState = DatabaseDeleteProtectionState = {}));
|
|
64
|
-
var PointInTimeRecoveryEnablementOption;
|
|
65
|
-
(function (PointInTimeRecoveryEnablementOption) {
|
|
66
|
-
PointInTimeRecoveryEnablementOption["ENABLED"] = "ENABLED";
|
|
67
|
-
PointInTimeRecoveryEnablementOption["DISABLED"] = "DISABLED";
|
|
68
|
-
})(PointInTimeRecoveryEnablementOption || (exports.PointInTimeRecoveryEnablementOption = PointInTimeRecoveryEnablementOption = {}));
|
|
69
66
|
var PointInTimeRecoveryEnablement;
|
|
70
67
|
(function (PointInTimeRecoveryEnablement) {
|
|
71
68
|
PointInTimeRecoveryEnablement["ENABLED"] = "POINT_IN_TIME_RECOVERY_ENABLED";
|
|
72
69
|
PointInTimeRecoveryEnablement["DISABLED"] = "POINT_IN_TIME_RECOVERY_DISABLED";
|
|
73
70
|
})(PointInTimeRecoveryEnablement || (exports.PointInTimeRecoveryEnablement = PointInTimeRecoveryEnablement = {}));
|
|
71
|
+
var RealtimeUpdatesMode;
|
|
72
|
+
(function (RealtimeUpdatesMode) {
|
|
73
|
+
RealtimeUpdatesMode["ENABLED"] = "REALTIME_UPDATES_MODE_ENABLED";
|
|
74
|
+
RealtimeUpdatesMode["DISABLED"] = "REALTIME_UPDATES_MODE_DISABLED";
|
|
75
|
+
})(RealtimeUpdatesMode || (exports.RealtimeUpdatesMode = RealtimeUpdatesMode = {}));
|
|
76
|
+
var DataAccessMode;
|
|
77
|
+
(function (DataAccessMode) {
|
|
78
|
+
DataAccessMode["UNSPECIFIED"] = "DATA_ACCESS_MODE_UNSPECIFIED";
|
|
79
|
+
DataAccessMode["ENABLED"] = "DATA_ACCESS_MODE_ENABLED";
|
|
80
|
+
DataAccessMode["DISABLED"] = "DATA_ACCESS_MODE_DISABLED";
|
|
81
|
+
})(DataAccessMode || (exports.DataAccessMode = DataAccessMode = {}));
|
|
74
82
|
var DatabaseEdition;
|
|
75
83
|
(function (DatabaseEdition) {
|
|
76
84
|
DatabaseEdition["DATABASE_EDITION_UNSPECIFIED"] = "DATABASE_EDITION_UNSPECIFIED";
|
|
@@ -82,3 +90,10 @@ var RecurrenceType;
|
|
|
82
90
|
RecurrenceType["DAILY"] = "DAILY";
|
|
83
91
|
RecurrenceType["WEEKLY"] = "WEEKLY";
|
|
84
92
|
})(RecurrenceType || (exports.RecurrenceType = RecurrenceType = {}));
|
|
93
|
+
function validateEnablementOption(optionValue, flagName, helpCommandText) {
|
|
94
|
+
if (optionValue &&
|
|
95
|
+
optionValue !== EnablementOption.ENABLED &&
|
|
96
|
+
optionValue !== EnablementOption.DISABLED) {
|
|
97
|
+
throw new error_1.FirebaseError(`Invalid value for flag --${flagName}. ${helpCommandText}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
package/lib/firestore/api.js
CHANGED
|
@@ -536,6 +536,9 @@ class FirestoreApi {
|
|
|
536
536
|
databaseEdition: req.databaseEdition,
|
|
537
537
|
deleteProtectionState: req.deleteProtectionState,
|
|
538
538
|
pointInTimeRecoveryEnablement: req.pointInTimeRecoveryEnablement,
|
|
539
|
+
realtimeUpdatesMode: req.realtimeUpdatesMode,
|
|
540
|
+
firestoreDataAccessMode: req.firestoreDataAccessMode,
|
|
541
|
+
mongodbCompatibleDataAccessMode: req.mongodbCompatibleDataAccessMode,
|
|
539
542
|
cmekConfig: req.cmekConfig,
|
|
540
543
|
};
|
|
541
544
|
const options = { queryParams: { databaseId: req.databaseId } };
|
package/lib/gcp/iam.js
CHANGED
|
@@ -63,7 +63,7 @@ async function testResourceIamPermissions(origin, apiVersion, resourceName, perm
|
|
|
63
63
|
}
|
|
64
64
|
const headers = {};
|
|
65
65
|
if (quotaUser) {
|
|
66
|
-
headers["x-goog-
|
|
66
|
+
headers["x-goog-user-project"] = quotaUser;
|
|
67
67
|
}
|
|
68
68
|
const response = await localClient.post(`/${resourceName}:testIamPermissions`, { permissions }, { headers });
|
|
69
69
|
const allowed = new Set(response.body.permissions || []);
|
package/lib/gcp/rules.js
CHANGED
|
@@ -28,9 +28,12 @@ function _handleErrorResponse(response) {
|
|
|
28
28
|
code: 2,
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
-
async function getLatestRulesetName(projectId, service) {
|
|
31
|
+
async function getLatestRulesetName(projectId, service, resourceName) {
|
|
32
32
|
const releases = await listAllReleases(projectId);
|
|
33
|
-
|
|
33
|
+
let prefix = `projects/${projectId}/releases/${service}`;
|
|
34
|
+
if (resourceName) {
|
|
35
|
+
prefix += `/${resourceName}`;
|
|
36
|
+
}
|
|
34
37
|
const release = releases.find((r) => r.name.startsWith(prefix));
|
|
35
38
|
if (!release) {
|
|
36
39
|
return null;
|
package/lib/gcp/serviceusage.js
CHANGED
|
@@ -22,7 +22,7 @@ const serviceUsagePollerOptions = {
|
|
|
22
22
|
async function generateServiceIdentity(projectNumber, service, prefix) {
|
|
23
23
|
utils.logLabeledBullet(prefix, `generating the service identity for ${(0, colorette_1.bold)(service)}...`);
|
|
24
24
|
try {
|
|
25
|
-
const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`, {}, { headers: { "x-goog-
|
|
25
|
+
const res = await exports.apiClient.post(`projects/${projectNumber}/services/${service}:generateServiceIdentity`, {}, { headers: { "x-goog-user-project": `projects/${projectNumber}` } });
|
|
26
26
|
return res.body;
|
|
27
27
|
}
|
|
28
28
|
catch (err) {
|
|
@@ -39,6 +39,6 @@ async function generateServiceIdentityAndPoll(projectNumber, service, prefix) {
|
|
|
39
39
|
await poller.pollOperation({
|
|
40
40
|
...serviceUsagePollerOptions,
|
|
41
41
|
operationResourceName: op.name,
|
|
42
|
-
headers: { "x-goog-
|
|
42
|
+
headers: { "x-goog-user-project": `projects/${projectNumber}` },
|
|
43
43
|
});
|
|
44
44
|
}
|
|
@@ -33,7 +33,7 @@ async function initIndexes(setup, config, info) {
|
|
|
33
33
|
utils.logBullet(`Downloaded the existing Firestore indexes from the Firebase console`);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
-
info.
|
|
36
|
+
info.writeIndexes = await config.confirmWriteProjectFile(info.indexesFilename, info.indexes);
|
|
37
37
|
}
|
|
38
38
|
async function getIndexesFromConsole(projectId, databaseId) {
|
|
39
39
|
const indexesPromise = indexes.listIndexes(projectId, databaseId);
|
|
@@ -3,10 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.askQuestions = askQuestions;
|
|
4
4
|
exports.actuate = actuate;
|
|
5
5
|
const clc = require("colorette");
|
|
6
|
-
const logger_1 = require("
|
|
7
|
-
const prompt_1 = require("
|
|
8
|
-
const templates_1 = require("
|
|
9
|
-
const error_1 = require("
|
|
6
|
+
const logger_1 = require("../../../logger");
|
|
7
|
+
const prompt_1 = require("../../../prompt");
|
|
8
|
+
const templates_1 = require("../../../templates");
|
|
9
|
+
const error_1 = require("../../../error");
|
|
10
|
+
const rules_1 = require("./rules");
|
|
10
11
|
const RULES_TEMPLATE = (0, templates_1.readTemplateSync)("init/storage/storage.rules");
|
|
11
12
|
const DEFAULT_RULES_FILE = "storage.rules";
|
|
12
13
|
async function askQuestions(setup, config) {
|
|
@@ -20,6 +21,13 @@ async function askQuestions(setup, config) {
|
|
|
20
21
|
rules: RULES_TEMPLATE,
|
|
21
22
|
writeRules: true,
|
|
22
23
|
};
|
|
24
|
+
if (setup.projectId) {
|
|
25
|
+
const downloadedRules = await (0, rules_1.getRulesFromConsole)(setup.projectId);
|
|
26
|
+
if (downloadedRules) {
|
|
27
|
+
info.rules = downloadedRules;
|
|
28
|
+
logger_1.logger.info(`Downloaded the existing Storage Security Rules from the Firebase console`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
23
31
|
info.rulesFilename = await (0, prompt_1.input)({
|
|
24
32
|
message: "What file should be used for Storage Rules?",
|
|
25
33
|
default: DEFAULT_RULES_FILE,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRulesFromConsole = getRulesFromConsole;
|
|
4
|
+
const gcp = require("../../../gcp");
|
|
5
|
+
const utils = require("../../../utils");
|
|
6
|
+
async function getRulesFromConsole(projectId) {
|
|
7
|
+
const defaultBucket = await gcp.storage.getDefaultBucket(projectId);
|
|
8
|
+
const name = await gcp.rules.getLatestRulesetName(projectId, "firebase.storage", defaultBucket);
|
|
9
|
+
if (!name) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const rules = await gcp.rules.getRulesetContent(name);
|
|
13
|
+
if (rules.length <= 0) {
|
|
14
|
+
return utils.reject("Ruleset has no files", { exit: 1 });
|
|
15
|
+
}
|
|
16
|
+
if (rules.length > 1) {
|
|
17
|
+
return utils.reject("Ruleset has too many files: " + rules.length, { exit: 1 });
|
|
18
|
+
}
|
|
19
|
+
return rules[0].content;
|
|
20
|
+
}
|
|
@@ -10,7 +10,7 @@ const getDefaultDatabaseInstance_1 = require("../../../getDefaultDatabaseInstanc
|
|
|
10
10
|
exports.get_security_rules = (0, tool_1.tool)("core", {
|
|
11
11
|
name: "get_security_rules",
|
|
12
12
|
description: "Use this to retrieve the security rules for a specified Firebase service. " +
|
|
13
|
-
"If there are multiple instances of that service in the product, the rules for the
|
|
13
|
+
"If there are multiple instances of that service in the product, the rules for the default instance are returned.",
|
|
14
14
|
inputSchema: zod_1.z.object({
|
|
15
15
|
type: zod_1.z.enum(["firestore", "rtdb", "storage"]).describe("The service to get rules for."),
|
|
16
16
|
}),
|
|
@@ -31,7 +31,7 @@ function toText(response, filters) {
|
|
|
31
31
|
}
|
|
32
32
|
exports.get_report = (0, tool_1.tool)("crashlytics", {
|
|
33
33
|
name: "get_report",
|
|
34
|
-
description: `Use this to request numerical reports from Crashlytics. The result aggregates the sum of events and impacted users, grouped by a dimension appropriate for that report. Agents must read the [Firebase Crashlytics Reports Guide](firebase://guides/crashlytics/reports) using the \`firebase_read_resources\` tool before calling to understand
|
|
34
|
+
description: `Use this to request numerical reports from Crashlytics. The result aggregates the sum of events and impacted users, grouped by a dimension appropriate for that report. Agents must read the [Firebase Crashlytics Reports Guide](firebase://guides/crashlytics/reports) using the \`firebase_read_resources\` tool before calling to understand critical prerequisites for requesting reports and how to interpret the results.
|
|
35
35
|
`.trim(),
|
|
36
36
|
inputSchema: reports_1.ReportInputSchema,
|
|
37
37
|
annotations: {
|
|
@@ -9,7 +9,7 @@ const delete_1 = require("../../../firestore/delete");
|
|
|
9
9
|
const types_1 = require("../../../emulator/types");
|
|
10
10
|
exports.delete_document = (0, tool_1.tool)("firestore", {
|
|
11
11
|
name: "delete_document",
|
|
12
|
-
description: "Use this to delete
|
|
12
|
+
description: "Use this to delete Firestore documents from a database in the current project by full document paths. Use this if you know the exact path of a document.",
|
|
13
13
|
inputSchema: zod_1.z.object({
|
|
14
14
|
database: zod_1.z
|
|
15
15
|
.string()
|
|
@@ -9,7 +9,7 @@ const converter_1 = require("./converter");
|
|
|
9
9
|
const types_1 = require("../../../emulator/types");
|
|
10
10
|
exports.query_collection = (0, tool_1.tool)("firestore", {
|
|
11
11
|
name: "query_collection",
|
|
12
|
-
description: "Use this to retrieve one or more Firestore documents from a collection
|
|
12
|
+
description: "Use this to retrieve one or more Firestore documents from a collection in 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.",
|
|
13
13
|
inputSchema: zod_1.z.object({
|
|
14
14
|
database: zod_1.z
|
|
15
15
|
.string()
|
package/lib/track.js
CHANGED
|
@@ -15,21 +15,6 @@ const configstore_1 = require("./configstore");
|
|
|
15
15
|
const logger_1 = require("./logger");
|
|
16
16
|
const env_1 = require("./env");
|
|
17
17
|
const pkg = require("../package.json");
|
|
18
|
-
function detectAIAgent() {
|
|
19
|
-
if (process.env.CLAUDECODE)
|
|
20
|
-
return "claude_code";
|
|
21
|
-
if (process.env.CLINE_ACTIVE)
|
|
22
|
-
return "cline";
|
|
23
|
-
if (process.env.CODEX_SANDBOX)
|
|
24
|
-
return "codex_cli";
|
|
25
|
-
if (process.env.CURSOR_AGENT)
|
|
26
|
-
return "cursor";
|
|
27
|
-
if (process.env.GEMINI_CLI)
|
|
28
|
-
return "gemini_cli";
|
|
29
|
-
if (process.env.OPENCODE)
|
|
30
|
-
return "open_code";
|
|
31
|
-
return "unknown";
|
|
32
|
-
}
|
|
33
18
|
exports.GA4_PROPERTIES = {
|
|
34
19
|
cli: {
|
|
35
20
|
measurementId: process.env.FIREBASE_CLI_GA4_MEASUREMENT_ID || "G-PDN0QWHQJR",
|
|
@@ -67,7 +52,7 @@ const GA4_USER_PROPS = {
|
|
|
67
52
|
value: (0, env_1.isFirebaseStudio)().toString(),
|
|
68
53
|
},
|
|
69
54
|
ai_agent: {
|
|
70
|
-
value: detectAIAgent(),
|
|
55
|
+
value: (0, env_1.detectAIAgent)(),
|
|
71
56
|
},
|
|
72
57
|
};
|
|
73
58
|
async function trackGA4(eventName, params, duration = 1) {
|