firebase-tools 15.18.0 → 15.19.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.
@@ -40,10 +40,16 @@ const PROVIDER_ID_INDEX_MAP = new Map([
40
40
  ["facebook.com", 11],
41
41
  ["twitter.com", 15],
42
42
  ["github.com", 19],
43
+ ["apple.com", 28],
44
+ ["microsoft.com", 32],
45
+ ["gc.apple.com", 36],
46
+ ["playgames.google.com", 40],
47
+ ["linkedin.com", 44],
48
+ ["yahoo.com", 48],
43
49
  ]);
44
- function escapeComma(str) {
45
- if (str.includes(",")) {
46
- return `"${str}"`;
50
+ function escapeCsv(str) {
51
+ if (str.includes(",") || str.includes('"') || str.includes("\n") || str.includes("\r")) {
52
+ return `"${str.replace(/"/g, '""')}"`;
47
53
  }
48
54
  return str;
49
55
  }
@@ -53,17 +59,18 @@ function convertToNormalBase64(data) {
53
59
  function addProviderUserInfo(providerInfo, arr, startPos) {
54
60
  arr[startPos] = providerInfo.rawId;
55
61
  arr[startPos + 1] = providerInfo.email || "";
56
- arr[startPos + 2] = escapeComma(providerInfo.displayName || "");
62
+ arr[startPos + 2] = escapeCsv(providerInfo.displayName || "");
57
63
  arr[startPos + 3] = providerInfo.photoUrl || "";
58
64
  }
59
65
  function transUserToArray(user) {
60
- const arr = Array(27).fill("");
66
+ const arrLength = Math.max(...PROVIDER_ID_INDEX_MAP.values()) + 4;
67
+ const arr = Array(arrLength).fill("");
61
68
  arr[0] = user.localId;
62
69
  arr[1] = user.email || "";
63
70
  arr[2] = user.emailVerified || false;
64
71
  arr[3] = convertToNormalBase64(user.passwordHash || "");
65
72
  arr[4] = convertToNormalBase64(user.salt || "");
66
- arr[5] = escapeComma(user.displayName || "");
73
+ arr[5] = escapeCsv(user.displayName || "");
67
74
  arr[6] = user.photoUrl || "";
68
75
  for (let i = 0; i < (!user.providerUserInfo ? 0 : user.providerUserInfo.length); i++) {
69
76
  const providerInfo = user.providerUserInfo[i];
@@ -32,7 +32,18 @@ const ALLOWED_JSON_KEYS_RENAMING = {
32
32
  lastSignedInAt: "lastLoginAt",
33
33
  };
34
34
  const ALLOWED_PROVIDER_USER_INFO_KEYS = ["providerId", "rawId", "email", "displayName", "photoUrl"];
35
- const ALLOWED_PROVIDER_IDS = ["google.com", "facebook.com", "twitter.com", "github.com"];
35
+ const ALLOWED_PROVIDER_IDS = [
36
+ "google.com",
37
+ "facebook.com",
38
+ "twitter.com",
39
+ "github.com",
40
+ "apple.com",
41
+ "microsoft.com",
42
+ "gc.apple.com",
43
+ "playgames.google.com",
44
+ "linkedin.com",
45
+ "yahoo.com",
46
+ ];
36
47
  function isValidBase64(str) {
37
48
  const expected = Buffer.from(str, "base64").toString("base64");
38
49
  if (str.length < expected.length && !str.endsWith("=")) {
@@ -125,6 +136,12 @@ function transArrayToUser(arr) {
125
136
  addProviderUserInfo(user, "facebook.com", arr.slice(11, 15));
126
137
  addProviderUserInfo(user, "twitter.com", arr.slice(15, 19));
127
138
  addProviderUserInfo(user, "github.com", arr.slice(19, 23));
139
+ addProviderUserInfo(user, "apple.com", arr.slice(28, 32));
140
+ addProviderUserInfo(user, "microsoft.com", arr.slice(32, 36));
141
+ addProviderUserInfo(user, "gc.apple.com", arr.slice(36, 40));
142
+ addProviderUserInfo(user, "playgames.google.com", arr.slice(40, 44));
143
+ addProviderUserInfo(user, "linkedin.com", arr.slice(44, 48));
144
+ addProviderUserInfo(user, "yahoo.com", arr.slice(48, 52));
128
145
  if (user.passwordHash && !isValidBase64(user.passwordHash)) {
129
146
  return {
130
147
  error: "Password hash should be base64 encoded.",
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
3
+ exports.LOCAL_BUILD_DIR_NAME = exports.ALLOWED_DEPLOY_METHODS = exports.DEFAULT_DEPLOY_METHOD = exports.DEFAULT_LOCATION = void 0;
4
4
  exports.DEFAULT_LOCATION = "us-east4";
5
5
  exports.DEFAULT_DEPLOY_METHOD = "github";
6
6
  exports.ALLOWED_DEPLOY_METHODS = [{ name: "Deploy using github", value: "github" }];
7
+ exports.LOCAL_BUILD_DIR_NAME = ".local_build";
@@ -11,21 +11,23 @@ const error_1 = require("../error");
11
11
  const logger_1 = require("../logger");
12
12
  const utils_1 = require("../utils");
13
13
  const universalMakerDownload_1 = require("./universalMakerDownload");
14
- async function runUniversalMaker(projectRoot, framework) {
14
+ async function runUniversalMaker(projectRoot, addedEnv) {
15
15
  const universalMakerBinary = await (0, universalMakerDownload_1.getOrDownloadUniversalMaker)();
16
- executeUniversalMakerBinary(universalMakerBinary, projectRoot);
17
- return processUniversalMakerOutput(projectRoot, framework);
16
+ executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv);
17
+ return processUniversalMakerOutput(projectRoot);
18
18
  }
19
- function executeUniversalMakerBinary(universalMakerBinary, projectRoot) {
19
+ function executeUniversalMakerBinary(universalMakerBinary, projectRoot, addedEnv) {
20
20
  try {
21
- const bundleOutput = path.join(projectRoot, "bundle_output");
22
- fs.removeSync(bundleOutput);
23
- fs.ensureDirSync(bundleOutput);
21
+ const targetAppHosting = path.join(projectRoot, ".apphosting");
22
+ fs.removeSync(targetAppHosting);
23
+ fs.ensureDirSync(targetAppHosting);
24
24
  const res = childProcess.spawnSync(universalMakerBinary, ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], {
25
+ cwd: projectRoot,
25
26
  env: {
26
27
  ...process.env,
28
+ ...addedEnv,
27
29
  X_GOOGLE_TARGET_PLATFORM: "fah",
28
- FIREBASE_OUTPUT_BUNDLE_DIR: bundleOutput,
30
+ FIREBASE_OUTPUT_BUNDLE_DIR: targetAppHosting,
29
31
  },
30
32
  stdio: "pipe",
31
33
  });
@@ -57,33 +59,16 @@ function parseBundleYaml(projectRoot, defaultRunCommand) {
57
59
  const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8");
58
60
  const bundleData = (0, utils_1.wrappedSafeLoad)(bundleRaw);
59
61
  const runCommand = bundleData?.runConfig?.runCommand ?? defaultRunCommand;
60
- const outputFiles = bundleData?.outputFiles?.serverApp?.include;
61
- if (!outputFiles) {
62
- throw new error_1.FirebaseError("Failed to resolve build artifacts. Ensure Universal Maker produced a valid bundle.yaml with outputFiles.");
63
- }
62
+ const outputFiles = bundleData?.outputFiles?.serverApp?.include ?? [];
64
63
  return { runCommand, outputFiles };
65
64
  }
66
- function processUniversalMakerOutput(projectRoot, framework) {
65
+ function processUniversalMakerOutput(projectRoot) {
67
66
  const outputFilePath = path.join(projectRoot, "build_output.json");
68
67
  if (!fs.existsSync(outputFilePath)) {
69
68
  throw new error_1.FirebaseError(`Universal Maker did not produce the expected output file at ${outputFilePath}`);
70
69
  }
71
70
  const outputRaw = fs.readFileSync(outputFilePath, "utf-8");
72
71
  fs.unlinkSync(outputFilePath);
73
- const bundleOutput = path.join(projectRoot, "bundle_output");
74
- const targetAppHosting = path.join(projectRoot, ".apphosting");
75
- if (fs.existsSync(bundleOutput)) {
76
- fs.ensureDirSync(targetAppHosting);
77
- const files = fs.readdirSync(bundleOutput);
78
- for (const file of files) {
79
- const dest = path.join(targetAppHosting, file);
80
- if (fs.existsSync(dest)) {
81
- fs.removeSync(dest);
82
- }
83
- fs.moveSync(path.join(bundleOutput, file), dest);
84
- }
85
- fs.removeSync(bundleOutput);
86
- }
87
72
  let umOutput;
88
73
  try {
89
74
  umOutput = JSON.parse(outputRaw);
@@ -94,11 +79,6 @@ function processUniversalMakerOutput(projectRoot, framework) {
94
79
  const defaultRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`;
95
80
  const { runCommand: finalRunCommand, outputFiles: finalOutputFiles } = parseBundleYaml(projectRoot, defaultRunCommand);
96
81
  return {
97
- metadata: {
98
- language: umOutput.language,
99
- runtime: umOutput.runtime,
100
- framework: framework || "nextjs",
101
- },
102
82
  runConfig: {
103
83
  runCommand: finalRunCommand,
104
84
  environmentVariables: Object.entries(umOutput.envVars || {})
@@ -116,7 +96,7 @@ function processUniversalMakerOutput(projectRoot, framework) {
116
96
  },
117
97
  };
118
98
  }
119
- async function localBuild(projectId, projectRoot, framework, env = {}, options) {
99
+ async function localBuild(projectId, projectRoot, env = {}, options) {
120
100
  const hasBuildAvailableSecrets = Object.values(env).some((v) => v.secret && (!v.availability || v.availability.includes("BUILD")));
121
101
  if (hasBuildAvailableSecrets && !options?.allowLocalBuildSecrets) {
122
102
  if (options?.nonInteractive) {
@@ -129,26 +109,8 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
129
109
  throw new error_1.FirebaseError("Cancelled local build due to BUILD-available secrets.");
130
110
  }
131
111
  }
132
- const originalEnv = { ...process.env };
133
112
  const addedEnv = await toProcessEnv(projectId, env);
134
- for (const [key, value] of Object.entries(addedEnv)) {
135
- process.env[key] = value;
136
- }
137
- let apphostingBuildOutput;
138
- try {
139
- apphostingBuildOutput = await runUniversalMaker(projectRoot, framework);
140
- }
141
- finally {
142
- for (const key in process.env) {
143
- if (!(key in originalEnv)) {
144
- delete process.env[key];
145
- }
146
- }
147
- for (const [key, value] of Object.entries(originalEnv)) {
148
- process.env[key] = value;
149
- }
150
- }
151
- const annotations = Object.fromEntries(Object.entries(apphostingBuildOutput.metadata).map(([key, value]) => [key, String(value)]));
113
+ const apphostingBuildOutput = await runUniversalMaker(projectRoot, addedEnv);
152
114
  const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
153
115
  variable,
154
116
  value,
@@ -156,7 +118,6 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
156
118
  }));
157
119
  return {
158
120
  outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [],
159
- annotations,
160
121
  buildConfig: {
161
122
  runCommand: apphostingBuildOutput.runConfig.runCommand,
162
123
  env: discoveredEnv ?? [],
@@ -164,18 +125,14 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
164
125
  };
165
126
  }
166
127
  async function toProcessEnv(projectId, env) {
167
- const entries = await Promise.all(Object.entries(env).map(async ([key, value]) => {
168
- if (value.availability && !value.availability.includes("BUILD")) {
169
- return null;
170
- }
171
- if (value.secret) {
172
- const resolvedValue = await (0, index_1.loadSecret)(projectId, value.secret);
173
- return [key, resolvedValue];
174
- }
175
- else {
176
- return [key, value.value || ""];
177
- }
128
+ const buildVars = Object.entries(env).filter(([, value]) => {
129
+ return !value.availability || value.availability.includes("BUILD");
130
+ });
131
+ const resolvedEntries = await Promise.all(buildVars.map(async ([key, value]) => {
132
+ const resolvedValue = value.secret
133
+ ? await (0, index_1.loadSecret)(projectId, value.secret)
134
+ : value.value || "";
135
+ return [key, resolvedValue];
178
136
  }));
179
- const filteredEntries = entries.filter((entry) => entry !== null);
180
- return Object.fromEntries(filteredEntries);
137
+ return Object.fromEntries(resolvedEntries);
181
138
  }
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "darwin_arm64": {
3
3
  "version": "1.0.0",
4
- "expectedSize": 16111618,
5
- "expectedChecksumSHA256": "4b77d02a5f80f26d9bd1428f388c293c1fb264995d75b51c7d50fec7c87bcf58",
4
+ "expectedSize": 16266914,
5
+ "expectedChecksumSHA256": "72dae4eeefedc096649134f6ee592248c17dba5cdaec5bf679f367b2093d41cc",
6
6
  "remoteUrl": "https://artifactregistry.googleapis.com/download/v1/projects/serverless-runtimes-qa/locations/us-central1/repositories/universal-maker/files/darwin-arm64%3A1.0.0%3Auniversal_maker:download?alt=media",
7
7
  "downloadPathRelativeToCacheDir": "universal-maker-darwin-arm64-1.0.0"
8
8
  },
9
9
  "linux_x64": {
10
10
  "version": "1.0.0",
11
- "expectedSize": 16856277,
12
- "expectedChecksumSHA256": "dfc8357b8ce23ef1897e5590d4390f166e27734f241b770f721d68273118845c",
11
+ "expectedSize": 17023802,
12
+ "expectedChecksumSHA256": "ba74d2d2ce61d1b773f5b9eefeb09ef48a6890f2767581b57f9871869693823d",
13
13
  "remoteUrl": "https://artifactregistry.googleapis.com/download/v1/projects/serverless-runtimes-qa/locations/us-central1/repositories/universal-maker/files/x86-64%3A1.0.0%3Auniversal_maker:download?alt=media",
14
14
  "downloadPathRelativeToCacheDir": "universal-maker-linux-x64-1.0.0"
15
15
  }
@@ -103,6 +103,9 @@ exports.command = new command_1.Command("dataconnect:sql:shell")
103
103
  user: username,
104
104
  database: databaseId,
105
105
  });
106
+ pool.on("error", (err) => {
107
+ logger_1.logger.debug("PostgreSQL pool error:", err);
108
+ });
106
109
  const conn = await pool.connect();
107
110
  await conn.query(`SET search_path TO "${schemaName}"`);
108
111
  logger_1.logger.info(`Logged in as ${username}`);
@@ -110,8 +113,23 @@ exports.command = new command_1.Command("dataconnect:sql:shell")
110
113
  logger_1.logger.info(clc.gray("Type your your SQL query or '.exit' to quit, queries should end with ';' or add empty line to execute."));
111
114
  await mainShellLoop(conn);
112
115
  logger_1.logger.info(clc.yellow("Exiting shell..."));
113
- conn.release();
114
- await pool.end();
115
- connector.close();
116
+ try {
117
+ conn.release();
118
+ }
119
+ catch (err) {
120
+ logger_1.logger.debug("Error releasing pg connection:", err);
121
+ }
122
+ try {
123
+ connector.close();
124
+ }
125
+ catch (err) {
126
+ logger_1.logger.debug("Error closing Cloud SQL connector:", err);
127
+ }
128
+ try {
129
+ await pool.end();
130
+ }
131
+ catch (err) {
132
+ logger_1.logger.debug("Error ending pg pool:", err);
133
+ }
116
134
  return { projectId };
117
135
  });
@@ -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 builtAppDir;
56
- const isLocalBuild = !!cfg.localBuild;
57
- if (isLocalBuild) {
58
- experiments.assertEnabled("apphostinglocalbuilds", "App Hosting local builds");
59
- builtAppDir = context.backendLocalBuilds[cfg.backendId].buildDir;
60
- if (!builtAppDir) {
61
- throw new error_1.FirebaseError(`No local build dir found for ${cfg.backendId}`);
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
- const zippedSourcePath = isLocalBuild
65
- ? await util.createLocalBuildTarArchive(cfg, rootDir, builtAppDir)
66
- : await util.createSourceDeployArchive(cfg, rootDir);
67
- (0, utils_1.logLabeledBullet)("apphosting", `Zipped ${isLocalBuild ? "built app" : "source"} for backend ${cfg.backendId}`);
68
- const backendLocation = context.backendLocations[cfg.backendId];
69
- if (!backendLocation) {
70
- 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.`);
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,10 @@ exports.default = default_1;
4
4
  exports.injectEnvVarsFromApphostingConfig = injectEnvVarsFromApphostingConfig;
5
5
  exports.injectAutoInitEnvVars = injectAutoInitEnvVars;
6
6
  exports.getBackendConfigs = getBackendConfigs;
7
+ const fs = require("fs");
7
8
  const path = require("path");
9
+ const fsAsync = require("../../fsAsync");
10
+ const util_1 = require("./util");
8
11
  const backend_1 = require("../../apphosting/backend");
9
12
  const apphosting_1 = require("../../gcp/apphosting");
10
13
  const yaml_1 = require("../../apphosting/yaml");
@@ -16,6 +19,7 @@ const getProjectNumber_1 = require("../../getProjectNumber");
16
19
  const prompt_1 = require("../../prompt");
17
20
  const utils_1 = require("../../utils");
18
21
  const localbuilds_1 = require("../../apphosting/localbuilds");
22
+ const constants_1 = require("../../apphosting/constants");
19
23
  const error_1 = require("../../error");
20
24
  const managementApps = require("../../management/apps");
21
25
  const utils_2 = require("../../apphosting/utils");
@@ -132,21 +136,21 @@ async function default_1(context, options) {
132
136
  (0, utils_1.logLabeledBullet)("apphosting", `Starting local build for backend ${cfg.backendId}`);
133
137
  await injectEnvVarsFromApphostingConfig(configs.filter((c) => c.backendId === cfg.backendId), options, buildEnv, runtimeEnv);
134
138
  await injectAutoInitEnvVars(cfg, backends, buildEnv, runtimeEnv);
139
+ const rootDir = options.projectRoot || process.cwd();
140
+ const localBuildScratchDir = path.join(rootDir, `${constants_1.LOCAL_BUILD_DIR_NAME}_${cfg.backendId}`);
135
141
  try {
136
- const { outputFiles, annotations, buildConfig } = await (0, localbuilds_1.localBuild)(projectId, options.projectRoot || "./", "nextjs", buildEnv[cfg.backendId] || {}, {
142
+ await prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg);
143
+ const { outputFiles, buildConfig } = await (0, localbuilds_1.localBuild)(projectId, localBuildScratchDir, buildEnv[cfg.backendId] || {}, {
137
144
  nonInteractive: options.nonInteractive,
138
145
  allowLocalBuildSecrets: !!options.allowLocalBuildSecrets,
139
146
  });
140
- if (outputFiles.length !== 1) {
141
- throw new error_1.FirebaseError(`Local build for backend ${cfg.backendId} failed: No output files found.`);
142
- }
143
147
  context.backendLocalBuilds[cfg.backendId] = {
144
- buildDir: outputFiles[0],
148
+ outputFiles,
149
+ localBuildScratchDir,
145
150
  buildConfig: {
146
151
  ...buildConfig,
147
152
  env: mergeEnvVars(buildConfig.env || [], runtimeEnv[cfg.backendId] || {}),
148
153
  },
149
- annotations,
150
154
  };
151
155
  }
152
156
  catch (e) {
@@ -244,3 +248,21 @@ async function ensureAppHostingServiceAgentRoles(projectId, projectNumber) {
244
248
  (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
249
  }
246
250
  }
251
+ async function prepareLocalBuildScratchDirectory(rootDir, localBuildScratchDir, cfg) {
252
+ const ignore = (0, util_1.resolveIgnorePatterns)(cfg);
253
+ if (fs.existsSync(localBuildScratchDir)) {
254
+ throw new error_1.FirebaseError(`The local build scratch directory '${localBuildScratchDir}' already exists. Please delete it and try again.`);
255
+ }
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 config_1 = require("../../apphosting/config");
13
- async function createLocalBuildTarArchive(config, rootDir, targetSubDir) {
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 targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
16
- const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
17
- const rdrFiles = await fsAsync.readdirRecursive({
18
- path: targetDir,
19
- ignoreStrings: ignore,
20
- supportGitIgnore: true,
21
- });
22
- const allFiles = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
23
- if (targetSubDir) {
24
- const defaultFiles = fs.readdirSync(rootDir).filter((file) => {
25
- return config_1.APPHOSTING_YAML_FILE_REGEX.test(file);
26
- });
27
- for (const file of defaultFiles) {
28
- if (!allFiles.includes(file)) {
29
- allFiles.push(file);
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.ignore || ["node_modules", ".git"];
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(fullEndpoint);
253
+ resolvedRegion = resolveRegionForBlockingTrigger(endpoint.blockingTrigger);
255
254
  }
256
255
  else if (build.isEventTriggered(endpoint)) {
257
- resolvedRegion = await resolveRegionForEventTrigger(fullEndpoint);
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(endpoint) {
269
- const eventType = endpoint.blockingTrigger.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.isGlobalAILogicEndpoint)(endpoint)) {
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(endpoint) {
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)(endpoint.project, databaseId);
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)(endpoint.project, instanceName);
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.isGlobalAILogicEndpoint = isGlobalAILogicEndpoint;
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 isGlobalAILogicEndpoint(endpoint) {
24
- if (!isAILogicEvent(endpoint)) {
25
- return false;
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() {