firebase-tools 15.17.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.
@@ -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,21 +19,35 @@ 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);
@@ -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) {
@@ -2,12 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
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;
@@ -78,6 +78,7 @@ async function prepare(context, options, payload) {
78
78
  }
79
79
  context.hasRuntimeConfig = Object.keys(runtimeConfig).some((k) => k !== "firebase");
80
80
  const wantBuilds = await loadCodebases(context.config, options, firebaseConfig, runtimeConfig, context.filters);
81
+ const existingBackend = await backend.existingBackend(context);
81
82
  if (Object.values(wantBuilds).some((b) => b.extensions)) {
82
83
  const extContext = {};
83
84
  const extPayload = {};
@@ -99,6 +100,10 @@ async function prepare(context, options, payload) {
99
100
  proto.convertIfPresent(userEnvOpt, localCfg, "configDir", (cd) => options.config.path(cd));
100
101
  const userEnvs = functionsEnv.loadUserEnvs(userEnvOpt);
101
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));
102
107
  const { backend: wantBackend, envs: resolvedEnvs } = await build.resolveBackend({
103
108
  build: wantBuild,
104
109
  firebaseConfig,
@@ -194,13 +199,6 @@ async function prepare(context, options, payload) {
194
199
  context.sources[codebase] = source;
195
200
  }
196
201
  payload.functions = {};
197
- const existingBackend = await backend.existingBackend(context);
198
- for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
199
- const relevantEndpoints = backend
200
- .allEndpoints(existingBackend)
201
- .filter((e) => e.codebase === codebase || e.codebase === undefined);
202
- await resolveDefaultRegions(wantBackend, backend.of(...relevantEndpoints));
203
- }
204
202
  const haveBackends = (0, functionsDeployHelper_1.groupEndpointsByCodebase)(wantBackends, backend.allEndpoints(existingBackend));
205
203
  for (const [codebase, wantBackend] of Object.entries(wantBackends)) {
206
204
  const haveBackend = haveBackends[codebase] || backend.empty();
@@ -210,6 +208,7 @@ async function prepare(context, options, payload) {
210
208
  inferDetailsFromExisting(wantBackend, haveBackend, codebaseUsesEnvs.includes(codebase));
211
209
  await (0, triggerRegionHelper_1.ensureTriggerRegions)(wantBackend);
212
210
  resolveCpuAndConcurrency(wantBackend);
211
+ resolveDefaultTimeout(wantBackend);
213
212
  validate.endpointsAreValid(wantBackend);
214
213
  inferBlockingDetails(wantBackend);
215
214
  }
@@ -232,52 +231,38 @@ async function prepare(context, options, payload) {
232
231
  validate.checkFiltersIntegrity(wantBackends, context.filters);
233
232
  (0, applyHash_1.applyBackendHashToBackends)(wantBackends, context);
234
233
  }
235
- function moveEndpointToRegion(backend, endpoint, region) {
236
- endpoint.region = region;
237
- backend.endpoints[region] = backend.endpoints[region] || {};
238
- backend.endpoints[region][endpoint.id] = endpoint;
239
- delete backend.endpoints[build.REGION_TBD][endpoint.id];
240
- if (Object.keys(backend.endpoints[build.REGION_TBD]).length === 0) {
241
- delete backend.endpoints[build.REGION_TBD];
242
- }
243
- }
244
- function matchRegionsForExisting(want, have) {
245
- for (const [id, wantE] of Object.entries(want.endpoints[build.REGION_TBD] || {})) {
246
- let matching;
247
- for (const region of Object.keys(have.endpoints)) {
248
- if (region === build.REGION_TBD) {
249
- continue;
250
- }
251
- if (have.endpoints[region][id]) {
252
- if (matching) {
253
- throw new error_1.FirebaseError(`Cannot resolve default region for function ${id}. It exists in multiple regions. The region must be specified to continue.`);
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];
254
245
  }
255
- matching = have.endpoints[region][id];
256
246
  }
257
- }
258
- if (!matching) {
259
- continue;
260
- }
261
- moveEndpointToRegion(want, wantE, matching.region);
262
- }
263
- }
264
- async function resolveDefaultRegions(want, have) {
265
- matchRegionsForExisting(want, have);
266
- const endpoints = Object.values(want.endpoints[build.REGION_TBD] || {});
267
- for (const endpoint of endpoints) {
268
- let resolvedRegion = "us-central1";
269
- try {
270
- if (backend.isBlockingTriggered(endpoint)) {
271
- resolvedRegion = resolveRegionForBlockingTrigger(endpoint);
247
+ if (matching) {
248
+ resolvedRegion = matching.region;
272
249
  }
273
- else if (backend.isEventTriggered(endpoint)) {
274
- resolvedRegion = await resolveRegionForEventTrigger(endpoint);
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
+ }
275
263
  }
264
+ endpoint.region = [resolvedRegion];
276
265
  }
277
- catch (err) {
278
- logger_1.logger.debug(`Failed to resolve region for endpoint ${endpoint.id}. Defaulting to us-central1.`, (0, error_1.getErrStack)(err));
279
- }
280
- moveEndpointToRegion(want, endpoint, resolvedRegion);
281
266
  }
282
267
  }
283
268
  function resolveRegionForBlockingTrigger(endpoint) {
@@ -389,6 +374,9 @@ function inferDetailsFromExisting(want, have, usedDotenv) {
389
374
  if (typeof wantE.cpu === "undefined" && haveE.cpu) {
390
375
  wantE.cpu = haveE.cpu;
391
376
  }
377
+ if (typeof wantE.timeoutSeconds === "undefined" && haveE.timeoutSeconds) {
378
+ wantE.timeoutSeconds = haveE.timeoutSeconds;
379
+ }
392
380
  wantE.securityLevel = haveE.securityLevel ? haveE.securityLevel : "SECURE_ALWAYS";
393
381
  maybeCopyTriggerRegion(wantE, haveE);
394
382
  }
@@ -454,6 +442,13 @@ function resolveCpuAndConcurrency(want) {
454
442
  }
455
443
  }
456
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
+ }
457
452
  async function loadCodebases(config, options, firebaseConfig, runtimeConfig, filters) {
458
453
  const codebases = (0, functionsDeployHelper_1.targetCodebases)(config, filters);
459
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);
@@ -32,6 +32,16 @@ class AILogicService {
32
32
  this.name = "ailogic";
33
33
  this.api = "firebasevertexai.googleapis.com";
34
34
  }
35
+ async requiredProjectBindings(projectNumber) {
36
+ return [
37
+ {
38
+ role: "roles/run.invoker",
39
+ members: [
40
+ `serviceAccount:service-${projectNumber}@gcp-sa-firebasevertexai.iam.gserviceaccount.com`,
41
+ ],
42
+ },
43
+ ];
44
+ }
35
45
  validateTrigger(endpoint, wantBackend) {
36
46
  if (!isAILogicEvent(endpoint)) {
37
47
  return;
@@ -221,7 +221,7 @@ async function secretsAreValid(projectId, wantBackend) {
221
221
  validatePlatformTargets(endpoints);
222
222
  await validateSecretVersions(projectId, endpoints);
223
223
  }
224
- const secretsSupportedPlatforms = ["gcfv1", "gcfv2"];
224
+ const secretsSupportedPlatforms = backend.AllFunctionsPlatforms;
225
225
  function validatePlatformTargets(endpoints) {
226
226
  const unsupported = endpoints.filter((e) => !secretsSupportedPlatforms.includes(e.platform));
227
227
  if (unsupported.length > 0) {
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.downloadToTmp = downloadToTmp;
4
+ exports.validateSize = validateSize;
5
+ exports.validateChecksum = validateChecksum;
4
6
  const url_1 = require("url");
7
+ const crypto = require("crypto");
5
8
  const fs = require("fs-extra");
6
9
  const ProgressBar = require("progress");
7
10
  const tmp = require("tmp");
@@ -37,3 +40,24 @@ async function downloadToTmp(remoteUrl, auth = false) {
37
40
  });
38
41
  return tmpfile.name;
39
42
  }
43
+ function validateSize(filepath, expectedSize) {
44
+ return new Promise((resolve, reject) => {
45
+ const stat = fs.statSync(filepath);
46
+ return stat.size === expectedSize
47
+ ? resolve()
48
+ : reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
49
+ });
50
+ }
51
+ function validateChecksum(filepath, expectedChecksum, algorithm = "md5") {
52
+ return new Promise((resolve, reject) => {
53
+ const hash = crypto.createHash(algorithm);
54
+ const stream = fs.createReadStream(filepath);
55
+ stream.on("data", (data) => hash.update(data));
56
+ stream.on("end", () => {
57
+ const checksum = hash.digest("hex");
58
+ return checksum === expectedChecksum
59
+ ? resolve()
60
+ : reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
61
+ });
62
+ });
63
+ }
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.downloadEmulator = downloadEmulator;
4
4
  exports.downloadExtensionVersion = downloadExtensionVersion;
5
- const crypto = require("crypto");
6
5
  const fs = require("fs-extra");
7
6
  const path = require("path");
8
7
  const tmp = require("tmp");
@@ -36,8 +35,8 @@ async function downloadEmulator(name) {
36
35
  throw err;
37
36
  }
38
37
  if (!emulator.opts.skipChecksumAndSize) {
39
- await validateSize(tmpfile, emulator.opts.expectedSize);
40
- await validateChecksum(tmpfile, emulator.opts.expectedChecksum);
38
+ await downloadUtils.validateSize(tmpfile, emulator.opts.expectedSize);
39
+ await downloadUtils.validateChecksum(tmpfile, emulator.opts.expectedChecksum, "md5");
41
40
  }
42
41
  if (emulator.opts.skipCache) {
43
42
  removeOldFiles(name, emulator, true);
@@ -82,24 +81,3 @@ function removeOldFiles(name, emulator, removeAllVersions = false) {
82
81
  }
83
82
  }
84
83
  }
85
- function validateSize(filepath, expectedSize) {
86
- return new Promise((resolve, reject) => {
87
- const stat = fs.statSync(filepath);
88
- return stat.size === expectedSize
89
- ? resolve()
90
- : reject(new error_1.FirebaseError(`download failed, expected ${expectedSize} bytes but got ${stat.size}`, { exit: 1 }));
91
- });
92
- }
93
- function validateChecksum(filepath, expectedChecksum) {
94
- return new Promise((resolve, reject) => {
95
- const hash = crypto.createHash("md5");
96
- const stream = fs.createReadStream(filepath);
97
- stream.on("data", (data) => hash.update(data));
98
- stream.on("end", () => {
99
- const checksum = hash.digest("hex");
100
- return checksum === expectedChecksum
101
- ? resolve()
102
- : reject(new error_1.FirebaseError(`download failed, expected checksum ${expectedChecksum} but got ${checksum}`, { exit: 1 }));
103
- });
104
- });
105
- }