@gigadrive/network-config 5.0.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -33,6 +33,9 @@ __export(src_exports, {
33
33
  ALLOWED_CONFIG_NAMES: () => ALLOWED_CONFIG_NAMES,
34
34
  AVAILABLE_REGIONS: () => AVAILABLE_REGIONS,
35
35
  AVAILABLE_RUNTIMES: () => AVAILABLE_RUNTIMES,
36
+ COMPOSE_FILE_NAMES: () => COMPOSE_FILE_NAMES,
37
+ CONTAINER_ENTRYPOINT_PREFIX: () => CONTAINER_ENTRYPOINT_PREFIX,
38
+ CONTAINER_RUNTIME: () => CONTAINER_RUNTIME,
36
39
  ConfigFileEmptyError: () => ConfigFileEmptyError,
37
40
  ConfigFileNotFoundError: () => ConfigFileNotFoundError,
38
41
  ConfigFileParseError: () => ConfigFileParseError,
@@ -40,7 +43,10 @@ __export(src_exports, {
40
43
  ConfigModuleLoader: () => ConfigModuleLoader,
41
44
  ConfigSchemaValidationError: () => ConfigSchemaValidationError,
42
45
  ConfigVersionError: () => ConfigVersionError,
46
+ ContainerConfigError: () => ContainerConfigError,
47
+ DEFAULT_CONTAINER_MEMORY_MB: () => DEFAULT_CONTAINER_MEMORY_MB,
43
48
  DEFAULT_FUNCTION_DURATION_SECONDS: () => DEFAULT_FUNCTION_DURATION_SECONDS,
49
+ DEFAULT_SIDECAR_MEMORY_MB: () => DEFAULT_SIDECAR_MEMORY_MB,
44
50
  FRAMEWORK_DEFINITIONS: () => FRAMEWORK_DEFINITIONS,
45
51
  FrameworkNotDetectedError: () => FrameworkNotDetectedError,
46
52
  FunctionConfigError: () => FunctionConfigError,
@@ -57,6 +63,7 @@ __export(src_exports, {
57
63
  GigadriveNextServerDescriptorSchema: () => GigadriveNextServerDescriptorSchema,
58
64
  GigadriveNextStaticAssetPrefixSchema: () => GigadriveNextStaticAssetPrefixSchema,
59
65
  MAX_FUNCTION_DURATION_SECONDS: () => MAX_FUNCTION_DURATION_SECONDS,
66
+ MAX_SIDECARS: () => MAX_SIDECARS,
60
67
  ManifestNotFoundError: () => ManifestNotFoundError,
61
68
  ManifestParseError: () => ManifestParseError,
62
69
  ManifestReadError: () => ManifestReadError,
@@ -69,20 +76,27 @@ __export(src_exports, {
69
76
  V4ConfigParser: () => V4ConfigParser,
70
77
  VercelBuildOutputParser: () => VercelBuildOutputParser,
71
78
  collectAssetFiles: () => collectAssetFiles,
79
+ composeDeclaresApp: () => composeDeclaresApp,
80
+ containerEntrypointPath: () => containerEntrypointPath,
72
81
  defineConfig: () => defineConfig,
82
+ detectContainerProject: () => detectContainerProject,
73
83
  detectFramework: () => detectFramework,
74
84
  detectPackageManager: () => detectPackageManager,
85
+ dockerFunctionContainerName: () => dockerFunctionContainerName,
75
86
  filterFunctionsFromAssets: () => filterFunctionsFromAssets,
76
87
  generateConfig: () => generateConfig,
77
88
  getFunctionSettings: () => getFunctionSettings,
78
89
  mergeWithFrameworkDefaults: () => mergeWithFrameworkDefaults,
90
+ normalizeContainers: () => normalizeContainers,
79
91
  parseConfig: () => parseConfig,
80
92
  parseConfigRaw: () => parseConfigRaw,
81
93
  parseGigadriveNextBuildManifest: () => parseGigadriveNextBuildManifest,
82
94
  parseGigadriveNextPrerenderManifest: () => parseGigadriveNextPrerenderManifest,
83
95
  parseStaticAssetManifest: () => parseStaticAssetManifest,
84
96
  postProcessConfig: () => postProcessConfig,
85
- readDependencies: () => readDependencies
97
+ readComposeContainers: () => readComposeContainers,
98
+ readDependencies: () => readDependencies,
99
+ splitCommandWords: () => splitCommandWords
86
100
  });
87
101
  module.exports = __toCommonJS(src_exports);
88
102
 
@@ -181,16 +195,824 @@ var collectAssetFiles = import_effect3.Effect.fn("collectAssetFiles")(function*
181
195
  return files.map((file) => file.replaceAll("\\", "/")).filter((file) => !DISALLOWED_ASSET_EXTENSIONS.some((extension) => file.toLowerCase().endsWith(extension))).sort();
182
196
  });
183
197
 
198
+ // src/containers.ts
199
+ var import_platform2 = require("@effect/platform");
200
+ var import_effect5 = require("effect");
201
+ var import_node_path = require("path");
202
+ var import_yaml = require("yaml");
203
+
204
+ // src/errors.ts
205
+ var import_effect4 = require("effect");
206
+ var ConfigFileNotFoundError = class extends import_effect4.Schema.TaggedError()("ConfigFileNotFoundError", {
207
+ message: import_effect4.Schema.String,
208
+ filePath: import_effect4.Schema.String
209
+ }) {
210
+ };
211
+ var ConfigFileEmptyError = class extends import_effect4.Schema.TaggedError()("ConfigFileEmptyError", {
212
+ message: import_effect4.Schema.String,
213
+ filePath: import_effect4.Schema.String
214
+ }) {
215
+ };
216
+ var ConfigFileParseError = class extends import_effect4.Schema.TaggedError()("ConfigFileParseError", {
217
+ message: import_effect4.Schema.String,
218
+ filePath: import_effect4.Schema.String,
219
+ cause: import_effect4.Schema.optional(import_effect4.Schema.String)
220
+ }) {
221
+ };
222
+ var ConfigModuleLoadError = class extends import_effect4.Schema.TaggedError()("ConfigModuleLoadError", {
223
+ message: import_effect4.Schema.String,
224
+ filePath: import_effect4.Schema.String,
225
+ cause: import_effect4.Schema.optional(import_effect4.Schema.String)
226
+ }) {
227
+ };
228
+ var ConfigVersionError = class extends import_effect4.Schema.TaggedError()("ConfigVersionError", {
229
+ message: import_effect4.Schema.String,
230
+ filePath: import_effect4.Schema.String,
231
+ version: import_effect4.Schema.optional(import_effect4.Schema.Number)
232
+ }) {
233
+ };
234
+ var ConfigSchemaValidationError = class extends import_effect4.Schema.TaggedError()(
235
+ "ConfigSchemaValidationError",
236
+ {
237
+ message: import_effect4.Schema.String,
238
+ filePath: import_effect4.Schema.String,
239
+ validationErrors: import_effect4.Schema.Array(import_effect4.Schema.String)
240
+ }
241
+ ) {
242
+ };
243
+ var FunctionConfigError = class extends import_effect4.Schema.TaggedError()("FunctionConfigError", {
244
+ message: import_effect4.Schema.String,
245
+ functionPath: import_effect4.Schema.String
246
+ }) {
247
+ };
248
+ var ContainerConfigError = class extends import_effect4.Schema.TaggedError()("ContainerConfigError", {
249
+ message: import_effect4.Schema.String,
250
+ containerName: import_effect4.Schema.optional(import_effect4.Schema.String),
251
+ filePath: import_effect4.Schema.optional(import_effect4.Schema.String)
252
+ }) {
253
+ };
254
+
255
+ // src/normalized-config.ts
256
+ var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
257
+ var MAX_FUNCTION_DURATION_SECONDS = 28800;
258
+ var CONTAINER_RUNTIME = "docker";
259
+ var CONTAINER_ENTRYPOINT_PREFIX = "container:";
260
+ var DEFAULT_CONTAINER_MEMORY_MB = 512;
261
+ var DEFAULT_SIDECAR_MEMORY_MB = 256;
262
+ var MAX_SIDECARS = 4;
263
+
264
+ // src/containers.ts
265
+ var COMPOSE_FILE_NAMES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
266
+ var CONTAINER_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,62}$/;
267
+ var CONTAINER_NAME_RULES = "1 to 63 lowercase letters, digits, '-' or '_', starting with a letter, and not 'localhost'";
268
+ var MIN_CONTAINER_MEMORY_MB = 128;
269
+ var MAX_CONTAINER_MEMORY_MB = 3009;
270
+ var MAX_CONTAINER_ENV_VARS = 100;
271
+ var MAX_CONTAINER_ENV_VALUE_LENGTH = 65536;
272
+ var containerEntrypointPath = (name) => `${CONTAINER_ENTRYPOINT_PREFIX}${name}`;
273
+ var toContainerName = (raw) => {
274
+ const cleaned = raw.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z0-9]+/, "");
275
+ const name = (cleaned === "" || /^[a-z]/.test(cleaned) ? cleaned : `app-${cleaned}`).slice(0, 63);
276
+ return name === "" || name === "localhost" ? "app" : name;
277
+ };
278
+ var dockerFunctionContainerName = (file) => {
279
+ const { dir, base } = import_node_path.posix.parse(file);
280
+ const variant = base.replace(/^Dockerfile[.-]?/i, "").replace(/[.-]?Dockerfile$/i, "");
281
+ const raw = [import_node_path.posix.basename(dir), variant === base ? import_node_path.posix.parse(base).name : variant].filter((part) => part !== "").join("-");
282
+ return toContainerName(raw);
283
+ };
284
+ var splitCommandWords = (input) => {
285
+ const words = [];
286
+ let current = "";
287
+ let inWord = false;
288
+ let quote;
289
+ for (let index = 0; index < input.length; index += 1) {
290
+ const char = input[index];
291
+ if (quote === "'") {
292
+ if (char === "'") quote = void 0;
293
+ else current += char;
294
+ } else if (char === "\\" && index + 1 < input.length) {
295
+ index += 1;
296
+ current += input[index];
297
+ inWord = true;
298
+ } else if (quote === '"') {
299
+ if (char === '"') quote = void 0;
300
+ else current += char;
301
+ } else if (char === "'" || char === '"') {
302
+ quote = char;
303
+ inWord = true;
304
+ } else if (/\s/.test(char)) {
305
+ if (inWord) words.push(current);
306
+ current = "";
307
+ inWord = false;
308
+ } else {
309
+ current += char;
310
+ inWord = true;
311
+ }
312
+ }
313
+ if (quote) throw new Error(`Unterminated ${quote} quote`);
314
+ if (inWord) words.push(current);
315
+ return words;
316
+ };
317
+ var normalizeProjectPath = (input) => {
318
+ if (import_node_path.posix.isAbsolute(input)) return void 0;
319
+ const normalized = import_node_path.posix.normalize(input.replace(/\\/g, "/")).replace(/\/+$/, "") || ".";
320
+ if (normalized === ".." || normalized.startsWith("../")) return void 0;
321
+ return normalized;
322
+ };
323
+ var resolveProjectRoot = (projectFolder) => import_effect5.Effect.flatMap(
324
+ import_platform2.FileSystem.FileSystem,
325
+ (fs) => fs.realPath(projectFolder).pipe(import_effect5.Effect.orElseSucceed(() => projectFolder))
326
+ );
327
+ var resolveInsideProject = (projectRoot, projectRelative, subject, context) => import_effect5.Effect.gen(function* () {
328
+ const fs = yield* import_platform2.FileSystem.FileSystem;
329
+ const pathSvc = yield* import_platform2.Path.Path;
330
+ const resolved = yield* fs.realPath(pathSvc.join(projectRoot, projectRelative)).pipe(import_effect5.Effect.option);
331
+ if (import_effect5.Option.isNone(resolved)) return resolved;
332
+ const relative = pathSvc.relative(projectRoot, resolved.value);
333
+ if (relative === ".." || relative.startsWith(`..${pathSvc.sep}`) || pathSvc.isAbsolute(relative)) {
334
+ return yield* new ContainerConfigError({
335
+ message: `${subject} leads through a symbolic link to a location outside the project. Only files inside the project can be deployed.`,
336
+ ...context
337
+ });
338
+ }
339
+ return resolved;
340
+ });
341
+ var readInsideProject = (projectRoot, projectRelative, subject, context) => import_effect5.Effect.gen(function* () {
342
+ const fs = yield* import_platform2.FileSystem.FileSystem;
343
+ const resolved = yield* resolveInsideProject(projectRoot, projectRelative, subject, context);
344
+ if (import_effect5.Option.isNone(resolved)) return import_effect5.Option.none();
345
+ return yield* fs.readFileString(resolved.value).pipe(import_effect5.Effect.option);
346
+ });
347
+ var toArgv = (name, key, value) => value === void 0 ? import_effect5.Effect.succeed(void 0) : Array.isArray(value) ? import_effect5.Effect.succeed(value) : import_effect5.Effect.try({
348
+ try: () => splitCommandWords(value),
349
+ catch: (error) => new ContainerConfigError({
350
+ message: `Container '${name}' has an invalid ${key}: ${error instanceof Error ? error.message : String(error)}`,
351
+ containerName: name
352
+ })
353
+ });
354
+ var isIntegerBetween = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
355
+ var validateContainer = (name, container) => {
356
+ const build = typeof container.build === "object" ? container.build : void 0;
357
+ const buildArgs = Object.values(build?.args ?? {});
358
+ const env = Object.entries(container.env ?? {});
359
+ const user = container.user === void 0 ? void 0 : String(container.user);
360
+ const badEnvKey = env.find(([key]) => key === "" || key.includes("=") || key.includes("\0"))?.[0];
361
+ const longEnvKey = env.find(([, value]) => value.length > MAX_CONTAINER_ENV_VALUE_LENGTH)?.[0];
362
+ const problem = [
363
+ [
364
+ container.image !== void 0 && !/^\S{1,512}$/.test(container.image),
365
+ "has an invalid image reference. Use 1 to 512 characters without whitespace."
366
+ ],
367
+ [
368
+ container.port !== void 0 && !isIntegerBetween(container.port, 1, 65535),
369
+ `listens on port ${container.port}. Use a whole number from 1 to 65535.`
370
+ ],
371
+ [
372
+ container.memory !== void 0 && !isIntegerBetween(container.memory, MIN_CONTAINER_MEMORY_MB, MAX_CONTAINER_MEMORY_MB),
373
+ `asks for ${container.memory} MB of memory. Use a whole number from ${MIN_CONTAINER_MEMORY_MB} to ${MAX_CONTAINER_MEMORY_MB}.`
374
+ ],
375
+ [
376
+ container.working_dir !== void 0 && !(container.working_dir.startsWith("/") && container.working_dir.length <= 1024),
377
+ `sets working_dir '${container.working_dir}', which must be an absolute path of at most 1024 characters.`
378
+ ],
379
+ [
380
+ typeof container.user === "number" && !isIntegerBetween(container.user, 0, Number.MAX_SAFE_INTEGER),
381
+ `sets user ${container.user}. A numeric user must be a non-negative whole number.`
382
+ ],
383
+ [
384
+ user !== void 0 && (user.length === 0 || user.length > 256),
385
+ "sets an empty or overlong user. Use 1 to 256 characters: a name, a uid, name:group or uid:gid."
386
+ ],
387
+ [
388
+ build?.target !== void 0 && (build.target.length === 0 || build.target.length > 128),
389
+ "has an empty build target or one longer than 128 characters."
390
+ ],
391
+ [
392
+ buildArgs.length > MAX_CONTAINER_ENV_VARS || buildArgs.some((value) => value.length > MAX_CONTAINER_ENV_VALUE_LENGTH),
393
+ `sets more than ${MAX_CONTAINER_ENV_VARS} build args, or one longer than ${MAX_CONTAINER_ENV_VALUE_LENGTH} characters.`
394
+ ],
395
+ [
396
+ env.length > MAX_CONTAINER_ENV_VARS,
397
+ `sets ${env.length} environment variables. At most ${MAX_CONTAINER_ENV_VARS} are allowed.`
398
+ ],
399
+ [
400
+ badEnvKey !== void 0,
401
+ `sets an environment variable named '${badEnvKey}'. Names must be non-empty and contain no '='.`
402
+ ],
403
+ [
404
+ longEnvKey !== void 0,
405
+ `sets environment variable '${longEnvKey}' to a value longer than ${MAX_CONTAINER_ENV_VALUE_LENGTH} characters.`
406
+ ]
407
+ ].find(([failed]) => failed)?.[1];
408
+ return problem === void 0 ? import_effect5.Effect.void : import_effect5.Effect.fail(new ContainerConfigError({ message: `Container '${name}' ${problem}`, containerName: name }));
409
+ };
410
+ var toContainerSpec = import_effect5.Effect.fn("toContainerSpec")(function* (name, container, projectFolder) {
411
+ yield* validateContainer(name, container);
412
+ let source;
413
+ if (container.image !== void 0 && container.build === void 0) {
414
+ source = { type: "registry", reference: container.image };
415
+ } else if (container.build !== void 0 && container.image === void 0) {
416
+ const build = typeof container.build === "string" ? { context: container.build } : container.build;
417
+ const dockerfile = build.dockerfile ?? "Dockerfile";
418
+ const context = normalizeProjectPath(build.context ?? ".");
419
+ const dockerfilePath = context === void 0 || import_node_path.posix.isAbsolute(dockerfile) ? void 0 : normalizeProjectPath(import_node_path.posix.join(context, dockerfile));
420
+ if (context === void 0 || dockerfilePath === void 0) {
421
+ return yield* new ContainerConfigError({
422
+ message: `Container '${name}' has a build context or Dockerfile outside the project.`,
423
+ containerName: name
424
+ });
425
+ }
426
+ const projectRoot = yield* resolveProjectRoot(projectFolder);
427
+ yield* resolveInsideProject(projectRoot, context, `The build context '${context}' of container '${name}'`, {
428
+ containerName: name
429
+ });
430
+ const dockerfileReal = yield* resolveInsideProject(
431
+ projectRoot,
432
+ dockerfilePath,
433
+ `The Dockerfile '${dockerfilePath}' of container '${name}'`,
434
+ { containerName: name }
435
+ );
436
+ if (import_effect5.Option.isNone(dockerfileReal)) {
437
+ return yield* new ContainerConfigError({
438
+ message: `Container '${name}' builds '${dockerfilePath}', which does not exist.`,
439
+ containerName: name
440
+ });
441
+ }
442
+ source = {
443
+ type: "dockerfile",
444
+ context,
445
+ dockerfile: import_node_path.posix.relative(context, dockerfilePath),
446
+ ...build.target !== void 0 && { target: build.target },
447
+ ...build.args !== void 0 && Object.keys(build.args).length > 0 && { buildArgs: { ...build.args } }
448
+ };
449
+ } else {
450
+ return yield* new ContainerConfigError({
451
+ message: `Container '${name}' must set exactly one of 'image' and 'build'.`,
452
+ containerName: name
453
+ });
454
+ }
455
+ const entrypoint = yield* toArgv(name, "entrypoint", container.entrypoint);
456
+ const command = yield* toArgv(name, "command", container.command);
457
+ const spec = {
458
+ name,
459
+ source,
460
+ ...container.port !== void 0 && { port: container.port },
461
+ ...entrypoint !== void 0 && { entrypoint },
462
+ ...command !== void 0 && { command },
463
+ ...container.working_dir !== void 0 && { workingDirectory: container.working_dir },
464
+ ...container.user !== void 0 && { user: String(container.user) },
465
+ ...container.env !== void 0 && Object.keys(container.env).length > 0 && { environmentVariables: { ...container.env } }
466
+ };
467
+ return spec;
468
+ });
469
+ var normalizeContainers = import_effect5.Effect.fn("normalizeContainers")(function* (containers, projectFolder) {
470
+ const entrypoints = [];
471
+ const sidecars = [];
472
+ const warnings = [];
473
+ for (const [name, container] of Object.entries(containers)) {
474
+ if (!CONTAINER_NAME_PATTERN.test(name) || name === "localhost") {
475
+ return yield* new ContainerConfigError({
476
+ message: `Container name '${name}' is invalid. The name is also the hostname the function reaches the container by, so it must be ${CONTAINER_NAME_RULES}. Try '${toContainerName(name)}'.`,
477
+ containerName: name
478
+ });
479
+ }
480
+ const spec = yield* toContainerSpec(name, container, projectFolder);
481
+ if (container.sidecar === true) {
482
+ const ignored = ["max_duration", "streaming", "schedule"].filter(
483
+ (key) => container[key] !== void 0
484
+ );
485
+ if (ignored.length > 0) {
486
+ warnings.push(
487
+ `Sidecar '${name}' ignores ${ignored.join(", ")}: those settings apply to container functions only.`
488
+ );
489
+ }
490
+ sidecars.push({ ...spec, memory: container.memory ?? DEFAULT_SIDECAR_MEMORY_MB });
491
+ continue;
492
+ }
493
+ entrypoints.push({
494
+ path: containerEntrypointPath(name),
495
+ displayName: name,
496
+ runtime: CONTAINER_RUNTIME,
497
+ memory: container.memory ?? DEFAULT_CONTAINER_MEMORY_MB,
498
+ maxDuration: container.max_duration ?? DEFAULT_FUNCTION_DURATION_SECONDS,
499
+ streaming: container.streaming ?? true,
500
+ ...container.schedule !== void 0 && { schedule: container.schedule },
501
+ container: spec
502
+ });
503
+ }
504
+ if (sidecars.length > MAX_SIDECARS) {
505
+ return yield* new ContainerConfigError({
506
+ message: `A deployment may declare at most ${MAX_SIDECARS} sidecars; this one declares ${sidecars.length}.`
507
+ });
508
+ }
509
+ const sidecarPorts = /* @__PURE__ */ new Map();
510
+ for (const sidecar of sidecars) {
511
+ if (sidecar.port === void 0) continue;
512
+ const owner = sidecarPorts.get(sidecar.port);
513
+ if (owner !== void 0) {
514
+ return yield* new ContainerConfigError({
515
+ message: `Sidecars '${owner}' and '${sidecar.name}' both listen on port ${sidecar.port}. Sidecars share one network namespace, so their ports must differ.`,
516
+ containerName: sidecar.name
517
+ });
518
+ }
519
+ sidecarPorts.set(sidecar.port, sidecar.name);
520
+ }
521
+ for (const entrypoint of entrypoints) {
522
+ const port = entrypoint.container?.port;
523
+ const sidecar = port === void 0 ? void 0 : sidecarPorts.get(port);
524
+ if (sidecar !== void 0) {
525
+ return yield* new ContainerConfigError({
526
+ message: `Container '${entrypoint.displayName}' and sidecar '${sidecar}' both listen on port ${port}. Sidecars run inside the function's microVM, so their ports must differ.`,
527
+ containerName: sidecar
528
+ });
529
+ }
530
+ }
531
+ return { entrypoints, sidecars, warnings };
532
+ });
533
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
534
+ var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
535
+ var scalarToString = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : void 0;
536
+ var gigadriveExtension = (service) => isRecord(service["x-gigadrive"]) ? service["x-gigadrive"] : {};
537
+ var COMPOSE_OVERRIDE_FILE_NAMES = [
538
+ "compose.override.yaml",
539
+ "compose.override.yml",
540
+ "docker-compose.override.yaml",
541
+ "docker-compose.override.yml"
542
+ ];
543
+ var findClosingQuote = (body, quote) => {
544
+ for (let index = 0; index < body.length; index += 1) {
545
+ if (quote === '"' && body[index] === "\\") index += 1;
546
+ else if (body[index] === quote) return index;
547
+ }
548
+ return -1;
549
+ };
550
+ var DOUBLE_QUOTE_ESCAPES = { n: "\n", r: "\r", t: " " };
551
+ var parseDotEnv = (content) => {
552
+ const values = {};
553
+ const lines = content.split(/\r?\n/);
554
+ for (let index = 0; index < lines.length; index += 1) {
555
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=(.*)$/.exec(lines[index]);
556
+ if (!match) continue;
557
+ const [, key, rest] = match;
558
+ const quote = rest.trimStart()[0];
559
+ if (quote === '"' || quote === "'") {
560
+ let body = rest.trimStart().slice(1);
561
+ let end = index;
562
+ let close = findClosingQuote(body, quote);
563
+ while (close === -1 && end + 1 < lines.length) {
564
+ end += 1;
565
+ body += `
566
+ ${lines[end]}`;
567
+ close = findClosingQuote(body, quote);
568
+ }
569
+ if (close !== -1) {
570
+ const raw = body.slice(0, close);
571
+ values[key] = quote === '"' ? raw.replace(/\\([nrt"\\])/g, (_match, char) => DOUBLE_QUOTE_ESCAPES[char] ?? char) : raw;
572
+ index = end;
573
+ continue;
574
+ }
575
+ }
576
+ values[key] = rest.replace(/\s+#.*$/, "").trim();
577
+ }
578
+ return values;
579
+ };
580
+ var InterpolationError = class extends Error {
581
+ };
582
+ var findClosingBrace = (value, start) => {
583
+ let depth = 0;
584
+ for (let index = start; index < value.length; index += 1) {
585
+ if (value[index] === "$" && (value[index + 1] === "$" || value[index + 1] === "{")) {
586
+ if (value[index + 1] === "{") depth += 1;
587
+ index += 1;
588
+ } else if (value[index] === "}") {
589
+ if (depth === 0) return index;
590
+ depth -= 1;
591
+ }
592
+ }
593
+ return -1;
594
+ };
595
+ var interpolate = (value, variables, missing, where) => {
596
+ let result = "";
597
+ let index = 0;
598
+ while (index < value.length) {
599
+ const dollar = value.indexOf("$", index);
600
+ if (dollar === -1) {
601
+ result += value.slice(index);
602
+ break;
603
+ }
604
+ result += value.slice(index, dollar);
605
+ const next = value[dollar + 1];
606
+ if (next === "$") {
607
+ result += "$";
608
+ index = dollar + 2;
609
+ continue;
610
+ }
611
+ if (next === "{") {
612
+ const close = findClosingBrace(value, dollar + 2);
613
+ const expression = close === -1 ? null : /^([A-Za-z_][A-Za-z0-9_]*)(?:(:?[-?+])([\s\S]*))?$/.exec(value.slice(dollar + 2, close));
614
+ if (expression === null) {
615
+ throw new InterpolationError(`has an invalid variable reference in ${where}. Write '$$' for a literal '$'.`);
616
+ }
617
+ const [, name, operator, operand = ""] = expression;
618
+ const current = variables[name];
619
+ const isSet = current !== void 0 && !(operator?.startsWith(":") === true && current === "");
620
+ if (operator === void 0) {
621
+ if (current === void 0) missing.add(name);
622
+ result += current ?? "";
623
+ } else if (operator.endsWith("-")) {
624
+ result += isSet ? current : interpolate(operand, variables, missing, where);
625
+ } else if (operator.endsWith("+")) {
626
+ result += isSet ? interpolate(operand, variables, missing, where) : "";
627
+ } else if (isSet) {
628
+ result += current;
629
+ } else {
630
+ const reason = interpolate(operand, variables, missing, where);
631
+ throw new InterpolationError(
632
+ `needs variable '${name}' in ${where}, which is ${current === void 0 ? "not set" : "empty"}${reason === "" ? "" : `: ${reason}`}. Compose variables come only from the .env file next to the Compose file.`
633
+ );
634
+ }
635
+ index = close + 1;
636
+ continue;
637
+ }
638
+ const bare = /^[A-Za-z_][A-Za-z0-9_]*/.exec(value.slice(dollar + 1))?.[0];
639
+ if (bare === void 0) {
640
+ result += "$";
641
+ index = dollar + 1;
642
+ continue;
643
+ }
644
+ if (variables[bare] === void 0) missing.add(bare);
645
+ result += variables[bare] ?? "";
646
+ index = dollar + 1 + bare.length;
647
+ }
648
+ return result;
649
+ };
650
+ var interpolateDeep = (value, variables, missing, where) => {
651
+ if (typeof value === "string") return interpolate(value, variables, missing, where);
652
+ if (Array.isArray(value)) {
653
+ return value.map((item, index) => interpolateDeep(item, variables, missing, `${where}[${index}]`));
654
+ }
655
+ if (isRecord(value)) {
656
+ return Object.fromEntries(
657
+ Object.entries(value).map(([key, item]) => [
658
+ key,
659
+ interpolateDeep(item, variables, missing, where === "" ? key : `${where}.${key}`)
660
+ ])
661
+ );
662
+ }
663
+ return value;
664
+ };
665
+ var parseComposeMemory = (value) => {
666
+ if (typeof value === "number") {
667
+ return Number.isFinite(value) && value >= 0 ? Math.ceil(value / 1024 ** 2) : void 0;
668
+ }
669
+ if (typeof value !== "string") return void 0;
670
+ const match = /^(\d+(?:\.\d+)?) ?([kmgtp])?i?b?$/i.exec(value.trim());
671
+ if (!match) return void 0;
672
+ const exponent = " kmgtp".indexOf((match[2] ?? " ").toLowerCase());
673
+ return Math.ceil(Number(match[1]) * 1024 ** exponent / 1024 ** 2);
674
+ };
675
+ var parseComposePort = (entry) => {
676
+ let port = entry;
677
+ if (isRecord(entry)) {
678
+ if (typeof entry.protocol === "string" && entry.protocol.toLowerCase() !== "tcp") return void 0;
679
+ port = entry.target;
680
+ } else if (typeof entry === "string") {
681
+ const [withoutProtocol, protocol] = entry.split("/");
682
+ if (protocol !== void 0 && protocol.toLowerCase() !== "tcp") return void 0;
683
+ port = (withoutProtocol.split(":").at(-1) ?? "").split("-")[0];
684
+ }
685
+ const number = typeof port === "number" ? port : typeof port === "string" && /^\d+$/.test(port) ? Number(port) : NaN;
686
+ return isIntegerBetween(number, 1, 65535) ? number : void 0;
687
+ };
688
+ var parseComposeEnvironment = (serviceName, value, warnings) => {
689
+ if (value === void 0 || value === null) return void 0;
690
+ const environment = {};
691
+ if (Array.isArray(value)) {
692
+ for (const item of value) {
693
+ if (typeof item !== "string") continue;
694
+ const separator = item.indexOf("=");
695
+ if (separator === -1) {
696
+ warnings.push(
697
+ `Compose service '${serviceName}' takes '${item}' from the host environment, which deployments do not have. Set it in the console instead.`
698
+ );
699
+ continue;
700
+ }
701
+ environment[item.slice(0, separator)] = item.slice(separator + 1);
702
+ }
703
+ } else if (isRecord(value)) {
704
+ for (const [key, item] of Object.entries(value)) {
705
+ const scalar = scalarToString(item);
706
+ if (scalar === void 0) {
707
+ warnings.push(
708
+ `Compose service '${serviceName}' takes '${key}' from the host environment, which deployments do not have. Set it in the console instead.`
709
+ );
710
+ continue;
711
+ }
712
+ environment[key] = scalar;
713
+ }
714
+ }
715
+ return environment;
716
+ };
717
+ var isPublicMarked = (service) => gigadriveExtension(service).public === true;
718
+ var buildsFromSource = (service) => service.build !== void 0 && gigadriveExtension(service).sidecar !== true;
719
+ var loadComposeServices = import_effect5.Effect.fn("loadComposeServices")(function* (composePath, projectFolder) {
720
+ const relativePath = normalizeProjectPath(composePath);
721
+ if (relativePath === void 0) {
722
+ return yield* new ContainerConfigError({
723
+ message: `Compose file '${composePath}' is outside the project.`,
724
+ filePath: composePath
725
+ });
726
+ }
727
+ const projectRoot = yield* resolveProjectRoot(projectFolder);
728
+ const content = yield* readInsideProject(projectRoot, relativePath, `Compose file '${relativePath}'`, {
729
+ filePath: relativePath
730
+ });
731
+ if (import_effect5.Option.isNone(content)) {
732
+ return yield* new ContainerConfigError({
733
+ message: `Compose file '${relativePath}' could not be read.`,
734
+ filePath: relativePath
735
+ });
736
+ }
737
+ const document = yield* import_effect5.Effect.try({
738
+ try: () => (0, import_yaml.parse)(content.value),
739
+ catch: (error) => {
740
+ const position = error instanceof import_yaml.YAMLError ? error.linePos?.[0] : void 0;
741
+ return new ContainerConfigError({
742
+ message: `Compose file '${relativePath}' is not valid YAML${position ? ` (line ${position.line}, column ${position.col})` : ""}.`,
743
+ filePath: relativePath
744
+ });
745
+ }
746
+ });
747
+ if (!isRecord(document) || !isRecord(document.services) || Object.keys(document.services).length === 0) {
748
+ return yield* new ContainerConfigError({
749
+ message: `Compose file '${relativePath}' declares no services.`,
750
+ filePath: relativePath
751
+ });
752
+ }
753
+ const services = Object.entries(document.services).filter(
754
+ (entry) => isRecord(entry[1]) && !(isStringArray(entry[1].profiles) && entry[1].profiles.length > 0) && gigadriveExtension(entry[1]).skip !== true
755
+ );
756
+ return { relativePath, projectRoot, services };
757
+ });
758
+ var composeDeclaresApp = import_effect5.Effect.fn("composeDeclaresApp")(function* (composePath, projectFolder) {
759
+ const { services } = yield* loadComposeServices(composePath, projectFolder);
760
+ return services.some(([, service]) => isPublicMarked(service) || buildsFromSource(service));
761
+ });
762
+ var readComposeContainers = import_effect5.Effect.fn("readComposeContainers")(function* (composePath, projectFolder) {
763
+ const fs = yield* import_platform2.FileSystem.FileSystem;
764
+ const pathSvc = yield* import_platform2.Path.Path;
765
+ const { relativePath, projectRoot, services } = yield* loadComposeServices(composePath, projectFolder);
766
+ const composeDirectory = import_node_path.posix.dirname(relativePath);
767
+ const warnings = [];
768
+ for (const [name] of services) {
769
+ if (!CONTAINER_NAME_PATTERN.test(name) || name === "localhost") {
770
+ return yield* new ContainerConfigError({
771
+ message: `Compose service '${name}' cannot be imported under that name. The service name becomes the hostname the function reaches it by, so it must be ${CONTAINER_NAME_RULES}. Rename the service to '${toContainerName(name)}' in '${relativePath}'.`,
772
+ containerName: name,
773
+ filePath: relativePath
774
+ });
775
+ }
776
+ }
777
+ const marked = services.filter(([, service]) => isPublicMarked(service)).map(([name]) => name);
778
+ const built = services.filter(([, service]) => buildsFromSource(service)).map(([name]) => name);
779
+ if (marked.length > 1) {
780
+ return yield* new ContainerConfigError({
781
+ message: `Compose file '${relativePath}' marks ${marked.map((name) => `'${name}'`).join(", ")} as public. One service serves HTTP: keep 'x-gigadrive: { public: true }' on that one only.`,
782
+ filePath: relativePath
783
+ });
784
+ }
785
+ const publicService = marked[0] ?? (built.length === 1 ? built[0] : void 0);
786
+ if (publicService === void 0) {
787
+ return yield* new ContainerConfigError({
788
+ message: built.length === 0 ? `Compose file '${relativePath}' has no service that builds from source, so it is unclear which service serves HTTP. Mark it with 'x-gigadrive: { public: true }'.` : `Compose file '${relativePath}' builds ${built.map((name) => `'${name}'`).join(", ")} from source, so it is unclear which service serves HTTP. Mark it with 'x-gigadrive: { public: true }'.`,
789
+ filePath: relativePath
790
+ });
791
+ }
792
+ if (COMPOSE_FILE_NAMES.includes(import_node_path.posix.basename(relativePath))) {
793
+ for (const overrideName of COMPOSE_OVERRIDE_FILE_NAMES) {
794
+ const overridePath = import_node_path.posix.join(composeDirectory, overrideName);
795
+ const overrideExists = yield* fs.exists(pathSvc.join(projectRoot, overridePath)).pipe(import_effect5.Effect.orElseSucceed(() => false));
796
+ if (overrideExists) {
797
+ warnings.push(
798
+ `Compose file '${relativePath}' has the override file '${overridePath}' next to it, which is not merged. Move its settings into '${relativePath}' or gigadrive.yaml.`
799
+ );
800
+ }
801
+ }
802
+ }
803
+ const dotEnv = yield* readInsideProject(
804
+ projectRoot,
805
+ import_node_path.posix.join(composeDirectory, ".env"),
806
+ `The .env file next to '${relativePath}'`,
807
+ { filePath: relativePath }
808
+ );
809
+ const variables = import_effect5.Option.isSome(dotEnv) ? parseDotEnv(dotEnv.value) : {};
810
+ const missing = /* @__PURE__ */ new Set();
811
+ const containers = {};
812
+ for (const [name, rawService] of services) {
813
+ const isPublic = name === publicService;
814
+ const service = yield* import_effect5.Effect.try({
815
+ try: () => interpolateDeep(rawService, variables, missing, ""),
816
+ catch: (error) => new ContainerConfigError({
817
+ message: `Compose service '${name}' ${error instanceof InterpolationError ? error.message : "could not be interpolated."}`,
818
+ containerName: name,
819
+ filePath: relativePath
820
+ })
821
+ });
822
+ const container = {};
823
+ if (service.extends !== void 0) {
824
+ warnings.push(
825
+ `Compose service '${name}' uses 'extends', which is not supported. The service is deployed as written, without the settings it would inherit.`
826
+ );
827
+ }
828
+ if (typeof service.image === "string" && service.build === void 0) {
829
+ container.image = service.image;
830
+ } else if (typeof service.build === "string" || isRecord(service.build)) {
831
+ const build = typeof service.build === "string" ? { context: service.build } : service.build;
832
+ if (build.dockerfile_inline !== void 0) {
833
+ return yield* new ContainerConfigError({
834
+ message: `Compose service '${name}' uses build.dockerfile_inline, which is not supported. Commit the Dockerfile and point build.dockerfile at it.`,
835
+ containerName: name,
836
+ filePath: relativePath
837
+ });
838
+ }
839
+ const rawContext = typeof build.context === "string" ? build.context : ".";
840
+ const context = import_node_path.posix.isAbsolute(rawContext) ? rawContext : import_node_path.posix.join(composeDirectory, rawContext);
841
+ const args = Array.isArray(build.args) ? Object.fromEntries(
842
+ build.args.filter((item) => typeof item === "string" && item.includes("=")).map((item) => [item.slice(0, item.indexOf("=")), item.slice(item.indexOf("=") + 1)])
843
+ ) : isRecord(build.args) ? Object.fromEntries(Object.entries(build.args).map(([key, item]) => [key, scalarToString(item) ?? ""])) : void 0;
844
+ container.build = {
845
+ context,
846
+ ...typeof build.dockerfile === "string" && { dockerfile: build.dockerfile },
847
+ ...typeof build.target === "string" && { target: build.target },
848
+ ...args !== void 0 && Object.keys(args).length > 0 && { args }
849
+ };
850
+ } else {
851
+ return yield* new ContainerConfigError({
852
+ message: `Compose service '${name}' has neither an image nor a build.`,
853
+ containerName: name,
854
+ filePath: relativePath
855
+ });
856
+ }
857
+ if (!isPublic) container.sidecar = true;
858
+ const ports = [
859
+ ...isPublic && Array.isArray(service.ports) ? service.ports : [],
860
+ ...Array.isArray(service.expose) ? service.expose : [],
861
+ ...!isPublic && Array.isArray(service.ports) ? service.ports : []
862
+ ];
863
+ const port = ports.map(parseComposePort).find((value) => value !== void 0);
864
+ if (port !== void 0) container.port = port;
865
+ for (const key of ["entrypoint", "command"]) {
866
+ const value = service[key];
867
+ if (typeof value === "string" || isStringArray(value)) container[key] = value;
868
+ }
869
+ const environment = {};
870
+ const envFiles = (typeof service.env_file === "string" ? [service.env_file] : Array.isArray(service.env_file) ? service.env_file : []).flatMap(
871
+ (entry) => typeof entry === "string" ? [{ path: entry }] : isRecord(entry) && typeof entry.path === "string" ? [{ path: entry.path, ...typeof entry.required === "boolean" && { required: entry.required } }] : []
872
+ );
873
+ for (const envFile of envFiles) {
874
+ const envFilePath = import_node_path.posix.isAbsolute(envFile.path) ? void 0 : normalizeProjectPath(import_node_path.posix.join(composeDirectory, envFile.path));
875
+ const envContent = envFilePath === void 0 ? import_effect5.Option.none() : yield* readInsideProject(
876
+ projectRoot,
877
+ envFilePath,
878
+ `The env_file '${envFile.path}' of Compose service '${name}'`,
879
+ { containerName: name, filePath: relativePath }
880
+ );
881
+ if (import_effect5.Option.isSome(envContent)) {
882
+ Object.assign(environment, parseDotEnv(envContent.value));
883
+ } else if (envFile.required === true) {
884
+ return yield* new ContainerConfigError({
885
+ message: `Compose service '${name}' requires env_file '${envFile.path}', which is not in the deployed source. Commit it, mark it 'required: false', or set those variables in the console.`,
886
+ containerName: name,
887
+ filePath: relativePath
888
+ });
889
+ } else if (envFile.required === void 0) {
890
+ warnings.push(
891
+ `Compose service '${name}' reads env_file '${envFile.path}', which is not in the deployed source. Set those variables in the console instead.`
892
+ );
893
+ }
894
+ }
895
+ Object.assign(environment, parseComposeEnvironment(name, service.environment, warnings));
896
+ if (Object.keys(environment).length > 0) container.env = environment;
897
+ if (typeof service.working_dir === "string") container.working_dir = service.working_dir;
898
+ if (typeof service.user === "string" || typeof service.user === "number") container.user = String(service.user);
899
+ const deploy = isRecord(service.deploy) ? service.deploy : {};
900
+ const resources = isRecord(deploy.resources) ? deploy.resources : {};
901
+ const limits = isRecord(resources.limits) ? resources.limits : {};
902
+ const memoryLimit = limits.memory ?? service.mem_limit;
903
+ if (memoryLimit !== void 0 && memoryLimit !== null) {
904
+ const memory = parseComposeMemory(memoryLimit);
905
+ if (memory === void 0) {
906
+ warnings.push(
907
+ `Compose service '${name}' sets the memory limit ${JSON.stringify(memoryLimit)}, which is not a size Docker accepts. The default is used instead.`
908
+ );
909
+ } else if (memory >= MIN_CONTAINER_MEMORY_MB && memory <= MAX_CONTAINER_MEMORY_MB) {
910
+ container.memory = memory;
911
+ } else if (memory > 0) {
912
+ warnings.push(
913
+ `Compose service '${name}' asks for ${memory} MB of memory, outside the supported ${MIN_CONTAINER_MEMORY_MB} to ${MAX_CONTAINER_MEMORY_MB} MB. The default is used instead.`
914
+ );
915
+ }
916
+ }
917
+ if (service.volumes !== void 0) {
918
+ warnings.push(
919
+ `Compose service '${name}' declares volumes. Containers on Gigadrive Network have no persistent volumes, so anything written there is lost when the instance stops.`
920
+ );
921
+ }
922
+ containers[name] = container;
923
+ }
924
+ if (missing.size > 0) {
925
+ warnings.push(
926
+ `Compose file '${relativePath}' uses ${[...missing].sort().join(", ")}, which ${missing.size === 1 ? "is" : "are"} not set. ${missing.size === 1 ? "It was" : "They were"} replaced with an empty string.`
927
+ );
928
+ }
929
+ return { containers, publicService, warnings };
930
+ });
931
+
184
932
  // src/define-config.ts
185
933
  var defineConfig = (config) => config;
186
934
 
935
+ // src/detection/detect-container-project.ts
936
+ var import_platform3 = require("@effect/platform");
937
+ var import_effect6 = require("effect");
938
+
939
+ // src/regions.ts
940
+ var REGION_DEFINITIONS = [
941
+ "us-east-1",
942
+ "us-east-2",
943
+ "us-west-1",
944
+ "us-west-2",
945
+ "ap-south-1",
946
+ "ap-northeast-3",
947
+ "ap-northeast-2",
948
+ "ap-southeast-1",
949
+ "ap-southeast-2",
950
+ "ap-northeast-1",
951
+ "ca-central-1",
952
+ "eu-central-1",
953
+ "eu-west-1",
954
+ "eu-west-2",
955
+ "eu-west-3",
956
+ "eu-north-1",
957
+ "sa-east-1"
958
+ ];
959
+ var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
960
+
961
+ // src/detection/detect-container-project.ts
962
+ var detectContainerProject = import_effect6.Effect.fn("detectContainerProject")(function* (projectFolder) {
963
+ const fs = yield* import_platform3.FileSystem.FileSystem;
964
+ const pathSvc = yield* import_platform3.Path.Path;
965
+ const exists = (file) => fs.exists(pathSvc.join(projectFolder, file)).pipe(import_effect6.Effect.catchAll(() => import_effect6.Effect.succeed(false)));
966
+ let detection;
967
+ const warnings = [];
968
+ const composeFile = yield* import_effect6.Effect.findFirst(COMPOSE_FILE_NAMES, exists);
969
+ if (composeFile._tag === "Some" && (yield* composeDeclaresApp(composeFile.value, projectFolder))) {
970
+ const imported = yield* readComposeContainers(composeFile.value, projectFolder);
971
+ warnings.push(...imported.warnings);
972
+ detection = { kind: "compose", file: composeFile.value, containers: imported.containers };
973
+ } else if (yield* exists("Dockerfile")) {
974
+ detection = { kind: "dockerfile", file: "Dockerfile", containers: { app: { build: "." } } };
975
+ } else {
976
+ return void 0;
977
+ }
978
+ const {
979
+ entrypoints,
980
+ sidecars,
981
+ warnings: containerWarnings
982
+ } = yield* normalizeContainers(detection.containers, projectFolder);
983
+ const [publicContainer] = entrypoints;
984
+ const config = {
985
+ regions: [...AVAILABLE_REGIONS],
986
+ environmentVariables: {},
987
+ commands: [],
988
+ entrypoints,
989
+ ...sidecars.length > 0 && { sidecars },
990
+ routes: publicContainer ? [
991
+ {
992
+ path: "/*",
993
+ destination: publicContainer.path,
994
+ handler: "SERVERLESS_FUNCTION_STREAMING",
995
+ methods: ["ANY"],
996
+ headers: {}
997
+ }
998
+ ] : [],
999
+ warnings: [
1000
+ `Auto-detected ${detection.kind === "compose" ? "Docker Compose" : "Dockerfile"} project (${detection.file}). Create a gigadrive.yaml to customize.`,
1001
+ ...warnings,
1002
+ ...containerWarnings
1003
+ ],
1004
+ errors: []
1005
+ };
1006
+ return { kind: detection.kind, file: detection.file, config };
1007
+ });
1008
+
187
1009
  // src/detection/detect-framework.ts
188
- var import_platform9 = require("@effect/platform");
189
- var import_effect14 = require("effect");
1010
+ var import_platform12 = require("@effect/platform");
1011
+ var import_effect18 = require("effect");
190
1012
 
191
1013
  // src/detection/detect-package-manager.ts
192
- var import_platform2 = require("@effect/platform");
193
- var import_effect4 = require("effect");
1014
+ var import_platform4 = require("@effect/platform");
1015
+ var import_effect7 = require("effect");
194
1016
  var LOCKFILE_MAP = [
195
1017
  { files: ["bun.lockb", "bun.lock"], pm: "bun" },
196
1018
  { files: ["pnpm-lock.yaml"], pm: "pnpm" },
@@ -198,19 +1020,19 @@ var LOCKFILE_MAP = [
198
1020
  { files: ["package-lock.json"], pm: "npm" },
199
1021
  { files: ["composer.lock"], pm: "composer" }
200
1022
  ];
201
- var detectPackageManager = import_effect4.Effect.fn("detectPackageManager")(function* (projectFolder) {
202
- const fs = yield* import_platform2.FileSystem.FileSystem;
203
- const pathService = yield* import_platform2.Path.Path;
1023
+ var detectPackageManager = import_effect7.Effect.fn("detectPackageManager")(function* (projectFolder) {
1024
+ const fs = yield* import_platform4.FileSystem.FileSystem;
1025
+ const pathService = yield* import_platform4.Path.Path;
204
1026
  for (const { files, pm } of LOCKFILE_MAP) {
205
1027
  for (const file of files) {
206
- const exists = yield* fs.exists(pathService.join(projectFolder, file)).pipe(import_effect4.Effect.catchAll(() => import_effect4.Effect.succeed(false)));
1028
+ const exists = yield* fs.exists(pathService.join(projectFolder, file)).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed(false)));
207
1029
  if (exists) {
208
- yield* import_effect4.Effect.logDebug(`Detected package manager: ${pm} (via ${file})`);
1030
+ yield* import_effect7.Effect.logDebug(`Detected package manager: ${pm} (via ${file})`);
209
1031
  return pm;
210
1032
  }
211
1033
  }
212
1034
  }
213
- yield* import_effect4.Effect.logDebug("No lockfile found, defaulting to npm");
1035
+ yield* import_effect7.Effect.logDebug("No lockfile found, defaulting to npm");
214
1036
  return "npm";
215
1037
  });
216
1038
 
@@ -235,9 +1057,71 @@ var astro = {
235
1057
  })
236
1058
  };
237
1059
 
1060
+ // src/detection/frameworks/php-front-controller.ts
1061
+ var phpFrontControllerDefaults = ({
1062
+ documentRoot,
1063
+ writablePaths = [],
1064
+ excludeFiles = [],
1065
+ memory = 256,
1066
+ environmentVariables = {}
1067
+ }) => {
1068
+ const entrypoint = documentRoot === "." ? "index.php" : `${documentRoot}/index.php`;
1069
+ return {
1070
+ runtime: "php-84",
1071
+ memory,
1072
+ maxDuration: 30,
1073
+ streaming: false,
1074
+ commands: [],
1075
+ entrypoint,
1076
+ assetsDir: documentRoot,
1077
+ excludeFiles: [".ddev", "node_modules/", ...excludeFiles],
1078
+ routes: [{ source: "/*", destination: entrypoint }],
1079
+ environmentVariables,
1080
+ ...writablePaths.length > 0 ? { package: { writablePaths } } : {}
1081
+ };
1082
+ };
1083
+
1084
+ // src/detection/frameworks/cakephp.ts
1085
+ var cakephp = {
1086
+ slug: "cakephp",
1087
+ name: "CakePHP",
1088
+ language: "php",
1089
+ detectors: [{ matchPackage: "cakephp/cakephp" }, { path: "webroot/index.php" }],
1090
+ priority: 80,
1091
+ getDefaultConfig: () => phpFrontControllerDefaults({
1092
+ documentRoot: "webroot",
1093
+ // config/paths.php puts TMP and LOGS inside the project.
1094
+ writablePaths: [
1095
+ "tmp/cache/models",
1096
+ "tmp/cache/persistent",
1097
+ "tmp/cache/views",
1098
+ "tmp/sessions",
1099
+ "tmp/tests",
1100
+ "logs"
1101
+ ],
1102
+ excludeFiles: ["tests/", "tmp/", "logs/"]
1103
+ })
1104
+ };
1105
+
1106
+ // src/detection/frameworks/codeigniter.ts
1107
+ var codeigniter = {
1108
+ slug: "codeigniter",
1109
+ name: "CodeIgniter",
1110
+ language: "php",
1111
+ detectors: [{ matchPackage: "codeigniter4/framework" }, { path: "spark" }],
1112
+ priority: 80,
1113
+ getDefaultConfig: () => phpFrontControllerDefaults({
1114
+ documentRoot: "public",
1115
+ // WRITEPATH: the cache, file session and log handlers write below it.
1116
+ writablePaths: ["writable/cache", "writable/debugbar", "writable/logs", "writable/session", "writable/uploads"],
1117
+ excludeFiles: ["tests/", "writable/"],
1118
+ environmentVariables: { CI_ENVIRONMENT: "production" }
1119
+ })
1120
+ };
1121
+
238
1122
  // src/detection/frameworks/resolve-node-entrypoint.ts
239
- var import_platform3 = require("@effect/platform");
240
- var import_effect5 = require("effect");
1123
+ var import_platform5 = require("@effect/platform");
1124
+ var import_effect8 = require("effect");
241
1125
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "tsx", "ts-node"]);
242
1126
  var COMMON_ENTRYPOINT_CANDIDATES = [
243
1127
  "index.js",
@@ -253,11 +1137,11 @@ var COMMON_ENTRYPOINT_CANDIDATES = [
253
1137
  "server.ts",
254
1138
  "src/server.ts"
255
1139
  ];
256
- var isRecord = (value) => typeof value === "object" && value !== null;
1140
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
257
1141
  var parsePackageJson = (content) => {
258
1142
  try {
259
1143
  const parsed = JSON.parse(content);
260
- if (!isRecord(parsed)) return void 0;
1144
+ if (!isRecord2(parsed)) return void 0;
261
1145
  return parsed;
262
1146
  } catch {
263
1147
  return void 0;
@@ -285,14 +1169,14 @@ var applyEntrypoint = (defaults, entrypoint) => ({
285
1169
  (route) => route.destination === defaults.entrypoint ? { ...route, destination: entrypoint } : route
286
1170
  )
287
1171
  });
288
- var resolveNodeServerEntrypoint = (defaults, projectFolder) => import_effect5.Effect.gen(function* () {
289
- const fs = yield* import_platform3.FileSystem.FileSystem;
290
- const pathService = yield* import_platform3.Path.Path;
1172
+ var resolveNodeServerEntrypoint = (defaults, projectFolder) => import_effect8.Effect.gen(function* () {
1173
+ const fs = yield* import_platform5.FileSystem.FileSystem;
1174
+ const pathService = yield* import_platform5.Path.Path;
291
1175
  const isFile = (relativePath) => fs.stat(pathService.join(projectFolder, relativePath)).pipe(
292
- import_effect5.Effect.map((info) => info.type === "File"),
293
- import_effect5.Effect.catchAll(() => import_effect5.Effect.succeed(false))
1176
+ import_effect8.Effect.map((info) => info.type === "File"),
1177
+ import_effect8.Effect.catchAll(() => import_effect8.Effect.succeed(false))
294
1178
  );
295
- const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(import_effect5.Effect.catchAll(() => import_effect5.Effect.succeed(void 0)));
1179
+ const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(import_effect8.Effect.catchAll(() => import_effect8.Effect.succeed(void 0)));
296
1180
  const manifest = content ? parsePackageJson(content) : void 0;
297
1181
  const startScript = typeof manifest?.scripts?.start === "string" ? manifest.scripts.start : void 0;
298
1182
  const candidates = [
@@ -392,6 +1276,21 @@ var hono = {
392
1276
  refineDefaultConfig: resolveNodeServerEntrypoint
393
1277
  };
394
1278
 
1279
+ // src/detection/frameworks/laminas.ts
1280
+ var laminas = {
1281
+ slug: "laminas",
1282
+ name: "Laminas MVC",
1283
+ language: "php",
1284
+ detectors: [{ matchPackage: "laminas/laminas-mvc" }, { path: "public/index.php" }],
1285
+ priority: 80,
1286
+ getDefaultConfig: () => phpFrontControllerDefaults({
1287
+ documentRoot: "public",
1288
+ // The merged-config and module-map caches of the skeleton.
1289
+ writablePaths: ["data/cache"],
1290
+ excludeFiles: ["test/", "data/cache/"]
1291
+ })
1292
+ };
1293
+
395
1294
  // src/detection/frameworks/laravel.ts
396
1295
  var laravel = {
397
1296
  slug: "laravel",
@@ -410,6 +1309,18 @@ var laravel = {
410
1309
  populateAssetCache: true,
411
1310
  excludeFiles: ["tests/", "storage/", ".ddev", "node_modules/"],
412
1311
  routes: [{ source: "/*", destination: "public/index.php" }],
1312
+ package: {
1313
+ // storage/ is excluded, so the directories Laravel writes compiled
1314
+ // views, file caches, sessions and logs to are created empty.
1315
+ writablePaths: [
1316
+ "bootstrap/cache",
1317
+ "storage/app/public",
1318
+ "storage/framework/cache/data",
1319
+ "storage/framework/sessions",
1320
+ "storage/framework/views",
1321
+ "storage/logs"
1322
+ ]
1323
+ },
413
1324
  environmentVariables: {
414
1325
  APP_NAME: "Laravel",
415
1326
  APP_ENV: "production",
@@ -423,14 +1334,29 @@ var laravel = {
423
1334
  })
424
1335
  };
425
1336
 
1337
+ // src/detection/frameworks/mezzio.ts
1338
+ var mezzio = {
1339
+ slug: "mezzio",
1340
+ name: "Mezzio",
1341
+ language: "php",
1342
+ detectors: [{ matchPackage: "mezzio/mezzio" }, { path: "public/index.php" }],
1343
+ priority: 80,
1344
+ getDefaultConfig: () => phpFrontControllerDefaults({
1345
+ documentRoot: "public",
1346
+ // config/config.php writes the merged config cache here.
1347
+ writablePaths: ["data/cache"],
1348
+ excludeFiles: ["test/", "data/cache/"]
1349
+ })
1350
+ };
1351
+
426
1352
  // src/detection/frameworks/nestjs.ts
427
- var import_platform4 = require("@effect/platform");
428
- var import_effect6 = require("effect");
429
- var isRecord2 = (value) => typeof value === "object" && value !== null;
1353
+ var import_platform6 = require("@effect/platform");
1354
+ var import_effect9 = require("effect");
1355
+ var isRecord3 = (value) => typeof value === "object" && value !== null;
430
1356
  var parseNestCliJson = (content) => {
431
1357
  try {
432
1358
  const parsed = JSON.parse(content);
433
- if (!isRecord2(parsed)) return void 0;
1359
+ if (!isRecord3(parsed)) return void 0;
434
1360
  return parsed;
435
1361
  } catch {
436
1362
  return void 0;
@@ -476,9 +1402,9 @@ var nestjs = {
476
1402
  detectors: [{ matchPackage: "@nestjs/core" }],
477
1403
  priority: 90,
478
1404
  getDefaultConfig: () => createNestConfig("dist"),
479
- refineDefaultConfig: (defaults, projectFolder) => import_effect6.Effect.gen(function* () {
480
- const fs = yield* import_platform4.FileSystem.FileSystem;
481
- const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(import_effect6.Effect.catchAll(() => import_effect6.Effect.succeed(void 0)));
1405
+ refineDefaultConfig: (defaults, projectFolder) => import_effect9.Effect.gen(function* () {
1406
+ const fs = yield* import_platform6.FileSystem.FileSystem;
1407
+ const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(void 0)));
482
1408
  if (!content) return defaults;
483
1409
  const nestConfig = parseNestCliJson(content);
484
1410
  const outputPath = nestConfig ? getProjectOutputPath(nestConfig) : void 0;
@@ -487,28 +1413,28 @@ var nestjs = {
487
1413
  };
488
1414
 
489
1415
  // src/detection/frameworks/nextjs.ts
490
- var import_platform6 = require("@effect/platform");
491
- var import_effect9 = require("effect");
1416
+ var import_platform8 = require("@effect/platform");
1417
+ var import_effect12 = require("effect");
492
1418
 
493
1419
  // src/build-output-v3/get-default-path-map.ts
494
- var import_platform5 = require("@effect/platform");
495
- var import_effect7 = require("effect");
496
- var getDefaultPathMap = import_effect7.Effect.fn("getDefaultPathMap")(function* (directory) {
497
- const fs = yield* import_platform5.FileSystem.FileSystem;
498
- const pathService = yield* import_platform5.Path.Path;
1420
+ var import_platform7 = require("@effect/platform");
1421
+ var import_effect10 = require("effect");
1422
+ var getDefaultPathMap = import_effect10.Effect.fn("getDefaultPathMap")(function* (directory) {
1423
+ const fs = yield* import_platform7.FileSystem.FileSystem;
1424
+ const pathService = yield* import_platform7.Path.Path;
499
1425
  const result = {};
500
- const exists = yield* fs.exists(directory).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed(false)));
1426
+ const exists = yield* fs.exists(directory).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed(false)));
501
1427
  if (!exists) {
502
1428
  return result;
503
1429
  }
504
1430
  const MAX_DEPTH = 100;
505
- const processDirectory = (currentPath, basePath = "", depth = 0) => import_effect7.Effect.gen(function* () {
1431
+ const processDirectory = (currentPath, basePath = "", depth = 0) => import_effect10.Effect.gen(function* () {
506
1432
  if (depth > MAX_DEPTH) return;
507
- const entries = yield* fs.readDirectory(currentPath).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed([])));
1433
+ const entries = yield* fs.readDirectory(currentPath).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed([])));
508
1434
  for (const name of entries) {
509
1435
  const fullPath = pathService.join(currentPath, name);
510
1436
  const relativePath = basePath ? pathService.join(basePath, name) : name;
511
- const stat = yield* fs.stat(fullPath).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed(null)));
1437
+ const stat = yield* fs.stat(fullPath).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed(null)));
512
1438
  if (!stat) continue;
513
1439
  if (stat.type === "Directory") {
514
1440
  yield* processDirectory(fullPath, relativePath, depth + 1);
@@ -525,291 +1451,269 @@ var getDefaultPathMap = import_effect7.Effect.fn("getDefaultPathMap")(function*
525
1451
  var MAXIMUM_ENTRY_PAGE_PATHS = 50;
526
1452
 
527
1453
  // src/nextjs-manifest.ts
528
- var import_effect8 = require("effect");
529
- var StringArraySchema = import_effect8.Schema.mutable(import_effect8.Schema.Array(import_effect8.Schema.String));
530
- var StringRecordSchema = import_effect8.Schema.mutable(import_effect8.Schema.Record({ key: import_effect8.Schema.String, value: import_effect8.Schema.String }));
531
- var JsonValueSchema = import_effect8.Schema.suspend(
532
- () => import_effect8.Schema.Union(
533
- import_effect8.Schema.Null,
534
- import_effect8.Schema.Boolean,
535
- import_effect8.Schema.Number,
536
- import_effect8.Schema.String,
537
- import_effect8.Schema.mutable(import_effect8.Schema.Array(JsonValueSchema)),
538
- import_effect8.Schema.mutable(import_effect8.Schema.Record({ key: import_effect8.Schema.String, value: JsonValueSchema }))
1454
+ var import_effect11 = require("effect");
1455
+ var StringArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.String));
1456
+ var StringRecordSchema = import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: import_effect11.Schema.String }));
1457
+ var JsonValueSchema = import_effect11.Schema.suspend(
1458
+ () => import_effect11.Schema.Union(
1459
+ import_effect11.Schema.Null,
1460
+ import_effect11.Schema.Boolean,
1461
+ import_effect11.Schema.Number,
1462
+ import_effect11.Schema.String,
1463
+ import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema)),
1464
+ import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: JsonValueSchema }))
539
1465
  )
540
1466
  );
541
- var ImageLocalPatternSchema = import_effect8.Schema.mutable(
542
- import_effect8.Schema.Struct({
543
- pathname: import_effect8.Schema.optional(import_effect8.Schema.String),
544
- search: import_effect8.Schema.optional(import_effect8.Schema.String)
1467
+ var ImageLocalPatternSchema = import_effect11.Schema.mutable(
1468
+ import_effect11.Schema.Struct({
1469
+ pathname: import_effect11.Schema.optional(import_effect11.Schema.String),
1470
+ search: import_effect11.Schema.optional(import_effect11.Schema.String)
545
1471
  })
546
1472
  );
547
- var ImageRemotePatternSchema = import_effect8.Schema.mutable(
548
- import_effect8.Schema.Struct({
549
- protocol: import_effect8.Schema.optional(import_effect8.Schema.Literal("http", "https")),
550
- hostname: import_effect8.Schema.String,
551
- port: import_effect8.Schema.optional(import_effect8.Schema.String),
552
- pathname: import_effect8.Schema.optional(import_effect8.Schema.String),
553
- search: import_effect8.Schema.optional(import_effect8.Schema.String)
1473
+ var ImageRemotePatternSchema = import_effect11.Schema.mutable(
1474
+ import_effect11.Schema.Struct({
1475
+ protocol: import_effect11.Schema.optional(import_effect11.Schema.Literal("http", "https")),
1476
+ hostname: import_effect11.Schema.String,
1477
+ port: import_effect11.Schema.optional(import_effect11.Schema.String),
1478
+ pathname: import_effect11.Schema.optional(import_effect11.Schema.String),
1479
+ search: import_effect11.Schema.optional(import_effect11.Schema.String)
554
1480
  })
555
1481
  );
556
- var ImageVariantSchema = import_effect8.Schema.mutable(
557
- import_effect8.Schema.Struct({
558
- width: import_effect8.Schema.optional(import_effect8.Schema.Number),
559
- height: import_effect8.Schema.optional(import_effect8.Schema.Number),
560
- quality: import_effect8.Schema.optional(import_effect8.Schema.Number),
561
- format: import_effect8.Schema.optional(import_effect8.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png")),
562
- fit: import_effect8.Schema.optional(import_effect8.Schema.Literal("contain", "cover", "fill", "inside", "outside"))
1482
+ var ImageVariantSchema = import_effect11.Schema.mutable(
1483
+ import_effect11.Schema.Struct({
1484
+ width: import_effect11.Schema.optional(import_effect11.Schema.Number),
1485
+ height: import_effect11.Schema.optional(import_effect11.Schema.Number),
1486
+ quality: import_effect11.Schema.optional(import_effect11.Schema.Number),
1487
+ format: import_effect11.Schema.optional(import_effect11.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png")),
1488
+ fit: import_effect11.Schema.optional(import_effect11.Schema.Literal("contain", "cover", "fill", "inside", "outside"))
563
1489
  })
564
1490
  );
565
- var PositiveIntSchema = import_effect8.Schema.Int.pipe(import_effect8.Schema.greaterThan(0));
566
- var ImagePolicySchema = import_effect8.Schema.mutable(
567
- import_effect8.Schema.Struct({
568
- localPatterns: import_effect8.Schema.mutable(import_effect8.Schema.Array(ImageLocalPatternSchema)),
569
- remotePatterns: import_effect8.Schema.mutable(import_effect8.Schema.Array(ImageRemotePatternSchema)),
570
- widths: import_effect8.Schema.mutable(import_effect8.Schema.Array(PositiveIntSchema)),
571
- heights: import_effect8.Schema.mutable(import_effect8.Schema.Array(PositiveIntSchema)),
572
- qualities: import_effect8.Schema.mutable(import_effect8.Schema.Array(import_effect8.Schema.Number.pipe(import_effect8.Schema.between(1, 100)))),
573
- formats: import_effect8.Schema.mutable(import_effect8.Schema.Array(import_effect8.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png"))),
574
- minimumCacheTTL: import_effect8.Schema.Number,
575
- dangerouslyAllowSVG: import_effect8.Schema.Boolean,
576
- contentSecurityPolicy: import_effect8.Schema.String,
577
- contentDispositionType: import_effect8.Schema.Literal("inline", "attachment"),
578
- maximumRedirects: import_effect8.Schema.Number,
579
- maximumResponseBody: import_effect8.Schema.Number,
580
- variants: import_effect8.Schema.mutable(import_effect8.Schema.Record({ key: import_effect8.Schema.String, value: ImageVariantSchema }))
1491
+ var PositiveIntSchema = import_effect11.Schema.Int.pipe(import_effect11.Schema.greaterThan(0));
1492
+ var ImagePolicySchema = import_effect11.Schema.mutable(
1493
+ import_effect11.Schema.Struct({
1494
+ localPatterns: import_effect11.Schema.mutable(import_effect11.Schema.Array(ImageLocalPatternSchema)),
1495
+ remotePatterns: import_effect11.Schema.mutable(import_effect11.Schema.Array(ImageRemotePatternSchema)),
1496
+ widths: import_effect11.Schema.mutable(import_effect11.Schema.Array(PositiveIntSchema)),
1497
+ heights: import_effect11.Schema.mutable(import_effect11.Schema.Array(PositiveIntSchema)),
1498
+ qualities: import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.Number.pipe(import_effect11.Schema.between(1, 100)))),
1499
+ formats: import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png"))),
1500
+ minimumCacheTTL: import_effect11.Schema.Number,
1501
+ dangerouslyAllowSVG: import_effect11.Schema.Boolean,
1502
+ contentSecurityPolicy: import_effect11.Schema.String,
1503
+ contentDispositionType: import_effect11.Schema.Literal("inline", "attachment"),
1504
+ maximumRedirects: import_effect11.Schema.Number,
1505
+ maximumResponseBody: import_effect11.Schema.Number,
1506
+ variants: import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: ImageVariantSchema }))
581
1507
  })
582
1508
  );
583
1509
  var GigadriveNextPrerenderOutputFields = {
584
- id: import_effect8.Schema.String,
585
- type: import_effect8.Schema.Literal("PRERENDER"),
1510
+ id: import_effect11.Schema.String,
1511
+ type: import_effect11.Schema.Literal("PRERENDER"),
586
1512
  pathname: UrlPathnameSchema,
587
- route: import_effect8.Schema.optional(UrlPathnameSchema),
588
- parentOutputId: import_effect8.Schema.String,
589
- groupId: import_effect8.Schema.Number,
590
- pprChain: import_effect8.Schema.optional(import_effect8.Schema.mutable(import_effect8.Schema.Struct({ headers: HttpSingleValueHeadersSchema }))),
591
- parentFallbackMode: import_effect8.Schema.optional(JsonValueSchema),
592
- fallback: import_effect8.Schema.optional(
593
- import_effect8.Schema.mutable(
594
- import_effect8.Schema.Struct({
595
- filePath: import_effect8.Schema.optional(PortableRelativePathSchema),
596
- initialStatus: import_effect8.Schema.optional(import_effect8.Schema.Int.pipe(import_effect8.Schema.between(100, 599))),
597
- initialHeaders: import_effect8.Schema.optional(HttpHeadersSchema),
598
- initialExpiration: import_effect8.Schema.optional(import_effect8.Schema.Number),
599
- initialRevalidate: import_effect8.Schema.optional(import_effect8.Schema.Union(import_effect8.Schema.Number, import_effect8.Schema.Literal(false))),
600
- postponedState: import_effect8.Schema.optional(import_effect8.Schema.String)
1513
+ route: import_effect11.Schema.optional(UrlPathnameSchema),
1514
+ parentOutputId: import_effect11.Schema.String,
1515
+ groupId: import_effect11.Schema.Number,
1516
+ pprChain: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Struct({ headers: HttpSingleValueHeadersSchema }))),
1517
+ parentFallbackMode: import_effect11.Schema.optional(JsonValueSchema),
1518
+ fallback: import_effect11.Schema.optional(
1519
+ import_effect11.Schema.mutable(
1520
+ import_effect11.Schema.Struct({
1521
+ filePath: import_effect11.Schema.optional(PortableRelativePathSchema),
1522
+ initialStatus: import_effect11.Schema.optional(import_effect11.Schema.Int.pipe(import_effect11.Schema.between(100, 599))),
1523
+ initialHeaders: import_effect11.Schema.optional(HttpHeadersSchema),
1524
+ initialExpiration: import_effect11.Schema.optional(import_effect11.Schema.Number),
1525
+ initialRevalidate: import_effect11.Schema.optional(import_effect11.Schema.Union(import_effect11.Schema.Number, import_effect11.Schema.Literal(false))),
1526
+ postponedState: import_effect11.Schema.optional(import_effect11.Schema.String)
601
1527
  })
602
1528
  )
603
1529
  ),
604
- config: import_effect8.Schema.mutable(
605
- import_effect8.Schema.Struct({
606
- allowQuery: import_effect8.Schema.optional(StringArraySchema),
607
- allowHeader: import_effect8.Schema.optional(StringArraySchema),
608
- bypassFor: import_effect8.Schema.optional(import_effect8.Schema.mutable(import_effect8.Schema.Array(JsonValueSchema))),
609
- renderingMode: import_effect8.Schema.optional(import_effect8.Schema.String),
610
- partialFallback: import_effect8.Schema.optional(import_effect8.Schema.Boolean),
611
- bypassToken: import_effect8.Schema.optional(import_effect8.Schema.String)
1530
+ config: import_effect11.Schema.mutable(
1531
+ import_effect11.Schema.Struct({
1532
+ allowQuery: import_effect11.Schema.optional(StringArraySchema),
1533
+ allowHeader: import_effect11.Schema.optional(StringArraySchema),
1534
+ bypassFor: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema))),
1535
+ renderingMode: import_effect11.Schema.optional(import_effect11.Schema.String),
1536
+ partialFallback: import_effect11.Schema.optional(import_effect11.Schema.Boolean),
1537
+ bypassToken: import_effect11.Schema.optional(import_effect11.Schema.String)
612
1538
  })
613
1539
  )
614
1540
  };
615
- var GigadriveNextPrerenderClassificationSchema = import_effect8.Schema.Struct({
616
- routeType: import_effect8.Schema.Literal("route", "fallback", "shell", "page"),
617
- response: import_effect8.Schema.Literal("empty", "initial", "complete"),
618
- compute: import_effect8.Schema.Literal("blocking", "resuming", "static"),
619
- htmlSize: import_effect8.Schema.optional(import_effect8.Schema.Number)
1541
+ var GigadriveNextPrerenderClassificationSchema = import_effect11.Schema.Struct({
1542
+ routeType: import_effect11.Schema.Literal("route", "fallback", "shell", "page"),
1543
+ response: import_effect11.Schema.Literal("empty", "initial", "complete"),
1544
+ compute: import_effect11.Schema.Literal("blocking", "resuming", "static"),
1545
+ htmlSize: import_effect11.Schema.optional(import_effect11.Schema.Number)
620
1546
  });
621
- var GigadriveNextPrerenderWithoutClassificationSchema = import_effect8.Schema.Struct({
622
- routeType: import_effect8.Schema.optional(import_effect8.Schema.Never),
623
- response: import_effect8.Schema.optional(import_effect8.Schema.Never),
624
- compute: import_effect8.Schema.optional(import_effect8.Schema.Never),
625
- htmlSize: import_effect8.Schema.optional(import_effect8.Schema.Never)
1547
+ var GigadriveNextPrerenderWithoutClassificationSchema = import_effect11.Schema.Struct({
1548
+ routeType: import_effect11.Schema.optional(import_effect11.Schema.Never),
1549
+ response: import_effect11.Schema.optional(import_effect11.Schema.Never),
1550
+ compute: import_effect11.Schema.optional(import_effect11.Schema.Never),
1551
+ htmlSize: import_effect11.Schema.optional(import_effect11.Schema.Never)
626
1552
  });
627
- var GigadriveNextPrerenderOutputSchema = import_effect8.Schema.Union(
628
- import_effect8.Schema.mutable(
629
- import_effect8.Schema.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
1553
+ var GigadriveNextPrerenderOutputSchema = import_effect11.Schema.Union(
1554
+ import_effect11.Schema.mutable(
1555
+ import_effect11.Schema.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
630
1556
  ),
631
- import_effect8.Schema.mutable(
632
- import_effect8.Schema.Struct({
1557
+ import_effect11.Schema.mutable(
1558
+ import_effect11.Schema.Struct({
633
1559
  ...GigadriveNextPrerenderOutputFields,
634
1560
  ...GigadriveNextPrerenderWithoutClassificationSchema.fields
635
1561
  })
636
1562
  )
637
1563
  );
638
- var GigadriveNextStaticAssetPrefixSchema = import_effect8.Schema.mutable(
639
- import_effect8.Schema.Struct({
1564
+ var GigadriveNextStaticAssetPrefixSchema = import_effect11.Schema.mutable(
1565
+ import_effect11.Schema.Struct({
640
1566
  sourceDir: PortableRelativePathSchema,
641
1567
  urlPrefix: PortableRelativePathSchema,
642
- immutable: import_effect8.Schema.Boolean
1568
+ immutable: import_effect11.Schema.Boolean
643
1569
  })
644
1570
  );
645
- var GigadriveNextRouteConditionSchema = import_effect8.Schema.Union(
646
- import_effect8.Schema.mutable(
647
- import_effect8.Schema.Struct({
648
- type: import_effect8.Schema.Literal("header", "cookie", "query"),
649
- key: import_effect8.Schema.String,
650
- value: import_effect8.Schema.optional(import_effect8.Schema.String)
1571
+ var GigadriveNextRouteConditionSchema = import_effect11.Schema.Union(
1572
+ import_effect11.Schema.mutable(
1573
+ import_effect11.Schema.Struct({
1574
+ type: import_effect11.Schema.Literal("header", "cookie", "query"),
1575
+ key: import_effect11.Schema.String,
1576
+ value: import_effect11.Schema.optional(import_effect11.Schema.String)
651
1577
  })
652
1578
  ),
653
- import_effect8.Schema.mutable(
654
- import_effect8.Schema.Struct({
655
- type: import_effect8.Schema.Literal("host"),
656
- value: import_effect8.Schema.String
1579
+ import_effect11.Schema.mutable(
1580
+ import_effect11.Schema.Struct({
1581
+ type: import_effect11.Schema.Literal("host"),
1582
+ value: import_effect11.Schema.String
657
1583
  })
658
1584
  )
659
1585
  );
660
- var GigadriveNextMiddlewareMatcherSchema = import_effect8.Schema.mutable(
661
- import_effect8.Schema.Struct({
662
- source: import_effect8.Schema.String,
663
- sourceRegex: import_effect8.Schema.String,
664
- has: import_effect8.Schema.optional(import_effect8.Schema.mutable(import_effect8.Schema.Array(GigadriveNextRouteConditionSchema))),
665
- missing: import_effect8.Schema.optional(import_effect8.Schema.mutable(import_effect8.Schema.Array(GigadriveNextRouteConditionSchema)))
1586
+ var GigadriveNextMiddlewareMatcherSchema = import_effect11.Schema.mutable(
1587
+ import_effect11.Schema.Struct({
1588
+ source: import_effect11.Schema.String,
1589
+ sourceRegex: import_effect11.Schema.String,
1590
+ has: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextRouteConditionSchema))),
1591
+ missing: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextRouteConditionSchema)))
666
1592
  })
667
1593
  );
668
- var GigadriveNextMiddlewareDescriptorSchema = import_effect8.Schema.Union(
669
- import_effect8.Schema.mutable(
670
- import_effect8.Schema.Struct({
671
- present: import_effect8.Schema.Literal(true),
672
- matchers: import_effect8.Schema.mutable(import_effect8.Schema.Array(GigadriveNextMiddlewareMatcherSchema))
1594
+ var GigadriveNextMiddlewareDescriptorSchema = import_effect11.Schema.Union(
1595
+ import_effect11.Schema.mutable(
1596
+ import_effect11.Schema.Struct({
1597
+ present: import_effect11.Schema.Literal(true),
1598
+ matchers: import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextMiddlewareMatcherSchema))
673
1599
  })
674
1600
  ),
675
- import_effect8.Schema.mutable(
676
- import_effect8.Schema.Struct({
677
- present: import_effect8.Schema.Literal(false),
678
- matchers: import_effect8.Schema.mutable(import_effect8.Schema.Tuple())
1601
+ import_effect11.Schema.mutable(
1602
+ import_effect11.Schema.Struct({
1603
+ present: import_effect11.Schema.Literal(false),
1604
+ matchers: import_effect11.Schema.mutable(import_effect11.Schema.Tuple())
679
1605
  })
680
1606
  )
681
1607
  );
682
- var GigadriveNextServerDescriptorSchema = import_effect8.Schema.mutable(
683
- import_effect8.Schema.Struct({
684
- maxDuration: import_effect8.Schema.optional(import_effect8.Schema.Number),
685
- preferredRegion: import_effect8.Schema.optional(import_effect8.Schema.Union(import_effect8.Schema.String, StringArraySchema)),
686
- env: import_effect8.Schema.optional(StringRecordSchema)
1608
+ var GigadriveNextServerDescriptorSchema = import_effect11.Schema.mutable(
1609
+ import_effect11.Schema.Struct({
1610
+ maxDuration: import_effect11.Schema.optional(import_effect11.Schema.Number),
1611
+ preferredRegion: import_effect11.Schema.optional(import_effect11.Schema.Union(import_effect11.Schema.String, StringArraySchema)),
1612
+ env: import_effect11.Schema.optional(StringRecordSchema)
687
1613
  })
688
1614
  );
689
- var PrerenderArraySchema = import_effect8.Schema.mutable(import_effect8.Schema.Array(GigadriveNextPrerenderOutputSchema));
690
- var StaticAssetArraySchema = import_effect8.Schema.mutable(import_effect8.Schema.Array(GigadriveNextStaticAssetPrefixSchema));
691
- var JsonValueArraySchema = import_effect8.Schema.mutable(import_effect8.Schema.Array(JsonValueSchema));
692
- var GigadriveNextPrerenderSeedMethodsSchema = import_effect8.Schema.mutable(
693
- import_effect8.Schema.Tuple(import_effect8.Schema.Literal("GET"), import_effect8.Schema.Literal("HEAD"))
1615
+ var PrerenderArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextPrerenderOutputSchema));
1616
+ var StaticAssetArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextStaticAssetPrefixSchema));
1617
+ var JsonValueArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema));
1618
+ var GigadriveNextPrerenderSeedMethodsSchema = import_effect11.Schema.mutable(
1619
+ import_effect11.Schema.Tuple(import_effect11.Schema.Literal("GET"), import_effect11.Schema.Literal("HEAD"))
694
1620
  );
695
- var GigadriveNextBuildManifestV1Schema = import_effect8.Schema.mutable(
696
- import_effect8.Schema.Struct({
697
- version: import_effect8.Schema.Literal(1),
698
- output: import_effect8.Schema.Literal("standalone", "export"),
1621
+ var GigadriveNextBuildManifestV1Schema = import_effect11.Schema.mutable(
1622
+ import_effect11.Schema.Struct({
1623
+ version: import_effect11.Schema.Literal(1),
1624
+ output: import_effect11.Schema.Literal("standalone", "export"),
699
1625
  distDir: PortableRelativePathSchema,
700
1626
  repoRootToProject: RepositoryRelativePathSchema,
701
- nextVersion: import_effect8.Schema.String,
702
- buildId: import_effect8.Schema.String
1627
+ nextVersion: import_effect11.Schema.String,
1628
+ buildId: import_effect11.Schema.String
703
1629
  })
704
1630
  );
705
- var GigadriveNextBuildManifestV2ExportSchema = import_effect8.Schema.mutable(
706
- import_effect8.Schema.Struct({
707
- version: import_effect8.Schema.Literal(2),
708
- mode: import_effect8.Schema.Literal("export"),
1631
+ var GigadriveNextBuildManifestV2ExportSchema = import_effect11.Schema.mutable(
1632
+ import_effect11.Schema.Struct({
1633
+ version: import_effect11.Schema.Literal(2),
1634
+ mode: import_effect11.Schema.Literal("export"),
709
1635
  distDir: PortableRelativePathSchema,
710
1636
  repoRootToProject: RepositoryRelativePathSchema,
711
- nextVersion: import_effect8.Schema.String,
712
- buildId: import_effect8.Schema.String
1637
+ nextVersion: import_effect11.Schema.String,
1638
+ buildId: import_effect11.Schema.String
713
1639
  })
714
1640
  );
715
- var GigadriveNextBuildManifestV2StandaloneSchema = import_effect8.Schema.mutable(
716
- import_effect8.Schema.Struct({
717
- version: import_effect8.Schema.Literal(2),
718
- mode: import_effect8.Schema.Literal("standalone-v2"),
1641
+ var GigadriveNextBuildManifestV2StandaloneSchema = import_effect11.Schema.mutable(
1642
+ import_effect11.Schema.Struct({
1643
+ version: import_effect11.Schema.Literal(2),
1644
+ mode: import_effect11.Schema.Literal("standalone-v2"),
719
1645
  distDir: PortableRelativePathSchema,
720
1646
  repoRootToProject: RepositoryRelativePathSchema,
721
- nextVersion: import_effect8.Schema.String,
722
- buildId: import_effect8.Schema.String,
1647
+ nextVersion: import_effect11.Schema.String,
1648
+ buildId: import_effect11.Schema.String,
723
1649
  server: GigadriveNextServerDescriptorSchema,
724
- config: import_effect8.Schema.mutable(
725
- import_effect8.Schema.Struct({
726
- basePath: import_effect8.Schema.String,
727
- trailingSlash: import_effect8.Schema.Boolean,
728
- cacheComponents: import_effect8.Schema.Boolean,
729
- i18n: import_effect8.Schema.optional(JsonValueSchema),
730
- images: import_effect8.Schema.optional(ImagePolicySchema)
1650
+ config: import_effect11.Schema.mutable(
1651
+ import_effect11.Schema.Struct({
1652
+ basePath: import_effect11.Schema.String,
1653
+ trailingSlash: import_effect11.Schema.Boolean,
1654
+ cacheComponents: import_effect11.Schema.Boolean,
1655
+ i18n: import_effect11.Schema.optional(JsonValueSchema),
1656
+ images: import_effect11.Schema.optional(ImagePolicySchema)
731
1657
  })
732
1658
  ),
733
- routing: import_effect8.Schema.mutable(
734
- import_effect8.Schema.Struct({
1659
+ routing: import_effect11.Schema.mutable(
1660
+ import_effect11.Schema.Struct({
735
1661
  beforeMiddleware: JsonValueArraySchema,
736
1662
  beforeFiles: JsonValueArraySchema,
737
1663
  afterFiles: JsonValueArraySchema,
738
1664
  dynamicRoutes: JsonValueArraySchema,
739
1665
  onMatch: JsonValueArraySchema,
740
1666
  fallback: JsonValueArraySchema,
741
- shouldNormalizeNextData: import_effect8.Schema.Boolean,
1667
+ shouldNormalizeNextData: import_effect11.Schema.Boolean,
742
1668
  rsc: JsonValueSchema
743
1669
  })
744
1670
  ),
745
- outputs: import_effect8.Schema.mutable(
746
- import_effect8.Schema.Struct({
1671
+ outputs: import_effect11.Schema.mutable(
1672
+ import_effect11.Schema.Struct({
747
1673
  prerenders: PrerenderArraySchema,
748
- prerenderManifest: import_effect8.Schema.optional(PortableRelativePathSchema),
749
- assetManifest: import_effect8.Schema.optional(PortableRelativePathSchema),
750
- entryPagePaths: import_effect8.Schema.optional(import_effect8.Schema.mutable(import_effect8.Schema.Array(UrlPathnameSchema))),
751
- staticAssets: StaticAssetArraySchema,
752
- middleware: import_effect8.Schema.optional(GigadriveNextMiddlewareDescriptorSchema)
753
- })
754
- )
755
- })
756
- );
757
- var GigadriveNextBuildManifestSchema = import_effect8.Schema.Union(
758
- GigadriveNextBuildManifestV1Schema,
759
- GigadriveNextBuildManifestV2ExportSchema,
760
- GigadriveNextBuildManifestV2StandaloneSchema
761
- );
762
- var GigadriveNextPrerenderManifestV1Schema = import_effect8.Schema.mutable(
763
- import_effect8.Schema.Struct({
764
- version: import_effect8.Schema.Literal(1),
765
- seedMethods: import_effect8.Schema.optional(GigadriveNextPrerenderSeedMethodsSchema),
766
- prerenders: PrerenderArraySchema
767
- })
768
- );
769
- var parseGigadriveNextBuildManifest = (content) => decodeJson(GigadriveNextBuildManifestSchema, content);
770
- var parseGigadriveNextPrerenderManifest = (content) => decodeJson(GigadriveNextPrerenderManifestV1Schema, content);
771
-
772
- // src/regions.ts
773
- var REGION_DEFINITIONS = [
774
- "us-east-1",
775
- "us-east-2",
776
- "us-west-1",
777
- "us-west-2",
778
- "ap-south-1",
779
- "ap-northeast-3",
780
- "ap-northeast-2",
781
- "ap-southeast-1",
782
- "ap-southeast-2",
783
- "ap-northeast-1",
784
- "ca-central-1",
785
- "eu-central-1",
786
- "eu-west-1",
787
- "eu-west-2",
788
- "eu-west-3",
789
- "eu-north-1",
790
- "sa-east-1"
791
- ];
792
- var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
1674
+ prerenderManifest: import_effect11.Schema.optional(PortableRelativePathSchema),
1675
+ assetManifest: import_effect11.Schema.optional(PortableRelativePathSchema),
1676
+ entryPagePaths: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(UrlPathnameSchema))),
1677
+ staticAssets: StaticAssetArraySchema,
1678
+ middleware: import_effect11.Schema.optional(GigadriveNextMiddlewareDescriptorSchema)
1679
+ })
1680
+ )
1681
+ })
1682
+ );
1683
+ var GigadriveNextBuildManifestSchema = import_effect11.Schema.Union(
1684
+ GigadriveNextBuildManifestV1Schema,
1685
+ GigadriveNextBuildManifestV2ExportSchema,
1686
+ GigadriveNextBuildManifestV2StandaloneSchema
1687
+ );
1688
+ var GigadriveNextPrerenderManifestV1Schema = import_effect11.Schema.mutable(
1689
+ import_effect11.Schema.Struct({
1690
+ version: import_effect11.Schema.Literal(1),
1691
+ seedMethods: import_effect11.Schema.optional(GigadriveNextPrerenderSeedMethodsSchema),
1692
+ prerenders: PrerenderArraySchema
1693
+ })
1694
+ );
1695
+ var parseGigadriveNextBuildManifest = (content) => decodeJson(GigadriveNextBuildManifestSchema, content);
1696
+ var parseGigadriveNextPrerenderManifest = (content) => decodeJson(GigadriveNextPrerenderManifestV1Schema, content);
793
1697
 
794
1698
  // src/detection/frameworks/nextjs.ts
795
1699
  var toPortablePath = (value) => value.replaceAll("\\", "/");
796
1700
  var STANDALONE_SERVER_MEMORY_MB = 1024;
797
- var resolveStandaloneServer = import_effect9.Effect.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
798
- const fs = yield* import_platform6.FileSystem.FileSystem;
799
- const pathService = yield* import_platform6.Path.Path;
1701
+ var resolveStandaloneServer = import_effect12.Effect.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
1702
+ const fs = yield* import_platform8.FileSystem.FileSystem;
1703
+ const pathService = yield* import_platform8.Path.Path;
800
1704
  const standaloneRoot = pathService.join(projectFolder, distDir, "standalone");
801
1705
  const monorepoServer = pathService.join(standaloneRoot, repoRootToProject, "server.js");
802
1706
  const rootServer = pathService.join(standaloneRoot, "server.js");
803
- const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(false)));
1707
+ const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(false)));
804
1708
  const serverPath = monorepoServerExists ? monorepoServer : rootServer;
805
- const serverExists = yield* fs.exists(serverPath).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(false)));
1709
+ const serverExists = yield* fs.exists(serverPath).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(false)));
806
1710
  if (!serverExists) return void 0;
807
1711
  const serverDirectory = pathService.dirname(serverPath);
808
1712
  const entrypoint = toPortablePath(pathService.relative(standaloneRoot, serverPath));
809
1713
  return { standaloneRoot, serverDirectory, entrypoint };
810
1714
  });
811
- var buildStandalonePublicAssets = import_effect9.Effect.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
812
- const pathService = yield* import_platform6.Path.Path;
1715
+ var buildStandalonePublicAssets = import_effect12.Effect.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
1716
+ const pathService = yield* import_platform8.Path.Path;
813
1717
  const publicFiles = yield* getDefaultPathMap(pathService.join(projectFolder, "public"));
814
1718
  const assetPaths = [];
815
1719
  const assetOverrides = {};
@@ -825,8 +1729,8 @@ var buildStandalonePublicAssets = import_effect9.Effect.fn("nextjs.buildStandalo
825
1729
  }
826
1730
  return { assetPaths, assetOverrides, filePathMap };
827
1731
  });
828
- var createStaticExportConfig = import_effect9.Effect.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
829
- const pathService = yield* import_platform6.Path.Path;
1732
+ var createStaticExportConfig = import_effect12.Effect.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
1733
+ const pathService = yield* import_platform8.Path.Path;
830
1734
  const outputDirectory = pathService.join(projectFolder, "out");
831
1735
  const files = yield* getDefaultPathMap(outputDirectory);
832
1736
  const assetPaths = [];
@@ -849,8 +1753,8 @@ var createStaticExportConfig = import_effect9.Effect.fn("nextjs.createStaticExpo
849
1753
  package: void 0
850
1754
  };
851
1755
  });
852
- var createStandaloneConfig = import_effect9.Effect.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
853
- const pathService = yield* import_platform6.Path.Path;
1756
+ var createStandaloneConfig = import_effect12.Effect.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
1757
+ const pathService = yield* import_platform8.Path.Path;
854
1758
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
855
1759
  if (!resolved) return defaults;
856
1760
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -883,7 +1787,7 @@ var createStandaloneConfig = import_effect9.Effect.fn("nextjs.createStandaloneCo
883
1787
  }
884
1788
  };
885
1789
  });
886
- var createStandaloneV2Config = import_effect9.Effect.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
1790
+ var createStandaloneV2Config = import_effect12.Effect.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
887
1791
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
888
1792
  if (!resolved) return defaults;
889
1793
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -991,9 +1895,9 @@ var nextjs = {
991
1895
  routes: [{ source: "/*", destination: ".next/standalone/server.js" }],
992
1896
  environmentVariables: { NODE_ENV: "production" }
993
1897
  }),
994
- refineDefaultConfig: (defaults, projectFolder) => import_effect9.Effect.gen(function* () {
995
- const fs = yield* import_platform6.FileSystem.FileSystem;
996
- const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(void 0)));
1898
+ refineDefaultConfig: (defaults, projectFolder) => import_effect12.Effect.gen(function* () {
1899
+ const fs = yield* import_platform8.FileSystem.FileSystem;
1900
+ const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(void 0)));
997
1901
  const manifest = manifestContent ? parseGigadriveNextBuildManifest(manifestContent) : void 0;
998
1902
  if (!manifest) return defaults;
999
1903
  if (manifest.version === 2) {
@@ -1004,8 +1908,8 @@ var nextjs = {
1004
1908
  };
1005
1909
 
1006
1910
  // src/detection/frameworks/nuxt.ts
1007
- var import_platform7 = require("@effect/platform");
1008
- var import_effect10 = require("effect");
1911
+ var import_platform9 = require("@effect/platform");
1912
+ var import_effect13 = require("effect");
1009
1913
  var isVercelBuildOutputV3 = (content) => {
1010
1914
  try {
1011
1915
  const config = JSON.parse(content);
@@ -1032,9 +1936,9 @@ var nuxt = {
1032
1936
  routes: [{ source: "/*", destination: ".output/server/index.mjs" }],
1033
1937
  environmentVariables: { NODE_ENV: "production", NITRO_PRESET: "node-server" }
1034
1938
  }),
1035
- refineDefaultConfig: (defaults, projectFolder) => import_effect10.Effect.gen(function* () {
1036
- const fs = yield* import_platform7.FileSystem.FileSystem;
1037
- const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed(void 0)));
1939
+ refineDefaultConfig: (defaults, projectFolder) => import_effect13.Effect.gen(function* () {
1940
+ const fs = yield* import_platform9.FileSystem.FileSystem;
1941
+ const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(import_effect13.Effect.catchAll(() => import_effect13.Effect.succeed(void 0)));
1038
1942
  if (!buildOutputConfig || !isVercelBuildOutputV3(buildOutputConfig)) {
1039
1943
  return defaults;
1040
1944
  }
@@ -1048,6 +1952,43 @@ var nuxt = {
1048
1952
  })
1049
1953
  };
1050
1954
 
1955
+ // src/detection/frameworks/php.ts
1956
+ var import_platform10 = require("@effect/platform");
1957
+ var import_effect14 = require("effect");
1958
+ var COMPOSER_FILES = ["composer.json", "composer.lock"];
1959
+ var DOCUMENT_ROOTS = ["public", "web", "webroot", "public_html", "htdocs", "."];
1960
+ var refinePhpDocumentRoot = (defaults, projectFolder) => import_effect14.Effect.gen(function* () {
1961
+ const fs = yield* import_platform10.FileSystem.FileSystem;
1962
+ const pathService = yield* import_platform10.Path.Path;
1963
+ for (const documentRoot of DOCUMENT_ROOTS) {
1964
+ const frontController = pathService.join(projectFolder, documentRoot, "index.php");
1965
+ if (yield* fs.exists(frontController).pipe(import_effect14.Effect.catchAll(() => import_effect14.Effect.succeed(false)))) {
1966
+ return phpFrontControllerDefaults({ documentRoot, excludeFiles: COMPOSER_FILES });
1967
+ }
1968
+ }
1969
+ return defaults;
1970
+ });
1971
+ var getDefaultConfig = () => phpFrontControllerDefaults({ documentRoot: ".", excludeFiles: COMPOSER_FILES });
1972
+ var frontControllerPath = (documentRoot) => documentRoot === "." ? "index.php" : `${documentRoot}/index.php`;
1973
+ var phpComposerProjects = DOCUMENT_ROOTS.map((documentRoot) => ({
1974
+ slug: "php",
1975
+ name: "PHP",
1976
+ language: "php",
1977
+ detectors: [{ path: "composer.json" }, { path: frontControllerPath(documentRoot) }],
1978
+ priority: 7,
1979
+ getDefaultConfig,
1980
+ refineDefaultConfig: refinePhpDocumentRoot
1981
+ }));
1982
+ var phpSites = DOCUMENT_ROOTS.map((documentRoot) => ({
1983
+ slug: "php-site",
1984
+ name: "PHP",
1985
+ language: "php",
1986
+ detectors: [{ path: frontControllerPath(documentRoot) }],
1987
+ priority: 6,
1988
+ getDefaultConfig: () => ({ ...getDefaultConfig(), installCommand: "" }),
1989
+ refineDefaultConfig: (defaults, projectFolder) => refinePhpDocumentRoot(defaults, projectFolder).pipe(import_effect14.Effect.map((refined) => ({ ...refined, installCommand: "" })))
1990
+ }));
1991
+
1051
1992
  // src/detection/frameworks/remix.ts
1052
1993
  var remix = {
1053
1994
  slug: "remix",
@@ -1069,6 +2010,21 @@ var remix = {
1069
2010
  })
1070
2011
  };
1071
2012
 
2013
+ // src/detection/frameworks/slim.ts
2014
+ var slim = {
2015
+ slug: "slim",
2016
+ name: "Slim",
2017
+ language: "php",
2018
+ detectors: [{ matchPackage: "slim/slim" }, { path: "public/index.php" }],
2019
+ priority: 75,
2020
+ getDefaultConfig: () => phpFrontControllerDefaults({
2021
+ documentRoot: "public",
2022
+ // slim/slim-skeleton logs to logs/ and caches the container and routes in var/cache.
2023
+ writablePaths: ["logs", "var/cache"],
2024
+ excludeFiles: ["tests/", "logs/", "var/"]
2025
+ })
2026
+ };
2027
+
1072
2028
  // src/detection/frameworks/sveltekit.ts
1073
2029
  var sveltekit = {
1074
2030
  slug: "sveltekit",
@@ -1108,12 +2064,13 @@ var symfony = {
1108
2064
  assetsDir: "public",
1109
2065
  populateAssetCache: true,
1110
2066
  excludeFiles: ["tests/*", ".ddev", "var"],
1111
- symlinks: { var: "/tmp" },
1112
2067
  routes: [{ source: "/*", destination: "public/index.php" }],
2068
+ // The kernel's cache and log directories. A symlink to /tmp used to stand
2069
+ // in for them, which the build rejected as pointing outside the project.
2070
+ package: { writablePaths: ["var/cache", "var/log"] },
1113
2071
  environmentVariables: {
1114
2072
  APP_ENV: "prod",
1115
2073
  APP_DEBUG: "false",
1116
- APP_SECRET: "please_change_this_value_in_your_settings",
1117
2074
  MESSENGER_TRANSPORT_DSN: "doctrine://default?auto_setup=0"
1118
2075
  }
1119
2076
  })
@@ -1139,6 +2096,22 @@ var vite = {
1139
2096
  })
1140
2097
  };
1141
2098
 
2099
+ // src/detection/frameworks/yii.ts
2100
+ var yii = {
2101
+ slug: "yii",
2102
+ name: "Yii 2",
2103
+ language: "php",
2104
+ detectors: [{ matchPackage: "yiisoft/yii2" }, { path: "web/index.php" }],
2105
+ priority: 80,
2106
+ getDefaultConfig: () => phpFrontControllerDefaults({
2107
+ documentRoot: "web",
2108
+ // @runtime holds cache, logs and sessions; the asset manager publishes
2109
+ // bundles from vendor/ into web/assets when a page first uses them.
2110
+ writablePaths: ["runtime", "web/assets"],
2111
+ excludeFiles: ["tests/", "runtime/", "web/assets/"]
2112
+ })
2113
+ };
2114
+
1142
2115
  // src/detection/frameworks/index.ts
1143
2116
  var FRAMEWORK_DEFINITIONS = [
1144
2117
  nextjs,
@@ -1149,15 +2122,23 @@ var FRAMEWORK_DEFINITIONS = [
1149
2122
  astro,
1150
2123
  laravel,
1151
2124
  symfony,
2125
+ cakephp,
2126
+ codeigniter,
2127
+ laminas,
2128
+ mezzio,
2129
+ yii,
2130
+ slim,
1152
2131
  hono,
1153
2132
  elysia,
1154
2133
  fastify,
1155
2134
  express,
2135
+ ...phpComposerProjects,
2136
+ ...phpSites,
1156
2137
  vite
1157
2138
  ].sort((a, b) => b.priority - a.priority);
1158
2139
 
1159
2140
  // src/detection/generate-config.ts
1160
- var import_effect11 = require("effect");
2141
+ var import_effect15 = require("effect");
1161
2142
  var getInstallCommand = (pm) => {
1162
2143
  switch (pm) {
1163
2144
  case "bun":
@@ -1173,11 +2154,16 @@ var getInstallCommand = (pm) => {
1173
2154
  return "npm install";
1174
2155
  }
1175
2156
  };
1176
- var generateConfig = import_effect11.Effect.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
2157
+ var COMPOSER_ROOT_FILES = /* @__PURE__ */ new Set(["composer.json", "composer.lock", "auth.json"]);
2158
+ var isPrivatePhpProjectFile = (projectPath) => {
2159
+ const segments = projectPath.split("/").filter((segment) => segment !== "" && segment !== ".");
2160
+ return segments.some((segment) => segment.startsWith(".")) || segments[0] === "vendor" || segments[0] === "node_modules" || segments.length === 1 && COMPOSER_ROOT_FILES.has(segments[0] ?? "");
2161
+ };
2162
+ var generateConfig = import_effect15.Effect.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
1177
2163
  const defaults = refinedDefaults ?? framework.getDefaultConfig(packageManager);
1178
2164
  const installCmd = defaults.installCommand ?? getInstallCommand(packageManager);
1179
- const commands = [installCmd, ...defaults.commands];
1180
- yield* import_effect11.Effect.logDebug(`Generating config for ${framework.name}`, {
2165
+ const commands = installCmd ? [installCmd, ...defaults.commands] : [...defaults.commands];
2166
+ yield* import_effect15.Effect.logDebug(`Generating config for ${framework.name}`, {
1181
2167
  runtime: defaults.runtime,
1182
2168
  entrypoint: defaults.entrypoint,
1183
2169
  commands
@@ -1219,6 +2205,7 @@ var generateConfig = import_effect11.Effect.fn("generateConfig")(function* (fram
1219
2205
  if (assetsDir && !defaults.assetPaths && !defaults.assetPrefixes && !defaults.assetManifests) {
1220
2206
  for (const file of yield* collectAssetFiles(projectFolder, assetsDir)) {
1221
2207
  if (framework.language === "php" && /\.(php|phtml|phar)$/i.test(file)) continue;
2208
+ if (framework.language === "php" && isPrivatePhpProjectFile(`${assetsDir}/${file}`)) continue;
1222
2209
  paths.push(`${assetsDir}/${file}`);
1223
2210
  if (!servesDirectoryIndexes || file in overrides) continue;
1224
2211
  if (file === "index.html") {
@@ -1242,31 +2229,31 @@ var generateConfig = import_effect11.Effect.fn("generateConfig")(function* (fram
1242
2229
  });
1243
2230
 
1244
2231
  // src/detection/read-dependencies.ts
1245
- var import_platform8 = require("@effect/platform");
1246
- var import_effect13 = require("effect");
2232
+ var import_platform11 = require("@effect/platform");
2233
+ var import_effect17 = require("effect");
1247
2234
 
1248
2235
  // src/detection/types.ts
1249
- var import_effect12 = require("effect");
1250
- var ManifestNotFoundError = class extends import_effect12.Schema.TaggedError()("ManifestNotFoundError", {
1251
- message: import_effect12.Schema.String,
1252
- filePath: import_effect12.Schema.String
2236
+ var import_effect16 = require("effect");
2237
+ var ManifestNotFoundError = class extends import_effect16.Schema.TaggedError()("ManifestNotFoundError", {
2238
+ message: import_effect16.Schema.String,
2239
+ filePath: import_effect16.Schema.String
1253
2240
  }) {
1254
2241
  };
1255
- var ManifestReadError = class extends import_effect12.Schema.TaggedError()("ManifestReadError", {
1256
- message: import_effect12.Schema.String,
1257
- filePath: import_effect12.Schema.String
2242
+ var ManifestReadError = class extends import_effect16.Schema.TaggedError()("ManifestReadError", {
2243
+ message: import_effect16.Schema.String,
2244
+ filePath: import_effect16.Schema.String
1258
2245
  }) {
1259
2246
  };
1260
- var ManifestParseError = class extends import_effect12.Schema.TaggedError()("ManifestParseError", {
1261
- message: import_effect12.Schema.String,
1262
- filePath: import_effect12.Schema.String
2247
+ var ManifestParseError = class extends import_effect16.Schema.TaggedError()("ManifestParseError", {
2248
+ message: import_effect16.Schema.String,
2249
+ filePath: import_effect16.Schema.String
1263
2250
  }) {
1264
2251
  };
1265
- var FrameworkNotDetectedError = class extends import_effect12.Schema.TaggedError()(
2252
+ var FrameworkNotDetectedError = class extends import_effect16.Schema.TaggedError()(
1266
2253
  "FrameworkNotDetectedError",
1267
2254
  {
1268
- message: import_effect12.Schema.String,
1269
- directory: import_effect12.Schema.String
2255
+ message: import_effect16.Schema.String,
2256
+ directory: import_effect16.Schema.String
1270
2257
  }
1271
2258
  ) {
1272
2259
  };
@@ -1276,25 +2263,25 @@ var MANIFEST_CONFIG = {
1276
2263
  node: { file: "package.json", depKeys: ["dependencies", "devDependencies"] },
1277
2264
  php: { file: "composer.json", depKeys: ["require", "require-dev"] }
1278
2265
  };
1279
- var readManifest = import_effect13.Effect.fn("readManifest")(function* (filePath) {
1280
- const fs = yield* import_platform8.FileSystem.FileSystem;
1281
- const exists = yield* fs.exists(filePath).pipe(import_effect13.Effect.catchAll(() => import_effect13.Effect.succeed(false)));
2266
+ var readManifest = import_effect17.Effect.fn("readManifest")(function* (filePath) {
2267
+ const fs = yield* import_platform11.FileSystem.FileSystem;
2268
+ const exists = yield* fs.exists(filePath).pipe(import_effect17.Effect.catchAll(() => import_effect17.Effect.succeed(false)));
1282
2269
  if (!exists) {
1283
- return yield* import_effect13.Effect.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
2270
+ return yield* import_effect17.Effect.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
1284
2271
  }
1285
- const content = yield* fs.readFileString(filePath).pipe(import_effect13.Effect.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
1286
- const parsed = yield* import_effect13.Effect.try({
2272
+ const content = yield* fs.readFileString(filePath).pipe(import_effect17.Effect.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
2273
+ const parsed = yield* import_effect17.Effect.try({
1287
2274
  try: () => JSON.parse(content),
1288
2275
  catch: () => new ManifestParseError({ message: "Failed to parse manifest JSON", filePath })
1289
2276
  });
1290
2277
  return parsed;
1291
2278
  });
1292
- var readDependencies = import_effect13.Effect.fn("readDependencies")(function* (projectFolder, language) {
1293
- const pathService = yield* import_platform8.Path.Path;
2279
+ var readDependencies = import_effect17.Effect.fn("readDependencies")(function* (projectFolder, language) {
2280
+ const pathService = yield* import_platform11.Path.Path;
1294
2281
  const config = MANIFEST_CONFIG[language];
1295
2282
  const filePath = pathService.join(projectFolder, config.file);
1296
2283
  const manifest = yield* readManifest(filePath).pipe(
1297
- import_effect13.Effect.catchTag("ManifestNotFoundError", () => import_effect13.Effect.succeed(null))
2284
+ import_effect17.Effect.catchTag("ManifestNotFoundError", () => import_effect17.Effect.succeed(null))
1298
2285
  );
1299
2286
  if (!manifest) {
1300
2287
  return /* @__PURE__ */ new Set();
@@ -1312,9 +2299,9 @@ var readDependencies = import_effect13.Effect.fn("readDependencies")(function* (
1312
2299
  });
1313
2300
 
1314
2301
  // src/detection/detect-framework.ts
1315
- var matchesDetectionItem = import_effect14.Effect.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
1316
- const fs = yield* import_platform9.FileSystem.FileSystem;
1317
- const pathService = yield* import_platform9.Path.Path;
2302
+ var matchesDetectionItem = import_effect18.Effect.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
2303
+ const fs = yield* import_platform12.FileSystem.FileSystem;
2304
+ const pathService = yield* import_platform12.Path.Path;
1318
2305
  if (item.matchPackage != null) {
1319
2306
  const deps = yield* cachedReadDeps(`${projectFolder}\0${framework.language}`);
1320
2307
  if (!deps.has(item.matchPackage)) {
@@ -1323,12 +2310,12 @@ var matchesDetectionItem = import_effect14.Effect.fn("matchesDetectionItem")(fun
1323
2310
  }
1324
2311
  if (item.path != null) {
1325
2312
  const filePath = pathService.join(projectFolder, item.path);
1326
- const exists = yield* fs.exists(filePath).pipe(import_effect14.Effect.catchAll(() => import_effect14.Effect.succeed(false)));
2313
+ const exists = yield* fs.exists(filePath).pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed(false)));
1327
2314
  if (!exists) {
1328
2315
  return false;
1329
2316
  }
1330
2317
  if (item.matchContent != null) {
1331
- const content = yield* fs.readFileString(filePath).pipe(import_effect14.Effect.catchAll(() => import_effect14.Effect.succeed("")));
2318
+ const content = yield* fs.readFileString(filePath).pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed("")));
1332
2319
  let regexMatch = false;
1333
2320
  try {
1334
2321
  regexMatch = new RegExp(item.matchContent).test(content);
@@ -1341,7 +2328,7 @@ var matchesDetectionItem = import_effect14.Effect.fn("matchesDetectionItem")(fun
1341
2328
  }
1342
2329
  return true;
1343
2330
  });
1344
- var matchesFramework = import_effect14.Effect.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
2331
+ var matchesFramework = import_effect18.Effect.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
1345
2332
  for (const detector of framework.detectors) {
1346
2333
  const matches = yield* matchesDetectionItem(detector, projectFolder, framework, cachedReadDeps);
1347
2334
  if (!matches) {
@@ -1350,9 +2337,9 @@ var matchesFramework = import_effect14.Effect.fn("matchesFramework")(function* (
1350
2337
  }
1351
2338
  return true;
1352
2339
  });
1353
- var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (projectFolder) {
1354
- yield* import_effect14.Effect.logDebug(`Detecting framework in ${projectFolder}`);
1355
- const cachedReadDeps = yield* import_effect14.Effect.cachedFunction((key) => {
2340
+ var detectFramework = import_effect18.Effect.fn("detectFramework")(function* (projectFolder) {
2341
+ yield* import_effect18.Effect.logDebug(`Detecting framework in ${projectFolder}`);
2342
+ const cachedReadDeps = yield* import_effect18.Effect.cachedFunction((key) => {
1356
2343
  const sepIndex = key.lastIndexOf("\0");
1357
2344
  const folder = key.slice(0, sepIndex);
1358
2345
  const language = key.slice(sepIndex + 1);
@@ -1361,7 +2348,7 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1361
2348
  for (const framework of FRAMEWORK_DEFINITIONS) {
1362
2349
  const matches = yield* matchesFramework(framework, projectFolder, cachedReadDeps);
1363
2350
  if (matches) {
1364
- yield* import_effect14.Effect.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
2351
+ yield* import_effect18.Effect.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
1365
2352
  const detectedPackageManager = yield* detectPackageManager(projectFolder);
1366
2353
  const packageManager = framework.language === "php" ? "composer" : detectedPackageManager;
1367
2354
  const defaults = framework.getDefaultConfig(packageManager);
@@ -1370,7 +2357,7 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1370
2357
  return { framework, packageManager, config };
1371
2358
  }
1372
2359
  }
1373
- return yield* import_effect14.Effect.fail(
2360
+ return yield* import_effect18.Effect.fail(
1374
2361
  new FrameworkNotDetectedError({
1375
2362
  message: `No framework detected in project directory`,
1376
2363
  directory: projectFolder
@@ -1379,15 +2366,22 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1379
2366
  });
1380
2367
 
1381
2368
  // src/detection/merge-config.ts
1382
- var import_effect15 = require("effect");
1383
- var mergeWithFrameworkDefaults = import_effect15.Effect.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
1384
- yield* import_effect15.Effect.logDebug("Merging user config with framework defaults");
2369
+ var import_effect19 = require("effect");
2370
+ var mergeWithFrameworkDefaults = import_effect19.Effect.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
2371
+ yield* import_effect19.Effect.logDebug("Merging user config with framework defaults");
1385
2372
  const userHasAssets = (userConfig.assets?.paths?.length ?? 0) > 0 || (userConfig.assets?.prefixes?.length ?? 0) > 0 || (userConfig.assets?.manifests?.length ?? 0) > 0;
2373
+ const isContainer = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME;
2374
+ const userFileFunctions = userConfig.entrypoints.filter((entrypoint) => !isContainer(entrypoint));
2375
+ const containerPaths = new Set(userConfig.entrypoints.filter(isContainer).map((entrypoint) => entrypoint.path));
2376
+ const targetsContainer = (route) => containerPaths.has(route.destination.replace(/^\/+/, ""));
1386
2377
  const merged = {
1387
2378
  regions: userConfig.regions,
1388
2379
  commands: userConfig.commands.length > 0 ? userConfig.commands : frameworkConfig.commands,
1389
- entrypoints: userConfig.entrypoints.length > 0 ? userConfig.entrypoints : frameworkConfig.entrypoints,
1390
- routes: userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
2380
+ entrypoints: [
2381
+ ...userFileFunctions.length > 0 ? userFileFunctions : frameworkConfig.entrypoints,
2382
+ ...userConfig.entrypoints.filter(isContainer)
2383
+ ],
2384
+ routes: containerPaths.size > 0 && userConfig.routes.every(targetsContainer) ? [...userConfig.routes, ...frameworkConfig.routes] : userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
1391
2385
  assets: userHasAssets ? userConfig.assets : frameworkConfig.assets,
1392
2386
  environmentVariables: {
1393
2387
  ...frameworkConfig.environmentVariables,
@@ -1395,6 +2389,7 @@ var mergeWithFrameworkDefaults = import_effect15.Effect.fn("mergeWithFrameworkDe
1395
2389
  },
1396
2390
  excludeFiles: userConfig.excludeFiles && userConfig.excludeFiles.length > 0 ? userConfig.excludeFiles : frameworkConfig.excludeFiles,
1397
2391
  services: userConfig.services,
2392
+ ...userConfig.sidecars !== void 0 && { sidecars: userConfig.sidecars },
1398
2393
  userArchive: userConfig.userArchive,
1399
2394
  warnings: [...frameworkConfig.warnings, ...userConfig.warnings],
1400
2395
  errors: [...frameworkConfig.errors, ...userConfig.errors]
@@ -1402,51 +2397,6 @@ var mergeWithFrameworkDefaults = import_effect15.Effect.fn("mergeWithFrameworkDe
1402
2397
  return merged;
1403
2398
  });
1404
2399
 
1405
- // src/errors.ts
1406
- var import_effect16 = require("effect");
1407
- var ConfigFileNotFoundError = class extends import_effect16.Schema.TaggedError()("ConfigFileNotFoundError", {
1408
- message: import_effect16.Schema.String,
1409
- filePath: import_effect16.Schema.String
1410
- }) {
1411
- };
1412
- var ConfigFileEmptyError = class extends import_effect16.Schema.TaggedError()("ConfigFileEmptyError", {
1413
- message: import_effect16.Schema.String,
1414
- filePath: import_effect16.Schema.String
1415
- }) {
1416
- };
1417
- var ConfigFileParseError = class extends import_effect16.Schema.TaggedError()("ConfigFileParseError", {
1418
- message: import_effect16.Schema.String,
1419
- filePath: import_effect16.Schema.String,
1420
- cause: import_effect16.Schema.optional(import_effect16.Schema.String)
1421
- }) {
1422
- };
1423
- var ConfigModuleLoadError = class extends import_effect16.Schema.TaggedError()("ConfigModuleLoadError", {
1424
- message: import_effect16.Schema.String,
1425
- filePath: import_effect16.Schema.String,
1426
- cause: import_effect16.Schema.optional(import_effect16.Schema.String)
1427
- }) {
1428
- };
1429
- var ConfigVersionError = class extends import_effect16.Schema.TaggedError()("ConfigVersionError", {
1430
- message: import_effect16.Schema.String,
1431
- filePath: import_effect16.Schema.String,
1432
- version: import_effect16.Schema.optional(import_effect16.Schema.Number)
1433
- }) {
1434
- };
1435
- var ConfigSchemaValidationError = class extends import_effect16.Schema.TaggedError()(
1436
- "ConfigSchemaValidationError",
1437
- {
1438
- message: import_effect16.Schema.String,
1439
- filePath: import_effect16.Schema.String,
1440
- validationErrors: import_effect16.Schema.Array(import_effect16.Schema.String)
1441
- }
1442
- ) {
1443
- };
1444
- var FunctionConfigError = class extends import_effect16.Schema.TaggedError()("FunctionConfigError", {
1445
- message: import_effect16.Schema.String,
1446
- functionPath: import_effect16.Schema.String
1447
- }) {
1448
- };
1449
-
1450
2400
  // src/filter-functions-from-assets.ts
1451
2401
  var filterFunctionsFromAssets = (config) => {
1452
2402
  return {
@@ -1458,28 +2408,24 @@ var filterFunctionsFromAssets = (config) => {
1458
2408
  };
1459
2409
  };
1460
2410
 
1461
- // src/normalized-config.ts
1462
- var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
1463
- var MAX_FUNCTION_DURATION_SECONDS = 28800;
1464
-
1465
2411
  // src/parse-config.ts
1466
- var import_effect22 = require("effect");
2412
+ var import_effect25 = require("effect");
1467
2413
 
1468
2414
  // src/services/raw-config-reader.ts
1469
- var import_platform10 = require("@effect/platform");
1470
- var import_effect18 = require("effect");
1471
- var import_yaml = require("yaml");
2415
+ var import_platform13 = require("@effect/platform");
2416
+ var import_effect21 = require("effect");
2417
+ var import_yaml2 = require("yaml");
1472
2418
 
1473
2419
  // src/services/config-module-loader.ts
1474
- var import_effect17 = require("effect");
2420
+ var import_effect20 = require("effect");
1475
2421
  var import_jiti = require("jiti");
1476
2422
  var MISSING_DEFAULT_EXPORT = /* @__PURE__ */ Symbol("MissingDefaultExport");
1477
2423
  var STRICT_DEFAULT_EXPORT_EXTENSIONS = [".ts", ".mts", ".mjs"];
1478
- var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigModuleLoader", {
1479
- effect: import_effect17.Effect.gen(function* () {
1480
- const loadConfigModule = import_effect17.Effect.fn("ConfigModuleLoader.loadConfigModule")(function* (filePath) {
2424
+ var ConfigModuleLoader = class extends import_effect20.Effect.Service()("ConfigModuleLoader", {
2425
+ effect: import_effect20.Effect.gen(function* () {
2426
+ const loadConfigModule = import_effect20.Effect.fn("ConfigModuleLoader.loadConfigModule")(function* (filePath) {
1481
2427
  const extension = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1482
- const mod = yield* import_effect17.Effect.tryPromise({
2428
+ const mod = yield* import_effect20.Effect.tryPromise({
1483
2429
  try: async () => {
1484
2430
  const jiti = (0, import_jiti.createJiti)(filePath, { fsCache: false, interopDefault: true, moduleCache: false });
1485
2431
  const namespace = await jiti.import(filePath);
@@ -1498,7 +2444,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1498
2444
  })
1499
2445
  });
1500
2446
  if (mod === MISSING_DEFAULT_EXPORT) {
1501
- return yield* import_effect17.Effect.fail(
2447
+ return yield* import_effect20.Effect.fail(
1502
2448
  new ConfigModuleLoadError({
1503
2449
  message: `Config module at ${filePath} does not have a default export. Export the config object as the default export (or via module.exports for CommonJS files), optionally wrapped in defineConfig.`,
1504
2450
  filePath
@@ -1506,7 +2452,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1506
2452
  );
1507
2453
  }
1508
2454
  if (typeof mod === "function") {
1509
- return yield* import_effect17.Effect.fail(
2455
+ return yield* import_effect20.Effect.fail(
1510
2456
  new ConfigModuleLoadError({
1511
2457
  message: `Config module at ${filePath} default-exports a function; config factories are not supported. Export a config object instead (optionally wrapped in defineConfig).`,
1512
2458
  filePath
@@ -1514,7 +2460,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1514
2460
  );
1515
2461
  }
1516
2462
  if (typeof mod !== "object" || mod === null || Array.isArray(mod)) {
1517
- return yield* import_effect17.Effect.fail(
2463
+ return yield* import_effect20.Effect.fail(
1518
2464
  new ConfigModuleLoadError({
1519
2465
  message: `Config module at ${filePath} must default-export a config object.`,
1520
2466
  filePath
@@ -1544,36 +2490,36 @@ var ALLOWED_CONFIG_NAMES = [
1544
2490
  "nebula.json"
1545
2491
  ];
1546
2492
  var MODULE_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
1547
- var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigReader", {
2493
+ var RawConfigReader = class extends import_effect21.Effect.Service()("RawConfigReader", {
1548
2494
  dependencies: [ConfigModuleLoader.Default],
1549
- effect: import_effect18.Effect.gen(function* () {
1550
- const fs = yield* import_platform10.FileSystem.FileSystem;
1551
- const pathService = yield* import_platform10.Path.Path;
2495
+ effect: import_effect21.Effect.gen(function* () {
2496
+ const fs = yield* import_platform13.FileSystem.FileSystem;
2497
+ const pathService = yield* import_platform13.Path.Path;
1552
2498
  const moduleLoader = yield* ConfigModuleLoader;
1553
- const findConfig = import_effect18.Effect.fn("RawConfigReader.findConfig")(function* (projectFolder) {
2499
+ const findConfig = import_effect21.Effect.fn("RawConfigReader.findConfig")(function* (projectFolder) {
1554
2500
  for (const name of ALLOWED_CONFIG_NAMES) {
1555
2501
  const filePath = pathService.join(projectFolder, name);
1556
- const exists = yield* fs.exists(filePath).pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed(false)));
2502
+ const exists = yield* fs.exists(filePath).pipe(import_effect21.Effect.catchAll(() => import_effect21.Effect.succeed(false)));
1557
2503
  if (exists) {
1558
2504
  return filePath;
1559
2505
  }
1560
2506
  }
1561
2507
  return null;
1562
2508
  });
1563
- const readRawConfig = import_effect18.Effect.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
1564
- const exists = yield* fs.exists(filePath).pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed(false)));
2509
+ const readRawConfig = import_effect21.Effect.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
2510
+ const exists = yield* fs.exists(filePath).pipe(import_effect21.Effect.catchAll(() => import_effect21.Effect.succeed(false)));
1565
2511
  if (!exists) {
1566
- return yield* import_effect18.Effect.fail(
2512
+ return yield* import_effect21.Effect.fail(
1567
2513
  new ConfigFileNotFoundError({ message: `Config file not found at ${filePath}`, filePath })
1568
2514
  );
1569
2515
  }
1570
2516
  const fileContents = yield* fs.readFileString(filePath).pipe(
1571
- import_effect18.Effect.mapError(
2517
+ import_effect21.Effect.mapError(
1572
2518
  () => new ConfigFileParseError({ message: `Failed to read config file at ${filePath}`, filePath })
1573
2519
  )
1574
2520
  );
1575
2521
  if (fileContents.length === 0) {
1576
- return yield* import_effect18.Effect.fail(
2522
+ return yield* import_effect21.Effect.fail(
1577
2523
  new ConfigFileEmptyError({ message: `Config file is empty at ${filePath}`, filePath })
1578
2524
  );
1579
2525
  }
@@ -1586,10 +2532,10 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1586
2532
  if (fileExtension === ".json") {
1587
2533
  parsed = JSON.parse(fileContents);
1588
2534
  } else {
1589
- parsed = (0, import_yaml.parse)(fileContents);
2535
+ parsed = (0, import_yaml2.parse)(fileContents);
1590
2536
  }
1591
2537
  } catch (error) {
1592
- return yield* import_effect18.Effect.fail(
2538
+ return yield* import_effect21.Effect.fail(
1593
2539
  new ConfigFileParseError({
1594
2540
  message: `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
1595
2541
  filePath,
@@ -1599,21 +2545,21 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1599
2545
  }
1600
2546
  }
1601
2547
  if (parsed == null) {
1602
- return yield* import_effect18.Effect.fail(
2548
+ return yield* import_effect21.Effect.fail(
1603
2549
  new ConfigFileParseError({ message: `Config file could not be parsed at ${filePath}`, filePath })
1604
2550
  );
1605
2551
  }
1606
2552
  if (!options?.disableVersionCheck && typeof parsed.version !== "number") {
1607
- return yield* import_effect18.Effect.fail(
2553
+ return yield* import_effect21.Effect.fail(
1608
2554
  new ConfigVersionError({ message: `Config file is missing version at ${filePath}`, filePath })
1609
2555
  );
1610
2556
  }
1611
2557
  return parsed;
1612
2558
  });
1613
2559
  const readConfigFile = (configFilePath) => readRawConfig(configFilePath, { disableVersionCheck: true }).pipe(
1614
- import_effect18.Effect.map((parsed) => parsed),
1615
- import_effect18.Effect.catchTag("ConfigFileNotFoundError", () => import_effect18.Effect.succeed(null)),
1616
- import_effect18.Effect.withSpan("RawConfigReader.readConfigFile")
2560
+ import_effect21.Effect.map((parsed) => parsed),
2561
+ import_effect21.Effect.catchTag("ConfigFileNotFoundError", () => import_effect21.Effect.succeed(null)),
2562
+ import_effect21.Effect.withSpan("RawConfigReader.readConfigFile")
1617
2563
  );
1618
2564
  return { findConfig, readRawConfig, readConfigFile };
1619
2565
  })
@@ -1623,17 +2569,17 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1623
2569
  // src/services/schema-validator.ts
1624
2570
  var import_ajv = __toESM(require("ajv"));
1625
2571
  var import_ajv_formats = __toESM(require("ajv-formats"));
1626
- var import_effect19 = require("effect");
1627
- var SchemaValidator = class extends import_effect19.Effect.Service()("SchemaValidator", {
1628
- effect: import_effect19.Effect.gen(function* () {
2572
+ var import_effect22 = require("effect");
2573
+ var SchemaValidator = class extends import_effect22.Effect.Service()("SchemaValidator", {
2574
+ effect: import_effect22.Effect.gen(function* () {
1629
2575
  const ajv = new import_ajv.default();
1630
2576
  (0, import_ajv_formats.default)(ajv);
1631
- const validate = import_effect19.Effect.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
2577
+ const validate = import_effect22.Effect.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
1632
2578
  const compiledValidate = ajv.compile(schema);
1633
2579
  const valid = compiledValidate(config);
1634
2580
  if (!valid) {
1635
2581
  const errors = compiledValidate.errors?.map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`.trim()) ?? [];
1636
- return yield* import_effect19.Effect.fail(
2582
+ return yield* import_effect22.Effect.fail(
1637
2583
  new ConfigSchemaValidationError({
1638
2584
  message: `Config file at ${filePath} is invalid: ${ajv.errorsText(compiledValidate.errors)}`,
1639
2585
  filePath,
@@ -1689,8 +2635,9 @@ var getFilesForPattern = async (pattern, projectFolder, excludeFiles = []) => {
1689
2635
  };
1690
2636
 
1691
2637
  // src/services/v4-config-parser.ts
1692
- var import_effect20 = require("effect");
2638
+ var import_effect23 = require("effect");
1693
2639
  var import_minimatch2 = require("minimatch");
2640
+ var import_node_path2 = require("path");
1694
2641
  var import_safe_regex2 = __toESM(require("safe-regex2"));
1695
2642
  var DEFAULT_FUNCTION_SETTINGS = {
1696
2643
  memory: 128,
@@ -1723,6 +2670,46 @@ var normalizeImagePolicy = (images) => {
1723
2670
  variants: images.variants ?? {}
1724
2671
  };
1725
2672
  };
2673
+ var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
2674
+ var queueDurationSeconds = (value) => {
2675
+ if (value === void 0) return void 0;
2676
+ if (typeof value === "number") return value;
2677
+ const match = /^(\d+(?:\.\d+)?)(s|m|h|d)$/.exec(value);
2678
+ if (!match)
2679
+ throw new Error(`Invalid queue duration "${value}". Use seconds or a string such as 30s, 10m, 1.5h or 4d.`);
2680
+ return Math.ceil(Number(match[1]) * DURATION_UNIT_SECONDS[match[2]]);
2681
+ };
2682
+ var byName = (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
2683
+ var definedOnly = (value) => Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
2684
+ var normalizeQueue = (name, queue) => {
2685
+ if (queue === null) return { name };
2686
+ const schedules = queue.schedules ? Object.entries(queue.schedules).map(
2687
+ ([scheduleName, schedule]) => definedOnly({
2688
+ name: scheduleName,
2689
+ cron: schedule.cron,
2690
+ timezone: schedule.timezone,
2691
+ body: schedule.body === void 0 || typeof schedule.body === "string" ? schedule.body : JSON.stringify(schedule.body),
2692
+ // A plain-text body labelled JSON would fail to decode on every fire.
2693
+ contentType: schedule.contentType ?? (typeof schedule.body === "string" ? "text/plain; charset=utf-8" : void 0),
2694
+ headers: schedule.headers,
2695
+ enabled: schedule.enabled
2696
+ })
2697
+ ).sort(byName) : void 0;
2698
+ return definedOnly({
2699
+ name,
2700
+ consumer: queue.consumer,
2701
+ visibilityTimeoutSeconds: queueDurationSeconds(queue.visibilityTimeout),
2702
+ retentionSeconds: queueDurationSeconds(queue.retention),
2703
+ maxAttempts: queue.maxAttempts,
2704
+ retryBackoffMinSeconds: queueDurationSeconds(queue.retryBackoff?.min),
2705
+ retryBackoffMaxSeconds: queueDurationSeconds(queue.retryBackoff?.max),
2706
+ deduplicationWindowSeconds: queueDurationSeconds(queue.deduplicationWindow),
2707
+ concurrency: queue.concurrency,
2708
+ rateLimit: queue.rateLimit == null ? queue.rateLimit : { count: queue.rateLimit.count, periodSeconds: queueDurationSeconds(queue.rateLimit.period) ?? 1 },
2709
+ deadLetter: queue.deadLetter,
2710
+ schedules
2711
+ });
2712
+ };
1726
2713
  var normalizeServices = (services) => {
1727
2714
  if (services == null) return void 0;
1728
2715
  const normalized = [];
@@ -1735,7 +2722,13 @@ var normalizeServices = (services) => {
1735
2722
  if (services.storage !== void 0) {
1736
2723
  normalized.push({
1737
2724
  type: "storage",
1738
- buckets: Object.entries(services.storage.buckets).map(([name, bucket]) => ({ name, visibility: bucket?.visibility ?? "private" })).sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0)
2725
+ buckets: Object.entries(services.storage.buckets).map(([name, bucket]) => ({ name, visibility: bucket?.visibility ?? "private" })).sort(byName)
2726
+ });
2727
+ }
2728
+ if (services.queues !== void 0) {
2729
+ normalized.push({
2730
+ type: "queues",
2731
+ queues: Object.entries(services.queues).map(([name, queue]) => normalizeQueue(name, queue)).sort(byName)
1739
2732
  });
1740
2733
  }
1741
2734
  return normalized.length > 0 ? normalized : void 0;
@@ -1793,9 +2786,10 @@ var mapRoute = (route, entrypoints) => ({
1793
2786
  negativeRequirements: route.missing,
1794
2787
  status: route.statusCode
1795
2788
  });
1796
- var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (config, projectFolder) {
2789
+ var parseEntrypoints = import_effect23.Effect.fn("parseEntrypoints")(function* (config, projectFolder) {
1797
2790
  const entrypoints = [];
1798
- if (config.functions == null) return entrypoints;
2791
+ const dockerFunctions = [];
2792
+ if (config.functions == null) return { entrypoints, dockerFunctions };
1799
2793
  for (const [fnPath, func] of Object.entries(config.functions)) {
1800
2794
  if (getFunctionSettings(fnPath, config) == null) {
1801
2795
  return yield* new FunctionConfigError({
@@ -1803,7 +2797,7 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1803
2797
  functionPath: fnPath
1804
2798
  });
1805
2799
  }
1806
- const matchedFiles = yield* import_effect20.Effect.tryPromise({
2800
+ const matchedFiles = yield* import_effect23.Effect.tryPromise({
1807
2801
  try: () => getFilesForPattern(fnPath, projectFolder, func.excludeFiles),
1808
2802
  catch: (error) => new FunctionConfigError({
1809
2803
  message: `Failed to resolve files for function pattern '${fnPath}': ${error instanceof Error ? error.message : String(error)}`,
@@ -1820,6 +2814,35 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1820
2814
  functionPath: file
1821
2815
  });
1822
2816
  }
2817
+ if (settings.runtime === CONTAINER_RUNTIME) {
2818
+ const unsupported = ["symlinks", "includeFiles"].filter((key) => settings[key] !== void 0);
2819
+ if (unsupported.length > 0) {
2820
+ return yield* new FunctionConfigError({
2821
+ message: `Function '${file}' runs a Dockerfile, so ${unsupported.join(" and ")} do not apply. Copy files in the Dockerfile instead.`,
2822
+ functionPath: file
2823
+ });
2824
+ }
2825
+ const declared = Object.entries(config.functions).filter(([pattern]) => matchesPattern(file, pattern)).reduce((merged, [, value]) => ({ ...merged, ...value }), {});
2826
+ dockerFunctions.push({
2827
+ file,
2828
+ name: dockerFunctionContainerName(file),
2829
+ container: {
2830
+ build: { context: import_node_path2.posix.dirname(file), dockerfile: import_node_path2.posix.basename(file) },
2831
+ ...declared.port !== void 0 && { port: declared.port },
2832
+ ...declared.memory !== void 0 && { memory: declared.memory },
2833
+ ...declared.max_duration !== void 0 && { max_duration: declared.max_duration },
2834
+ ...declared.streaming !== void 0 && { streaming: declared.streaming },
2835
+ ...func.schedule !== void 0 && { schedule: func.schedule }
2836
+ }
2837
+ });
2838
+ continue;
2839
+ }
2840
+ if (settings.port !== void 0) {
2841
+ return yield* new FunctionConfigError({
2842
+ message: `Function '${file}' sets port, which only applies to 'runtime: docker'.`,
2843
+ functionPath: file
2844
+ });
2845
+ }
1823
2846
  const runtime = settings.runtime ?? "node-20";
1824
2847
  const streaming = settings.streaming ?? runtimeStreamsByDefault(runtime);
1825
2848
  entrypoints.push({
@@ -1837,25 +2860,84 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1837
2860
  });
1838
2861
  }
1839
2862
  }
1840
- return entrypoints;
2863
+ dockerFunctions.sort((left, right) => left.file < right.file ? -1 : left.file > right.file ? 1 : 0);
2864
+ for (const [index, { file, name }] of dockerFunctions.entries()) {
2865
+ const clash = dockerFunctions.slice(0, index).find((candidate) => candidate.name === name);
2866
+ if (clash !== void 0) {
2867
+ return yield* new FunctionConfigError({
2868
+ message: `Functions '${clash.file}' and '${file}' would both be named '${name}'. Declare one of them under 'containers' with an explicit name.`,
2869
+ functionPath: file
2870
+ });
2871
+ }
2872
+ }
2873
+ return { entrypoints, dockerFunctions };
1841
2874
  });
1842
- var collectAssets = import_effect20.Effect.fn("collectAssets")(function* (config, projectFolder) {
2875
+ var collectAssets = import_effect23.Effect.fn("collectAssets")(function* (config, projectFolder) {
1843
2876
  if (config.assets == null) return [];
1844
2877
  const assetNames = yield* collectAssetFiles(projectFolder, config.assets);
1845
2878
  return assetNames.filter((assetName) => getFunctionSettings(`${config.assets}/${assetName}`, config) == null).map((assetName) => `${config.assets}/${assetName}`);
1846
2879
  });
1847
- var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigParser", {
2880
+ var reservedRuntimePorts = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME ? [] : entrypoint.runtime.startsWith("php-") ? [8080, 9e3] : [8080];
2881
+ var parseContainers = import_effect23.Effect.fn("parseContainers")(function* (config, projectFolder, fileEntrypoints, dockerFunctions) {
2882
+ const warnings = [];
2883
+ let containers = {};
2884
+ if (config.compose != null) {
2885
+ const imported = yield* readComposeContainers(config.compose, projectFolder);
2886
+ containers = imported.containers;
2887
+ warnings.push(...imported.warnings);
2888
+ }
2889
+ containers = { ...containers, ...config.containers ?? {} };
2890
+ for (const { file, name, container } of dockerFunctions) {
2891
+ if (containers[name] !== void 0) {
2892
+ return yield* new ContainerConfigError({
2893
+ message: `Function '${file}' runs as container '${name}', which 'containers' or the Compose file already declares. Rename one of them.`,
2894
+ containerName: name
2895
+ });
2896
+ }
2897
+ containers[name] = container;
2898
+ }
2899
+ const result = yield* normalizeContainers(containers, projectFolder);
2900
+ warnings.push(...result.warnings);
2901
+ for (const entrypoint of fileEntrypoints) {
2902
+ for (const port of reservedRuntimePorts(entrypoint)) {
2903
+ const sidecar = result.sidecars.find((candidate) => candidate.port === port);
2904
+ if (sidecar !== void 0) {
2905
+ return yield* new ContainerConfigError({
2906
+ message: `Sidecar '${sidecar.name}' listens on port ${port}, which the ${entrypoint.runtime} runtime of '${entrypoint.path}' uses inside the same microVM. Choose another port.`,
2907
+ containerName: sidecar.name
2908
+ });
2909
+ }
2910
+ }
2911
+ }
2912
+ return { ...result, warnings };
2913
+ });
2914
+ var V4ConfigParser = class extends import_effect23.Effect.Service()("V4ConfigParser", {
1848
2915
  accessors: true,
1849
- effect: import_effect20.Effect.succeed({
2916
+ effect: import_effect23.Effect.succeed({
1850
2917
  /**
1851
2918
  * Parses a ConfigV4 into a NormalizedConfig.
1852
2919
  *
1853
2920
  * @param config - The raw V4 config object
1854
2921
  * @param projectFolder - Absolute path to the project root
1855
2922
  */
1856
- parse: import_effect20.Effect.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
1857
- const entrypoints = yield* parseEntrypoints(config, projectFolder);
2923
+ parse: import_effect23.Effect.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
2924
+ const { entrypoints: fileEntrypoints, dockerFunctions } = yield* parseEntrypoints(config, projectFolder);
2925
+ const containers = yield* parseContainers(config, projectFolder, fileEntrypoints, dockerFunctions);
2926
+ const entrypoints = [...fileEntrypoints, ...containers.entrypoints];
1858
2927
  const assets = yield* collectAssets(config, projectFolder);
2928
+ const dockerFunctionPaths = new Map(
2929
+ dockerFunctions.map(({ file, name }) => [file, containerEntrypointPath(name)])
2930
+ );
2931
+ const routes = (config.routes ?? []).map(
2932
+ (route) => mapRoute(
2933
+ {
2934
+ ...route,
2935
+ destination: dockerFunctionPaths.get(normalizeRouteDestination(route.destination)) ?? route.destination
2936
+ },
2937
+ entrypoints
2938
+ )
2939
+ );
2940
+ const warnings = [...containers.warnings];
1859
2941
  return {
1860
2942
  regions: resolveRegions(config.regions),
1861
2943
  assets: {
@@ -1868,10 +2950,11 @@ var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigPar
1868
2950
  commands: config.build_commands ?? [],
1869
2951
  images: normalizeImagePolicy(config.images),
1870
2952
  services: normalizeServices(config.services),
2953
+ ...containers.sidecars.length > 0 && { sidecars: containers.sidecars },
1871
2954
  entrypoints,
1872
2955
  errors: [],
1873
- warnings: [],
1874
- routes: (config.routes ?? []).map((route) => mapRoute(route, entrypoints))
2956
+ warnings,
2957
+ routes
1875
2958
  };
1876
2959
  })
1877
2960
  })
@@ -1879,7 +2962,7 @@ var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigPar
1879
2962
  };
1880
2963
 
1881
2964
  // src/services/vercel-build-output-parser.ts
1882
- var import_platform11 = require("@effect/platform");
2965
+ var import_platform14 = require("@effect/platform");
1883
2966
 
1884
2967
  // ../commons/src/deep-merge.ts
1885
2968
  var deepMerge = (target, ...partials) => {
@@ -1908,7 +2991,7 @@ var deepMerge = (target, ...partials) => {
1908
2991
  };
1909
2992
 
1910
2993
  // src/services/vercel-build-output-parser.ts
1911
- var import_effect21 = require("effect");
2994
+ var import_effect24 = require("effect");
1912
2995
 
1913
2996
  // src/build-output-v3/determine-repo-root.ts
1914
2997
  var import_path = __toESM(require("path"));
@@ -2004,11 +3087,11 @@ var translateVercelRuntime = (runtime) => {
2004
3087
  var CONFIG_PATH = ".vercel/output/config.json";
2005
3088
  var FUNCTIONS_FOLDER = ".vercel/output/functions";
2006
3089
  var FUNCTION_CONFIGS_PATTERN = `${FUNCTIONS_FOLDER}/**/*.func/.vc-config.json`;
2007
- var VercelBuildOutputParser = class extends import_effect21.Effect.Service()("VercelBuildOutputParser", {
2008
- effect: import_effect21.Effect.gen(function* () {
3090
+ var VercelBuildOutputParser = class extends import_effect24.Effect.Service()("VercelBuildOutputParser", {
3091
+ effect: import_effect24.Effect.gen(function* () {
2009
3092
  const rawConfigReader = yield* RawConfigReader;
2010
- const pathService = yield* import_platform11.Path.Path;
2011
- const parse = import_effect21.Effect.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
3093
+ const pathService = yield* import_platform14.Path.Path;
3094
+ const parse = import_effect24.Effect.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
2012
3095
  const config = yield* rawConfigReader.readConfigFile(pathService.join(projectFolder, CONFIG_PATH));
2013
3096
  if (!config || typeof config.version !== "number" || config.version < 3) {
2014
3097
  return parseResult;
@@ -2020,8 +3103,8 @@ var VercelBuildOutputParser = class extends import_effect21.Effect.Service()("Ve
2020
3103
  merged.regions = [...new Set(merged.regions)];
2021
3104
  return merged;
2022
3105
  });
2023
- const loadFunctions = import_effect21.Effect.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
2024
- const functionConfigs = yield* import_effect21.Effect.tryPromise({
3106
+ const loadFunctions = import_effect24.Effect.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
3107
+ const functionConfigs = yield* import_effect24.Effect.tryPromise({
2025
3108
  try: () => getFilesForPattern(FUNCTION_CONFIGS_PATTERN, projectFolder),
2026
3109
  catch: (error) => new ConfigFileParseError({
2027
3110
  message: `Failed to scan for function configs: ${error instanceof Error ? error.message : String(error)}`,
@@ -2307,17 +3390,22 @@ var schema_default = {
2307
3390
  minimum: 1
2308
3391
  },
2309
3392
  runtime: {
2310
- $ref: "#/$defs/runtimes",
2311
- description: "The runtime to use for the function."
3393
+ anyOf: [{ $ref: "#/$defs/runtimes" }, { const: "docker" }],
3394
+ description: "The runtime to use for the function. `docker` builds the matched file as a Dockerfile, with its directory as the build context, and runs the image as the function."
3395
+ },
3396
+ port: {
3397
+ type: "integer",
3398
+ minimum: 1,
3399
+ maximum: 65535,
3400
+ description: "TCP port the image listens on. Only for `runtime: docker`. Defaults to the first exposed port of the image, then 8080."
2312
3401
  },
2313
3402
  streaming: {
2314
3403
  type: "boolean",
2315
3404
  description: "Enable function response streaming. When omitted, Node and Bun runtimes stream by default."
2316
3405
  },
2317
3406
  schedule: {
2318
- type: "string",
2319
- description: "An expression to schedule the function to run at specific times.",
2320
- pattern: "^(?:rate[(](?:(?:1[ ]+(hour|minute|day))|(?:[0-9]+[ ]+(hours|minutes|days)))[)])|(?:cron[(](?:(?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:[?][ ]+(?:(?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?(?:[,](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))[#][0-5])|(?:(?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*])(?:(?:[-](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*]))|(?:[/][0-9]+))?(?:[,](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*])(?:(?:[-](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*]))|(?:[/][0-9]+))?)*)))|(?:(?:(?:(?:(?:[1-3]?[0-9])W?)|LW|[L*])(?:(?:[-](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*]))|(?:[/][0-9]+))?(?:[,](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*])(?:(?:[-](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?(?:[,](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?)*)[ ]+[?]))[ ]+(?:(?:(?:[12][0-9]{3})|[*])(?:(?:[-](?:(?:[12][0-9]{3})|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[12][0-9]{3})|[*])(?:(?:[-](?:(?:[12][0-9]{3})|[*]))|(?:[/][0-9]+))?)*)[)])$"
3407
+ $ref: "#/$defs/schedule",
3408
+ description: "An expression to schedule the function to run at specific times."
2321
3409
  },
2322
3410
  symlinks: {
2323
3411
  type: "object",
@@ -2409,6 +3497,151 @@ var schema_default = {
2409
3497
  }
2410
3498
  }
2411
3499
  },
3500
+ compose: {
3501
+ type: ["string", "null"],
3502
+ minLength: 1,
3503
+ maxLength: 1024,
3504
+ description: "Project-relative path of a Compose file whose services are imported as containers. Entries in `containers` win over imported services of the same name."
3505
+ },
3506
+ containers: {
3507
+ type: ["object", "null"],
3508
+ description: "Container images to run, keyed by name. A container runs as a function unless `sidecar` is true; routes target it with `destination: container:<name>`. A sidecar runs next to every function instance and answers at `<name>:<port>`.",
3509
+ maxProperties: 16,
3510
+ propertyNames: {
3511
+ pattern: "^[a-z][a-z0-9_-]{0,62}$",
3512
+ not: {
3513
+ enum: ["localhost"]
3514
+ }
3515
+ },
3516
+ additionalProperties: {
3517
+ type: "object",
3518
+ additionalProperties: false,
3519
+ oneOf: [
3520
+ {
3521
+ required: ["image"]
3522
+ },
3523
+ {
3524
+ required: ["build"]
3525
+ }
3526
+ ],
3527
+ properties: {
3528
+ image: {
3529
+ type: "string",
3530
+ minLength: 1,
3531
+ maxLength: 512,
3532
+ pattern: "^[^\\s]+$",
3533
+ description: "Registry image reference, e.g. `redis:7-alpine` or `ghcr.io/acme/api:1.4`."
3534
+ },
3535
+ build: {
3536
+ description: "Build context directory, or the full build settings, for a Dockerfile in the repository.",
3537
+ oneOf: [
3538
+ {
3539
+ type: "string",
3540
+ minLength: 1,
3541
+ maxLength: 1024
3542
+ },
3543
+ {
3544
+ type: "object",
3545
+ additionalProperties: false,
3546
+ properties: {
3547
+ context: {
3548
+ type: "string",
3549
+ minLength: 1,
3550
+ maxLength: 1024
3551
+ },
3552
+ dockerfile: {
3553
+ type: "string",
3554
+ minLength: 1,
3555
+ maxLength: 1024
3556
+ },
3557
+ target: {
3558
+ type: "string",
3559
+ minLength: 1,
3560
+ maxLength: 128
3561
+ },
3562
+ args: {
3563
+ type: "object",
3564
+ maxProperties: 100,
3565
+ additionalProperties: {
3566
+ type: "string",
3567
+ maxLength: 65536
3568
+ }
3569
+ }
3570
+ }
3571
+ }
3572
+ ]
3573
+ },
3574
+ sidecar: {
3575
+ type: "boolean",
3576
+ description: "Run next to every function instance instead of serving routes. Defaults to false."
3577
+ },
3578
+ port: {
3579
+ type: "integer",
3580
+ minimum: 1,
3581
+ maximum: 65535,
3582
+ description: "TCP port the container listens on. Defaults to the first exposed port of the image, then 8080."
3583
+ },
3584
+ entrypoint: {
3585
+ $ref: "#/$defs/stringOrStringArray",
3586
+ description: "Replaces the image ENTRYPOINT. A string is split into words the way Compose splits it."
3587
+ },
3588
+ command: {
3589
+ $ref: "#/$defs/stringOrStringArray",
3590
+ description: "Replaces the image CMD. A string is split into words the way Compose splits it."
3591
+ },
3592
+ env: {
3593
+ type: "object",
3594
+ maxProperties: 100,
3595
+ additionalProperties: {
3596
+ type: "string",
3597
+ maxLength: 65536
3598
+ },
3599
+ description: "Environment variables baked into the container, merged over the image ENV."
3600
+ },
3601
+ working_dir: {
3602
+ type: "string",
3603
+ minLength: 1,
3604
+ maxLength: 1024,
3605
+ pattern: "^/",
3606
+ description: "Replaces the image WORKDIR. Must be absolute."
3607
+ },
3608
+ user: {
3609
+ oneOf: [
3610
+ {
3611
+ type: "string",
3612
+ minLength: 1,
3613
+ maxLength: 256
3614
+ },
3615
+ {
3616
+ type: "integer",
3617
+ minimum: 0
3618
+ }
3619
+ ],
3620
+ description: "Replaces the image USER: a name, a uid, `name:group` or `uid:gid`. A bare uid may be a number."
3621
+ },
3622
+ memory: {
3623
+ type: "integer",
3624
+ minimum: 128,
3625
+ maximum: 3009,
3626
+ description: "Memory in MB. Defaults to 512 for a container function and 256 for a sidecar."
3627
+ },
3628
+ max_duration: {
3629
+ type: "number",
3630
+ minimum: 1,
3631
+ maximum: 28800,
3632
+ description: "Maximum lifetime in seconds of one request. Container functions only."
3633
+ },
3634
+ streaming: {
3635
+ type: "boolean",
3636
+ description: "Stream responses. Defaults to true. Container functions only."
3637
+ },
3638
+ schedule: {
3639
+ $ref: "#/$defs/schedule",
3640
+ description: "An expression to run the container function at specific times. Container functions only."
3641
+ }
3642
+ }
3643
+ }
3644
+ },
2412
3645
  services: {
2413
3646
  type: "object",
2414
3647
  description: "Managed services to provision for the deployment environment.",
@@ -2559,11 +3792,179 @@ var schema_default = {
2559
3792
  }
2560
3793
  }
2561
3794
  }
3795
+ },
3796
+ queues: {
3797
+ type: "object",
3798
+ description: "Queues to provision in the deployment environment, keyed by name. Use null for a pull queue with default settings. Queues you stop declaring are kept, with their messages.",
3799
+ propertyNames: {
3800
+ maxLength: 255,
3801
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.~:@+=-]*$"
3802
+ },
3803
+ additionalProperties: {
3804
+ oneOf: [
3805
+ {
3806
+ type: "null"
3807
+ },
3808
+ {
3809
+ type: "object",
3810
+ additionalProperties: false,
3811
+ properties: {
3812
+ consumer: {
3813
+ type: ["string", "null"],
3814
+ pattern: "^/(?!/)\\S*$",
3815
+ maxLength: 2048,
3816
+ description: "Deployment-relative path that receives each message as a signed POST, for example /api/queues/emails. Omit it for a pull queue that you drain with receive."
3817
+ },
3818
+ visibilityTimeout: {
3819
+ $ref: "#/$defs/queueDuration",
3820
+ description: "How long a push delivery may run, or how long a received message stays leased. Defaults to 60 seconds, at most 12 hours."
3821
+ },
3822
+ retention: {
3823
+ $ref: "#/$defs/queueDuration",
3824
+ description: "How long an undelivered message is kept. Defaults to 4 days, at most 14 days."
3825
+ },
3826
+ maxAttempts: {
3827
+ type: "integer",
3828
+ minimum: 1,
3829
+ maximum: 100,
3830
+ description: "Deliveries per message before it moves to the dead-letter list. Defaults to 10."
3831
+ },
3832
+ retryBackoff: {
3833
+ type: "object",
3834
+ additionalProperties: false,
3835
+ description: "Exponential backoff between failed deliveries. Defaults to 5 seconds, doubling up to 15 minutes.",
3836
+ properties: {
3837
+ min: {
3838
+ $ref: "#/$defs/queueDuration"
3839
+ },
3840
+ max: {
3841
+ $ref: "#/$defs/queueDuration"
3842
+ }
3843
+ }
3844
+ },
3845
+ deduplicationWindow: {
3846
+ $ref: "#/$defs/queueDuration",
3847
+ description: "How long a deduplication key is remembered. Defaults to 24 hours, at most 7 days."
3848
+ },
3849
+ concurrency: {
3850
+ type: ["integer", "null"],
3851
+ minimum: 1,
3852
+ maximum: 1e3,
3853
+ description: "Most push deliveries in flight at once. Defaults to 100."
3854
+ },
3855
+ rateLimit: {
3856
+ oneOf: [
3857
+ {
3858
+ type: "null"
3859
+ },
3860
+ {
3861
+ type: "object",
3862
+ required: ["count"],
3863
+ additionalProperties: false,
3864
+ properties: {
3865
+ count: {
3866
+ type: "integer",
3867
+ minimum: 1,
3868
+ maximum: 1e6
3869
+ },
3870
+ period: {
3871
+ $ref: "#/$defs/queueDuration"
3872
+ }
3873
+ }
3874
+ }
3875
+ ],
3876
+ description: "Most deliveries started per period. The period defaults to one second."
3877
+ },
3878
+ deadLetter: {
3879
+ type: "boolean",
3880
+ description: "Keep messages that run out of attempts so you can inspect and redrive them. Defaults to true."
3881
+ },
3882
+ schedules: {
3883
+ type: "object",
3884
+ description: "Cron schedules that send a message into this queue, keyed by schedule name.",
3885
+ propertyNames: {
3886
+ maxLength: 128,
3887
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.-]*$"
3888
+ },
3889
+ additionalProperties: {
3890
+ type: "object",
3891
+ required: ["cron"],
3892
+ additionalProperties: false,
3893
+ properties: {
3894
+ cron: {
3895
+ type: "string",
3896
+ minLength: 1,
3897
+ maxLength: 255,
3898
+ description: "Five-field cron expression, or a macro such as @hourly or @daily."
3899
+ },
3900
+ timezone: {
3901
+ type: "string",
3902
+ description: "IANA time zone the expression is evaluated in. Defaults to UTC."
3903
+ },
3904
+ body: {
3905
+ description: "Message body. Strings are sent as is; any other value is sent as JSON."
3906
+ },
3907
+ contentType: {
3908
+ type: "string",
3909
+ description: "Content type of the body. Defaults to text/plain for a string body and application/json otherwise."
3910
+ },
3911
+ headers: {
3912
+ type: "object",
3913
+ additionalProperties: {
3914
+ type: "string"
3915
+ },
3916
+ description: "Headers delivered with every message from this schedule."
3917
+ },
3918
+ enabled: {
3919
+ type: "boolean",
3920
+ description: "Set to false to keep the schedule without sending. Defaults to true."
3921
+ }
3922
+ }
3923
+ }
3924
+ }
3925
+ }
3926
+ }
3927
+ ]
3928
+ }
2562
3929
  }
2563
3930
  }
2564
3931
  }
2565
3932
  },
2566
3933
  $defs: {
3934
+ schedule: {
3935
+ type: "string",
3936
+ pattern: "^(?:rate[(](?:(?:1[ ]+(hour|minute|day))|(?:[0-9]+[ ]+(hours|minutes|days)))[)])|(?:cron[(](?:(?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[0-5]?[0-9])|[*])(?:(?:[-](?:(?:[0-5]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[0-2]?[0-9])|[*])(?:(?:[-](?:(?:[0-2]?[0-9])|[*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:[?][ ]+(?:(?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?(?:[,](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))[#][0-5])|(?:(?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*])(?:(?:[-](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*]))|(?:[/][0-9]+))?(?:[,](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*])(?:(?:[-](?:(?:(?:[1-7]|(?:SUN|MON|TUE|WED|THU|FRI|SAT))L?)|[L*]))|(?:[/][0-9]+))?)*)))|(?:(?:(?:(?:(?:[1-3]?[0-9])W?)|LW|[L*])(?:(?:[-](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*]))|(?:[/][0-9]+))?(?:[,](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*])(?:(?:[-](?:(?:(?:[1-3]?[0-9])W?)|LW|[L*]))|(?:[/][0-9]+))?)*)[ ]+(?:(?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?(?:[,](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:(?:[-](?:(?:[1]?[0-9])|(?:JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)|[*])(?:[/][0-9]+)?)|(?:[/][0-9]+))?)*)[ ]+[?]))[ ]+(?:(?:(?:[12][0-9]{3})|[*])(?:(?:[-](?:(?:[12][0-9]{3})|[*]))|(?:[/][0-9]+))?(?:[,](?:(?:[12][0-9]{3})|[*])(?:(?:[-](?:(?:[12][0-9]{3})|[*]))|(?:[/][0-9]+))?)*)[)])$"
3937
+ },
3938
+ stringOrStringArray: {
3939
+ oneOf: [
3940
+ {
3941
+ type: "string",
3942
+ minLength: 1,
3943
+ maxLength: 4096
3944
+ },
3945
+ {
3946
+ type: "array",
3947
+ maxItems: 256,
3948
+ items: {
3949
+ type: "string",
3950
+ maxLength: 4096
3951
+ }
3952
+ }
3953
+ ]
3954
+ },
3955
+ queueDuration: {
3956
+ oneOf: [
3957
+ {
3958
+ type: "integer",
3959
+ minimum: 0
3960
+ },
3961
+ {
3962
+ type: "string",
3963
+ pattern: "^[0-9]+(\\.[0-9]+)?(s|m|h|d)$"
3964
+ }
3965
+ ],
3966
+ description: "A duration in seconds, or a string such as 30s, 10m, 1.5h or 4d."
3967
+ },
2567
3968
  runtimes: {
2568
3969
  type: "string",
2569
3970
  enum: ["php-84", "php-83", "php-81", "node-22", "node-20", "node-18", "node-16", "bun-1"]
@@ -2620,7 +4021,7 @@ var schema_default = {
2620
4021
  };
2621
4022
 
2622
4023
  // src/parse-config.ts
2623
- var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (filePath, projectFolder) {
4024
+ var parseConfigRaw = import_effect25.Effect.fn("parseConfigRaw")(function* (filePath, projectFolder) {
2624
4025
  const rawReader = yield* RawConfigReader;
2625
4026
  const validator = yield* SchemaValidator;
2626
4027
  const v4Parser = yield* V4ConfigParser;
@@ -2631,7 +4032,7 @@ var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (file
2631
4032
  };
2632
4033
  const schema = schemaMap[version];
2633
4034
  if (!schema) {
2634
- return yield* import_effect22.Effect.fail(
4035
+ return yield* import_effect25.Effect.fail(
2635
4036
  new ConfigVersionError({
2636
4037
  message: `Config file at ${filePath} has an unsupported version: ${version}`,
2637
4038
  filePath,
@@ -2642,14 +4043,42 @@ var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (file
2642
4043
  yield* validator.validate(parsed, schema, filePath);
2643
4044
  return yield* v4Parser.parse(parsed, projectFolder);
2644
4045
  });
2645
- var parseConfig = import_effect22.Effect.fn("parseConfig")(function* (filePath, projectFolder) {
4046
+ var parseConfig = import_effect25.Effect.fn("parseConfig")(function* (filePath, projectFolder) {
2646
4047
  const parseResult = yield* parseConfigRaw(filePath, projectFolder);
2647
4048
  return yield* postProcessConfig(parseResult, projectFolder);
2648
4049
  });
2649
- var postProcessConfig = import_effect22.Effect.fn("postProcessConfig")(function* (config, projectFolder) {
4050
+ var postProcessConfig = import_effect25.Effect.fn("postProcessConfig")(function* (config, projectFolder) {
2650
4051
  const vercelParser = yield* VercelBuildOutputParser;
2651
4052
  let result = yield* vercelParser.parse(config, projectFolder);
2652
4053
  const hasAssets = (result.assets?.paths?.length ?? 0) > 0 || (result.assets?.prefixes?.length ?? 0) > 0 || (result.assets?.manifests?.length ?? 0) > 0;
4054
+ const containerFunctions = result.entrypoints.filter((entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME);
4055
+ const [onlyFunction] = result.entrypoints;
4056
+ if (onlyFunction !== void 0 && onlyFunction.runtime === CONTAINER_RUNTIME && result.entrypoints.length === 1 && result.routes.length === 0 && !hasAssets) {
4057
+ result = {
4058
+ ...result,
4059
+ routes: [
4060
+ {
4061
+ path: "/*",
4062
+ destination: onlyFunction.path,
4063
+ handler: onlyFunction.streaming === true ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION",
4064
+ methods: ["ANY"],
4065
+ headers: {}
4066
+ }
4067
+ ]
4068
+ };
4069
+ } else {
4070
+ for (const entrypoint of containerFunctions) {
4071
+ const routed = result.routes.some((route) => route.destination.replace(/^\/+/, "") === entrypoint.path);
4072
+ if (!routed && entrypoint.schedule === void 0) {
4073
+ result.warnings.push(
4074
+ `Container function '${entrypoint.displayName ?? entrypoint.path}' has no route and no schedule, so nothing reaches it. Add a route with destination '${entrypoint.path}'.`
4075
+ );
4076
+ }
4077
+ }
4078
+ }
4079
+ if ((result.sidecars?.length ?? 0) > 0 && result.entrypoints.length === 0) {
4080
+ result.warnings.push("Sidecars run next to functions, and this deployment has none, so no sidecar will start.");
4081
+ }
2653
4082
  if (result.entrypoints.length === 0 && !hasAssets && result.routes.length === 0) {
2654
4083
  result.errors.push(
2655
4084
  "The current project config does not resolve to any functions, assets or routes and can not be deployed."
@@ -2675,19 +4104,22 @@ var RUNTIME_DEFINITIONS = [
2675
4104
  var AVAILABLE_RUNTIMES = [...RUNTIME_DEFINITIONS];
2676
4105
 
2677
4106
  // src/services/index.ts
2678
- var import_effect23 = require("effect");
2679
- var BaseLayers = import_effect23.Layer.mergeAll(
4107
+ var import_effect26 = require("effect");
4108
+ var BaseLayers = import_effect26.Layer.mergeAll(
2680
4109
  ConfigModuleLoader.Default,
2681
4110
  RawConfigReader.Default,
2682
4111
  SchemaValidator.Default,
2683
4112
  V4ConfigParser.Default
2684
4113
  );
2685
- var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Layer.provideMerge(BaseLayers));
4114
+ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect26.Layer.provideMerge(BaseLayers));
2686
4115
  // Annotate the CommonJS export names for ESM import in node:
2687
4116
  0 && (module.exports = {
2688
4117
  ALLOWED_CONFIG_NAMES,
2689
4118
  AVAILABLE_REGIONS,
2690
4119
  AVAILABLE_RUNTIMES,
4120
+ COMPOSE_FILE_NAMES,
4121
+ CONTAINER_ENTRYPOINT_PREFIX,
4122
+ CONTAINER_RUNTIME,
2691
4123
  ConfigFileEmptyError,
2692
4124
  ConfigFileNotFoundError,
2693
4125
  ConfigFileParseError,
@@ -2695,7 +4127,10 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2695
4127
  ConfigModuleLoader,
2696
4128
  ConfigSchemaValidationError,
2697
4129
  ConfigVersionError,
4130
+ ContainerConfigError,
4131
+ DEFAULT_CONTAINER_MEMORY_MB,
2698
4132
  DEFAULT_FUNCTION_DURATION_SECONDS,
4133
+ DEFAULT_SIDECAR_MEMORY_MB,
2699
4134
  FRAMEWORK_DEFINITIONS,
2700
4135
  FrameworkNotDetectedError,
2701
4136
  FunctionConfigError,
@@ -2712,6 +4147,7 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2712
4147
  GigadriveNextServerDescriptorSchema,
2713
4148
  GigadriveNextStaticAssetPrefixSchema,
2714
4149
  MAX_FUNCTION_DURATION_SECONDS,
4150
+ MAX_SIDECARS,
2715
4151
  ManifestNotFoundError,
2716
4152
  ManifestParseError,
2717
4153
  ManifestReadError,
@@ -2724,19 +4160,26 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2724
4160
  V4ConfigParser,
2725
4161
  VercelBuildOutputParser,
2726
4162
  collectAssetFiles,
4163
+ composeDeclaresApp,
4164
+ containerEntrypointPath,
2727
4165
  defineConfig,
4166
+ detectContainerProject,
2728
4167
  detectFramework,
2729
4168
  detectPackageManager,
4169
+ dockerFunctionContainerName,
2730
4170
  filterFunctionsFromAssets,
2731
4171
  generateConfig,
2732
4172
  getFunctionSettings,
2733
4173
  mergeWithFrameworkDefaults,
4174
+ normalizeContainers,
2734
4175
  parseConfig,
2735
4176
  parseConfigRaw,
2736
4177
  parseGigadriveNextBuildManifest,
2737
4178
  parseGigadriveNextPrerenderManifest,
2738
4179
  parseStaticAssetManifest,
2739
4180
  postProcessConfig,
2740
- readDependencies
4181
+ readComposeContainers,
4182
+ readDependencies,
4183
+ splitCommandWords
2741
4184
  });
2742
4185
  //# sourceMappingURL=index.js.map