@gigadrive/network-config 5.0.0 → 5.1.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_platform11 = require("@effect/platform");
1011
+ var import_effect17 = 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
 
@@ -236,8 +1058,8 @@ var astro = {
236
1058
  };
237
1059
 
238
1060
  // src/detection/frameworks/resolve-node-entrypoint.ts
239
- var import_platform3 = require("@effect/platform");
240
- var import_effect5 = require("effect");
1061
+ var import_platform5 = require("@effect/platform");
1062
+ var import_effect8 = require("effect");
241
1063
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "tsx", "ts-node"]);
242
1064
  var COMMON_ENTRYPOINT_CANDIDATES = [
243
1065
  "index.js",
@@ -253,11 +1075,11 @@ var COMMON_ENTRYPOINT_CANDIDATES = [
253
1075
  "server.ts",
254
1076
  "src/server.ts"
255
1077
  ];
256
- var isRecord = (value) => typeof value === "object" && value !== null;
1078
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
257
1079
  var parsePackageJson = (content) => {
258
1080
  try {
259
1081
  const parsed = JSON.parse(content);
260
- if (!isRecord(parsed)) return void 0;
1082
+ if (!isRecord2(parsed)) return void 0;
261
1083
  return parsed;
262
1084
  } catch {
263
1085
  return void 0;
@@ -285,14 +1107,14 @@ var applyEntrypoint = (defaults, entrypoint) => ({
285
1107
  (route) => route.destination === defaults.entrypoint ? { ...route, destination: entrypoint } : route
286
1108
  )
287
1109
  });
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;
1110
+ var resolveNodeServerEntrypoint = (defaults, projectFolder) => import_effect8.Effect.gen(function* () {
1111
+ const fs = yield* import_platform5.FileSystem.FileSystem;
1112
+ const pathService = yield* import_platform5.Path.Path;
291
1113
  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))
1114
+ import_effect8.Effect.map((info) => info.type === "File"),
1115
+ import_effect8.Effect.catchAll(() => import_effect8.Effect.succeed(false))
294
1116
  );
295
- const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(import_effect5.Effect.catchAll(() => import_effect5.Effect.succeed(void 0)));
1117
+ const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(import_effect8.Effect.catchAll(() => import_effect8.Effect.succeed(void 0)));
296
1118
  const manifest = content ? parsePackageJson(content) : void 0;
297
1119
  const startScript = typeof manifest?.scripts?.start === "string" ? manifest.scripts.start : void 0;
298
1120
  const candidates = [
@@ -424,13 +1246,13 @@ var laravel = {
424
1246
  };
425
1247
 
426
1248
  // 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;
1249
+ var import_platform6 = require("@effect/platform");
1250
+ var import_effect9 = require("effect");
1251
+ var isRecord3 = (value) => typeof value === "object" && value !== null;
430
1252
  var parseNestCliJson = (content) => {
431
1253
  try {
432
1254
  const parsed = JSON.parse(content);
433
- if (!isRecord2(parsed)) return void 0;
1255
+ if (!isRecord3(parsed)) return void 0;
434
1256
  return parsed;
435
1257
  } catch {
436
1258
  return void 0;
@@ -476,9 +1298,9 @@ var nestjs = {
476
1298
  detectors: [{ matchPackage: "@nestjs/core" }],
477
1299
  priority: 90,
478
1300
  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)));
1301
+ refineDefaultConfig: (defaults, projectFolder) => import_effect9.Effect.gen(function* () {
1302
+ const fs = yield* import_platform6.FileSystem.FileSystem;
1303
+ const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(void 0)));
482
1304
  if (!content) return defaults;
483
1305
  const nestConfig = parseNestCliJson(content);
484
1306
  const outputPath = nestConfig ? getProjectOutputPath(nestConfig) : void 0;
@@ -487,28 +1309,28 @@ var nestjs = {
487
1309
  };
488
1310
 
489
1311
  // src/detection/frameworks/nextjs.ts
490
- var import_platform6 = require("@effect/platform");
491
- var import_effect9 = require("effect");
1312
+ var import_platform8 = require("@effect/platform");
1313
+ var import_effect12 = require("effect");
492
1314
 
493
1315
  // 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;
1316
+ var import_platform7 = require("@effect/platform");
1317
+ var import_effect10 = require("effect");
1318
+ var getDefaultPathMap = import_effect10.Effect.fn("getDefaultPathMap")(function* (directory) {
1319
+ const fs = yield* import_platform7.FileSystem.FileSystem;
1320
+ const pathService = yield* import_platform7.Path.Path;
499
1321
  const result = {};
500
- const exists = yield* fs.exists(directory).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed(false)));
1322
+ const exists = yield* fs.exists(directory).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed(false)));
501
1323
  if (!exists) {
502
1324
  return result;
503
1325
  }
504
1326
  const MAX_DEPTH = 100;
505
- const processDirectory = (currentPath, basePath = "", depth = 0) => import_effect7.Effect.gen(function* () {
1327
+ const processDirectory = (currentPath, basePath = "", depth = 0) => import_effect10.Effect.gen(function* () {
506
1328
  if (depth > MAX_DEPTH) return;
507
- const entries = yield* fs.readDirectory(currentPath).pipe(import_effect7.Effect.catchAll(() => import_effect7.Effect.succeed([])));
1329
+ const entries = yield* fs.readDirectory(currentPath).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed([])));
508
1330
  for (const name of entries) {
509
1331
  const fullPath = pathService.join(currentPath, name);
510
1332
  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)));
1333
+ const stat = yield* fs.stat(fullPath).pipe(import_effect10.Effect.catchAll(() => import_effect10.Effect.succeed(null)));
512
1334
  if (!stat) continue;
513
1335
  if (stat.type === "Directory") {
514
1336
  yield* processDirectory(fullPath, relativePath, depth + 1);
@@ -525,291 +1347,269 @@ var getDefaultPathMap = import_effect7.Effect.fn("getDefaultPathMap")(function*
525
1347
  var MAXIMUM_ENTRY_PAGE_PATHS = 50;
526
1348
 
527
1349
  // 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 }))
1350
+ var import_effect11 = require("effect");
1351
+ var StringArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.String));
1352
+ var StringRecordSchema = import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: import_effect11.Schema.String }));
1353
+ var JsonValueSchema = import_effect11.Schema.suspend(
1354
+ () => import_effect11.Schema.Union(
1355
+ import_effect11.Schema.Null,
1356
+ import_effect11.Schema.Boolean,
1357
+ import_effect11.Schema.Number,
1358
+ import_effect11.Schema.String,
1359
+ import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema)),
1360
+ import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: JsonValueSchema }))
539
1361
  )
540
1362
  );
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)
1363
+ var ImageLocalPatternSchema = import_effect11.Schema.mutable(
1364
+ import_effect11.Schema.Struct({
1365
+ pathname: import_effect11.Schema.optional(import_effect11.Schema.String),
1366
+ search: import_effect11.Schema.optional(import_effect11.Schema.String)
545
1367
  })
546
1368
  );
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)
1369
+ var ImageRemotePatternSchema = import_effect11.Schema.mutable(
1370
+ import_effect11.Schema.Struct({
1371
+ protocol: import_effect11.Schema.optional(import_effect11.Schema.Literal("http", "https")),
1372
+ hostname: import_effect11.Schema.String,
1373
+ port: import_effect11.Schema.optional(import_effect11.Schema.String),
1374
+ pathname: import_effect11.Schema.optional(import_effect11.Schema.String),
1375
+ search: import_effect11.Schema.optional(import_effect11.Schema.String)
554
1376
  })
555
1377
  );
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"))
1378
+ var ImageVariantSchema = import_effect11.Schema.mutable(
1379
+ import_effect11.Schema.Struct({
1380
+ width: import_effect11.Schema.optional(import_effect11.Schema.Number),
1381
+ height: import_effect11.Schema.optional(import_effect11.Schema.Number),
1382
+ quality: import_effect11.Schema.optional(import_effect11.Schema.Number),
1383
+ format: import_effect11.Schema.optional(import_effect11.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png")),
1384
+ fit: import_effect11.Schema.optional(import_effect11.Schema.Literal("contain", "cover", "fill", "inside", "outside"))
563
1385
  })
564
1386
  );
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 }))
1387
+ var PositiveIntSchema = import_effect11.Schema.Int.pipe(import_effect11.Schema.greaterThan(0));
1388
+ var ImagePolicySchema = import_effect11.Schema.mutable(
1389
+ import_effect11.Schema.Struct({
1390
+ localPatterns: import_effect11.Schema.mutable(import_effect11.Schema.Array(ImageLocalPatternSchema)),
1391
+ remotePatterns: import_effect11.Schema.mutable(import_effect11.Schema.Array(ImageRemotePatternSchema)),
1392
+ widths: import_effect11.Schema.mutable(import_effect11.Schema.Array(PositiveIntSchema)),
1393
+ heights: import_effect11.Schema.mutable(import_effect11.Schema.Array(PositiveIntSchema)),
1394
+ qualities: import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.Number.pipe(import_effect11.Schema.between(1, 100)))),
1395
+ formats: import_effect11.Schema.mutable(import_effect11.Schema.Array(import_effect11.Schema.Literal("image/avif", "image/webp", "image/jpeg", "image/png"))),
1396
+ minimumCacheTTL: import_effect11.Schema.Number,
1397
+ dangerouslyAllowSVG: import_effect11.Schema.Boolean,
1398
+ contentSecurityPolicy: import_effect11.Schema.String,
1399
+ contentDispositionType: import_effect11.Schema.Literal("inline", "attachment"),
1400
+ maximumRedirects: import_effect11.Schema.Number,
1401
+ maximumResponseBody: import_effect11.Schema.Number,
1402
+ variants: import_effect11.Schema.mutable(import_effect11.Schema.Record({ key: import_effect11.Schema.String, value: ImageVariantSchema }))
581
1403
  })
582
1404
  );
583
1405
  var GigadriveNextPrerenderOutputFields = {
584
- id: import_effect8.Schema.String,
585
- type: import_effect8.Schema.Literal("PRERENDER"),
1406
+ id: import_effect11.Schema.String,
1407
+ type: import_effect11.Schema.Literal("PRERENDER"),
586
1408
  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)
1409
+ route: import_effect11.Schema.optional(UrlPathnameSchema),
1410
+ parentOutputId: import_effect11.Schema.String,
1411
+ groupId: import_effect11.Schema.Number,
1412
+ pprChain: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Struct({ headers: HttpSingleValueHeadersSchema }))),
1413
+ parentFallbackMode: import_effect11.Schema.optional(JsonValueSchema),
1414
+ fallback: import_effect11.Schema.optional(
1415
+ import_effect11.Schema.mutable(
1416
+ import_effect11.Schema.Struct({
1417
+ filePath: import_effect11.Schema.optional(PortableRelativePathSchema),
1418
+ initialStatus: import_effect11.Schema.optional(import_effect11.Schema.Int.pipe(import_effect11.Schema.between(100, 599))),
1419
+ initialHeaders: import_effect11.Schema.optional(HttpHeadersSchema),
1420
+ initialExpiration: import_effect11.Schema.optional(import_effect11.Schema.Number),
1421
+ initialRevalidate: import_effect11.Schema.optional(import_effect11.Schema.Union(import_effect11.Schema.Number, import_effect11.Schema.Literal(false))),
1422
+ postponedState: import_effect11.Schema.optional(import_effect11.Schema.String)
601
1423
  })
602
1424
  )
603
1425
  ),
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)
1426
+ config: import_effect11.Schema.mutable(
1427
+ import_effect11.Schema.Struct({
1428
+ allowQuery: import_effect11.Schema.optional(StringArraySchema),
1429
+ allowHeader: import_effect11.Schema.optional(StringArraySchema),
1430
+ bypassFor: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema))),
1431
+ renderingMode: import_effect11.Schema.optional(import_effect11.Schema.String),
1432
+ partialFallback: import_effect11.Schema.optional(import_effect11.Schema.Boolean),
1433
+ bypassToken: import_effect11.Schema.optional(import_effect11.Schema.String)
612
1434
  })
613
1435
  )
614
1436
  };
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)
1437
+ var GigadriveNextPrerenderClassificationSchema = import_effect11.Schema.Struct({
1438
+ routeType: import_effect11.Schema.Literal("route", "fallback", "shell", "page"),
1439
+ response: import_effect11.Schema.Literal("empty", "initial", "complete"),
1440
+ compute: import_effect11.Schema.Literal("blocking", "resuming", "static"),
1441
+ htmlSize: import_effect11.Schema.optional(import_effect11.Schema.Number)
620
1442
  });
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)
1443
+ var GigadriveNextPrerenderWithoutClassificationSchema = import_effect11.Schema.Struct({
1444
+ routeType: import_effect11.Schema.optional(import_effect11.Schema.Never),
1445
+ response: import_effect11.Schema.optional(import_effect11.Schema.Never),
1446
+ compute: import_effect11.Schema.optional(import_effect11.Schema.Never),
1447
+ htmlSize: import_effect11.Schema.optional(import_effect11.Schema.Never)
626
1448
  });
627
- var GigadriveNextPrerenderOutputSchema = import_effect8.Schema.Union(
628
- import_effect8.Schema.mutable(
629
- import_effect8.Schema.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
1449
+ var GigadriveNextPrerenderOutputSchema = import_effect11.Schema.Union(
1450
+ import_effect11.Schema.mutable(
1451
+ import_effect11.Schema.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
630
1452
  ),
631
- import_effect8.Schema.mutable(
632
- import_effect8.Schema.Struct({
1453
+ import_effect11.Schema.mutable(
1454
+ import_effect11.Schema.Struct({
633
1455
  ...GigadriveNextPrerenderOutputFields,
634
1456
  ...GigadriveNextPrerenderWithoutClassificationSchema.fields
635
1457
  })
636
1458
  )
637
1459
  );
638
- var GigadriveNextStaticAssetPrefixSchema = import_effect8.Schema.mutable(
639
- import_effect8.Schema.Struct({
1460
+ var GigadriveNextStaticAssetPrefixSchema = import_effect11.Schema.mutable(
1461
+ import_effect11.Schema.Struct({
640
1462
  sourceDir: PortableRelativePathSchema,
641
1463
  urlPrefix: PortableRelativePathSchema,
642
- immutable: import_effect8.Schema.Boolean
1464
+ immutable: import_effect11.Schema.Boolean
643
1465
  })
644
1466
  );
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)
1467
+ var GigadriveNextRouteConditionSchema = import_effect11.Schema.Union(
1468
+ import_effect11.Schema.mutable(
1469
+ import_effect11.Schema.Struct({
1470
+ type: import_effect11.Schema.Literal("header", "cookie", "query"),
1471
+ key: import_effect11.Schema.String,
1472
+ value: import_effect11.Schema.optional(import_effect11.Schema.String)
651
1473
  })
652
1474
  ),
653
- import_effect8.Schema.mutable(
654
- import_effect8.Schema.Struct({
655
- type: import_effect8.Schema.Literal("host"),
656
- value: import_effect8.Schema.String
1475
+ import_effect11.Schema.mutable(
1476
+ import_effect11.Schema.Struct({
1477
+ type: import_effect11.Schema.Literal("host"),
1478
+ value: import_effect11.Schema.String
657
1479
  })
658
1480
  )
659
1481
  );
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)))
1482
+ var GigadriveNextMiddlewareMatcherSchema = import_effect11.Schema.mutable(
1483
+ import_effect11.Schema.Struct({
1484
+ source: import_effect11.Schema.String,
1485
+ sourceRegex: import_effect11.Schema.String,
1486
+ has: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextRouteConditionSchema))),
1487
+ missing: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextRouteConditionSchema)))
666
1488
  })
667
1489
  );
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))
1490
+ var GigadriveNextMiddlewareDescriptorSchema = import_effect11.Schema.Union(
1491
+ import_effect11.Schema.mutable(
1492
+ import_effect11.Schema.Struct({
1493
+ present: import_effect11.Schema.Literal(true),
1494
+ matchers: import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextMiddlewareMatcherSchema))
673
1495
  })
674
1496
  ),
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())
1497
+ import_effect11.Schema.mutable(
1498
+ import_effect11.Schema.Struct({
1499
+ present: import_effect11.Schema.Literal(false),
1500
+ matchers: import_effect11.Schema.mutable(import_effect11.Schema.Tuple())
679
1501
  })
680
1502
  )
681
1503
  );
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)
1504
+ var GigadriveNextServerDescriptorSchema = import_effect11.Schema.mutable(
1505
+ import_effect11.Schema.Struct({
1506
+ maxDuration: import_effect11.Schema.optional(import_effect11.Schema.Number),
1507
+ preferredRegion: import_effect11.Schema.optional(import_effect11.Schema.Union(import_effect11.Schema.String, StringArraySchema)),
1508
+ env: import_effect11.Schema.optional(StringRecordSchema)
687
1509
  })
688
1510
  );
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"))
1511
+ var PrerenderArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextPrerenderOutputSchema));
1512
+ var StaticAssetArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(GigadriveNextStaticAssetPrefixSchema));
1513
+ var JsonValueArraySchema = import_effect11.Schema.mutable(import_effect11.Schema.Array(JsonValueSchema));
1514
+ var GigadriveNextPrerenderSeedMethodsSchema = import_effect11.Schema.mutable(
1515
+ import_effect11.Schema.Tuple(import_effect11.Schema.Literal("GET"), import_effect11.Schema.Literal("HEAD"))
694
1516
  );
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"),
1517
+ var GigadriveNextBuildManifestV1Schema = import_effect11.Schema.mutable(
1518
+ import_effect11.Schema.Struct({
1519
+ version: import_effect11.Schema.Literal(1),
1520
+ output: import_effect11.Schema.Literal("standalone", "export"),
699
1521
  distDir: PortableRelativePathSchema,
700
1522
  repoRootToProject: RepositoryRelativePathSchema,
701
- nextVersion: import_effect8.Schema.String,
702
- buildId: import_effect8.Schema.String
1523
+ nextVersion: import_effect11.Schema.String,
1524
+ buildId: import_effect11.Schema.String
703
1525
  })
704
1526
  );
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"),
1527
+ var GigadriveNextBuildManifestV2ExportSchema = import_effect11.Schema.mutable(
1528
+ import_effect11.Schema.Struct({
1529
+ version: import_effect11.Schema.Literal(2),
1530
+ mode: import_effect11.Schema.Literal("export"),
709
1531
  distDir: PortableRelativePathSchema,
710
1532
  repoRootToProject: RepositoryRelativePathSchema,
711
- nextVersion: import_effect8.Schema.String,
712
- buildId: import_effect8.Schema.String
1533
+ nextVersion: import_effect11.Schema.String,
1534
+ buildId: import_effect11.Schema.String
713
1535
  })
714
1536
  );
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"),
1537
+ var GigadriveNextBuildManifestV2StandaloneSchema = import_effect11.Schema.mutable(
1538
+ import_effect11.Schema.Struct({
1539
+ version: import_effect11.Schema.Literal(2),
1540
+ mode: import_effect11.Schema.Literal("standalone-v2"),
719
1541
  distDir: PortableRelativePathSchema,
720
1542
  repoRootToProject: RepositoryRelativePathSchema,
721
- nextVersion: import_effect8.Schema.String,
722
- buildId: import_effect8.Schema.String,
1543
+ nextVersion: import_effect11.Schema.String,
1544
+ buildId: import_effect11.Schema.String,
723
1545
  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)
1546
+ config: import_effect11.Schema.mutable(
1547
+ import_effect11.Schema.Struct({
1548
+ basePath: import_effect11.Schema.String,
1549
+ trailingSlash: import_effect11.Schema.Boolean,
1550
+ cacheComponents: import_effect11.Schema.Boolean,
1551
+ i18n: import_effect11.Schema.optional(JsonValueSchema),
1552
+ images: import_effect11.Schema.optional(ImagePolicySchema)
731
1553
  })
732
1554
  ),
733
- routing: import_effect8.Schema.mutable(
734
- import_effect8.Schema.Struct({
1555
+ routing: import_effect11.Schema.mutable(
1556
+ import_effect11.Schema.Struct({
735
1557
  beforeMiddleware: JsonValueArraySchema,
736
1558
  beforeFiles: JsonValueArraySchema,
737
1559
  afterFiles: JsonValueArraySchema,
738
1560
  dynamicRoutes: JsonValueArraySchema,
739
1561
  onMatch: JsonValueArraySchema,
740
1562
  fallback: JsonValueArraySchema,
741
- shouldNormalizeNextData: import_effect8.Schema.Boolean,
1563
+ shouldNormalizeNextData: import_effect11.Schema.Boolean,
742
1564
  rsc: JsonValueSchema
743
1565
  })
744
1566
  ),
745
- outputs: import_effect8.Schema.mutable(
746
- import_effect8.Schema.Struct({
1567
+ outputs: import_effect11.Schema.mutable(
1568
+ import_effect11.Schema.Struct({
747
1569
  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))),
1570
+ prerenderManifest: import_effect11.Schema.optional(PortableRelativePathSchema),
1571
+ assetManifest: import_effect11.Schema.optional(PortableRelativePathSchema),
1572
+ entryPagePaths: import_effect11.Schema.optional(import_effect11.Schema.mutable(import_effect11.Schema.Array(UrlPathnameSchema))),
751
1573
  staticAssets: StaticAssetArraySchema,
752
- middleware: import_effect8.Schema.optional(GigadriveNextMiddlewareDescriptorSchema)
1574
+ middleware: import_effect11.Schema.optional(GigadriveNextMiddlewareDescriptorSchema)
753
1575
  })
754
1576
  )
755
1577
  })
756
1578
  );
757
- var GigadriveNextBuildManifestSchema = import_effect8.Schema.Union(
1579
+ var GigadriveNextBuildManifestSchema = import_effect11.Schema.Union(
758
1580
  GigadriveNextBuildManifestV1Schema,
759
1581
  GigadriveNextBuildManifestV2ExportSchema,
760
1582
  GigadriveNextBuildManifestV2StandaloneSchema
761
1583
  );
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),
1584
+ var GigadriveNextPrerenderManifestV1Schema = import_effect11.Schema.mutable(
1585
+ import_effect11.Schema.Struct({
1586
+ version: import_effect11.Schema.Literal(1),
1587
+ seedMethods: import_effect11.Schema.optional(GigadriveNextPrerenderSeedMethodsSchema),
766
1588
  prerenders: PrerenderArraySchema
767
1589
  })
768
1590
  );
769
1591
  var parseGigadriveNextBuildManifest = (content) => decodeJson(GigadriveNextBuildManifestSchema, content);
770
1592
  var parseGigadriveNextPrerenderManifest = (content) => decodeJson(GigadriveNextPrerenderManifestV1Schema, content);
771
1593
 
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];
793
-
794
1594
  // src/detection/frameworks/nextjs.ts
795
1595
  var toPortablePath = (value) => value.replaceAll("\\", "/");
796
1596
  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;
1597
+ var resolveStandaloneServer = import_effect12.Effect.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
1598
+ const fs = yield* import_platform8.FileSystem.FileSystem;
1599
+ const pathService = yield* import_platform8.Path.Path;
800
1600
  const standaloneRoot = pathService.join(projectFolder, distDir, "standalone");
801
1601
  const monorepoServer = pathService.join(standaloneRoot, repoRootToProject, "server.js");
802
1602
  const rootServer = pathService.join(standaloneRoot, "server.js");
803
- const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(false)));
1603
+ const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(false)));
804
1604
  const serverPath = monorepoServerExists ? monorepoServer : rootServer;
805
- const serverExists = yield* fs.exists(serverPath).pipe(import_effect9.Effect.catchAll(() => import_effect9.Effect.succeed(false)));
1605
+ const serverExists = yield* fs.exists(serverPath).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(false)));
806
1606
  if (!serverExists) return void 0;
807
1607
  const serverDirectory = pathService.dirname(serverPath);
808
1608
  const entrypoint = toPortablePath(pathService.relative(standaloneRoot, serverPath));
809
1609
  return { standaloneRoot, serverDirectory, entrypoint };
810
1610
  });
811
- var buildStandalonePublicAssets = import_effect9.Effect.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
812
- const pathService = yield* import_platform6.Path.Path;
1611
+ var buildStandalonePublicAssets = import_effect12.Effect.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
1612
+ const pathService = yield* import_platform8.Path.Path;
813
1613
  const publicFiles = yield* getDefaultPathMap(pathService.join(projectFolder, "public"));
814
1614
  const assetPaths = [];
815
1615
  const assetOverrides = {};
@@ -825,8 +1625,8 @@ var buildStandalonePublicAssets = import_effect9.Effect.fn("nextjs.buildStandalo
825
1625
  }
826
1626
  return { assetPaths, assetOverrides, filePathMap };
827
1627
  });
828
- var createStaticExportConfig = import_effect9.Effect.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
829
- const pathService = yield* import_platform6.Path.Path;
1628
+ var createStaticExportConfig = import_effect12.Effect.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
1629
+ const pathService = yield* import_platform8.Path.Path;
830
1630
  const outputDirectory = pathService.join(projectFolder, "out");
831
1631
  const files = yield* getDefaultPathMap(outputDirectory);
832
1632
  const assetPaths = [];
@@ -849,8 +1649,8 @@ var createStaticExportConfig = import_effect9.Effect.fn("nextjs.createStaticExpo
849
1649
  package: void 0
850
1650
  };
851
1651
  });
852
- var createStandaloneConfig = import_effect9.Effect.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
853
- const pathService = yield* import_platform6.Path.Path;
1652
+ var createStandaloneConfig = import_effect12.Effect.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
1653
+ const pathService = yield* import_platform8.Path.Path;
854
1654
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
855
1655
  if (!resolved) return defaults;
856
1656
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -883,7 +1683,7 @@ var createStandaloneConfig = import_effect9.Effect.fn("nextjs.createStandaloneCo
883
1683
  }
884
1684
  };
885
1685
  });
886
- var createStandaloneV2Config = import_effect9.Effect.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
1686
+ var createStandaloneV2Config = import_effect12.Effect.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
887
1687
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
888
1688
  if (!resolved) return defaults;
889
1689
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -991,9 +1791,9 @@ var nextjs = {
991
1791
  routes: [{ source: "/*", destination: ".next/standalone/server.js" }],
992
1792
  environmentVariables: { NODE_ENV: "production" }
993
1793
  }),
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)));
1794
+ refineDefaultConfig: (defaults, projectFolder) => import_effect12.Effect.gen(function* () {
1795
+ const fs = yield* import_platform8.FileSystem.FileSystem;
1796
+ const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(import_effect12.Effect.catchAll(() => import_effect12.Effect.succeed(void 0)));
997
1797
  const manifest = manifestContent ? parseGigadriveNextBuildManifest(manifestContent) : void 0;
998
1798
  if (!manifest) return defaults;
999
1799
  if (manifest.version === 2) {
@@ -1004,8 +1804,8 @@ var nextjs = {
1004
1804
  };
1005
1805
 
1006
1806
  // src/detection/frameworks/nuxt.ts
1007
- var import_platform7 = require("@effect/platform");
1008
- var import_effect10 = require("effect");
1807
+ var import_platform9 = require("@effect/platform");
1808
+ var import_effect13 = require("effect");
1009
1809
  var isVercelBuildOutputV3 = (content) => {
1010
1810
  try {
1011
1811
  const config = JSON.parse(content);
@@ -1032,9 +1832,9 @@ var nuxt = {
1032
1832
  routes: [{ source: "/*", destination: ".output/server/index.mjs" }],
1033
1833
  environmentVariables: { NODE_ENV: "production", NITRO_PRESET: "node-server" }
1034
1834
  }),
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)));
1835
+ refineDefaultConfig: (defaults, projectFolder) => import_effect13.Effect.gen(function* () {
1836
+ const fs = yield* import_platform9.FileSystem.FileSystem;
1837
+ const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(import_effect13.Effect.catchAll(() => import_effect13.Effect.succeed(void 0)));
1038
1838
  if (!buildOutputConfig || !isVercelBuildOutputV3(buildOutputConfig)) {
1039
1839
  return defaults;
1040
1840
  }
@@ -1157,7 +1957,7 @@ var FRAMEWORK_DEFINITIONS = [
1157
1957
  ].sort((a, b) => b.priority - a.priority);
1158
1958
 
1159
1959
  // src/detection/generate-config.ts
1160
- var import_effect11 = require("effect");
1960
+ var import_effect14 = require("effect");
1161
1961
  var getInstallCommand = (pm) => {
1162
1962
  switch (pm) {
1163
1963
  case "bun":
@@ -1173,11 +1973,11 @@ var getInstallCommand = (pm) => {
1173
1973
  return "npm install";
1174
1974
  }
1175
1975
  };
1176
- var generateConfig = import_effect11.Effect.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
1976
+ var generateConfig = import_effect14.Effect.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
1177
1977
  const defaults = refinedDefaults ?? framework.getDefaultConfig(packageManager);
1178
1978
  const installCmd = defaults.installCommand ?? getInstallCommand(packageManager);
1179
1979
  const commands = [installCmd, ...defaults.commands];
1180
- yield* import_effect11.Effect.logDebug(`Generating config for ${framework.name}`, {
1980
+ yield* import_effect14.Effect.logDebug(`Generating config for ${framework.name}`, {
1181
1981
  runtime: defaults.runtime,
1182
1982
  entrypoint: defaults.entrypoint,
1183
1983
  commands
@@ -1242,31 +2042,31 @@ var generateConfig = import_effect11.Effect.fn("generateConfig")(function* (fram
1242
2042
  });
1243
2043
 
1244
2044
  // src/detection/read-dependencies.ts
1245
- var import_platform8 = require("@effect/platform");
1246
- var import_effect13 = require("effect");
2045
+ var import_platform10 = require("@effect/platform");
2046
+ var import_effect16 = require("effect");
1247
2047
 
1248
2048
  // 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
2049
+ var import_effect15 = require("effect");
2050
+ var ManifestNotFoundError = class extends import_effect15.Schema.TaggedError()("ManifestNotFoundError", {
2051
+ message: import_effect15.Schema.String,
2052
+ filePath: import_effect15.Schema.String
1253
2053
  }) {
1254
2054
  };
1255
- var ManifestReadError = class extends import_effect12.Schema.TaggedError()("ManifestReadError", {
1256
- message: import_effect12.Schema.String,
1257
- filePath: import_effect12.Schema.String
2055
+ var ManifestReadError = class extends import_effect15.Schema.TaggedError()("ManifestReadError", {
2056
+ message: import_effect15.Schema.String,
2057
+ filePath: import_effect15.Schema.String
1258
2058
  }) {
1259
2059
  };
1260
- var ManifestParseError = class extends import_effect12.Schema.TaggedError()("ManifestParseError", {
1261
- message: import_effect12.Schema.String,
1262
- filePath: import_effect12.Schema.String
2060
+ var ManifestParseError = class extends import_effect15.Schema.TaggedError()("ManifestParseError", {
2061
+ message: import_effect15.Schema.String,
2062
+ filePath: import_effect15.Schema.String
1263
2063
  }) {
1264
2064
  };
1265
- var FrameworkNotDetectedError = class extends import_effect12.Schema.TaggedError()(
2065
+ var FrameworkNotDetectedError = class extends import_effect15.Schema.TaggedError()(
1266
2066
  "FrameworkNotDetectedError",
1267
2067
  {
1268
- message: import_effect12.Schema.String,
1269
- directory: import_effect12.Schema.String
2068
+ message: import_effect15.Schema.String,
2069
+ directory: import_effect15.Schema.String
1270
2070
  }
1271
2071
  ) {
1272
2072
  };
@@ -1276,25 +2076,25 @@ var MANIFEST_CONFIG = {
1276
2076
  node: { file: "package.json", depKeys: ["dependencies", "devDependencies"] },
1277
2077
  php: { file: "composer.json", depKeys: ["require", "require-dev"] }
1278
2078
  };
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)));
2079
+ var readManifest = import_effect16.Effect.fn("readManifest")(function* (filePath) {
2080
+ const fs = yield* import_platform10.FileSystem.FileSystem;
2081
+ const exists = yield* fs.exists(filePath).pipe(import_effect16.Effect.catchAll(() => import_effect16.Effect.succeed(false)));
1282
2082
  if (!exists) {
1283
- return yield* import_effect13.Effect.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
2083
+ return yield* import_effect16.Effect.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
1284
2084
  }
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({
2085
+ const content = yield* fs.readFileString(filePath).pipe(import_effect16.Effect.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
2086
+ const parsed = yield* import_effect16.Effect.try({
1287
2087
  try: () => JSON.parse(content),
1288
2088
  catch: () => new ManifestParseError({ message: "Failed to parse manifest JSON", filePath })
1289
2089
  });
1290
2090
  return parsed;
1291
2091
  });
1292
- var readDependencies = import_effect13.Effect.fn("readDependencies")(function* (projectFolder, language) {
1293
- const pathService = yield* import_platform8.Path.Path;
2092
+ var readDependencies = import_effect16.Effect.fn("readDependencies")(function* (projectFolder, language) {
2093
+ const pathService = yield* import_platform10.Path.Path;
1294
2094
  const config = MANIFEST_CONFIG[language];
1295
2095
  const filePath = pathService.join(projectFolder, config.file);
1296
2096
  const manifest = yield* readManifest(filePath).pipe(
1297
- import_effect13.Effect.catchTag("ManifestNotFoundError", () => import_effect13.Effect.succeed(null))
2097
+ import_effect16.Effect.catchTag("ManifestNotFoundError", () => import_effect16.Effect.succeed(null))
1298
2098
  );
1299
2099
  if (!manifest) {
1300
2100
  return /* @__PURE__ */ new Set();
@@ -1312,9 +2112,9 @@ var readDependencies = import_effect13.Effect.fn("readDependencies")(function* (
1312
2112
  });
1313
2113
 
1314
2114
  // 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;
2115
+ var matchesDetectionItem = import_effect17.Effect.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
2116
+ const fs = yield* import_platform11.FileSystem.FileSystem;
2117
+ const pathService = yield* import_platform11.Path.Path;
1318
2118
  if (item.matchPackage != null) {
1319
2119
  const deps = yield* cachedReadDeps(`${projectFolder}\0${framework.language}`);
1320
2120
  if (!deps.has(item.matchPackage)) {
@@ -1323,12 +2123,12 @@ var matchesDetectionItem = import_effect14.Effect.fn("matchesDetectionItem")(fun
1323
2123
  }
1324
2124
  if (item.path != null) {
1325
2125
  const filePath = pathService.join(projectFolder, item.path);
1326
- const exists = yield* fs.exists(filePath).pipe(import_effect14.Effect.catchAll(() => import_effect14.Effect.succeed(false)));
2126
+ const exists = yield* fs.exists(filePath).pipe(import_effect17.Effect.catchAll(() => import_effect17.Effect.succeed(false)));
1327
2127
  if (!exists) {
1328
2128
  return false;
1329
2129
  }
1330
2130
  if (item.matchContent != null) {
1331
- const content = yield* fs.readFileString(filePath).pipe(import_effect14.Effect.catchAll(() => import_effect14.Effect.succeed("")));
2131
+ const content = yield* fs.readFileString(filePath).pipe(import_effect17.Effect.catchAll(() => import_effect17.Effect.succeed("")));
1332
2132
  let regexMatch = false;
1333
2133
  try {
1334
2134
  regexMatch = new RegExp(item.matchContent).test(content);
@@ -1341,7 +2141,7 @@ var matchesDetectionItem = import_effect14.Effect.fn("matchesDetectionItem")(fun
1341
2141
  }
1342
2142
  return true;
1343
2143
  });
1344
- var matchesFramework = import_effect14.Effect.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
2144
+ var matchesFramework = import_effect17.Effect.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
1345
2145
  for (const detector of framework.detectors) {
1346
2146
  const matches = yield* matchesDetectionItem(detector, projectFolder, framework, cachedReadDeps);
1347
2147
  if (!matches) {
@@ -1350,9 +2150,9 @@ var matchesFramework = import_effect14.Effect.fn("matchesFramework")(function* (
1350
2150
  }
1351
2151
  return true;
1352
2152
  });
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) => {
2153
+ var detectFramework = import_effect17.Effect.fn("detectFramework")(function* (projectFolder) {
2154
+ yield* import_effect17.Effect.logDebug(`Detecting framework in ${projectFolder}`);
2155
+ const cachedReadDeps = yield* import_effect17.Effect.cachedFunction((key) => {
1356
2156
  const sepIndex = key.lastIndexOf("\0");
1357
2157
  const folder = key.slice(0, sepIndex);
1358
2158
  const language = key.slice(sepIndex + 1);
@@ -1361,7 +2161,7 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1361
2161
  for (const framework of FRAMEWORK_DEFINITIONS) {
1362
2162
  const matches = yield* matchesFramework(framework, projectFolder, cachedReadDeps);
1363
2163
  if (matches) {
1364
- yield* import_effect14.Effect.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
2164
+ yield* import_effect17.Effect.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
1365
2165
  const detectedPackageManager = yield* detectPackageManager(projectFolder);
1366
2166
  const packageManager = framework.language === "php" ? "composer" : detectedPackageManager;
1367
2167
  const defaults = framework.getDefaultConfig(packageManager);
@@ -1370,7 +2170,7 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1370
2170
  return { framework, packageManager, config };
1371
2171
  }
1372
2172
  }
1373
- return yield* import_effect14.Effect.fail(
2173
+ return yield* import_effect17.Effect.fail(
1374
2174
  new FrameworkNotDetectedError({
1375
2175
  message: `No framework detected in project directory`,
1376
2176
  directory: projectFolder
@@ -1379,15 +2179,22 @@ var detectFramework = import_effect14.Effect.fn("detectFramework")(function* (pr
1379
2179
  });
1380
2180
 
1381
2181
  // 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");
2182
+ var import_effect18 = require("effect");
2183
+ var mergeWithFrameworkDefaults = import_effect18.Effect.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
2184
+ yield* import_effect18.Effect.logDebug("Merging user config with framework defaults");
1385
2185
  const userHasAssets = (userConfig.assets?.paths?.length ?? 0) > 0 || (userConfig.assets?.prefixes?.length ?? 0) > 0 || (userConfig.assets?.manifests?.length ?? 0) > 0;
2186
+ const isContainer = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME;
2187
+ const userFileFunctions = userConfig.entrypoints.filter((entrypoint) => !isContainer(entrypoint));
2188
+ const containerPaths = new Set(userConfig.entrypoints.filter(isContainer).map((entrypoint) => entrypoint.path));
2189
+ const targetsContainer = (route) => containerPaths.has(route.destination.replace(/^\/+/, ""));
1386
2190
  const merged = {
1387
2191
  regions: userConfig.regions,
1388
2192
  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,
2193
+ entrypoints: [
2194
+ ...userFileFunctions.length > 0 ? userFileFunctions : frameworkConfig.entrypoints,
2195
+ ...userConfig.entrypoints.filter(isContainer)
2196
+ ],
2197
+ routes: containerPaths.size > 0 && userConfig.routes.every(targetsContainer) ? [...userConfig.routes, ...frameworkConfig.routes] : userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
1391
2198
  assets: userHasAssets ? userConfig.assets : frameworkConfig.assets,
1392
2199
  environmentVariables: {
1393
2200
  ...frameworkConfig.environmentVariables,
@@ -1395,6 +2202,7 @@ var mergeWithFrameworkDefaults = import_effect15.Effect.fn("mergeWithFrameworkDe
1395
2202
  },
1396
2203
  excludeFiles: userConfig.excludeFiles && userConfig.excludeFiles.length > 0 ? userConfig.excludeFiles : frameworkConfig.excludeFiles,
1397
2204
  services: userConfig.services,
2205
+ ...userConfig.sidecars !== void 0 && { sidecars: userConfig.sidecars },
1398
2206
  userArchive: userConfig.userArchive,
1399
2207
  warnings: [...frameworkConfig.warnings, ...userConfig.warnings],
1400
2208
  errors: [...frameworkConfig.errors, ...userConfig.errors]
@@ -1402,51 +2210,6 @@ var mergeWithFrameworkDefaults = import_effect15.Effect.fn("mergeWithFrameworkDe
1402
2210
  return merged;
1403
2211
  });
1404
2212
 
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
2213
  // src/filter-functions-from-assets.ts
1451
2214
  var filterFunctionsFromAssets = (config) => {
1452
2215
  return {
@@ -1458,28 +2221,24 @@ var filterFunctionsFromAssets = (config) => {
1458
2221
  };
1459
2222
  };
1460
2223
 
1461
- // src/normalized-config.ts
1462
- var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
1463
- var MAX_FUNCTION_DURATION_SECONDS = 28800;
1464
-
1465
2224
  // src/parse-config.ts
1466
- var import_effect22 = require("effect");
2225
+ var import_effect24 = require("effect");
1467
2226
 
1468
2227
  // 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");
2228
+ var import_platform12 = require("@effect/platform");
2229
+ var import_effect20 = require("effect");
2230
+ var import_yaml2 = require("yaml");
1472
2231
 
1473
2232
  // src/services/config-module-loader.ts
1474
- var import_effect17 = require("effect");
2233
+ var import_effect19 = require("effect");
1475
2234
  var import_jiti = require("jiti");
1476
2235
  var MISSING_DEFAULT_EXPORT = /* @__PURE__ */ Symbol("MissingDefaultExport");
1477
2236
  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) {
2237
+ var ConfigModuleLoader = class extends import_effect19.Effect.Service()("ConfigModuleLoader", {
2238
+ effect: import_effect19.Effect.gen(function* () {
2239
+ const loadConfigModule = import_effect19.Effect.fn("ConfigModuleLoader.loadConfigModule")(function* (filePath) {
1481
2240
  const extension = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1482
- const mod = yield* import_effect17.Effect.tryPromise({
2241
+ const mod = yield* import_effect19.Effect.tryPromise({
1483
2242
  try: async () => {
1484
2243
  const jiti = (0, import_jiti.createJiti)(filePath, { fsCache: false, interopDefault: true, moduleCache: false });
1485
2244
  const namespace = await jiti.import(filePath);
@@ -1498,7 +2257,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1498
2257
  })
1499
2258
  });
1500
2259
  if (mod === MISSING_DEFAULT_EXPORT) {
1501
- return yield* import_effect17.Effect.fail(
2260
+ return yield* import_effect19.Effect.fail(
1502
2261
  new ConfigModuleLoadError({
1503
2262
  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
2263
  filePath
@@ -1506,7 +2265,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1506
2265
  );
1507
2266
  }
1508
2267
  if (typeof mod === "function") {
1509
- return yield* import_effect17.Effect.fail(
2268
+ return yield* import_effect19.Effect.fail(
1510
2269
  new ConfigModuleLoadError({
1511
2270
  message: `Config module at ${filePath} default-exports a function; config factories are not supported. Export a config object instead (optionally wrapped in defineConfig).`,
1512
2271
  filePath
@@ -1514,7 +2273,7 @@ var ConfigModuleLoader = class extends import_effect17.Effect.Service()("ConfigM
1514
2273
  );
1515
2274
  }
1516
2275
  if (typeof mod !== "object" || mod === null || Array.isArray(mod)) {
1517
- return yield* import_effect17.Effect.fail(
2276
+ return yield* import_effect19.Effect.fail(
1518
2277
  new ConfigModuleLoadError({
1519
2278
  message: `Config module at ${filePath} must default-export a config object.`,
1520
2279
  filePath
@@ -1544,36 +2303,36 @@ var ALLOWED_CONFIG_NAMES = [
1544
2303
  "nebula.json"
1545
2304
  ];
1546
2305
  var MODULE_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
1547
- var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigReader", {
2306
+ var RawConfigReader = class extends import_effect20.Effect.Service()("RawConfigReader", {
1548
2307
  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;
2308
+ effect: import_effect20.Effect.gen(function* () {
2309
+ const fs = yield* import_platform12.FileSystem.FileSystem;
2310
+ const pathService = yield* import_platform12.Path.Path;
1552
2311
  const moduleLoader = yield* ConfigModuleLoader;
1553
- const findConfig = import_effect18.Effect.fn("RawConfigReader.findConfig")(function* (projectFolder) {
2312
+ const findConfig = import_effect20.Effect.fn("RawConfigReader.findConfig")(function* (projectFolder) {
1554
2313
  for (const name of ALLOWED_CONFIG_NAMES) {
1555
2314
  const filePath = pathService.join(projectFolder, name);
1556
- const exists = yield* fs.exists(filePath).pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed(false)));
2315
+ const exists = yield* fs.exists(filePath).pipe(import_effect20.Effect.catchAll(() => import_effect20.Effect.succeed(false)));
1557
2316
  if (exists) {
1558
2317
  return filePath;
1559
2318
  }
1560
2319
  }
1561
2320
  return null;
1562
2321
  });
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)));
2322
+ const readRawConfig = import_effect20.Effect.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
2323
+ const exists = yield* fs.exists(filePath).pipe(import_effect20.Effect.catchAll(() => import_effect20.Effect.succeed(false)));
1565
2324
  if (!exists) {
1566
- return yield* import_effect18.Effect.fail(
2325
+ return yield* import_effect20.Effect.fail(
1567
2326
  new ConfigFileNotFoundError({ message: `Config file not found at ${filePath}`, filePath })
1568
2327
  );
1569
2328
  }
1570
2329
  const fileContents = yield* fs.readFileString(filePath).pipe(
1571
- import_effect18.Effect.mapError(
2330
+ import_effect20.Effect.mapError(
1572
2331
  () => new ConfigFileParseError({ message: `Failed to read config file at ${filePath}`, filePath })
1573
2332
  )
1574
2333
  );
1575
2334
  if (fileContents.length === 0) {
1576
- return yield* import_effect18.Effect.fail(
2335
+ return yield* import_effect20.Effect.fail(
1577
2336
  new ConfigFileEmptyError({ message: `Config file is empty at ${filePath}`, filePath })
1578
2337
  );
1579
2338
  }
@@ -1586,10 +2345,10 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1586
2345
  if (fileExtension === ".json") {
1587
2346
  parsed = JSON.parse(fileContents);
1588
2347
  } else {
1589
- parsed = (0, import_yaml.parse)(fileContents);
2348
+ parsed = (0, import_yaml2.parse)(fileContents);
1590
2349
  }
1591
2350
  } catch (error) {
1592
- return yield* import_effect18.Effect.fail(
2351
+ return yield* import_effect20.Effect.fail(
1593
2352
  new ConfigFileParseError({
1594
2353
  message: `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
1595
2354
  filePath,
@@ -1599,21 +2358,21 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1599
2358
  }
1600
2359
  }
1601
2360
  if (parsed == null) {
1602
- return yield* import_effect18.Effect.fail(
2361
+ return yield* import_effect20.Effect.fail(
1603
2362
  new ConfigFileParseError({ message: `Config file could not be parsed at ${filePath}`, filePath })
1604
2363
  );
1605
2364
  }
1606
2365
  if (!options?.disableVersionCheck && typeof parsed.version !== "number") {
1607
- return yield* import_effect18.Effect.fail(
2366
+ return yield* import_effect20.Effect.fail(
1608
2367
  new ConfigVersionError({ message: `Config file is missing version at ${filePath}`, filePath })
1609
2368
  );
1610
2369
  }
1611
2370
  return parsed;
1612
2371
  });
1613
2372
  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")
2373
+ import_effect20.Effect.map((parsed) => parsed),
2374
+ import_effect20.Effect.catchTag("ConfigFileNotFoundError", () => import_effect20.Effect.succeed(null)),
2375
+ import_effect20.Effect.withSpan("RawConfigReader.readConfigFile")
1617
2376
  );
1618
2377
  return { findConfig, readRawConfig, readConfigFile };
1619
2378
  })
@@ -1623,17 +2382,17 @@ var RawConfigReader = class extends import_effect18.Effect.Service()("RawConfigR
1623
2382
  // src/services/schema-validator.ts
1624
2383
  var import_ajv = __toESM(require("ajv"));
1625
2384
  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* () {
2385
+ var import_effect21 = require("effect");
2386
+ var SchemaValidator = class extends import_effect21.Effect.Service()("SchemaValidator", {
2387
+ effect: import_effect21.Effect.gen(function* () {
1629
2388
  const ajv = new import_ajv.default();
1630
2389
  (0, import_ajv_formats.default)(ajv);
1631
- const validate = import_effect19.Effect.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
2390
+ const validate = import_effect21.Effect.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
1632
2391
  const compiledValidate = ajv.compile(schema);
1633
2392
  const valid = compiledValidate(config);
1634
2393
  if (!valid) {
1635
2394
  const errors = compiledValidate.errors?.map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`.trim()) ?? [];
1636
- return yield* import_effect19.Effect.fail(
2395
+ return yield* import_effect21.Effect.fail(
1637
2396
  new ConfigSchemaValidationError({
1638
2397
  message: `Config file at ${filePath} is invalid: ${ajv.errorsText(compiledValidate.errors)}`,
1639
2398
  filePath,
@@ -1689,8 +2448,9 @@ var getFilesForPattern = async (pattern, projectFolder, excludeFiles = []) => {
1689
2448
  };
1690
2449
 
1691
2450
  // src/services/v4-config-parser.ts
1692
- var import_effect20 = require("effect");
2451
+ var import_effect22 = require("effect");
1693
2452
  var import_minimatch2 = require("minimatch");
2453
+ var import_node_path2 = require("path");
1694
2454
  var import_safe_regex2 = __toESM(require("safe-regex2"));
1695
2455
  var DEFAULT_FUNCTION_SETTINGS = {
1696
2456
  memory: 128,
@@ -1723,6 +2483,46 @@ var normalizeImagePolicy = (images) => {
1723
2483
  variants: images.variants ?? {}
1724
2484
  };
1725
2485
  };
2486
+ var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
2487
+ var queueDurationSeconds = (value) => {
2488
+ if (value === void 0) return void 0;
2489
+ if (typeof value === "number") return value;
2490
+ const match = /^(\d+(?:\.\d+)?)(s|m|h|d)$/.exec(value);
2491
+ if (!match)
2492
+ throw new Error(`Invalid queue duration "${value}". Use seconds or a string such as 30s, 10m, 1.5h or 4d.`);
2493
+ return Math.ceil(Number(match[1]) * DURATION_UNIT_SECONDS[match[2]]);
2494
+ };
2495
+ var byName = (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
2496
+ var definedOnly = (value) => Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
2497
+ var normalizeQueue = (name, queue) => {
2498
+ if (queue === null) return { name };
2499
+ const schedules = queue.schedules ? Object.entries(queue.schedules).map(
2500
+ ([scheduleName, schedule]) => definedOnly({
2501
+ name: scheduleName,
2502
+ cron: schedule.cron,
2503
+ timezone: schedule.timezone,
2504
+ body: schedule.body === void 0 || typeof schedule.body === "string" ? schedule.body : JSON.stringify(schedule.body),
2505
+ // A plain-text body labelled JSON would fail to decode on every fire.
2506
+ contentType: schedule.contentType ?? (typeof schedule.body === "string" ? "text/plain; charset=utf-8" : void 0),
2507
+ headers: schedule.headers,
2508
+ enabled: schedule.enabled
2509
+ })
2510
+ ).sort(byName) : void 0;
2511
+ return definedOnly({
2512
+ name,
2513
+ consumer: queue.consumer,
2514
+ visibilityTimeoutSeconds: queueDurationSeconds(queue.visibilityTimeout),
2515
+ retentionSeconds: queueDurationSeconds(queue.retention),
2516
+ maxAttempts: queue.maxAttempts,
2517
+ retryBackoffMinSeconds: queueDurationSeconds(queue.retryBackoff?.min),
2518
+ retryBackoffMaxSeconds: queueDurationSeconds(queue.retryBackoff?.max),
2519
+ deduplicationWindowSeconds: queueDurationSeconds(queue.deduplicationWindow),
2520
+ concurrency: queue.concurrency,
2521
+ rateLimit: queue.rateLimit == null ? queue.rateLimit : { count: queue.rateLimit.count, periodSeconds: queueDurationSeconds(queue.rateLimit.period) ?? 1 },
2522
+ deadLetter: queue.deadLetter,
2523
+ schedules
2524
+ });
2525
+ };
1726
2526
  var normalizeServices = (services) => {
1727
2527
  if (services == null) return void 0;
1728
2528
  const normalized = [];
@@ -1735,7 +2535,13 @@ var normalizeServices = (services) => {
1735
2535
  if (services.storage !== void 0) {
1736
2536
  normalized.push({
1737
2537
  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)
2538
+ buckets: Object.entries(services.storage.buckets).map(([name, bucket]) => ({ name, visibility: bucket?.visibility ?? "private" })).sort(byName)
2539
+ });
2540
+ }
2541
+ if (services.queues !== void 0) {
2542
+ normalized.push({
2543
+ type: "queues",
2544
+ queues: Object.entries(services.queues).map(([name, queue]) => normalizeQueue(name, queue)).sort(byName)
1739
2545
  });
1740
2546
  }
1741
2547
  return normalized.length > 0 ? normalized : void 0;
@@ -1793,9 +2599,10 @@ var mapRoute = (route, entrypoints) => ({
1793
2599
  negativeRequirements: route.missing,
1794
2600
  status: route.statusCode
1795
2601
  });
1796
- var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (config, projectFolder) {
2602
+ var parseEntrypoints = import_effect22.Effect.fn("parseEntrypoints")(function* (config, projectFolder) {
1797
2603
  const entrypoints = [];
1798
- if (config.functions == null) return entrypoints;
2604
+ const dockerFunctions = [];
2605
+ if (config.functions == null) return { entrypoints, dockerFunctions };
1799
2606
  for (const [fnPath, func] of Object.entries(config.functions)) {
1800
2607
  if (getFunctionSettings(fnPath, config) == null) {
1801
2608
  return yield* new FunctionConfigError({
@@ -1803,7 +2610,7 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1803
2610
  functionPath: fnPath
1804
2611
  });
1805
2612
  }
1806
- const matchedFiles = yield* import_effect20.Effect.tryPromise({
2613
+ const matchedFiles = yield* import_effect22.Effect.tryPromise({
1807
2614
  try: () => getFilesForPattern(fnPath, projectFolder, func.excludeFiles),
1808
2615
  catch: (error) => new FunctionConfigError({
1809
2616
  message: `Failed to resolve files for function pattern '${fnPath}': ${error instanceof Error ? error.message : String(error)}`,
@@ -1820,6 +2627,35 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1820
2627
  functionPath: file
1821
2628
  });
1822
2629
  }
2630
+ if (settings.runtime === CONTAINER_RUNTIME) {
2631
+ const unsupported = ["symlinks", "includeFiles"].filter((key) => settings[key] !== void 0);
2632
+ if (unsupported.length > 0) {
2633
+ return yield* new FunctionConfigError({
2634
+ message: `Function '${file}' runs a Dockerfile, so ${unsupported.join(" and ")} do not apply. Copy files in the Dockerfile instead.`,
2635
+ functionPath: file
2636
+ });
2637
+ }
2638
+ const declared = Object.entries(config.functions).filter(([pattern]) => matchesPattern(file, pattern)).reduce((merged, [, value]) => ({ ...merged, ...value }), {});
2639
+ dockerFunctions.push({
2640
+ file,
2641
+ name: dockerFunctionContainerName(file),
2642
+ container: {
2643
+ build: { context: import_node_path2.posix.dirname(file), dockerfile: import_node_path2.posix.basename(file) },
2644
+ ...declared.port !== void 0 && { port: declared.port },
2645
+ ...declared.memory !== void 0 && { memory: declared.memory },
2646
+ ...declared.max_duration !== void 0 && { max_duration: declared.max_duration },
2647
+ ...declared.streaming !== void 0 && { streaming: declared.streaming },
2648
+ ...func.schedule !== void 0 && { schedule: func.schedule }
2649
+ }
2650
+ });
2651
+ continue;
2652
+ }
2653
+ if (settings.port !== void 0) {
2654
+ return yield* new FunctionConfigError({
2655
+ message: `Function '${file}' sets port, which only applies to 'runtime: docker'.`,
2656
+ functionPath: file
2657
+ });
2658
+ }
1823
2659
  const runtime = settings.runtime ?? "node-20";
1824
2660
  const streaming = settings.streaming ?? runtimeStreamsByDefault(runtime);
1825
2661
  entrypoints.push({
@@ -1837,25 +2673,84 @@ var parseEntrypoints = import_effect20.Effect.fn("parseEntrypoints")(function* (
1837
2673
  });
1838
2674
  }
1839
2675
  }
1840
- return entrypoints;
2676
+ dockerFunctions.sort((left, right) => left.file < right.file ? -1 : left.file > right.file ? 1 : 0);
2677
+ for (const [index, { file, name }] of dockerFunctions.entries()) {
2678
+ const clash = dockerFunctions.slice(0, index).find((candidate) => candidate.name === name);
2679
+ if (clash !== void 0) {
2680
+ return yield* new FunctionConfigError({
2681
+ message: `Functions '${clash.file}' and '${file}' would both be named '${name}'. Declare one of them under 'containers' with an explicit name.`,
2682
+ functionPath: file
2683
+ });
2684
+ }
2685
+ }
2686
+ return { entrypoints, dockerFunctions };
1841
2687
  });
1842
- var collectAssets = import_effect20.Effect.fn("collectAssets")(function* (config, projectFolder) {
2688
+ var collectAssets = import_effect22.Effect.fn("collectAssets")(function* (config, projectFolder) {
1843
2689
  if (config.assets == null) return [];
1844
2690
  const assetNames = yield* collectAssetFiles(projectFolder, config.assets);
1845
2691
  return assetNames.filter((assetName) => getFunctionSettings(`${config.assets}/${assetName}`, config) == null).map((assetName) => `${config.assets}/${assetName}`);
1846
2692
  });
1847
- var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigParser", {
2693
+ var reservedRuntimePorts = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME ? [] : entrypoint.runtime.startsWith("php-") ? [8080, 9e3] : [8080];
2694
+ var parseContainers = import_effect22.Effect.fn("parseContainers")(function* (config, projectFolder, fileEntrypoints, dockerFunctions) {
2695
+ const warnings = [];
2696
+ let containers = {};
2697
+ if (config.compose != null) {
2698
+ const imported = yield* readComposeContainers(config.compose, projectFolder);
2699
+ containers = imported.containers;
2700
+ warnings.push(...imported.warnings);
2701
+ }
2702
+ containers = { ...containers, ...config.containers ?? {} };
2703
+ for (const { file, name, container } of dockerFunctions) {
2704
+ if (containers[name] !== void 0) {
2705
+ return yield* new ContainerConfigError({
2706
+ message: `Function '${file}' runs as container '${name}', which 'containers' or the Compose file already declares. Rename one of them.`,
2707
+ containerName: name
2708
+ });
2709
+ }
2710
+ containers[name] = container;
2711
+ }
2712
+ const result = yield* normalizeContainers(containers, projectFolder);
2713
+ warnings.push(...result.warnings);
2714
+ for (const entrypoint of fileEntrypoints) {
2715
+ for (const port of reservedRuntimePorts(entrypoint)) {
2716
+ const sidecar = result.sidecars.find((candidate) => candidate.port === port);
2717
+ if (sidecar !== void 0) {
2718
+ return yield* new ContainerConfigError({
2719
+ message: `Sidecar '${sidecar.name}' listens on port ${port}, which the ${entrypoint.runtime} runtime of '${entrypoint.path}' uses inside the same microVM. Choose another port.`,
2720
+ containerName: sidecar.name
2721
+ });
2722
+ }
2723
+ }
2724
+ }
2725
+ return { ...result, warnings };
2726
+ });
2727
+ var V4ConfigParser = class extends import_effect22.Effect.Service()("V4ConfigParser", {
1848
2728
  accessors: true,
1849
- effect: import_effect20.Effect.succeed({
2729
+ effect: import_effect22.Effect.succeed({
1850
2730
  /**
1851
2731
  * Parses a ConfigV4 into a NormalizedConfig.
1852
2732
  *
1853
2733
  * @param config - The raw V4 config object
1854
2734
  * @param projectFolder - Absolute path to the project root
1855
2735
  */
1856
- parse: import_effect20.Effect.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
1857
- const entrypoints = yield* parseEntrypoints(config, projectFolder);
2736
+ parse: import_effect22.Effect.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
2737
+ const { entrypoints: fileEntrypoints, dockerFunctions } = yield* parseEntrypoints(config, projectFolder);
2738
+ const containers = yield* parseContainers(config, projectFolder, fileEntrypoints, dockerFunctions);
2739
+ const entrypoints = [...fileEntrypoints, ...containers.entrypoints];
1858
2740
  const assets = yield* collectAssets(config, projectFolder);
2741
+ const dockerFunctionPaths = new Map(
2742
+ dockerFunctions.map(({ file, name }) => [file, containerEntrypointPath(name)])
2743
+ );
2744
+ const routes = (config.routes ?? []).map(
2745
+ (route) => mapRoute(
2746
+ {
2747
+ ...route,
2748
+ destination: dockerFunctionPaths.get(normalizeRouteDestination(route.destination)) ?? route.destination
2749
+ },
2750
+ entrypoints
2751
+ )
2752
+ );
2753
+ const warnings = [...containers.warnings];
1859
2754
  return {
1860
2755
  regions: resolveRegions(config.regions),
1861
2756
  assets: {
@@ -1868,10 +2763,11 @@ var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigPar
1868
2763
  commands: config.build_commands ?? [],
1869
2764
  images: normalizeImagePolicy(config.images),
1870
2765
  services: normalizeServices(config.services),
2766
+ ...containers.sidecars.length > 0 && { sidecars: containers.sidecars },
1871
2767
  entrypoints,
1872
2768
  errors: [],
1873
- warnings: [],
1874
- routes: (config.routes ?? []).map((route) => mapRoute(route, entrypoints))
2769
+ warnings,
2770
+ routes
1875
2771
  };
1876
2772
  })
1877
2773
  })
@@ -1879,7 +2775,7 @@ var V4ConfigParser = class extends import_effect20.Effect.Service()("V4ConfigPar
1879
2775
  };
1880
2776
 
1881
2777
  // src/services/vercel-build-output-parser.ts
1882
- var import_platform11 = require("@effect/platform");
2778
+ var import_platform13 = require("@effect/platform");
1883
2779
 
1884
2780
  // ../commons/src/deep-merge.ts
1885
2781
  var deepMerge = (target, ...partials) => {
@@ -1908,7 +2804,7 @@ var deepMerge = (target, ...partials) => {
1908
2804
  };
1909
2805
 
1910
2806
  // src/services/vercel-build-output-parser.ts
1911
- var import_effect21 = require("effect");
2807
+ var import_effect23 = require("effect");
1912
2808
 
1913
2809
  // src/build-output-v3/determine-repo-root.ts
1914
2810
  var import_path = __toESM(require("path"));
@@ -2004,11 +2900,11 @@ var translateVercelRuntime = (runtime) => {
2004
2900
  var CONFIG_PATH = ".vercel/output/config.json";
2005
2901
  var FUNCTIONS_FOLDER = ".vercel/output/functions";
2006
2902
  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* () {
2903
+ var VercelBuildOutputParser = class extends import_effect23.Effect.Service()("VercelBuildOutputParser", {
2904
+ effect: import_effect23.Effect.gen(function* () {
2009
2905
  const rawConfigReader = yield* RawConfigReader;
2010
- const pathService = yield* import_platform11.Path.Path;
2011
- const parse = import_effect21.Effect.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
2906
+ const pathService = yield* import_platform13.Path.Path;
2907
+ const parse = import_effect23.Effect.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
2012
2908
  const config = yield* rawConfigReader.readConfigFile(pathService.join(projectFolder, CONFIG_PATH));
2013
2909
  if (!config || typeof config.version !== "number" || config.version < 3) {
2014
2910
  return parseResult;
@@ -2020,8 +2916,8 @@ var VercelBuildOutputParser = class extends import_effect21.Effect.Service()("Ve
2020
2916
  merged.regions = [...new Set(merged.regions)];
2021
2917
  return merged;
2022
2918
  });
2023
- const loadFunctions = import_effect21.Effect.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
2024
- const functionConfigs = yield* import_effect21.Effect.tryPromise({
2919
+ const loadFunctions = import_effect23.Effect.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
2920
+ const functionConfigs = yield* import_effect23.Effect.tryPromise({
2025
2921
  try: () => getFilesForPattern(FUNCTION_CONFIGS_PATTERN, projectFolder),
2026
2922
  catch: (error) => new ConfigFileParseError({
2027
2923
  message: `Failed to scan for function configs: ${error instanceof Error ? error.message : String(error)}`,
@@ -2307,17 +3203,22 @@ var schema_default = {
2307
3203
  minimum: 1
2308
3204
  },
2309
3205
  runtime: {
2310
- $ref: "#/$defs/runtimes",
2311
- description: "The runtime to use for the function."
3206
+ anyOf: [{ $ref: "#/$defs/runtimes" }, { const: "docker" }],
3207
+ 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."
3208
+ },
3209
+ port: {
3210
+ type: "integer",
3211
+ minimum: 1,
3212
+ maximum: 65535,
3213
+ description: "TCP port the image listens on. Only for `runtime: docker`. Defaults to the first exposed port of the image, then 8080."
2312
3214
  },
2313
3215
  streaming: {
2314
3216
  type: "boolean",
2315
3217
  description: "Enable function response streaming. When omitted, Node and Bun runtimes stream by default."
2316
3218
  },
2317
3219
  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]+))?)*)[)])$"
3220
+ $ref: "#/$defs/schedule",
3221
+ description: "An expression to schedule the function to run at specific times."
2321
3222
  },
2322
3223
  symlinks: {
2323
3224
  type: "object",
@@ -2409,6 +3310,151 @@ var schema_default = {
2409
3310
  }
2410
3311
  }
2411
3312
  },
3313
+ compose: {
3314
+ type: ["string", "null"],
3315
+ minLength: 1,
3316
+ maxLength: 1024,
3317
+ 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."
3318
+ },
3319
+ containers: {
3320
+ type: ["object", "null"],
3321
+ 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>`.",
3322
+ maxProperties: 16,
3323
+ propertyNames: {
3324
+ pattern: "^[a-z][a-z0-9_-]{0,62}$",
3325
+ not: {
3326
+ enum: ["localhost"]
3327
+ }
3328
+ },
3329
+ additionalProperties: {
3330
+ type: "object",
3331
+ additionalProperties: false,
3332
+ oneOf: [
3333
+ {
3334
+ required: ["image"]
3335
+ },
3336
+ {
3337
+ required: ["build"]
3338
+ }
3339
+ ],
3340
+ properties: {
3341
+ image: {
3342
+ type: "string",
3343
+ minLength: 1,
3344
+ maxLength: 512,
3345
+ pattern: "^[^\\s]+$",
3346
+ description: "Registry image reference, e.g. `redis:7-alpine` or `ghcr.io/acme/api:1.4`."
3347
+ },
3348
+ build: {
3349
+ description: "Build context directory, or the full build settings, for a Dockerfile in the repository.",
3350
+ oneOf: [
3351
+ {
3352
+ type: "string",
3353
+ minLength: 1,
3354
+ maxLength: 1024
3355
+ },
3356
+ {
3357
+ type: "object",
3358
+ additionalProperties: false,
3359
+ properties: {
3360
+ context: {
3361
+ type: "string",
3362
+ minLength: 1,
3363
+ maxLength: 1024
3364
+ },
3365
+ dockerfile: {
3366
+ type: "string",
3367
+ minLength: 1,
3368
+ maxLength: 1024
3369
+ },
3370
+ target: {
3371
+ type: "string",
3372
+ minLength: 1,
3373
+ maxLength: 128
3374
+ },
3375
+ args: {
3376
+ type: "object",
3377
+ maxProperties: 100,
3378
+ additionalProperties: {
3379
+ type: "string",
3380
+ maxLength: 65536
3381
+ }
3382
+ }
3383
+ }
3384
+ }
3385
+ ]
3386
+ },
3387
+ sidecar: {
3388
+ type: "boolean",
3389
+ description: "Run next to every function instance instead of serving routes. Defaults to false."
3390
+ },
3391
+ port: {
3392
+ type: "integer",
3393
+ minimum: 1,
3394
+ maximum: 65535,
3395
+ description: "TCP port the container listens on. Defaults to the first exposed port of the image, then 8080."
3396
+ },
3397
+ entrypoint: {
3398
+ $ref: "#/$defs/stringOrStringArray",
3399
+ description: "Replaces the image ENTRYPOINT. A string is split into words the way Compose splits it."
3400
+ },
3401
+ command: {
3402
+ $ref: "#/$defs/stringOrStringArray",
3403
+ description: "Replaces the image CMD. A string is split into words the way Compose splits it."
3404
+ },
3405
+ env: {
3406
+ type: "object",
3407
+ maxProperties: 100,
3408
+ additionalProperties: {
3409
+ type: "string",
3410
+ maxLength: 65536
3411
+ },
3412
+ description: "Environment variables baked into the container, merged over the image ENV."
3413
+ },
3414
+ working_dir: {
3415
+ type: "string",
3416
+ minLength: 1,
3417
+ maxLength: 1024,
3418
+ pattern: "^/",
3419
+ description: "Replaces the image WORKDIR. Must be absolute."
3420
+ },
3421
+ user: {
3422
+ oneOf: [
3423
+ {
3424
+ type: "string",
3425
+ minLength: 1,
3426
+ maxLength: 256
3427
+ },
3428
+ {
3429
+ type: "integer",
3430
+ minimum: 0
3431
+ }
3432
+ ],
3433
+ description: "Replaces the image USER: a name, a uid, `name:group` or `uid:gid`. A bare uid may be a number."
3434
+ },
3435
+ memory: {
3436
+ type: "integer",
3437
+ minimum: 128,
3438
+ maximum: 3009,
3439
+ description: "Memory in MB. Defaults to 512 for a container function and 256 for a sidecar."
3440
+ },
3441
+ max_duration: {
3442
+ type: "number",
3443
+ minimum: 1,
3444
+ maximum: 28800,
3445
+ description: "Maximum lifetime in seconds of one request. Container functions only."
3446
+ },
3447
+ streaming: {
3448
+ type: "boolean",
3449
+ description: "Stream responses. Defaults to true. Container functions only."
3450
+ },
3451
+ schedule: {
3452
+ $ref: "#/$defs/schedule",
3453
+ description: "An expression to run the container function at specific times. Container functions only."
3454
+ }
3455
+ }
3456
+ }
3457
+ },
2412
3458
  services: {
2413
3459
  type: "object",
2414
3460
  description: "Managed services to provision for the deployment environment.",
@@ -2559,11 +3605,179 @@ var schema_default = {
2559
3605
  }
2560
3606
  }
2561
3607
  }
3608
+ },
3609
+ queues: {
3610
+ type: "object",
3611
+ 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.",
3612
+ propertyNames: {
3613
+ maxLength: 255,
3614
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.~:@+=-]*$"
3615
+ },
3616
+ additionalProperties: {
3617
+ oneOf: [
3618
+ {
3619
+ type: "null"
3620
+ },
3621
+ {
3622
+ type: "object",
3623
+ additionalProperties: false,
3624
+ properties: {
3625
+ consumer: {
3626
+ type: ["string", "null"],
3627
+ pattern: "^/(?!/)\\S*$",
3628
+ maxLength: 2048,
3629
+ 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."
3630
+ },
3631
+ visibilityTimeout: {
3632
+ $ref: "#/$defs/queueDuration",
3633
+ description: "How long a push delivery may run, or how long a received message stays leased. Defaults to 60 seconds, at most 12 hours."
3634
+ },
3635
+ retention: {
3636
+ $ref: "#/$defs/queueDuration",
3637
+ description: "How long an undelivered message is kept. Defaults to 4 days, at most 14 days."
3638
+ },
3639
+ maxAttempts: {
3640
+ type: "integer",
3641
+ minimum: 1,
3642
+ maximum: 100,
3643
+ description: "Deliveries per message before it moves to the dead-letter list. Defaults to 10."
3644
+ },
3645
+ retryBackoff: {
3646
+ type: "object",
3647
+ additionalProperties: false,
3648
+ description: "Exponential backoff between failed deliveries. Defaults to 5 seconds, doubling up to 15 minutes.",
3649
+ properties: {
3650
+ min: {
3651
+ $ref: "#/$defs/queueDuration"
3652
+ },
3653
+ max: {
3654
+ $ref: "#/$defs/queueDuration"
3655
+ }
3656
+ }
3657
+ },
3658
+ deduplicationWindow: {
3659
+ $ref: "#/$defs/queueDuration",
3660
+ description: "How long a deduplication key is remembered. Defaults to 24 hours, at most 7 days."
3661
+ },
3662
+ concurrency: {
3663
+ type: ["integer", "null"],
3664
+ minimum: 1,
3665
+ maximum: 1e3,
3666
+ description: "Most push deliveries in flight at once. Defaults to 100."
3667
+ },
3668
+ rateLimit: {
3669
+ oneOf: [
3670
+ {
3671
+ type: "null"
3672
+ },
3673
+ {
3674
+ type: "object",
3675
+ required: ["count"],
3676
+ additionalProperties: false,
3677
+ properties: {
3678
+ count: {
3679
+ type: "integer",
3680
+ minimum: 1,
3681
+ maximum: 1e6
3682
+ },
3683
+ period: {
3684
+ $ref: "#/$defs/queueDuration"
3685
+ }
3686
+ }
3687
+ }
3688
+ ],
3689
+ description: "Most deliveries started per period. The period defaults to one second."
3690
+ },
3691
+ deadLetter: {
3692
+ type: "boolean",
3693
+ description: "Keep messages that run out of attempts so you can inspect and redrive them. Defaults to true."
3694
+ },
3695
+ schedules: {
3696
+ type: "object",
3697
+ description: "Cron schedules that send a message into this queue, keyed by schedule name.",
3698
+ propertyNames: {
3699
+ maxLength: 128,
3700
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.-]*$"
3701
+ },
3702
+ additionalProperties: {
3703
+ type: "object",
3704
+ required: ["cron"],
3705
+ additionalProperties: false,
3706
+ properties: {
3707
+ cron: {
3708
+ type: "string",
3709
+ minLength: 1,
3710
+ maxLength: 255,
3711
+ description: "Five-field cron expression, or a macro such as @hourly or @daily."
3712
+ },
3713
+ timezone: {
3714
+ type: "string",
3715
+ description: "IANA time zone the expression is evaluated in. Defaults to UTC."
3716
+ },
3717
+ body: {
3718
+ description: "Message body. Strings are sent as is; any other value is sent as JSON."
3719
+ },
3720
+ contentType: {
3721
+ type: "string",
3722
+ description: "Content type of the body. Defaults to text/plain for a string body and application/json otherwise."
3723
+ },
3724
+ headers: {
3725
+ type: "object",
3726
+ additionalProperties: {
3727
+ type: "string"
3728
+ },
3729
+ description: "Headers delivered with every message from this schedule."
3730
+ },
3731
+ enabled: {
3732
+ type: "boolean",
3733
+ description: "Set to false to keep the schedule without sending. Defaults to true."
3734
+ }
3735
+ }
3736
+ }
3737
+ }
3738
+ }
3739
+ }
3740
+ ]
3741
+ }
2562
3742
  }
2563
3743
  }
2564
3744
  }
2565
3745
  },
2566
3746
  $defs: {
3747
+ schedule: {
3748
+ type: "string",
3749
+ 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]+))?)*)[)])$"
3750
+ },
3751
+ stringOrStringArray: {
3752
+ oneOf: [
3753
+ {
3754
+ type: "string",
3755
+ minLength: 1,
3756
+ maxLength: 4096
3757
+ },
3758
+ {
3759
+ type: "array",
3760
+ maxItems: 256,
3761
+ items: {
3762
+ type: "string",
3763
+ maxLength: 4096
3764
+ }
3765
+ }
3766
+ ]
3767
+ },
3768
+ queueDuration: {
3769
+ oneOf: [
3770
+ {
3771
+ type: "integer",
3772
+ minimum: 0
3773
+ },
3774
+ {
3775
+ type: "string",
3776
+ pattern: "^[0-9]+(\\.[0-9]+)?(s|m|h|d)$"
3777
+ }
3778
+ ],
3779
+ description: "A duration in seconds, or a string such as 30s, 10m, 1.5h or 4d."
3780
+ },
2567
3781
  runtimes: {
2568
3782
  type: "string",
2569
3783
  enum: ["php-84", "php-83", "php-81", "node-22", "node-20", "node-18", "node-16", "bun-1"]
@@ -2620,7 +3834,7 @@ var schema_default = {
2620
3834
  };
2621
3835
 
2622
3836
  // src/parse-config.ts
2623
- var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (filePath, projectFolder) {
3837
+ var parseConfigRaw = import_effect24.Effect.fn("parseConfigRaw")(function* (filePath, projectFolder) {
2624
3838
  const rawReader = yield* RawConfigReader;
2625
3839
  const validator = yield* SchemaValidator;
2626
3840
  const v4Parser = yield* V4ConfigParser;
@@ -2631,7 +3845,7 @@ var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (file
2631
3845
  };
2632
3846
  const schema = schemaMap[version];
2633
3847
  if (!schema) {
2634
- return yield* import_effect22.Effect.fail(
3848
+ return yield* import_effect24.Effect.fail(
2635
3849
  new ConfigVersionError({
2636
3850
  message: `Config file at ${filePath} has an unsupported version: ${version}`,
2637
3851
  filePath,
@@ -2642,14 +3856,42 @@ var parseConfigRaw = import_effect22.Effect.fn("parseConfigRaw")(function* (file
2642
3856
  yield* validator.validate(parsed, schema, filePath);
2643
3857
  return yield* v4Parser.parse(parsed, projectFolder);
2644
3858
  });
2645
- var parseConfig = import_effect22.Effect.fn("parseConfig")(function* (filePath, projectFolder) {
3859
+ var parseConfig = import_effect24.Effect.fn("parseConfig")(function* (filePath, projectFolder) {
2646
3860
  const parseResult = yield* parseConfigRaw(filePath, projectFolder);
2647
3861
  return yield* postProcessConfig(parseResult, projectFolder);
2648
3862
  });
2649
- var postProcessConfig = import_effect22.Effect.fn("postProcessConfig")(function* (config, projectFolder) {
3863
+ var postProcessConfig = import_effect24.Effect.fn("postProcessConfig")(function* (config, projectFolder) {
2650
3864
  const vercelParser = yield* VercelBuildOutputParser;
2651
3865
  let result = yield* vercelParser.parse(config, projectFolder);
2652
3866
  const hasAssets = (result.assets?.paths?.length ?? 0) > 0 || (result.assets?.prefixes?.length ?? 0) > 0 || (result.assets?.manifests?.length ?? 0) > 0;
3867
+ const containerFunctions = result.entrypoints.filter((entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME);
3868
+ const [onlyFunction] = result.entrypoints;
3869
+ if (onlyFunction !== void 0 && onlyFunction.runtime === CONTAINER_RUNTIME && result.entrypoints.length === 1 && result.routes.length === 0 && !hasAssets) {
3870
+ result = {
3871
+ ...result,
3872
+ routes: [
3873
+ {
3874
+ path: "/*",
3875
+ destination: onlyFunction.path,
3876
+ handler: onlyFunction.streaming === true ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION",
3877
+ methods: ["ANY"],
3878
+ headers: {}
3879
+ }
3880
+ ]
3881
+ };
3882
+ } else {
3883
+ for (const entrypoint of containerFunctions) {
3884
+ const routed = result.routes.some((route) => route.destination.replace(/^\/+/, "") === entrypoint.path);
3885
+ if (!routed && entrypoint.schedule === void 0) {
3886
+ result.warnings.push(
3887
+ `Container function '${entrypoint.displayName ?? entrypoint.path}' has no route and no schedule, so nothing reaches it. Add a route with destination '${entrypoint.path}'.`
3888
+ );
3889
+ }
3890
+ }
3891
+ }
3892
+ if ((result.sidecars?.length ?? 0) > 0 && result.entrypoints.length === 0) {
3893
+ result.warnings.push("Sidecars run next to functions, and this deployment has none, so no sidecar will start.");
3894
+ }
2653
3895
  if (result.entrypoints.length === 0 && !hasAssets && result.routes.length === 0) {
2654
3896
  result.errors.push(
2655
3897
  "The current project config does not resolve to any functions, assets or routes and can not be deployed."
@@ -2675,19 +3917,22 @@ var RUNTIME_DEFINITIONS = [
2675
3917
  var AVAILABLE_RUNTIMES = [...RUNTIME_DEFINITIONS];
2676
3918
 
2677
3919
  // src/services/index.ts
2678
- var import_effect23 = require("effect");
2679
- var BaseLayers = import_effect23.Layer.mergeAll(
3920
+ var import_effect25 = require("effect");
3921
+ var BaseLayers = import_effect25.Layer.mergeAll(
2680
3922
  ConfigModuleLoader.Default,
2681
3923
  RawConfigReader.Default,
2682
3924
  SchemaValidator.Default,
2683
3925
  V4ConfigParser.Default
2684
3926
  );
2685
- var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Layer.provideMerge(BaseLayers));
3927
+ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect25.Layer.provideMerge(BaseLayers));
2686
3928
  // Annotate the CommonJS export names for ESM import in node:
2687
3929
  0 && (module.exports = {
2688
3930
  ALLOWED_CONFIG_NAMES,
2689
3931
  AVAILABLE_REGIONS,
2690
3932
  AVAILABLE_RUNTIMES,
3933
+ COMPOSE_FILE_NAMES,
3934
+ CONTAINER_ENTRYPOINT_PREFIX,
3935
+ CONTAINER_RUNTIME,
2691
3936
  ConfigFileEmptyError,
2692
3937
  ConfigFileNotFoundError,
2693
3938
  ConfigFileParseError,
@@ -2695,7 +3940,10 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2695
3940
  ConfigModuleLoader,
2696
3941
  ConfigSchemaValidationError,
2697
3942
  ConfigVersionError,
3943
+ ContainerConfigError,
3944
+ DEFAULT_CONTAINER_MEMORY_MB,
2698
3945
  DEFAULT_FUNCTION_DURATION_SECONDS,
3946
+ DEFAULT_SIDECAR_MEMORY_MB,
2699
3947
  FRAMEWORK_DEFINITIONS,
2700
3948
  FrameworkNotDetectedError,
2701
3949
  FunctionConfigError,
@@ -2712,6 +3960,7 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2712
3960
  GigadriveNextServerDescriptorSchema,
2713
3961
  GigadriveNextStaticAssetPrefixSchema,
2714
3962
  MAX_FUNCTION_DURATION_SECONDS,
3963
+ MAX_SIDECARS,
2715
3964
  ManifestNotFoundError,
2716
3965
  ManifestParseError,
2717
3966
  ManifestReadError,
@@ -2724,19 +3973,26 @@ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(import_effect23.Lay
2724
3973
  V4ConfigParser,
2725
3974
  VercelBuildOutputParser,
2726
3975
  collectAssetFiles,
3976
+ composeDeclaresApp,
3977
+ containerEntrypointPath,
2727
3978
  defineConfig,
3979
+ detectContainerProject,
2728
3980
  detectFramework,
2729
3981
  detectPackageManager,
3982
+ dockerFunctionContainerName,
2730
3983
  filterFunctionsFromAssets,
2731
3984
  generateConfig,
2732
3985
  getFunctionSettings,
2733
3986
  mergeWithFrameworkDefaults,
3987
+ normalizeContainers,
2734
3988
  parseConfig,
2735
3989
  parseConfigRaw,
2736
3990
  parseGigadriveNextBuildManifest,
2737
3991
  parseGigadriveNextPrerenderManifest,
2738
3992
  parseStaticAssetManifest,
2739
3993
  postProcessConfig,
2740
- readDependencies
3994
+ readComposeContainers,
3995
+ readDependencies,
3996
+ splitCommandWords
2741
3997
  });
2742
3998
  //# sourceMappingURL=index.js.map