firebase-tools 15.16.0 → 15.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/api.js +1 -1
- package/lib/apphosting/localbuilds.js +116 -5
- package/lib/apphosting/universalMakerDownload.js +72 -0
- package/lib/apphosting/universalMakerInfo.json +16 -0
- package/lib/archiveDirectory.js +1 -1
- package/lib/deploy/apphosting/release.js +27 -13
- package/lib/deploy/apphosting/util.js +4 -19
- package/lib/deploy/functions/backend.js +2 -1
- package/lib/deploy/functions/prepare.js +140 -35
- package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
- package/lib/deploy/functions/services/ailogic.js +17 -0
- package/lib/deploy/functions/services/database.js +16 -0
- package/lib/deploy/functions/services/firestore.js +1 -0
- package/lib/deploy/functions/services/storage.js +15 -1
- package/lib/deploy/functions/triggerRegionHelper.js +111 -2
- package/lib/deploy/functions/validate.js +1 -1
- package/lib/downloadUtils.js +24 -0
- package/lib/emulator/download.js +2 -24
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/emulator/functionsEmulatorShared.js +2 -1
- package/lib/env.js +5 -1
- package/lib/firestore/api-sort.js +22 -0
- package/lib/firestore/api-types.js +11 -1
- package/lib/firestore/api.js +21 -1
- package/lib/firestore/fsConfig.js +8 -0
- package/lib/firestore/pretty-print.js +26 -8
- package/lib/frameworks/next/index.js +1 -1
- package/lib/fsAsync.js +53 -10
- package/lib/mcp/apps/deploy/mcp-app.js +120 -0
- package/lib/mcp/apps/deploy/vite.config.js +16 -0
- package/lib/mcp/apps/init/mcp-app.js +230 -0
- package/lib/mcp/apps/init/vite.config.js +16 -0
- package/lib/mcp/apps/update_environment/mcp-app.js +38 -36
- package/lib/mcp/apps/update_environment/vite.config.js +16 -0
- package/lib/mcp/index.js +16 -5
- package/lib/mcp/resources/deploy_ui.js +31 -0
- package/lib/mcp/resources/index.js +4 -0
- package/lib/mcp/resources/init_ui.js +31 -0
- package/lib/mcp/resources/update_environment_ui.js +3 -3
- package/lib/mcp/tools/auth/get_users.js +1 -1
- package/lib/mcp/tools/core/deploy.js +87 -0
- package/lib/mcp/tools/core/deploy_status.js +32 -0
- package/lib/mcp/tools/core/index.js +4 -0
- package/lib/mcp/tools/core/init.js +3 -0
- package/lib/mcp/tools/core/update_environment.js +3 -0
- package/lib/mcp/tools/firestore/query_collection.js +1 -1
- package/lib/mcp/tools/functions/list_functions.js +2 -2
- package/lib/mcp/util/jobs.js +31 -0
- package/lib/mcp/util.js +5 -4
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/templates/init/functions/dart/pubspec.yaml +1 -1
- package/templates/init/functions/dart/server.dart +2 -2
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AILogicService = exports.AI_LOGIC_EVENTS = exports.AI_LOGIC_AFTER_GENERATE_CONTENT = exports.AI_LOGIC_BEFORE_GENERATE_CONTENT = void 0;
|
|
4
4
|
exports.isAILogicEvent = isAILogicEvent;
|
|
5
|
+
exports.isGlobalAILogicEndpoint = isGlobalAILogicEndpoint;
|
|
5
6
|
const backend = require("../backend");
|
|
6
7
|
const error_1 = require("../../../error");
|
|
7
8
|
const ailogicApi = require("../../../gcp/ailogic");
|
|
@@ -19,12 +20,28 @@ function isAILogicEvent(endpoint) {
|
|
|
19
20
|
}
|
|
20
21
|
return exports.AI_LOGIC_EVENTS.includes(endpoint.blockingTrigger.eventType);
|
|
21
22
|
}
|
|
23
|
+
function isGlobalAILogicEndpoint(endpoint) {
|
|
24
|
+
if (!isAILogicEvent(endpoint)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return !endpoint.blockingTrigger.options?.regionalWebhook;
|
|
28
|
+
}
|
|
22
29
|
class AILogicService {
|
|
23
30
|
constructor() {
|
|
24
31
|
this.ensureTriggerRegion = () => Promise.resolve();
|
|
25
32
|
this.name = "ailogic";
|
|
26
33
|
this.api = "firebasevertexai.googleapis.com";
|
|
27
34
|
}
|
|
35
|
+
async requiredProjectBindings(projectNumber) {
|
|
36
|
+
return [
|
|
37
|
+
{
|
|
38
|
+
role: "roles/run.invoker",
|
|
39
|
+
members: [
|
|
40
|
+
`serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
}
|
|
28
45
|
validateTrigger(endpoint, wantBackend) {
|
|
29
46
|
if (!isAILogicEvent(endpoint)) {
|
|
30
47
|
return;
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearCache = clearCache;
|
|
4
|
+
exports.getDatabaseInstanceDetails = getDatabaseInstanceDetails;
|
|
3
5
|
exports.ensureDatabaseTriggerRegion = ensureDatabaseTriggerRegion;
|
|
4
6
|
const error_1 = require("../../../error");
|
|
7
|
+
const database_1 = require("../../../management/database");
|
|
8
|
+
const instanceCache = new Map();
|
|
9
|
+
function clearCache() {
|
|
10
|
+
instanceCache.clear();
|
|
11
|
+
}
|
|
12
|
+
async function getDatabaseInstanceDetails(projectId, instanceName) {
|
|
13
|
+
const key = `${projectId}/${instanceName}`;
|
|
14
|
+
if (instanceCache.has(key)) {
|
|
15
|
+
return instanceCache.get(key);
|
|
16
|
+
}
|
|
17
|
+
const details = await (0, database_1.getDatabaseInstanceDetails)(projectId, instanceName);
|
|
18
|
+
instanceCache.set(key, details);
|
|
19
|
+
return details;
|
|
20
|
+
}
|
|
5
21
|
function ensureDatabaseTriggerRegion(endpoint) {
|
|
6
22
|
if (!endpoint.eventTrigger.region) {
|
|
7
23
|
endpoint.eventTrigger.region = endpoint.region;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.clearCache = clearCache;
|
|
4
|
+
exports.getDatabase = getDatabase;
|
|
4
5
|
exports.ensureFirestoreTriggerRegion = ensureFirestoreTriggerRegion;
|
|
5
6
|
const firestore = require("../../../gcp/firestore");
|
|
6
7
|
const error_1 = require("../../../error");
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clearCache = clearCache;
|
|
4
|
+
exports.getBucket = getBucket;
|
|
3
5
|
exports.obtainStorageBindings = obtainStorageBindings;
|
|
4
6
|
exports.ensureStorageTriggerRegion = ensureStorageTriggerRegion;
|
|
5
7
|
const storage = require("../../../gcp/storage");
|
|
@@ -7,6 +9,18 @@ const logger_1 = require("../../../logger");
|
|
|
7
9
|
const error_1 = require("../../../error");
|
|
8
10
|
const location_1 = require("../../../gcp/location");
|
|
9
11
|
const PUBSUB_PUBLISHER_ROLE = "roles/pubsub.publisher";
|
|
12
|
+
const bucketCache = new Map();
|
|
13
|
+
function clearCache() {
|
|
14
|
+
bucketCache.clear();
|
|
15
|
+
}
|
|
16
|
+
async function getBucket(bucketName) {
|
|
17
|
+
if (bucketCache.has(bucketName)) {
|
|
18
|
+
return bucketCache.get(bucketName);
|
|
19
|
+
}
|
|
20
|
+
const b = await storage.getBucket(bucketName);
|
|
21
|
+
bucketCache.set(bucketName, b);
|
|
22
|
+
return b;
|
|
23
|
+
}
|
|
10
24
|
async function obtainStorageBindings(projectNumber) {
|
|
11
25
|
const storageResponse = await storage.getServiceAccount(projectNumber);
|
|
12
26
|
const storageServiceAgent = `serviceAccount:${storageResponse.email_address}`;
|
|
@@ -26,7 +40,7 @@ async function ensureStorageTriggerRegion(endpoint) {
|
|
|
26
40
|
}
|
|
27
41
|
logger_1.logger.debug(`Looking up bucket region for the storage event trigger on bucket ${eventTrigger.eventFilters.bucket}`);
|
|
28
42
|
try {
|
|
29
|
-
const bucket = await
|
|
43
|
+
const bucket = await getBucket(eventTrigger.eventFilters.bucket);
|
|
30
44
|
eventTrigger.region = bucket.location.toLowerCase();
|
|
31
45
|
logger_1.logger.debug("Setting the event trigger region to", eventTrigger.region, ".");
|
|
32
46
|
}
|
|
@@ -3,13 +3,122 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ensureTriggerRegions = ensureTriggerRegions;
|
|
4
4
|
const backend = require("./backend");
|
|
5
5
|
const services_1 = require("./services");
|
|
6
|
+
const logger_1 = require("../../logger");
|
|
7
|
+
const utils = require("../../utils");
|
|
8
|
+
const storage_1 = require("./services/storage");
|
|
9
|
+
const firestore_1 = require("./services/firestore");
|
|
10
|
+
const database_1 = require("./services/database");
|
|
6
11
|
async function ensureTriggerRegions(want) {
|
|
7
12
|
const regionLookups = [];
|
|
13
|
+
const triggerRegionMap = new Map();
|
|
8
14
|
for (const ep of backend.allEndpoints(want)) {
|
|
9
|
-
if (
|
|
15
|
+
if (!backend.isEventTriggered(ep)) {
|
|
10
16
|
continue;
|
|
11
17
|
}
|
|
12
|
-
|
|
18
|
+
if (ep.platform === "gcfv1") {
|
|
19
|
+
const eventType = ep.eventTrigger.eventType || "";
|
|
20
|
+
const resource = ep.eventTrigger.eventFilters?.resource;
|
|
21
|
+
if (eventType.includes("storage")) {
|
|
22
|
+
const bucketName = extractBucketName(resource);
|
|
23
|
+
if (bucketName) {
|
|
24
|
+
regionLookups.push((0, storage_1.getBucket)(bucketName)
|
|
25
|
+
.then((bucket) => {
|
|
26
|
+
triggerRegionMap.set(backend.functionName(ep), bucket.location.toLowerCase());
|
|
27
|
+
})
|
|
28
|
+
.catch((err) => {
|
|
29
|
+
logger_1.logger.debug(`Failed to resolve trigger region for V1 storage function ${ep.id}:`, err);
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (eventType.includes("firestore")) {
|
|
34
|
+
const dbId = extractDatabaseId(resource);
|
|
35
|
+
regionLookups.push((0, firestore_1.getDatabase)(ep.project, dbId)
|
|
36
|
+
.then((db) => {
|
|
37
|
+
triggerRegionMap.set(backend.functionName(ep), db.locationId.toLowerCase());
|
|
38
|
+
})
|
|
39
|
+
.catch((err) => {
|
|
40
|
+
logger_1.logger.debug(`Failed to resolve trigger region for V1 firestore function ${ep.id}:`, err);
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
else if (eventType.includes("database")) {
|
|
44
|
+
const instanceName = extractInstanceName(resource);
|
|
45
|
+
if (instanceName) {
|
|
46
|
+
regionLookups.push((0, database_1.getDatabaseInstanceDetails)(ep.project, instanceName)
|
|
47
|
+
.then((details) => {
|
|
48
|
+
if (details.location && details.location !== "-") {
|
|
49
|
+
triggerRegionMap.set(backend.functionName(ep), details.location.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
logger_1.logger.debug(`Failed to resolve trigger region for V1 database function ${ep.id}:`, err);
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
regionLookups.push((0, services_1.serviceForEndpoint)(ep).ensureTriggerRegion(ep));
|
|
60
|
+
}
|
|
13
61
|
}
|
|
14
62
|
await Promise.all(regionLookups);
|
|
63
|
+
if (process.env.FIREBASE_SUPPRESS_REGION_WARNING === "true") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const offendingFunctions = [];
|
|
67
|
+
for (const ep of backend.allEndpoints(want)) {
|
|
68
|
+
if (!backend.isEventTriggered(ep)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
let triggerRegion;
|
|
72
|
+
if (ep.platform === "gcfv1") {
|
|
73
|
+
triggerRegion = triggerRegionMap.get(backend.functionName(ep));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
triggerRegion = ep.eventTrigger.region;
|
|
77
|
+
}
|
|
78
|
+
if (ep.region !== "us-central1" || !triggerRegion || triggerRegion === "global") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (!isUSRegion(triggerRegion)) {
|
|
82
|
+
offendingFunctions.push(`- ${ep.id} (us-central1, Trigger: ${triggerRegion})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (offendingFunctions.length > 0) {
|
|
86
|
+
utils.logLabeledWarning("functions", `The following functions have triggers in different regions than they are located:\n` +
|
|
87
|
+
offendingFunctions.join("\n") +
|
|
88
|
+
`\nTo avoid unnecessary cross-region network hops, consider assigning these functions to their trigger regions or collocating them. ` +
|
|
89
|
+
`To suppress this warning, set FIREBASE_SUPPRESS_REGION_WARNING=true in your environment variables.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function extractBucketName(resource) {
|
|
93
|
+
if (!resource)
|
|
94
|
+
return null;
|
|
95
|
+
const match = /buckets\/([^/]+)/.exec(resource);
|
|
96
|
+
if (match)
|
|
97
|
+
return match[1];
|
|
98
|
+
if (!resource.includes("/"))
|
|
99
|
+
return resource;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function extractDatabaseId(resource) {
|
|
103
|
+
if (!resource)
|
|
104
|
+
return "(default)";
|
|
105
|
+
const match = /databases\/([^/]+)/.exec(resource);
|
|
106
|
+
if (match)
|
|
107
|
+
return match[1];
|
|
108
|
+
if (!resource.includes("/"))
|
|
109
|
+
return resource;
|
|
110
|
+
return "(default)";
|
|
111
|
+
}
|
|
112
|
+
function extractInstanceName(resource) {
|
|
113
|
+
if (!resource)
|
|
114
|
+
return null;
|
|
115
|
+
const match = /instances\/([^/]+)/.exec(resource);
|
|
116
|
+
if (match)
|
|
117
|
+
return match[1];
|
|
118
|
+
if (!resource.includes("/"))
|
|
119
|
+
return resource;
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function isUSRegion(region) {
|
|
123
|
+
return region === "us" || region.startsWith("nam") || region.startsWith("us-");
|
|
15
124
|
}
|
|
@@ -221,7 +221,7 @@ async function secretsAreValid(projectId, wantBackend) {
|
|
|
221
221
|
validatePlatformTargets(endpoints);
|
|
222
222
|
await validateSecretVersions(projectId, endpoints);
|
|
223
223
|
}
|
|
224
|
-
const secretsSupportedPlatforms =
|
|
224
|
+
const secretsSupportedPlatforms = backend.AllFunctionsPlatforms;
|
|
225
225
|
function validatePlatformTargets(endpoints) {
|
|
226
226
|
const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
|
|
227
227
|
if (unsupported.length > 0) {
|
package/lib/downloadUtils.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downloadToTmp = downloadToTmp;
|
|
4
|
+
exports.validateSize = validateSize;
|
|
5
|
+
exports.validateChecksum = validateChecksum;
|
|
4
6
|
const url_1 = require("url");
|
|
7
|
+
const crypto = require("crypto");
|
|
5
8
|
const fs = require("fs-extra");
|
|
6
9
|
const ProgressBar = require("progress");
|
|
7
10
|
const tmp = require("tmp");
|
|
@@ -37,3 +40,24 @@ async function downloadToTmp(remoteUrl, auth = false) {
|
|
|
37
40
|
});
|
|
38
41
|
return tmpfile.name;
|
|
39
42
|
}
|
|
43
|
+
function validateSize(filepath, expectedSize) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const stat = fs.statSync(filepath);
|
|
46
|
+
return stat.size === expectedSize
|
|
47
|
+
? resolve()
|
|
48
|
+
: reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function validateChecksum(filepath, expectedChecksum, algorithm = "md5") {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
const hash = crypto.createHash(algorithm);
|
|
54
|
+
const stream = fs.createReadStream(filepath);
|
|
55
|
+
stream.on("data", (data) => hash.update(data));
|
|
56
|
+
stream.on("end", () => {
|
|
57
|
+
const checksum = hash.digest("hex");
|
|
58
|
+
return checksum === expectedChecksum
|
|
59
|
+
? resolve()
|
|
60
|
+
: reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/lib/emulator/download.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.downloadEmulator = downloadEmulator;
|
|
4
4
|
exports.downloadExtensionVersion = downloadExtensionVersion;
|
|
5
|
-
const crypto = require("crypto");
|
|
6
5
|
const fs = require("fs-extra");
|
|
7
6
|
const path = require("path");
|
|
8
7
|
const tmp = require("tmp");
|
|
@@ -36,8 +35,8 @@ async function downloadEmulator(name) {
|
|
|
36
35
|
throw err;
|
|
37
36
|
}
|
|
38
37
|
if (!emulator.opts.skipChecksumAndSize) {
|
|
39
|
-
await validateSize(tmpfile, emulator.opts.expectedSize);
|
|
40
|
-
await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
|
|
38
|
+
await downloadUtils.validateSize(tmpfile, emulator.opts.expectedSize);
|
|
39
|
+
await downloadUtils.validateChecksum(tmpfile, emulator.opts.expectedChecksum, "md5");
|
|
41
40
|
}
|
|
42
41
|
if (emulator.opts.skipCache) {
|
|
43
42
|
removeOldFiles(name, emulator, true);
|
|
@@ -82,24 +81,3 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
|
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
}
|
|
85
|
-
function validateSize(filepath, expectedSize) {
|
|
86
|
-
return new Promise((resolve, reject) => {
|
|
87
|
-
const stat = fs.statSync(filepath);
|
|
88
|
-
return stat.size === expectedSize
|
|
89
|
-
? resolve()
|
|
90
|
-
: reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
function validateChecksum(filepath, expectedChecksum) {
|
|
94
|
-
return new Promise((resolve, reject) => {
|
|
95
|
-
const hash = crypto.createHash("md5");
|
|
96
|
-
const stream = fs.createReadStream(filepath);
|
|
97
|
-
stream.on("data", (data) => hash.update(data));
|
|
98
|
-
stream.on("end", () => {
|
|
99
|
-
const checksum = hash.digest("hex");
|
|
100
|
-
return checksum === expectedChecksum
|
|
101
|
-
? resolve()
|
|
102
|
-
: reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
}
|
|
@@ -44,46 +44,46 @@
|
|
|
44
44
|
}
|
|
45
45
|
},
|
|
46
46
|
"pubsub": {
|
|
47
|
-
"version": "0.8.
|
|
48
|
-
"expectedSize":
|
|
49
|
-
"expectedChecksum": "
|
|
50
|
-
"expectedChecksumSHA256": "
|
|
51
|
-
"remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.
|
|
52
|
-
"downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.
|
|
53
|
-
"binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.
|
|
47
|
+
"version": "0.8.31",
|
|
48
|
+
"expectedSize": 52942385,
|
|
49
|
+
"expectedChecksum": "04a85aa9873222e9b7a430c6c49cfd9e",
|
|
50
|
+
"expectedChecksumSHA256": "b474e7d3200f5a8b5f7f467ee3b087c94b939e7d41527e935296a7bcabdbea9a",
|
|
51
|
+
"remoteUrl": "https://storage.googleapis.com/firebase-preview-drop/emulator/pubsub-emulator-0.8.31.zip",
|
|
52
|
+
"downloadPathRelativeToCacheDir": "pubsub-emulator-0.8.31.zip",
|
|
53
|
+
"binaryPathRelativeToCacheDir": "pubsub-emulator-0.8.31/pubsub-emulator/bin/cloud-pubsub-emulator"
|
|
54
54
|
},
|
|
55
55
|
"dataconnect": {
|
|
56
56
|
"darwin": {
|
|
57
|
-
"version": "3.4.
|
|
58
|
-
"expectedSize":
|
|
59
|
-
"expectedChecksum": "
|
|
60
|
-
"expectedChecksumSHA256": "
|
|
61
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.
|
|
62
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.
|
|
57
|
+
"version": "3.4.8",
|
|
58
|
+
"expectedSize": 32398304,
|
|
59
|
+
"expectedChecksum": "fee47f6867b5aa939ab667fb801b9807",
|
|
60
|
+
"expectedChecksumSHA256": "0586ffa3614f9231932ee50fb424ee4385100188aac40de91289c705d36ceb67",
|
|
61
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-amd64-v3.4.8",
|
|
62
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.8"
|
|
63
63
|
},
|
|
64
64
|
"darwin_arm64": {
|
|
65
|
-
"version": "3.4.
|
|
66
|
-
"expectedSize":
|
|
67
|
-
"expectedChecksum": "
|
|
68
|
-
"expectedChecksumSHA256": "
|
|
69
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.
|
|
70
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.
|
|
65
|
+
"version": "3.4.8",
|
|
66
|
+
"expectedSize": 30537682,
|
|
67
|
+
"expectedChecksum": "f4ccfe080736fb0a35d60841cf439eab",
|
|
68
|
+
"expectedChecksumSHA256": "d3efdafa7c82f843b1cca7ae2b2404235fa9c918b892806ce4a9910e6907318f",
|
|
69
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-macos-arm64-v3.4.8",
|
|
70
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.8"
|
|
71
71
|
},
|
|
72
72
|
"win32": {
|
|
73
|
-
"version": "3.4.
|
|
74
|
-
"expectedSize":
|
|
75
|
-
"expectedChecksum": "
|
|
76
|
-
"expectedChecksumSHA256": "
|
|
77
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.
|
|
78
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.
|
|
73
|
+
"version": "3.4.8",
|
|
74
|
+
"expectedSize": 32438272,
|
|
75
|
+
"expectedChecksum": "5850b1887b23c351d83b32142bac6858",
|
|
76
|
+
"expectedChecksumSHA256": "d9c2ed151a5d0a9c0530f442f92b3434fe98469e5e6fee230628ba81e9a1540d",
|
|
77
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-windows-amd64-v3.4.8",
|
|
78
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.8.exe"
|
|
79
79
|
},
|
|
80
80
|
"linux": {
|
|
81
|
-
"version": "3.4.
|
|
82
|
-
"expectedSize":
|
|
83
|
-
"expectedChecksum": "
|
|
84
|
-
"expectedChecksumSHA256": "
|
|
85
|
-
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.
|
|
86
|
-
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.
|
|
81
|
+
"version": "3.4.8",
|
|
82
|
+
"expectedSize": 31555768,
|
|
83
|
+
"expectedChecksum": "c6b43a325a0e3559d7bc325591dda14f",
|
|
84
|
+
"expectedChecksumSHA256": "f1809b93e985e497da8ed0a23c049b28f9ff4482b021c9f86af6b8c1a6949fef",
|
|
85
|
+
"remoteUrl": "https://storage.googleapis.com/firemat-preview-drop/emulator/dataconnect-emulator-linux-amd64-v3.4.8",
|
|
86
|
+
"downloadPathRelativeToCacheDir": "dataconnect-emulator-3.4.8"
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -21,6 +21,7 @@ const fs = require("fs");
|
|
|
21
21
|
const crypto_1 = require("crypto");
|
|
22
22
|
const _ = require("lodash");
|
|
23
23
|
const backend = require("../deploy/functions/backend");
|
|
24
|
+
const build = require("../deploy/functions/build");
|
|
24
25
|
const constants_1 = require("./constants");
|
|
25
26
|
const manifest_1 = require("../extensions/manifest");
|
|
26
27
|
const extensionsHelper_1 = require("../extensions/extensionsHelper");
|
|
@@ -70,7 +71,7 @@ function prepareEndpoints(endpoints) {
|
|
|
70
71
|
function emulatedFunctionsFromEndpoints(endpoints) {
|
|
71
72
|
const regionDefinitions = [];
|
|
72
73
|
for (const endpoint of endpoints) {
|
|
73
|
-
if (!endpoint.region) {
|
|
74
|
+
if (!endpoint.region || endpoint.region === build.REGION_TBD) {
|
|
74
75
|
endpoint.region = "us-central1";
|
|
75
76
|
}
|
|
76
77
|
const def = {
|
package/lib/env.js
CHANGED
|
@@ -22,7 +22,7 @@ function setFirebaseMcp(value) {
|
|
|
22
22
|
isFirebaseMcpFlag = value;
|
|
23
23
|
}
|
|
24
24
|
function detectAIAgent() {
|
|
25
|
-
if (process.env.
|
|
25
|
+
if (process.env.ANTIGRAVITY_AGENT)
|
|
26
26
|
return "antigravity";
|
|
27
27
|
if (process.env.CLAUDECODE)
|
|
28
28
|
return "claude_code";
|
|
@@ -36,5 +36,9 @@ function detectAIAgent() {
|
|
|
36
36
|
return "gemini_cli";
|
|
37
37
|
if (process.env.OPENCODE)
|
|
38
38
|
return "open_code";
|
|
39
|
+
if (process.env.ANDROID_STUDIO_AGENT)
|
|
40
|
+
return "android_studio_agent";
|
|
41
|
+
if (process.env.KIRO_AGENT_PATH)
|
|
42
|
+
return "kiro";
|
|
39
43
|
return "unknown";
|
|
40
44
|
}
|
|
@@ -147,6 +147,9 @@ function compareIndexField(a, b) {
|
|
|
147
147
|
if (a.vectorConfig !== b.vectorConfig) {
|
|
148
148
|
return compareVectorConfig(a.vectorConfig, b.vectorConfig);
|
|
149
149
|
}
|
|
150
|
+
if (a.searchConfig !== b.searchConfig) {
|
|
151
|
+
return compareSearchConfig(a.searchConfig, b.searchConfig);
|
|
152
|
+
}
|
|
150
153
|
return 0;
|
|
151
154
|
}
|
|
152
155
|
function compareFieldIndex(a, b) {
|
|
@@ -232,6 +235,25 @@ function compareVectorConfig(a, b) {
|
|
|
232
235
|
}
|
|
233
236
|
return a.dimension - b.dimension;
|
|
234
237
|
}
|
|
238
|
+
function compareSearchConfig(a, b) {
|
|
239
|
+
if (!a) {
|
|
240
|
+
if (!b) {
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
return 1;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (!b) {
|
|
248
|
+
return -1;
|
|
249
|
+
}
|
|
250
|
+
const aTextSpecs = a?.textSpec?.indexSpecs?.length || 0;
|
|
251
|
+
const bTextSpecs = b?.textSpec?.indexSpecs?.length || 0;
|
|
252
|
+
if (aTextSpecs !== bTextSpecs) {
|
|
253
|
+
return aTextSpecs - bTextSpecs;
|
|
254
|
+
}
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
235
257
|
function compareArrays(a, b, fn) {
|
|
236
258
|
const minFields = Math.min(a.length, b.length);
|
|
237
259
|
for (let i = 0; i < minFields; i++) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
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;
|
|
3
|
+
exports.RecurrenceType = exports.DatabaseEdition = exports.DataAccessMode = exports.RealtimeUpdatesMode = exports.PointInTimeRecoveryEnablement = exports.DatabaseDeleteProtectionState = exports.EnablementOption = exports.DatabaseType = exports.StateTtl = exports.State = exports.TextMatchType = exports.TextIndexType = exports.ArrayConfig = exports.Order = exports.Density = exports.ApiScope = exports.QueryScope = exports.Mode = void 0;
|
|
4
4
|
exports.validateEnablementOption = validateEnablementOption;
|
|
5
5
|
const error_1 = require("../error");
|
|
6
6
|
var Mode;
|
|
@@ -36,6 +36,16 @@ var ArrayConfig;
|
|
|
36
36
|
(function (ArrayConfig) {
|
|
37
37
|
ArrayConfig["CONTAINS"] = "CONTAINS";
|
|
38
38
|
})(ArrayConfig || (exports.ArrayConfig = ArrayConfig = {}));
|
|
39
|
+
var TextIndexType;
|
|
40
|
+
(function (TextIndexType) {
|
|
41
|
+
TextIndexType["TEXT_INDEX_TYPE_UNSPECIFIED"] = "TEXT_INDEX_TYPE_UNSPECIFIED";
|
|
42
|
+
TextIndexType["TOKENIZED"] = "TOKENIZED";
|
|
43
|
+
})(TextIndexType || (exports.TextIndexType = TextIndexType = {}));
|
|
44
|
+
var TextMatchType;
|
|
45
|
+
(function (TextMatchType) {
|
|
46
|
+
TextMatchType["TEXT_MATCH_TYPE_UNSPECIFIED"] = "TEXT_MATCH_TYPE_UNSPECIFIED";
|
|
47
|
+
TextMatchType["MATCH_GLOBALLY"] = "MATCH_GLOBALLY";
|
|
48
|
+
})(TextMatchType || (exports.TextMatchType = TextMatchType = {}));
|
|
39
49
|
var State;
|
|
40
50
|
(function (State) {
|
|
41
51
|
State["CREATING"] = "CREATING";
|
package/lib/firestore/api.js
CHANGED
|
@@ -254,7 +254,7 @@ class FirestoreApi {
|
|
|
254
254
|
validator.assertHas(index, "fields");
|
|
255
255
|
index.fields.forEach((field) => {
|
|
256
256
|
validator.assertHas(field, "fieldPath");
|
|
257
|
-
validator.assertHasOneOf(field, ["order", "arrayConfig", "vectorConfig"]);
|
|
257
|
+
validator.assertHasOneOf(field, ["order", "arrayConfig", "searchConfig", "vectorConfig"]);
|
|
258
258
|
if (field.order) {
|
|
259
259
|
validator.assertEnum(field, "order", Object.keys(types.Order));
|
|
260
260
|
}
|
|
@@ -265,6 +265,20 @@ class FirestoreApi {
|
|
|
265
265
|
validator.assertType("vectorConfig.dimension", field.vectorConfig.dimension, "number");
|
|
266
266
|
validator.assertHas(field.vectorConfig, "flat");
|
|
267
267
|
}
|
|
268
|
+
if (field.searchConfig) {
|
|
269
|
+
if (field.searchConfig.textSpec) {
|
|
270
|
+
validator.assertHas(field.searchConfig.textSpec, "indexSpecs");
|
|
271
|
+
for (const spec of field.searchConfig.textSpec.indexSpecs) {
|
|
272
|
+
validator.assertHas(spec, "indexType");
|
|
273
|
+
validator.assertEnum(spec, "indexType", Object.keys(types.TextIndexType));
|
|
274
|
+
validator.assertHas(spec, "matchType");
|
|
275
|
+
validator.assertEnum(spec, "matchType", Object.keys(types.TextMatchType));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (field.searchConfig.geoSpec?.geoJsonIndexingDisabled !== undefined) {
|
|
279
|
+
validator.assertType("searchConfig.geoSpec.geoJsonIndexingDisabled", field.searchConfig.geoSpec.geoJsonIndexingDisabled, "boolean");
|
|
280
|
+
}
|
|
281
|
+
}
|
|
268
282
|
});
|
|
269
283
|
}
|
|
270
284
|
validateField(field) {
|
|
@@ -405,6 +419,9 @@ class FirestoreApi {
|
|
|
405
419
|
if (!utils.deepEqual(iField.vectorConfig, sField.vectorConfig)) {
|
|
406
420
|
return false;
|
|
407
421
|
}
|
|
422
|
+
if (!utils.deepEqual(iField.searchConfig, sField.searchConfig)) {
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
408
425
|
i++;
|
|
409
426
|
}
|
|
410
427
|
return true;
|
|
@@ -488,6 +505,9 @@ class FirestoreApi {
|
|
|
488
505
|
else if (field.vectorConfig) {
|
|
489
506
|
f.vectorConfig = field.vectorConfig;
|
|
490
507
|
}
|
|
508
|
+
else if (field.searchConfig) {
|
|
509
|
+
f.searchConfig = field.searchConfig;
|
|
510
|
+
}
|
|
491
511
|
else if (field.mode === types.Mode.ARRAY_CONTAINS) {
|
|
492
512
|
f.arrayConfig = types.ArrayConfig.CONTAINS;
|
|
493
513
|
}
|
|
@@ -35,6 +35,14 @@ function getFirestoreConfig(projectId, options) {
|
|
|
35
35
|
return [];
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
if (onlyDatabases.has("rules")) {
|
|
39
|
+
onlyDatabases.delete("rules");
|
|
40
|
+
allDatabases = true;
|
|
41
|
+
}
|
|
42
|
+
if (onlyDatabases.has("indexes")) {
|
|
43
|
+
onlyDatabases.delete("indexes");
|
|
44
|
+
allDatabases = true;
|
|
45
|
+
}
|
|
38
46
|
const results = [];
|
|
39
47
|
for (const c of fsConfig) {
|
|
40
48
|
const { database, target } = c;
|
|
@@ -19,6 +19,15 @@ class PrettyPrint {
|
|
|
19
19
|
logger_1.logger.info(this.prettyIndexString(index));
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
+
getDatabaseEdition(database) {
|
|
23
|
+
return !database.databaseEdition ||
|
|
24
|
+
database.databaseEdition === types.DatabaseEdition.DATABASE_EDITION_UNSPECIFIED
|
|
25
|
+
? types.DatabaseEdition.STANDARD
|
|
26
|
+
: database.databaseEdition;
|
|
27
|
+
}
|
|
28
|
+
getDatabaseApiType(database) {
|
|
29
|
+
return database.type;
|
|
30
|
+
}
|
|
22
31
|
prettyPrintDatabases(databases) {
|
|
23
32
|
if (databases.length === 0) {
|
|
24
33
|
logger_1.logger.info("No databases found.");
|
|
@@ -26,10 +35,18 @@ class PrettyPrint {
|
|
|
26
35
|
}
|
|
27
36
|
const sortedDatabases = databases.sort(sort.compareApiDatabase);
|
|
28
37
|
const table = new Table({
|
|
29
|
-
head: ["Database Name"],
|
|
30
|
-
colWidths: [
|
|
38
|
+
head: ["Database Name", "Edition", "Type"],
|
|
39
|
+
colWidths: [
|
|
40
|
+
Math.max(...sortedDatabases.map((database) => database.name.length + 5), 20),
|
|
41
|
+
20,
|
|
42
|
+
20,
|
|
43
|
+
],
|
|
31
44
|
});
|
|
32
|
-
table.push(...sortedDatabases.map((database) =>
|
|
45
|
+
table.push(...sortedDatabases.map((database) => {
|
|
46
|
+
const edition = this.getDatabaseEdition(database);
|
|
47
|
+
const apiType = this.getDatabaseApiType(database);
|
|
48
|
+
return [this.prettyDatabaseString(database), edition, apiType];
|
|
49
|
+
}));
|
|
33
50
|
logger_1.logger.info(table.toString());
|
|
34
51
|
}
|
|
35
52
|
prettyPrintDatabase(database) {
|
|
@@ -41,11 +58,9 @@ class PrettyPrint {
|
|
|
41
58
|
head: ["Field", "Value"],
|
|
42
59
|
colWidths: [30, colValueWidth],
|
|
43
60
|
});
|
|
44
|
-
const edition =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
: database.databaseEdition;
|
|
48
|
-
table.push(["Name", clc.yellow(database.name)], ["Create Time", clc.yellow(database.createTime)], ["Last Update Time", clc.yellow(database.updateTime)], ["Type", clc.yellow(database.type)], ["Edition", clc.yellow(edition)], ["Location", clc.yellow(database.locationId)], ["Delete Protection State", clc.yellow(database.deleteProtectionState)], ["Point In Time Recovery", clc.yellow(database.pointInTimeRecoveryEnablement)], ["Earliest Version Time", clc.yellow(database.earliestVersionTime)], ["Version Retention Period", clc.yellow(database.versionRetentionPeriod)]);
|
|
61
|
+
const edition = this.getDatabaseEdition(database);
|
|
62
|
+
const apiType = this.getDatabaseApiType(database);
|
|
63
|
+
table.push(["Name", clc.yellow(database.name)], ["Create Time", clc.yellow(database.createTime)], ["Last Update Time", clc.yellow(database.updateTime)], ["Type", clc.yellow(apiType)], ["Edition", clc.yellow(edition)], ["Location", clc.yellow(database.locationId)], ["Delete Protection State", clc.yellow(database.deleteProtectionState)], ["Point In Time Recovery", clc.yellow(database.pointInTimeRecoveryEnablement)], ["Earliest Version Time", clc.yellow(database.earliestVersionTime)], ["Version Retention Period", clc.yellow(database.versionRetentionPeriod)]);
|
|
49
64
|
if (database.cmekConfig) {
|
|
50
65
|
table.push(["KMS Key Name", clc.yellow(database.cmekConfig.kmsKeyName)]);
|
|
51
66
|
if (database.cmekConfig.activeKeyVersion) {
|
|
@@ -195,6 +210,9 @@ class PrettyPrint {
|
|
|
195
210
|
else if (field.arrayConfig) {
|
|
196
211
|
configString = field.arrayConfig;
|
|
197
212
|
}
|
|
213
|
+
else if (field.searchConfig) {
|
|
214
|
+
configString = "SEARCH";
|
|
215
|
+
}
|
|
198
216
|
else if (field.vectorConfig) {
|
|
199
217
|
configString = `VECTOR<${field.vectorConfig.dimension}>`;
|
|
200
218
|
}
|
|
@@ -31,7 +31,7 @@ const logger_1 = require("../../logger");
|
|
|
31
31
|
const env_1 = require("../../functions/env");
|
|
32
32
|
const DEFAULT_BUILD_SCRIPT = ["next build"];
|
|
33
33
|
const PUBLIC_DIR = "public";
|
|
34
|
-
exports.supportedRange = "12 -
|
|
34
|
+
exports.supportedRange = "12 - 16.0";
|
|
35
35
|
exports.name = "Next.js";
|
|
36
36
|
exports.support = "preview";
|
|
37
37
|
exports.type = 2;
|