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.
Files changed (54) hide show
  1. package/lib/api.js +1 -1
  2. package/lib/apphosting/localbuilds.js +116 -5
  3. package/lib/apphosting/universalMakerDownload.js +72 -0
  4. package/lib/apphosting/universalMakerInfo.json +16 -0
  5. package/lib/archiveDirectory.js +1 -1
  6. package/lib/deploy/apphosting/release.js +27 -13
  7. package/lib/deploy/apphosting/util.js +4 -19
  8. package/lib/deploy/functions/backend.js +2 -1
  9. package/lib/deploy/functions/prepare.js +140 -35
  10. package/lib/deploy/functions/prepareFunctionsUpload.js +1 -1
  11. package/lib/deploy/functions/services/ailogic.js +17 -0
  12. package/lib/deploy/functions/services/database.js +16 -0
  13. package/lib/deploy/functions/services/firestore.js +1 -0
  14. package/lib/deploy/functions/services/storage.js +15 -1
  15. package/lib/deploy/functions/triggerRegionHelper.js +111 -2
  16. package/lib/deploy/functions/validate.js +1 -1
  17. package/lib/downloadUtils.js +24 -0
  18. package/lib/emulator/download.js +2 -24
  19. package/lib/emulator/downloadableEmulatorInfo.json +31 -31
  20. package/lib/emulator/functionsEmulatorShared.js +2 -1
  21. package/lib/env.js +5 -1
  22. package/lib/firestore/api-sort.js +22 -0
  23. package/lib/firestore/api-types.js +11 -1
  24. package/lib/firestore/api.js +21 -1
  25. package/lib/firestore/fsConfig.js +8 -0
  26. package/lib/firestore/pretty-print.js +26 -8
  27. package/lib/frameworks/next/index.js +1 -1
  28. package/lib/fsAsync.js +53 -10
  29. package/lib/mcp/apps/deploy/mcp-app.js +120 -0
  30. package/lib/mcp/apps/deploy/vite.config.js +16 -0
  31. package/lib/mcp/apps/init/mcp-app.js +230 -0
  32. package/lib/mcp/apps/init/vite.config.js +16 -0
  33. package/lib/mcp/apps/update_environment/mcp-app.js +38 -36
  34. package/lib/mcp/apps/update_environment/vite.config.js +16 -0
  35. package/lib/mcp/index.js +16 -5
  36. package/lib/mcp/resources/deploy_ui.js +31 -0
  37. package/lib/mcp/resources/index.js +4 -0
  38. package/lib/mcp/resources/init_ui.js +31 -0
  39. package/lib/mcp/resources/update_environment_ui.js +3 -3
  40. package/lib/mcp/tools/auth/get_users.js +1 -1
  41. package/lib/mcp/tools/core/deploy.js +87 -0
  42. package/lib/mcp/tools/core/deploy_status.js +32 -0
  43. package/lib/mcp/tools/core/index.js +4 -0
  44. package/lib/mcp/tools/core/init.js +3 -0
  45. package/lib/mcp/tools/core/update_environment.js +3 -0
  46. package/lib/mcp/tools/firestore/query_collection.js +1 -1
  47. package/lib/mcp/tools/functions/list_functions.js +2 -2
  48. package/lib/mcp/util/jobs.js +31 -0
  49. package/lib/mcp/util.js +5 -4
  50. package/lib/tsconfig.compile.tsbuildinfo +1 -1
  51. package/lib/tsconfig.publish.tsbuildinfo +1 -1
  52. package/package.json +3 -3
  53. package/templates/init/functions/dart/pubspec.yaml +1 -1
  54. package/templates/init/functions/dart/server.dart +2 -2
package/lib/api.js CHANGED
@@ -66,7 +66,7 @@ const functionsV2Origin = () => utils.envOverride("FIREBASE_FUNCTIONS_V2_URL", "
66
66
  exports.functionsV2Origin = functionsV2Origin;
67
67
  const runOrigin = () => utils.envOverride("CLOUD_RUN_URL", "https://run.googleapis.com");
68
68
  exports.runOrigin = runOrigin;
69
- const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "us-central1");
69
+ const functionsDefaultRegion = () => utils.envOverride("FIREBASE_FUNCTIONS_DEFAULT_REGION", "REGION_TBD");
70
70
  exports.functionsDefaultRegion = functionsDefaultRegion;
71
71
  const cloudbuildOrigin = () => utils.envOverride("FIREBASE_CLOUDBUILD_URL", "https://cloudbuild.googleapis.com");
72
72
  exports.cloudbuildOrigin = cloudbuildOrigin;
@@ -1,10 +1,121 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runUniversalMaker = runUniversalMaker;
3
4
  exports.localBuild = localBuild;
4
- const build_1 = require("@apphosting/build");
5
- const secrets_1 = require("./secrets");
5
+ const childProcess = require("child_process");
6
+ const fs = require("fs-extra");
7
+ const path = require("path");
8
+ const index_1 = require("./secrets/index");
6
9
  const prompt_1 = require("../prompt");
7
10
  const error_1 = require("../error");
11
+ const logger_1 = require("../logger");
12
+ const utils_1 = require("../utils");
13
+ const universalMakerDownload_1 = require("./universalMakerDownload");
14
+ async function runUniversalMaker(projectRoot, framework) {
15
+ const universalMakerBinary = await (0, universalMakerDownload_1.getOrDownloadUniversalMaker)();
16
+ executeUniversalMakerBinary(universalMakerBinary, projectRoot);
17
+ return processUniversalMakerOutput(projectRoot, framework);
18
+ }
19
+ function executeUniversalMakerBinary(universalMakerBinary, projectRoot) {
20
+ try {
21
+ const bundleOutput = path.join(projectRoot, "bundle_output");
22
+ fs.removeSync(bundleOutput);
23
+ fs.ensureDirSync(bundleOutput);
24
+ const res = childProcess.spawnSync(universalMakerBinary, ["-application_dir", projectRoot, "-output_dir", projectRoot, "-output_format", "json"], {
25
+ env: {
26
+ ...process.env,
27
+ X_GOOGLE_TARGET_PLATFORM: "fah",
28
+ FIREBASE_OUTPUT_BUNDLE_DIR: bundleOutput,
29
+ },
30
+ stdio: "pipe",
31
+ });
32
+ if (res.stdout) {
33
+ logger_1.logger.debug("[Universal Maker stdout]:\n" + res.stdout.toString());
34
+ }
35
+ if (res.stderr) {
36
+ logger_1.logger.debug("[Universal Maker stderr]:\n" + res.stderr.toString());
37
+ }
38
+ if (res.error) {
39
+ throw res.error;
40
+ }
41
+ if (res.status !== 0) {
42
+ throw new error_1.FirebaseError(`Universal Maker failed with exit code ${res.status ?? "unknown"}.`);
43
+ }
44
+ }
45
+ catch (e) {
46
+ if (e && typeof e === "object" && "code" in e && e.code === "EACCES") {
47
+ throw new error_1.FirebaseError(`Failed to execute the Universal Maker binary at ${universalMakerBinary} due to permission constraints. Please assure you have set execution permissions (e.g., chmod +x) on the file.`);
48
+ }
49
+ throw e;
50
+ }
51
+ }
52
+ function parseBundleYaml(projectRoot, defaultRunCommand) {
53
+ const bundleYamlPath = path.join(projectRoot, ".apphosting", "bundle.yaml");
54
+ if (!fs.existsSync(bundleYamlPath)) {
55
+ throw new error_1.FirebaseError("Failed to resolve build artifacts. Ensure Universal Maker produced a valid bundle.yaml with outputFiles.");
56
+ }
57
+ const bundleRaw = fs.readFileSync(bundleYamlPath, "utf-8");
58
+ const bundleData = (0, utils_1.wrappedSafeLoad)(bundleRaw);
59
+ 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
+ }
64
+ return { runCommand, outputFiles };
65
+ }
66
+ function processUniversalMakerOutput(projectRoot, framework) {
67
+ const outputFilePath = path.join(projectRoot, "build_output.json");
68
+ if (!fs.existsSync(outputFilePath)) {
69
+ throw new error_1.FirebaseError(`Universal Maker did not produce the expected output file at ${outputFilePath}`);
70
+ }
71
+ const outputRaw = fs.readFileSync(outputFilePath, "utf-8");
72
+ 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
+ let umOutput;
88
+ try {
89
+ umOutput = JSON.parse(outputRaw);
90
+ }
91
+ catch (e) {
92
+ throw new error_1.FirebaseError(`Failed to parse build_output.json: ${(0, error_1.getErrMsg)(e)}`);
93
+ }
94
+ const defaultRunCommand = `${umOutput.command} ${umOutput.args.join(" ")}`;
95
+ const { runCommand: finalRunCommand, outputFiles: finalOutputFiles } = parseBundleYaml(projectRoot, defaultRunCommand);
96
+ return {
97
+ metadata: {
98
+ language: umOutput.language,
99
+ runtime: umOutput.runtime,
100
+ framework: framework || "nextjs",
101
+ },
102
+ runConfig: {
103
+ runCommand: finalRunCommand,
104
+ environmentVariables: Object.entries(umOutput.envVars || {})
105
+ .filter(([k]) => k !== "FIREBASE_OUTPUT_BUNDLE_DIR")
106
+ .map(([k, v]) => ({
107
+ variable: k,
108
+ value: String(v),
109
+ availability: ["RUNTIME"],
110
+ })),
111
+ },
112
+ outputFiles: {
113
+ serverApp: {
114
+ include: finalOutputFiles,
115
+ },
116
+ },
117
+ };
118
+ }
8
119
  async function localBuild(projectId, projectRoot, framework, env = {}, options) {
9
120
  const hasBuildAvailableSecrets = Object.values(env).some((v) => v.secret && (!v.availability || v.availability.includes("BUILD")));
10
121
  if (hasBuildAvailableSecrets && !options?.allowLocalBuildSecrets) {
@@ -25,7 +136,7 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
25
136
  }
26
137
  let apphostingBuildOutput;
27
138
  try {
28
- apphostingBuildOutput = await (0, build_1.localBuild)(projectRoot, framework);
139
+ apphostingBuildOutput = await runUniversalMaker(projectRoot, framework);
29
140
  }
30
141
  finally {
31
142
  for (const key in process.env) {
@@ -41,7 +152,7 @@ async function localBuild(projectId, projectRoot, framework, env = {}, options)
41
152
  const discoveredEnv = apphostingBuildOutput.runConfig.environmentVariables?.map(({ variable, value, availability }) => ({
42
153
  variable,
43
154
  value,
44
- availability,
155
+ availability: availability,
45
156
  }));
46
157
  return {
47
158
  outputFiles: apphostingBuildOutput.outputFiles?.serverApp.include ?? [],
@@ -58,7 +169,7 @@ async function toProcessEnv(projectId, env) {
58
169
  return null;
59
170
  }
60
171
  if (value.secret) {
61
- const resolvedValue = await (0, secrets_1.loadSecret)(projectId, value.secret);
172
+ const resolvedValue = await (0, index_1.loadSecret)(projectId, value.secret);
62
173
  return [key, resolvedValue];
63
174
  }
64
175
  else {
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOrDownloadUniversalMaker = getOrDownloadUniversalMaker;
4
+ const fs = require("fs-extra");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const error_1 = require("../error");
8
+ const downloadUtils = require("../downloadUtils");
9
+ const logger_1 = require("../logger");
10
+ const universalMakerInfo = require("./universalMakerInfo.json");
11
+ const CACHE_DIR = path.join(os.homedir(), ".cache", "firebase", "universal-maker");
12
+ const UNIVERSAL_MAKER_UPDATE_DETAILS = universalMakerInfo;
13
+ function getPlatformInfo() {
14
+ let platformKey = "";
15
+ if (process.platform === "darwin") {
16
+ if (process.arch === "arm64") {
17
+ platformKey = "darwin_arm64";
18
+ }
19
+ else {
20
+ throw new error_1.FirebaseError("macOS Intel (darwin_x64) is not currently supported for Universal Maker.");
21
+ }
22
+ }
23
+ else if (process.platform === "linux") {
24
+ if (process.arch === "x64") {
25
+ platformKey = "linux_x64";
26
+ }
27
+ else {
28
+ throw new error_1.FirebaseError("Linux ARM (linux_arm64) is not currently supported for Universal Maker.");
29
+ }
30
+ }
31
+ else if (process.platform === "win32") {
32
+ throw new error_1.FirebaseError("Windows (win32) is not currently supported for Universal Maker.");
33
+ }
34
+ else {
35
+ throw new error_1.FirebaseError(`Unsupported platform for Universal Maker: ${process.platform} ${process.arch}`);
36
+ }
37
+ const details = UNIVERSAL_MAKER_UPDATE_DETAILS[platformKey];
38
+ if (!details) {
39
+ throw new error_1.FirebaseError(`Could not find download details for platform: ${platformKey}`);
40
+ }
41
+ return details;
42
+ }
43
+ async function getOrDownloadUniversalMaker() {
44
+ const details = getPlatformInfo();
45
+ const downloadPath = path.join(CACHE_DIR, details.downloadPathRelativeToCacheDir);
46
+ const hasBinary = fs.existsSync(downloadPath);
47
+ if (hasBinary) {
48
+ logger_1.logger.debug(`[apphosting] Universal Maker binary found at cache: ${downloadPath}`);
49
+ try {
50
+ await downloadUtils.validateSize(downloadPath, details.expectedSize);
51
+ await downloadUtils.validateChecksum(downloadPath, details.expectedChecksumSHA256, "sha256");
52
+ return downloadPath;
53
+ }
54
+ catch (err) {
55
+ logger_1.logger.warn(`[apphosting] Cached Universal Maker binary failed verification: ${err.message}. Proceeding to redownload...`);
56
+ }
57
+ }
58
+ logger_1.logger.info("Downloading Universal Maker, a tool required to build your App Hosting application locally...");
59
+ fs.ensureDirSync(CACHE_DIR);
60
+ let tmpfile;
61
+ try {
62
+ tmpfile = await downloadUtils.downloadToTmp(details.remoteUrl);
63
+ }
64
+ catch (err) {
65
+ throw new error_1.FirebaseError(`Failed to download Universal Maker: ${err.message}`);
66
+ }
67
+ await downloadUtils.validateSize(tmpfile, details.expectedSize);
68
+ await downloadUtils.validateChecksum(tmpfile, details.expectedChecksumSHA256, "sha256");
69
+ fs.copySync(tmpfile, downloadPath);
70
+ fs.chmodSync(downloadPath, 0o755);
71
+ return downloadPath;
72
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "darwin_arm64": {
3
+ "version": "1.0.0",
4
+ "expectedSize": 16111618,
5
+ "expectedChecksumSHA256": "4b77d02a5f80f26d9bd1428f388c293c1fb264995d75b51c7d50fec7c87bcf58",
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
+ "downloadPathRelativeToCacheDir": "universal-maker-darwin-arm64-1.0.0"
8
+ },
9
+ "linux_x64": {
10
+ "version": "1.0.0",
11
+ "expectedSize": 16856277,
12
+ "expectedChecksumSHA256": "dfc8357b8ce23ef1897e5590d4390f166e27734f241b770f721d68273118845c",
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
+ "downloadPathRelativeToCacheDir": "universal-maker-linux-x64-1.0.0"
15
+ }
16
+ }
@@ -43,7 +43,7 @@ async function zipDirectory(sourceDirectory, tempFile, options) {
43
43
  try {
44
44
  files = await fsAsync.readdirRecursive({
45
45
  path: sourceDirectory,
46
- ignore: options.ignore,
46
+ ignoreStrings: options.ignore,
47
47
  ignoreSymlinks: true,
48
48
  });
49
49
  }
@@ -19,24 +19,39 @@ async function default_1(context, options) {
19
19
  return;
20
20
  }
21
21
  const projectId = (0, projectUtils_1.needProjectId)(options);
22
- const rollouts = backendIds.map((backendId) => (0, rollout_1.orchestrateRollout)({
23
- projectId,
24
- backendId,
25
- location: context.backendLocations[backendId],
26
- buildInput: {
27
- config: context.backendLocalBuilds[backendId]?.buildConfig,
28
- source: {
22
+ const rollouts = backendIds.map((backendId) => {
23
+ const localBuild = context.backendLocalBuilds[backendId];
24
+ const userStorageUri = context.backendStorageUris[backendId];
25
+ const rootDirectory = context.backendConfigs[backendId].rootDir;
26
+ const source = localBuild
27
+ ? {
28
+ locallyBuilt: {
29
+ userStorageUri,
30
+ rootDirectory,
31
+ runCommand: localBuild.buildConfig?.runCommand,
32
+ env: localBuild.buildConfig?.env,
33
+ },
34
+ }
35
+ : {
29
36
  archive: {
30
- userStorageUri: context.backendStorageUris[backendId],
31
- rootDirectory: context.backendConfigs[backendId].rootDir,
32
- locallyBuiltSource: !!context.backendLocalBuilds[backendId],
37
+ userStorageUri,
38
+ rootDirectory,
33
39
  },
40
+ };
41
+ return (0, rollout_1.orchestrateRollout)({
42
+ projectId,
43
+ backendId,
44
+ location: context.backendLocations[backendId],
45
+ buildInput: {
46
+ config: localBuild?.buildConfig,
47
+ source,
34
48
  },
35
- },
36
- }));
49
+ });
50
+ });
37
51
  (0, utils_1.logLabeledBullet)("apphosting", `You may also track the rollout(s) at:\n\t${(0, api_1.consoleOrigin)()}/project/${projectId}/apphosting`);
38
52
  const rolloutsSpinner = ora(`Starting rollout(s) for backend(s) ${backendIds.join(", ")}; this may take a few minutes. It's safe to exit now.\n`).start();
39
53
  const results = await Promise.allSettled(rollouts);
54
+ rolloutsSpinner.stop();
40
55
  let failed = false;
41
56
  for (let i = 0; i < results.length; i++) {
42
57
  const res = results[i];
@@ -51,7 +66,6 @@ async function default_1(context, options) {
51
66
  (0, utils_1.logLabeledError)("apphosting", `${res.reason}`);
52
67
  }
53
68
  }
54
- rolloutsSpinner.stop();
55
69
  if (failed) {
56
70
  throw new error_1.FirebaseError("One or more rollouts failed. Please review the errors above and try again.");
57
71
  }
@@ -16,8 +16,8 @@ async function createLocalBuildTarArchive(config, rootDir, targetSubDir) {
16
16
  const ignore = ["firebase-debug.log", "firebase-debug.*.log", ".git"];
17
17
  const rdrFiles = await fsAsync.readdirRecursive({
18
18
  path: targetDir,
19
- ignore: ignore,
20
- isGitIgnore: true,
19
+ ignoreStrings: ignore,
20
+ supportGitIgnore: true,
21
21
  });
22
22
  const allFiles = rdrFiles.map((rdrf) => path.relative(rootDir, rdrf.name));
23
23
  if (targetSubDir) {
@@ -60,13 +60,11 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
60
60
  const targetDir = targetSubDir ? path.join(rootDir, targetSubDir) : rootDir;
61
61
  const ignore = config.ignore || ["node_modules", ".git"];
62
62
  ignore.push("firebase-debug.log", "firebase-debug.*.log");
63
- const gitIgnorePatterns = parseGitIgnorePatterns(targetDir);
64
- ignore.push(...gitIgnorePatterns);
65
63
  try {
66
64
  const files = await fsAsync.readdirRecursive({
67
65
  path: targetDir,
68
- ignore: ignore,
69
- isGitIgnore: true,
66
+ ignoreStrings: ignore,
67
+ supportGitIgnore: true,
70
68
  });
71
69
  for (const file of files) {
72
70
  const name = path.relative(rootDir, file.name);
@@ -82,19 +80,6 @@ async function createSourceDeployArchive(config, rootDir, targetSubDir) {
82
80
  }
83
81
  return tmpFile;
84
82
  }
85
- function parseGitIgnorePatterns(projectRoot, gitIgnorePath = ".gitignore") {
86
- const absoluteFilePath = path.resolve(projectRoot, gitIgnorePath);
87
- if (!fs.existsSync(absoluteFilePath)) {
88
- return [];
89
- }
90
- const lines = fs
91
- .readFileSync(absoluteFilePath)
92
- .toString()
93
- .split("\n")
94
- .map((line) => line.trim())
95
- .filter((line) => !line.startsWith("#") && !(line === ""));
96
- return lines;
97
- }
98
83
  async function pipeAsync(from, to) {
99
84
  from.pipe(to);
100
85
  await from.finalize();
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
3
+ exports.missingEndpoint = exports.hasEndpoint = exports.AllFunctionsPlatforms = exports.SCHEDULED_FUNCTION_LABEL = exports.MIN_CPU_FOR_CONCURRENCY = exports.DEFAULT_TIMEOUT_SECONDS = exports.DEFAULT_MEMORY = exports.DEFAULT_CONCURRENCY = exports.AllIngressSettings = exports.AllVpcEgressSettings = void 0;
4
4
  exports.endpointTriggerType = endpointTriggerType;
5
5
  exports.isValidMemoryOption = isValidMemoryOption;
6
6
  exports.isValidEgressSetting = isValidEgressSetting;
@@ -116,6 +116,7 @@ function memoryToGen2Cpu(memory) {
116
116
  }
117
117
  exports.DEFAULT_CONCURRENCY = 80;
118
118
  exports.DEFAULT_MEMORY = 256;
119
+ exports.DEFAULT_TIMEOUT_SECONDS = 60;
119
120
  exports.MIN_CPU_FOR_CONCURRENCY = 1;
120
121
  exports.SCHEDULED_FUNCTION_LABEL = Object.freeze({ deployment: "firebase-schedule" });
121
122
  function secretVersionName(s) {
@@ -1,13 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EVENTARC_SOURCE_ENV = void 0;
3
+ exports.DEFAULT_FUNCTION_REGION = exports.EVENTARC_SOURCE_ENV = void 0;
4
4
  exports.prepare = prepare;
5
- exports.matchRegionsForExisting = matchRegionsForExisting;
6
- exports.resolveDefaultRegions = resolveDefaultRegions;
5
+ exports.resolveDefaultRegionsForBuild = resolveDefaultRegionsForBuild;
7
6
  exports.inferDetailsFromExisting = inferDetailsFromExisting;
8
7
  exports.updateEndpointTargetedStatus = updateEndpointTargetedStatus;
9
8
  exports.inferBlockingDetails = inferBlockingDetails;
10
9
  exports.resolveCpuAndConcurrency = resolveCpuAndConcurrency;
10
+ exports.resolveDefaultTimeout = resolveDefaultTimeout;
11
11
  exports.loadCodebases = loadCodebases;
12
12
  exports.warnIfNewGenkitFunctionIsMissingSecrets = warnIfNewGenkitFunctionIsMissingSecrets;
13
13
  exports.ensureAllRequiredAPIsEnabled = ensureAllRequiredAPIsEnabled;
@@ -23,6 +23,12 @@ const runtimes = require("./runtimes");
23
23
  const supported = require("./runtimes/supported");
24
24
  const validate = require("./validate");
25
25
  const ensure = require("./ensure");
26
+ const events = require("../../functions/events/v1");
27
+ const firestore_1 = require("./services/firestore");
28
+ const storage_1 = require("./services/storage");
29
+ const database_1 = require("./services/database");
30
+ const ailogic_1 = require("./services/ailogic");
31
+ const names_1 = require("../../dataconnect/names");
26
32
  const api_1 = require("../../api");
27
33
  const functionsDeployHelper_1 = require("./functionsDeployHelper");
28
34
  const utils_1 = require("../../utils");
@@ -43,6 +49,7 @@ const functional_1 = require("../../functional");
43
49
  const prepare_1 = require("../extensions/prepare");
44
50
  const prompt = require("../../prompt");
45
51
  exports.EVENTARC_SOURCE_ENV = "EVENTARC_CLOUD_EVENT_SOURCE";
52
+ exports.DEFAULT_FUNCTION_REGION = "us-central1";
46
53
  async function prepare(context, options, payload) {
47
54
  const projectId = (0, projectUtils_1.needProjectId)(options);
48
55
  const projectNumber = await (0, projectUtils_1.needProjectNumber)(options);
@@ -71,6 +78,7 @@ async function prepare(context, options, payload) {
71
78
  }
72
79
  context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
73
80
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
81
+ const existingBackend = await backend.existingBackend(context);
74
82
  if (Object.values(wantBuilds).some((b) => b.extensions)) {
75
83
  const extContext = {};
76
84
  const extPayload = {};
@@ -92,6 +100,10 @@ async function prepare(context, options, payload) {
92
100
  proto.convertIfPresent(userEnvOpt, localCfg, "configDir", (cd) => options.config.path(cd));
93
101
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
94
102
  const envs = { ...userEnvs, ...firebaseEnvs };
103
+ const relevantEndpoints = backend
104
+ .allEndpoints(existingBackend)
105
+ .filter((e) => e.codebase === codebase || e.codebase === undefined);
106
+ await resolveDefaultRegionsForBuild(wantBuild, backend.of(...relevantEndpoints));
95
107
  const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
96
108
  build: wantBuild,
97
109
  firebaseConfig,
@@ -187,13 +199,6 @@ async function prepare(context, options, payload) {
187
199
  context.sources[codebase] = source;
188
200
  }
189
201
  payload.functions = {};
190
- const existingBackend = await backend.existingBackend(context);
191
- for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
192
- const relevantEndpoints = backend
193
- .allEndpoints(existingBackend)
194
- .filter((e) => e.codebase === codebase || e.codebase === undefined);
195
- await resolveDefaultRegions(wantBackend, backend.of(...relevantEndpoints));
196
- }
197
202
  const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(existingBackend));
198
203
  for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
199
204
  const haveBackend = haveBackends[codebase] || backend.empty();
@@ -203,6 +208,7 @@ async function prepare(context, options, payload) {
203
208
  inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
204
209
  await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
205
210
  resolveCpuAndConcurrency(wantBackend);
211
+ resolveDefaultTimeout(wantBackend);
206
212
  validate.endpointsAreValid(wantBackend);
207
213
  inferBlockingDetails(wantBackend);
208
214
  }
@@ -225,40 +231,129 @@ async function prepare(context, options, payload) {
225
231
  validate.checkFiltersIntegrity(wantBackends, context.filters);
226
232
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
227
233
  }
228
- function moveEndpointToRegion(backend, endpoint, region) {
229
- endpoint.region = region;
230
- backend.endpoints[region] = backend.endpoints[region] || {};
231
- backend.endpoints[region][endpoint.id] = endpoint;
232
- delete backend.endpoints[build.REGION_TBD][endpoint.id];
233
- if (Object.keys(backend.endpoints[build.REGION_TBD]).length === 0) {
234
- delete backend.endpoints[build.REGION_TBD];
234
+ async function resolveDefaultRegionsForBuild(buildObj, have) {
235
+ for (const [id, endpoint] of Object.entries(buildObj.endpoints)) {
236
+ if (!endpoint.region?.length || endpoint.region.includes(build.REGION_TBD)) {
237
+ let resolvedRegion = exports.DEFAULT_FUNCTION_REGION;
238
+ let matching;
239
+ for (const region of Object.keys(have.endpoints)) {
240
+ if (have.endpoints[region][id]) {
241
+ if (matching) {
242
+ throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
243
+ }
244
+ matching = have.endpoints[region][id];
245
+ }
246
+ }
247
+ if (matching) {
248
+ resolvedRegion = matching.region;
249
+ }
250
+ else {
251
+ try {
252
+ const fullEndpoint = { ...endpoint, id };
253
+ if (build.isBlockingTriggered(endpoint)) {
254
+ resolvedRegion = resolveRegionForBlockingTrigger(fullEndpoint);
255
+ }
256
+ else if (build.isEventTriggered(endpoint)) {
257
+ resolvedRegion = await resolveRegionForEventTrigger(fullEndpoint);
258
+ }
259
+ }
260
+ catch (err) {
261
+ logger_1.logger.debug(`Failed to resolve region for endpoint ${id}. Defaulting to ${exports.DEFAULT_FUNCTION_REGION}.`, (0, error_1.getErrStack)(err));
262
+ }
263
+ }
264
+ endpoint.region = [resolvedRegion];
265
+ }
235
266
  }
236
267
  }
237
- function matchRegionsForExisting(want, have) {
238
- for (const [id, wantE] of Object.entries(want.endpoints[build.REGION_TBD] || {})) {
239
- let matching;
240
- for (const region of Object.keys(have.endpoints)) {
241
- if (region === build.REGION_TBD) {
242
- continue;
268
+ function resolveRegionForBlockingTrigger(endpoint) {
269
+ const eventType = endpoint.blockingTrigger.eventType;
270
+ if (events.AUTH_BLOCKING_EVENTS.includes(eventType)) {
271
+ return "us-east1";
272
+ }
273
+ if ((0, ailogic_1.isGlobalAILogicEndpoint)(endpoint)) {
274
+ return "us-east1";
275
+ }
276
+ return exports.DEFAULT_FUNCTION_REGION;
277
+ }
278
+ async function resolveRegionForEventTrigger(endpoint) {
279
+ const eventTrigger = endpoint.eventTrigger;
280
+ const eventType = eventTrigger.eventType;
281
+ if (eventType.startsWith("google.cloud.pubsub.") ||
282
+ eventType.startsWith("providers/cloud.auth/eventTypes/") ||
283
+ eventType.startsWith("providers/firebase.auth/eventTypes/") ||
284
+ eventType.startsWith("google.firebase.testlab.") ||
285
+ eventType.startsWith("google.firebase.remoteconfig.") ||
286
+ eventType.startsWith("google.firebase.firebasealerts.")) {
287
+ return "us-east1";
288
+ }
289
+ if (eventType.startsWith("google.cloud.firestore.")) {
290
+ try {
291
+ const databaseId = eventTrigger.eventFilters?.database || "(default)";
292
+ const db = await (0, firestore_1.getDatabase)(endpoint.project, databaseId);
293
+ const locationId = db.locationId.toLowerCase();
294
+ if (locationId === "nam5" || locationId === "nam7")
295
+ return "us-central1";
296
+ if (locationId === "eur3")
297
+ return "europe-west1";
298
+ return locationId;
299
+ }
300
+ catch (err) {
301
+ logger_1.logger.debug("Failed to resolve Firestore database location", (0, error_1.getErrStack)(err));
302
+ }
303
+ }
304
+ if (eventType.startsWith("google.cloud.storage.")) {
305
+ try {
306
+ const bucketName = eventTrigger.eventFilters?.bucket;
307
+ if (bucketName) {
308
+ const bucket = await (0, storage_1.getBucket)(bucketName);
309
+ const locationId = bucket.location.toLowerCase();
310
+ if (locationId === "us")
311
+ return "us-east1";
312
+ if (locationId === "eu")
313
+ return "europe-west1";
314
+ if (locationId === "asia")
315
+ return "asia-east1";
316
+ return locationId;
243
317
  }
244
- if (have.endpoints[region][id]) {
245
- if (matching) {
246
- throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
318
+ }
319
+ catch (err) {
320
+ logger_1.logger.debug("Failed to resolve Cloud Storage bucket location", (0, error_1.getErrStack)(err));
321
+ }
322
+ }
323
+ if (eventType.startsWith("google.firebase.database.")) {
324
+ if (eventTrigger.region)
325
+ return eventTrigger.region;
326
+ try {
327
+ const instanceName = eventTrigger.eventFilters?.instance;
328
+ if (instanceName) {
329
+ const details = await (0, database_1.getDatabaseInstanceDetails)(endpoint.project, instanceName);
330
+ if (details.location && details.location !== "-") {
331
+ return details.location.toLowerCase();
247
332
  }
248
- matching = have.endpoints[region][id];
249
333
  }
250
334
  }
251
- if (!matching) {
252
- continue;
335
+ catch (err) {
336
+ logger_1.logger.debug("Failed to resolve Realtime Database instance location", (0, error_1.getErrStack)(err));
253
337
  }
254
- moveEndpointToRegion(want, wantE, matching.region);
255
338
  }
256
- }
257
- async function resolveDefaultRegions(want, have) {
258
- matchRegionsForExisting(want, have);
259
- for (const endpoint of Object.values(want.endpoints[build.REGION_TBD] || {})) {
260
- moveEndpointToRegion(want, endpoint, "us-central1");
339
+ if (eventType.startsWith("google.firebase.dataconnect.")) {
340
+ if (eventTrigger.region)
341
+ return eventTrigger.region;
342
+ try {
343
+ const service = eventTrigger.eventFilters?.service;
344
+ if (service) {
345
+ return (0, names_1.parseServiceName)(service).location;
346
+ }
347
+ const connector = eventTrigger.eventFilters?.connector;
348
+ if (connector) {
349
+ return (0, names_1.parseConnectorName)(connector).location;
350
+ }
351
+ }
352
+ catch (err) {
353
+ logger_1.logger.debug("Failed to resolve DataConnect location", (0, error_1.getErrStack)(err));
354
+ }
261
355
  }
356
+ return exports.DEFAULT_FUNCTION_REGION;
262
357
  }
263
358
  function inferDetailsFromExisting(want, have, usedDotenv) {
264
359
  for (const wantE of backend.allEndpoints(want)) {
@@ -279,6 +374,9 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
279
374
  if (typeof wantE.cpu === "undefined" && haveE.cpu) {
280
375
  wantE.cpu = haveE.cpu;
281
376
  }
377
+ if (typeof wantE.timeoutSeconds === "undefined" && haveE.timeoutSeconds) {
378
+ wantE.timeoutSeconds = haveE.timeoutSeconds;
379
+ }
282
380
  wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
283
381
  maybeCopyTriggerRegion(wantE, haveE);
284
382
  }
@@ -344,6 +442,13 @@ function resolveCpuAndConcurrency(want) {
344
442
  }
345
443
  }
346
444
  }
445
+ function resolveDefaultTimeout(want) {
446
+ for (const e of backend.allEndpoints(want)) {
447
+ if (e.platform === "run" && e.timeoutSeconds === undefined) {
448
+ e.timeoutSeconds = backend.DEFAULT_TIMEOUT_SECONDS;
449
+ }
450
+ }
451
+ }
347
452
  async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
348
453
  const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
349
454
  const projectId = (0, projectUtils_1.needProjectId)(options);
@@ -78,7 +78,7 @@ async function packageSource(projectDir, sourceDir, config, additionalSources, r
78
78
  const ignore = config.ignore || ["node_modules", ".git"];
79
79
  ignore.push("firebase-debug.log", "firebase-debug.*.log", CONFIG_DEST_FILE);
80
80
  try {
81
- const files = await fsAsync.readdirRecursive({ path: sourceDir, ignore: ignore });
81
+ const files = await fsAsync.readdirRecursive({ path: sourceDir, ignoreStrings: ignore });
82
82
  hashes.push(...(await addFilesToArchive(archive, files, sourceDir, options?.executablePaths)));
83
83
  for (const name of additionalSources) {
84
84
  const absPath = utils.resolveWithin(projectDir, name);