firebase-tools 15.18.0 → 15.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/accountExporter.js +13 -6
- package/lib/accountImporter.js +18 -1
- package/lib/agentSkills.js +2 -1
- package/lib/api.js +1 -3
- package/lib/apphosting/constants.js +2 -1
- package/lib/apphosting/localbuilds.js +23 -66
- package/lib/apphosting/universalMakerInfo.json +4 -4
- package/lib/archiveFile.js +30 -0
- package/lib/command.js +7 -0
- package/lib/commands/crashlytics-sourcemap-upload.js +61 -0
- package/lib/commands/dataconnect-sql-shell.js +21 -3
- package/lib/commands/index.js +4 -0
- package/lib/crashlytics/sourcemap.js +270 -0
- package/lib/dataconnect/ensureApis.js +0 -13
- package/lib/deploy/apphosting/deploy.js +39 -23
- package/lib/deploy/apphosting/prepare.js +28 -6
- package/lib/deploy/apphosting/util.js +34 -21
- package/lib/deploy/functions/prepare.js +8 -10
- package/lib/deploy/functions/services/ailogic.js +4 -6
- package/lib/emulator/downloadableEmulatorInfo.json +31 -31
- package/lib/experiments.js +8 -3
- package/lib/firebase_studio/migrate.js +8 -0
- package/lib/gcp/cloudsql/connect.js +49 -25
- package/lib/gemini/fdcExperience.js +171 -26
- package/lib/init/features/dataconnect/index.js +49 -15
- package/lib/tsconfig.compile.tsbuildinfo +1 -1
- package/lib/tsconfig.publish.tsbuildinfo +1 -1
- package/lib/utils.js +48 -0
- package/package.json +19 -4
- package/templates/init/functions/dart/_gitignore +1 -9
- package/lib/dataconnect/cloudAICompanionTypes.js +0 -2
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONCURRENCY = void 0;
|
|
4
|
+
exports.checkGoogleAppID = checkGoogleAppID;
|
|
5
|
+
exports.getAppVersion = getAppVersion;
|
|
6
|
+
exports.getGitCommit = getGitCommit;
|
|
7
|
+
exports.getPackageVersion = getPackageVersion;
|
|
8
|
+
exports.upsertBucket = upsertBucket;
|
|
9
|
+
exports.findSourceMapMappings = findSourceMapMappings;
|
|
10
|
+
exports.getLinkedSourceMapPath = getLinkedSourceMapPath;
|
|
11
|
+
exports.uploadSourceMaps = uploadSourceMaps;
|
|
12
|
+
exports.uploadMap = uploadMap;
|
|
13
|
+
exports.normalizeFileName = normalizeFileName;
|
|
14
|
+
exports.registerSourceMap = registerSourceMap;
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const node_child_process_1 = require("node:child_process");
|
|
18
|
+
const pLimit = require("p-limit");
|
|
19
|
+
const apiv2_1 = require("../apiv2");
|
|
20
|
+
const error_1 = require("../error");
|
|
21
|
+
const logger_1 = require("../logger");
|
|
22
|
+
const utils_1 = require("../utils");
|
|
23
|
+
const gcs = require("../gcp/storage");
|
|
24
|
+
const archiveFile_1 = require("../archiveFile");
|
|
25
|
+
exports.CONCURRENCY = 25;
|
|
26
|
+
function checkGoogleAppID(options) {
|
|
27
|
+
if (!options.app) {
|
|
28
|
+
throw new error_1.FirebaseError("set --app <appId> to a valid Firebase application id, e.g. 1:00000000:android:0000000");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function getAppVersion(options) {
|
|
32
|
+
if (options.appVersion) {
|
|
33
|
+
return options.appVersion;
|
|
34
|
+
}
|
|
35
|
+
const gitCommit = getGitCommit();
|
|
36
|
+
if (gitCommit) {
|
|
37
|
+
(0, utils_1.logLabeledBullet)("crashlytics", `Using git commit as app version: ${gitCommit}`);
|
|
38
|
+
return gitCommit;
|
|
39
|
+
}
|
|
40
|
+
const packageVersion = getPackageVersion();
|
|
41
|
+
if (packageVersion) {
|
|
42
|
+
(0, utils_1.logLabeledBullet)("crashlytics", `Using package version as app version: ${packageVersion}`);
|
|
43
|
+
return packageVersion;
|
|
44
|
+
}
|
|
45
|
+
return "unset";
|
|
46
|
+
}
|
|
47
|
+
function getGitCommit() {
|
|
48
|
+
if (!(0, utils_1.commandExistsSync)("git")) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
return (0, node_child_process_1.execSync)("git rev-parse HEAD").toString().trim();
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getPackageVersion() {
|
|
59
|
+
if (!(0, utils_1.commandExistsSync)("npm")) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return (0, node_child_process_1.execSync)("npm pkg get version").toString().trim().replaceAll('"', "");
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function upsertBucket(projectId, projectNumber, options) {
|
|
70
|
+
let loc = "US-CENTRAL1";
|
|
71
|
+
if (options.bucketLocation) {
|
|
72
|
+
loc = options.bucketLocation.toUpperCase();
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
(0, utils_1.logLabeledBullet)("crashlytics", "No Google Cloud Storage bucket location specified. Defaulting to US-CENTRAL1.");
|
|
76
|
+
}
|
|
77
|
+
const baseName = `firebasecrashlytics-sourcemaps-${projectNumber}-${loc.toLowerCase()}`;
|
|
78
|
+
return await gcs.upsertBucket({
|
|
79
|
+
product: "crashlytics",
|
|
80
|
+
createMessage: `Creating Cloud Storage bucket in ${loc} to store Crashlytics source maps at ${baseName}...`,
|
|
81
|
+
projectId,
|
|
82
|
+
req: {
|
|
83
|
+
baseName,
|
|
84
|
+
purposeLabel: `crashlytics-sourcemaps-${loc.toLowerCase()}`,
|
|
85
|
+
location: loc,
|
|
86
|
+
lifecycle: {
|
|
87
|
+
rule: [
|
|
88
|
+
{
|
|
89
|
+
action: {
|
|
90
|
+
type: "Delete",
|
|
91
|
+
},
|
|
92
|
+
condition: {
|
|
93
|
+
age: 30,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function findSourceMapMappings(files, rootDir) {
|
|
102
|
+
const jsFiles = files.filter((f) => f.name.endsWith(".js"));
|
|
103
|
+
const mapFiles = files.filter((f) => f.name.endsWith(".js.map"));
|
|
104
|
+
const mappings = [];
|
|
105
|
+
const mapFilePathsSet = new Set(mapFiles.map((f) => f.name));
|
|
106
|
+
const mapFilesLinkedInJsComment = new Set();
|
|
107
|
+
const limit = pLimit(exports.CONCURRENCY);
|
|
108
|
+
const results = await Promise.all(jsFiles.map((jsFile) => limit(async () => {
|
|
109
|
+
const mapFilePath = await getLinkedSourceMapPath(jsFile.name);
|
|
110
|
+
return { jsFile, mapFilePath };
|
|
111
|
+
})));
|
|
112
|
+
for (const { jsFile, mapFilePath } of results) {
|
|
113
|
+
if (mapFilePath && mapFilePathsSet.has(mapFilePath)) {
|
|
114
|
+
mappings.push({
|
|
115
|
+
mapFilePath,
|
|
116
|
+
obfuscatedFilePath: path.relative(rootDir, path.resolve(jsFile.name)),
|
|
117
|
+
});
|
|
118
|
+
mapFilesLinkedInJsComment.add(mapFilePath);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const mapFile of mapFiles) {
|
|
122
|
+
if (!mapFilesLinkedInJsComment.has(mapFile.name)) {
|
|
123
|
+
mappings.push({
|
|
124
|
+
mapFilePath: mapFile.name,
|
|
125
|
+
obfuscatedFilePath: path.relative(rootDir, path.resolve(mapFile.name)),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return mappings;
|
|
130
|
+
}
|
|
131
|
+
async function getLinkedSourceMapPath(jsFilePath) {
|
|
132
|
+
let fileHandle;
|
|
133
|
+
try {
|
|
134
|
+
const stat = await fs.promises.stat(jsFilePath);
|
|
135
|
+
const size = stat.size;
|
|
136
|
+
const bufferSize = Math.min(size, 4096);
|
|
137
|
+
if (bufferSize === 0) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
fileHandle = await fs.promises.open(jsFilePath, "r");
|
|
141
|
+
const buffer = Buffer.alloc(bufferSize);
|
|
142
|
+
const { bytesRead } = await fileHandle.read(buffer, 0, bufferSize, size - bufferSize);
|
|
143
|
+
const tail = buffer.toString("utf-8", 0, bytesRead);
|
|
144
|
+
const regex = /^\/\/\s*[#@]\s*sourceMappingURL=(?<sourceMappingURL>.+)\s*$/m;
|
|
145
|
+
const match = regex.exec(tail);
|
|
146
|
+
const sourceMappingURL = match?.groups?.sourceMappingURL?.trim();
|
|
147
|
+
if (sourceMappingURL) {
|
|
148
|
+
return path.join(path.dirname(jsFilePath), sourceMappingURL);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
logger_1.logger.debug(`Error reading sourceMappingURL from ${jsFilePath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (fileHandle) {
|
|
156
|
+
try {
|
|
157
|
+
await fileHandle.close();
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
async function uploadSourceMaps(mappings, request) {
|
|
166
|
+
const { projectId, bucketName, appVersion, options } = request;
|
|
167
|
+
const limit = pLimit(exports.CONCURRENCY);
|
|
168
|
+
const results = await Promise.all(mappings.map((mapping) => limit(async () => {
|
|
169
|
+
const uploadRequest = {
|
|
170
|
+
projectId,
|
|
171
|
+
mappingFile: mapping.mapFilePath,
|
|
172
|
+
obfuscatedFilePath: mapping.obfuscatedFilePath,
|
|
173
|
+
bucketName,
|
|
174
|
+
appVersion,
|
|
175
|
+
options,
|
|
176
|
+
};
|
|
177
|
+
let success = await uploadMap(uploadRequest, 1);
|
|
178
|
+
if (!success) {
|
|
179
|
+
await new Promise((res) => setTimeout(res, options.retryDelay || 5000));
|
|
180
|
+
success = await uploadMap(uploadRequest);
|
|
181
|
+
}
|
|
182
|
+
return success;
|
|
183
|
+
})));
|
|
184
|
+
let successCount = 0;
|
|
185
|
+
const failedFiles = [];
|
|
186
|
+
for (const [i, success] of results.entries()) {
|
|
187
|
+
if (success) {
|
|
188
|
+
successCount++;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
failedFiles.push(mappings[i].mapFilePath);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
successCount,
|
|
196
|
+
failedFiles,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function uploadMap(request, attemptsRemaining = 0) {
|
|
200
|
+
const { projectId, mappingFile, obfuscatedFilePath, bucketName, appVersion, options } = request;
|
|
201
|
+
const filePath = path.relative(options.projectRoot ?? process.cwd(), mappingFile);
|
|
202
|
+
const obfuscatedPath = obfuscatedFilePath
|
|
203
|
+
.split(path.sep)
|
|
204
|
+
.map((p) => (p === ".next" ? "_next" : p))
|
|
205
|
+
.filter((p) => p !== "dev")
|
|
206
|
+
.join("/");
|
|
207
|
+
const tmpArchive = await (0, archiveFile_1.archiveFile)(filePath, { archivedFileName: "mapping.js.map" });
|
|
208
|
+
const appId = options.app || "";
|
|
209
|
+
const gcsFile = `${appId}-${appVersion}-${normalizeFileName(obfuscatedPath)}.zip`;
|
|
210
|
+
const uid = (0, utils_1.murmurHashV3)(`${appId}-${appVersion}-${obfuscatedPath}`);
|
|
211
|
+
const name = `projects/${projectId}/locations/global/mappingFiles/${uid}`;
|
|
212
|
+
const stream = fs.createReadStream(tmpArchive);
|
|
213
|
+
stream.on("error", (err) => {
|
|
214
|
+
logger_1.logger.debug(`Stream error on tmpArchive: ${err instanceof Error ? err.message : String(err)}`);
|
|
215
|
+
});
|
|
216
|
+
try {
|
|
217
|
+
const { bucket, object } = await gcs.uploadObject({
|
|
218
|
+
file: gcsFile,
|
|
219
|
+
stream,
|
|
220
|
+
}, bucketName);
|
|
221
|
+
const fileUri = `gs://${bucket}/${object}`;
|
|
222
|
+
logger_1.logger.debug(`Uploaded mapping file ${filePath} to ${fileUri}`);
|
|
223
|
+
await registerSourceMap({
|
|
224
|
+
name,
|
|
225
|
+
appId,
|
|
226
|
+
version: appVersion,
|
|
227
|
+
obfuscatedFilePath: `/${obfuscatedPath}`,
|
|
228
|
+
fileUri,
|
|
229
|
+
});
|
|
230
|
+
logger_1.logger.debug(`Registered mapping file ${filePath}`);
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
catch (e) {
|
|
234
|
+
if (attemptsRemaining === 0) {
|
|
235
|
+
(0, utils_1.logLabeledWarning)("crashlytics", `Failed to upload mapping file ${filePath}:\n${e instanceof Error ? e.message : String(e)}`);
|
|
236
|
+
}
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
stream.destroy();
|
|
241
|
+
try {
|
|
242
|
+
fs.rmSync(tmpArchive, { force: true });
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
logger_1.logger.debug(`Failed to delete temporary archive ${tmpArchive}: ${err instanceof Error ? err.message : String(err)}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function normalizeFileName(fileName) {
|
|
250
|
+
return fileName.replaceAll(/\//g, "-");
|
|
251
|
+
}
|
|
252
|
+
async function registerSourceMap(sourceMap) {
|
|
253
|
+
const client = new apiv2_1.Client({
|
|
254
|
+
urlPrefix: "https://firebasetelemetryadmin.googleapis.com",
|
|
255
|
+
auth: true,
|
|
256
|
+
apiVersion: "v1",
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
await client.patch(sourceMap.name, sourceMap, { queryParams: { allowMissing: "true" } });
|
|
260
|
+
logger_1.logger.debug(`Registered source map ${sourceMap.obfuscatedFilePath} with Firebase Telemetry service`);
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
if (e instanceof error_1.FirebaseError) {
|
|
264
|
+
if (e.status === 409) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
throw new error_1.FirebaseError(`Failed to register source map ${sourceMap.obfuscatedFilePath} with Firebase Telemetry service:\n${e instanceof Error ? e.message : String(e)}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ensureApis = ensureApis;
|
|
4
|
-
exports.ensureGIFApiTos = ensureGIFApiTos;
|
|
5
4
|
const api = require("../api");
|
|
6
|
-
const configstore_1 = require("../configstore");
|
|
7
5
|
const ensureApiEnabled_1 = require("../ensureApiEnabled");
|
|
8
6
|
const prefix = "dataconnect";
|
|
9
7
|
async function ensureApis(projectId, silent = false) {
|
|
@@ -12,14 +10,3 @@ async function ensureApis(projectId, silent = false) {
|
|
|
12
10
|
(0, ensureApiEnabled_1.ensure)(projectId, api.cloudSQLAdminOrigin(), prefix, silent),
|
|
13
11
|
]);
|
|
14
12
|
}
|
|
15
|
-
async function ensureGIFApiTos(projectId) {
|
|
16
|
-
if (configstore_1.configstore.get("gemini")) {
|
|
17
|
-
await (0, ensureApiEnabled_1.ensure)(projectId, api.cloudAiCompanionOrigin(), "");
|
|
18
|
-
}
|
|
19
|
-
else {
|
|
20
|
-
if (!(await (0, ensureApiEnabled_1.check)(projectId, api.cloudAiCompanionOrigin(), ""))) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
@@ -10,6 +10,7 @@ const projectUtils_1 = require("../../projectUtils");
|
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
11
|
const util = require("./util");
|
|
12
12
|
const experiments = require("../../experiments");
|
|
13
|
+
const logger_1 = require("../../logger");
|
|
13
14
|
async function default_1(context, options) {
|
|
14
15
|
if (Object.entries(context.backendConfigs).length === 0) {
|
|
15
16
|
return;
|
|
@@ -52,31 +53,46 @@ async function default_1(context, options) {
|
|
|
52
53
|
}));
|
|
53
54
|
await Promise.all(Object.values(context.backendConfigs).map(async (cfg) => {
|
|
54
55
|
const rootDir = options.projectRoot ?? process.cwd();
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
let localBuildScratchDir;
|
|
57
|
+
try {
|
|
58
|
+
const isLocalBuild = cfg.localBuild;
|
|
59
|
+
let outputFiles;
|
|
60
|
+
if (isLocalBuild) {
|
|
61
|
+
experiments.assertEnabled("apphostinglocalbuilds", "App Hosting local builds");
|
|
62
|
+
const localBuild = context.backendLocalBuilds[cfg.backendId];
|
|
63
|
+
outputFiles = localBuild?.outputFiles;
|
|
64
|
+
localBuildScratchDir = localBuild?.localBuildScratchDir;
|
|
65
|
+
if (!outputFiles || !localBuildScratchDir) {
|
|
66
|
+
throw new error_1.FirebaseError(`No local build output files found for ${cfg.backendId}`);
|
|
67
|
+
}
|
|
62
68
|
}
|
|
69
|
+
const zippedSourcePath = isLocalBuild
|
|
70
|
+
? await util.createLocalBuildTarArchive(cfg, localBuildScratchDir, outputFiles ?? [])
|
|
71
|
+
: await util.createSourceDeployArchive(cfg, rootDir);
|
|
72
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Zipped ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}`);
|
|
73
|
+
const backendLocation = context.backendLocations[cfg.backendId];
|
|
74
|
+
if (!backendLocation) {
|
|
75
|
+
throw new error_1.FirebaseError(`Failed to find location for backend ${cfg.backendId}. Please contact support with the contents of your firebase-debug.log to report your issue.`);
|
|
76
|
+
}
|
|
77
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
78
|
+
const bucketName = bucketsPerLocation[backendLocation];
|
|
79
|
+
const { bucket, object } = await gcs.uploadObject({
|
|
80
|
+
file: zippedSourcePath,
|
|
81
|
+
stream: fs.createReadStream(zippedSourcePath),
|
|
82
|
+
}, bucketName, isLocalBuild ? gcs.ContentType.TAR : gcs.ContentType.ZIP);
|
|
83
|
+
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
84
|
+
context.backendStorageUris[cfg.backendId] =
|
|
85
|
+
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
63
86
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
finally {
|
|
88
|
+
if (localBuildScratchDir && fs.existsSync(localBuildScratchDir)) {
|
|
89
|
+
try {
|
|
90
|
+
fs.rmSync(localBuildScratchDir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger_1.logger.debug(`Failed to clean up local build directory ${localBuildScratchDir}: ${err}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
71
96
|
}
|
|
72
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploading ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}...`);
|
|
73
|
-
const bucketName = bucketsPerLocation[backendLocation];
|
|
74
|
-
const { bucket, object } = await gcs.uploadObject({
|
|
75
|
-
file: zippedSourcePath,
|
|
76
|
-
stream: fs.createReadStream(zippedSourcePath),
|
|
77
|
-
}, bucketName, isLocalBuild ? gcs.ContentType.TAR : gcs.ContentType.ZIP);
|
|
78
|
-
(0, utils_1.logLabeledBullet)("apphosting", `Uploaded at gs://${bucket}/${object}`);
|
|
79
|
-
context.backendStorageUris[cfg.backendId] =
|
|
80
|
-
`gs://${bucketName}/${path.basename(zippedSourcePath)}`;
|
|
81
97
|
}));
|
|
82
98
|
}
|
|
@@ -4,7 +4,12 @@ exports.default = default_1;
|
|
|
4
4
|
exports.injectEnvVarsFromApphostingConfig = injectEnvVarsFromApphostingConfig;
|
|
5
5
|
exports.injectAutoInitEnvVars = injectAutoInitEnvVars;
|
|
6
6
|
exports.getBackendConfigs = getBackendConfigs;
|
|
7
|
+
const crypto = require("crypto");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
7
10
|
const path = require("path");
|
|
11
|
+
const fsAsync = require("../../fsAsync");
|
|
12
|
+
const util_1 = require("./util");
|
|
8
13
|
const backend_1 = require("../../apphosting/backend");
|
|
9
14
|
const apphosting_1 = require("../../gcp/apphosting");
|
|
10
15
|
const yaml_1 = require("../../apphosting/yaml");
|
|
@@ -132,21 +137,22 @@ async function default_1(context, options) {
|
|
|
132
137
|
(0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
|
|
133
138
|
await injectEnvVarsFromApphostingConfig(configs.filter((c) => c.backendId === cfg.backendId), options, buildEnv, runtimeEnv);
|
|
134
139
|
await injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv);
|
|
140
|
+
const rootDir = path.resolve(options.projectRoot || process.cwd());
|
|
141
|
+
const pathHash = crypto.createHash("md5").update(rootDir).digest("hex").substring(0, 8);
|
|
142
|
+
const localBuildScratchDir = path.join(os.tmpdir(), `apphosting-local-build-${cfg.backendId}-${pathHash}`);
|
|
135
143
|
try {
|
|
136
|
-
|
|
144
|
+
await prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg);
|
|
145
|
+
const { outputFiles, buildConfig } = await (0, localbuilds_1.localBuild)(projectId, localBuildScratchDir, buildEnv[cfg.backendId] || {}, {
|
|
137
146
|
nonInteractive: options.nonInteractive,
|
|
138
147
|
allowLocalBuildSecrets: !!options.allowLocalBuildSecrets,
|
|
139
148
|
});
|
|
140
|
-
if (outputFiles.length !== 1) {
|
|
141
|
-
throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
|
|
142
|
-
}
|
|
143
149
|
context.backendLocalBuilds[cfg.backendId] = {
|
|
144
|
-
|
|
150
|
+
outputFiles,
|
|
151
|
+
localBuildScratchDir,
|
|
145
152
|
buildConfig: {
|
|
146
153
|
...buildConfig,
|
|
147
154
|
env: mergeEnvVars(buildConfig.env || [], runtimeEnv[cfg.backendId] || {}),
|
|
148
155
|
},
|
|
149
|
-
annotations,
|
|
150
156
|
};
|
|
151
157
|
}
|
|
152
158
|
catch (e) {
|
|
@@ -244,3 +250,19 @@ async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
|
|
|
244
250
|
(0, utils_1.logLabeledWarning)("apphosting", `Unable to verify App Hosting service agent permissions for ${p4saEmail}. If you encounter a PERMISSION_DENIED error during rollout, please ensure the service agent has the "Storage Object Viewer" role.`);
|
|
245
251
|
}
|
|
246
252
|
}
|
|
253
|
+
async function prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg) {
|
|
254
|
+
const ignore = (0, util_1.resolveIgnorePatterns)(cfg);
|
|
255
|
+
fs.rmSync(localBuildScratchDir, { recursive: true, force: true });
|
|
256
|
+
fs.mkdirSync(localBuildScratchDir, { recursive: true });
|
|
257
|
+
const filesToCopy = await fsAsync.readdirRecursive({
|
|
258
|
+
path: rootDir,
|
|
259
|
+
ignoreStrings: ignore,
|
|
260
|
+
supportGitIgnore: true,
|
|
261
|
+
});
|
|
262
|
+
for (const file of filesToCopy) {
|
|
263
|
+
const relativePath = path.relative(rootDir, file.name);
|
|
264
|
+
const destPath = path.join(localBuildScratchDir, relativePath);
|
|
265
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
266
|
+
fs.copyFileSync(file.name, destPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createLocalBuildTarArchive = createLocalBuildTarArchive;
|
|
4
4
|
exports.createSourceDeployArchive = createSourceDeployArchive;
|
|
5
|
+
exports.resolveIgnorePatterns = resolveIgnorePatterns;
|
|
5
6
|
const archiver = require("archiver");
|
|
6
7
|
const fs = require("fs");
|
|
7
8
|
const path = require("path");
|
|
@@ -9,25 +10,28 @@ const tar = require("tar");
|
|
|
9
10
|
const tmp = require("tmp");
|
|
10
11
|
const error_1 = require("../../error");
|
|
11
12
|
const fsAsync = require("../../fsAsync");
|
|
12
|
-
const
|
|
13
|
-
async function createLocalBuildTarArchive(config, rootDir,
|
|
13
|
+
const utils_1 = require("../../utils");
|
|
14
|
+
async function createLocalBuildTarArchive(config, rootDir, outputFiles) {
|
|
14
15
|
const tmpFile = tmp.fileSync({ prefix: `${config.backendId}-`, postfix: ".tar.gz" }).name;
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
path
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
const filesToPackage = outputFiles.length > 0 ? outputFiles : ["."];
|
|
17
|
+
const allFiles = [];
|
|
18
|
+
for (const fileOrDir of filesToPackage) {
|
|
19
|
+
const absolutePath = path.join(rootDir, fileOrDir);
|
|
20
|
+
if (!fs.existsSync(absolutePath)) {
|
|
21
|
+
(0, utils_1.logLabeledWarning)("apphosting", `Expected build output file or directory not found: ${fileOrDir}`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const stat = fs.statSync(absolutePath);
|
|
25
|
+
if (stat.isDirectory()) {
|
|
26
|
+
const rdrFiles = await fsAsync.readdirRecursive({
|
|
27
|
+
path: absolutePath,
|
|
28
|
+
ignoreStrings: ["firebase-debug.log", "firebase-debug.*.log"],
|
|
29
|
+
supportGitIgnore: false,
|
|
30
|
+
});
|
|
31
|
+
allFiles.push(...rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name)));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
allFiles.push(path.relative(rootDir, absolutePath));
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
try {
|
|
@@ -58,8 +62,7 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
58
62
|
});
|
|
59
63
|
const archive = archiver("zip");
|
|
60
64
|
const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
|
|
61
|
-
const ignore = config
|
|
62
|
-
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
65
|
+
const ignore = resolveIgnorePatterns(config);
|
|
63
66
|
try {
|
|
64
67
|
const files = await fsAsync.readdirRecursive({
|
|
65
68
|
path: targetDir,
|
|
@@ -76,10 +79,20 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
|
|
|
76
79
|
await pipeAsync(archive, fileStream);
|
|
77
80
|
}
|
|
78
81
|
catch (err) {
|
|
79
|
-
throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${err}`, { original: err, exit: 1 });
|
|
82
|
+
throw new error_1.FirebaseError(`Could not read source directory. Remove links and shortcuts and try again. Original: ${String(err)}`, { original: err, exit: 1 });
|
|
80
83
|
}
|
|
81
84
|
return tmpFile;
|
|
82
85
|
}
|
|
86
|
+
function resolveIgnorePatterns(config, skipDefaultNodeModules = false) {
|
|
87
|
+
const ignore = config.ignore
|
|
88
|
+
? [...config.ignore]
|
|
89
|
+
: skipDefaultNodeModules
|
|
90
|
+
? [".git"]
|
|
91
|
+
: ["node_modules", ".git"];
|
|
92
|
+
ignore.push("firebase-debug.log", "firebase-debug.*.log");
|
|
93
|
+
ignore.push(".local_build_*");
|
|
94
|
+
return ignore;
|
|
95
|
+
}
|
|
83
96
|
async function pipeAsync(from, to) {
|
|
84
97
|
from.pipe(to);
|
|
85
98
|
await from.finalize();
|
|
@@ -249,12 +249,11 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
251
|
try {
|
|
252
|
-
const fullEndpoint = { ...endpoint, id };
|
|
253
252
|
if (build.isBlockingTriggered(endpoint)) {
|
|
254
|
-
resolvedRegion = resolveRegionForBlockingTrigger(
|
|
253
|
+
resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
|
|
255
254
|
}
|
|
256
255
|
else if (build.isEventTriggered(endpoint)) {
|
|
257
|
-
resolvedRegion = await resolveRegionForEventTrigger(
|
|
256
|
+
resolvedRegion = await resolveRegionForEventTrigger(endpoint.project, endpoint.eventTrigger);
|
|
258
257
|
}
|
|
259
258
|
}
|
|
260
259
|
catch (err) {
|
|
@@ -265,18 +264,17 @@ async function resolveDefaultRegionsForBuild(buildObj, have) {
|
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
}
|
|
268
|
-
function resolveRegionForBlockingTrigger(
|
|
269
|
-
const eventType =
|
|
267
|
+
function resolveRegionForBlockingTrigger(blockingTrigger) {
|
|
268
|
+
const eventType = blockingTrigger.eventType;
|
|
270
269
|
if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
|
|
271
270
|
return "us-east1";
|
|
272
271
|
}
|
|
273
|
-
if ((0, ailogic_1.
|
|
272
|
+
if ((0, ailogic_1.isGlobalAILogicTrigger)(blockingTrigger)) {
|
|
274
273
|
return "us-east1";
|
|
275
274
|
}
|
|
276
275
|
return exports.DEFAULT_FUNCTION_REGION;
|
|
277
276
|
}
|
|
278
|
-
async function resolveRegionForEventTrigger(
|
|
279
|
-
const eventTrigger = endpoint.eventTrigger;
|
|
277
|
+
async function resolveRegionForEventTrigger(project, eventTrigger) {
|
|
280
278
|
const eventType = eventTrigger.eventType;
|
|
281
279
|
if (eventType.startsWith("google.cloud.pubsub.") ||
|
|
282
280
|
eventType.startsWith("providers/cloud.auth/eventTypes/") ||
|
|
@@ -289,7 +287,7 @@ async function resolveRegionForEventTrigger(endpoint) {
|
|
|
289
287
|
if (eventType.startsWith("google.cloud.firestore.")) {
|
|
290
288
|
try {
|
|
291
289
|
const databaseId = eventTrigger.eventFilters?.database || "(default)";
|
|
292
|
-
const db = await (0, firestore_1.getDatabase)(
|
|
290
|
+
const db = await (0, firestore_1.getDatabase)(project, databaseId);
|
|
293
291
|
const locationId = db.locationId.toLowerCase();
|
|
294
292
|
if (locationId === "nam5" || locationId === "nam7")
|
|
295
293
|
return "us-central1";
|
|
@@ -326,7 +324,7 @@ async function resolveRegionForEventTrigger(endpoint) {
|
|
|
326
324
|
try {
|
|
327
325
|
const instanceName = eventTrigger.eventFilters?.instance;
|
|
328
326
|
if (instanceName) {
|
|
329
|
-
const details = await (0, database_1.getDatabaseInstanceDetails)(
|
|
327
|
+
const details = await (0, database_1.getDatabaseInstanceDetails)(project, instanceName);
|
|
330
328
|
if (details.location && details.location !== "-") {
|
|
331
329
|
return details.location.toLowerCase();
|
|
332
330
|
}
|
|
@@ -2,7 +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.
|
|
5
|
+
exports.isGlobalAILogicTrigger = isGlobalAILogicTrigger;
|
|
6
6
|
const backend = require("../backend");
|
|
7
7
|
const error_1 = require("../../../error");
|
|
8
8
|
const ailogicApi = require("../../../gcp/ailogic");
|
|
@@ -20,11 +20,9 @@ function isAILogicEvent(endpoint) {
|
|
|
20
20
|
}
|
|
21
21
|
return exports.AI_LOGIC_EVENTS.includes(endpoint.blockingTrigger.eventType);
|
|
22
22
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
return !endpoint.blockingTrigger.options?.regionalWebhook;
|
|
23
|
+
function isGlobalAILogicTrigger(blockingTrigger) {
|
|
24
|
+
return (exports.AI_LOGIC_EVENTS.includes(blockingTrigger.eventType) &&
|
|
25
|
+
!blockingTrigger.options?.regionalWebhook);
|
|
28
26
|
}
|
|
29
27
|
class AILogicService {
|
|
30
28
|
constructor() {
|