@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.mjs CHANGED
@@ -93,16 +93,824 @@ var collectAssetFiles = Effect.fn("collectAssetFiles")(function* (projectFolder,
93
93
  return files.map((file) => file.replaceAll("\\", "/")).filter((file) => !DISALLOWED_ASSET_EXTENSIONS.some((extension) => file.toLowerCase().endsWith(extension))).sort();
94
94
  });
95
95
 
96
+ // src/containers.ts
97
+ import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
98
+ import { Effect as Effect2, Option as Option2 } from "effect";
99
+ import { posix } from "path";
100
+ import { parse as parseYaml, YAMLError } from "yaml";
101
+
102
+ // src/errors.ts
103
+ import { Schema as Schema3 } from "effect";
104
+ var ConfigFileNotFoundError = class extends Schema3.TaggedError()("ConfigFileNotFoundError", {
105
+ message: Schema3.String,
106
+ filePath: Schema3.String
107
+ }) {
108
+ };
109
+ var ConfigFileEmptyError = class extends Schema3.TaggedError()("ConfigFileEmptyError", {
110
+ message: Schema3.String,
111
+ filePath: Schema3.String
112
+ }) {
113
+ };
114
+ var ConfigFileParseError = class extends Schema3.TaggedError()("ConfigFileParseError", {
115
+ message: Schema3.String,
116
+ filePath: Schema3.String,
117
+ cause: Schema3.optional(Schema3.String)
118
+ }) {
119
+ };
120
+ var ConfigModuleLoadError = class extends Schema3.TaggedError()("ConfigModuleLoadError", {
121
+ message: Schema3.String,
122
+ filePath: Schema3.String,
123
+ cause: Schema3.optional(Schema3.String)
124
+ }) {
125
+ };
126
+ var ConfigVersionError = class extends Schema3.TaggedError()("ConfigVersionError", {
127
+ message: Schema3.String,
128
+ filePath: Schema3.String,
129
+ version: Schema3.optional(Schema3.Number)
130
+ }) {
131
+ };
132
+ var ConfigSchemaValidationError = class extends Schema3.TaggedError()(
133
+ "ConfigSchemaValidationError",
134
+ {
135
+ message: Schema3.String,
136
+ filePath: Schema3.String,
137
+ validationErrors: Schema3.Array(Schema3.String)
138
+ }
139
+ ) {
140
+ };
141
+ var FunctionConfigError = class extends Schema3.TaggedError()("FunctionConfigError", {
142
+ message: Schema3.String,
143
+ functionPath: Schema3.String
144
+ }) {
145
+ };
146
+ var ContainerConfigError = class extends Schema3.TaggedError()("ContainerConfigError", {
147
+ message: Schema3.String,
148
+ containerName: Schema3.optional(Schema3.String),
149
+ filePath: Schema3.optional(Schema3.String)
150
+ }) {
151
+ };
152
+
153
+ // src/normalized-config.ts
154
+ var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
155
+ var MAX_FUNCTION_DURATION_SECONDS = 28800;
156
+ var CONTAINER_RUNTIME = "docker";
157
+ var CONTAINER_ENTRYPOINT_PREFIX = "container:";
158
+ var DEFAULT_CONTAINER_MEMORY_MB = 512;
159
+ var DEFAULT_SIDECAR_MEMORY_MB = 256;
160
+ var MAX_SIDECARS = 4;
161
+
162
+ // src/containers.ts
163
+ var COMPOSE_FILE_NAMES = ["compose.yaml", "compose.yml", "docker-compose.yaml", "docker-compose.yml"];
164
+ var CONTAINER_NAME_PATTERN = /^[a-z][a-z0-9_-]{0,62}$/;
165
+ var CONTAINER_NAME_RULES = "1 to 63 lowercase letters, digits, '-' or '_', starting with a letter, and not 'localhost'";
166
+ var MIN_CONTAINER_MEMORY_MB = 128;
167
+ var MAX_CONTAINER_MEMORY_MB = 3009;
168
+ var MAX_CONTAINER_ENV_VARS = 100;
169
+ var MAX_CONTAINER_ENV_VALUE_LENGTH = 65536;
170
+ var containerEntrypointPath = (name) => `${CONTAINER_ENTRYPOINT_PREFIX}${name}`;
171
+ var toContainerName = (raw) => {
172
+ const cleaned = raw.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z0-9]+/, "");
173
+ const name = (cleaned === "" || /^[a-z]/.test(cleaned) ? cleaned : `app-${cleaned}`).slice(0, 63);
174
+ return name === "" || name === "localhost" ? "app" : name;
175
+ };
176
+ var dockerFunctionContainerName = (file) => {
177
+ const { dir, base } = posix.parse(file);
178
+ const variant = base.replace(/^Dockerfile[.-]?/i, "").replace(/[.-]?Dockerfile$/i, "");
179
+ const raw = [posix.basename(dir), variant === base ? posix.parse(base).name : variant].filter((part) => part !== "").join("-");
180
+ return toContainerName(raw);
181
+ };
182
+ var splitCommandWords = (input) => {
183
+ const words = [];
184
+ let current = "";
185
+ let inWord = false;
186
+ let quote;
187
+ for (let index = 0; index < input.length; index += 1) {
188
+ const char = input[index];
189
+ if (quote === "'") {
190
+ if (char === "'") quote = void 0;
191
+ else current += char;
192
+ } else if (char === "\\" && index + 1 < input.length) {
193
+ index += 1;
194
+ current += input[index];
195
+ inWord = true;
196
+ } else if (quote === '"') {
197
+ if (char === '"') quote = void 0;
198
+ else current += char;
199
+ } else if (char === "'" || char === '"') {
200
+ quote = char;
201
+ inWord = true;
202
+ } else if (/\s/.test(char)) {
203
+ if (inWord) words.push(current);
204
+ current = "";
205
+ inWord = false;
206
+ } else {
207
+ current += char;
208
+ inWord = true;
209
+ }
210
+ }
211
+ if (quote) throw new Error(`Unterminated ${quote} quote`);
212
+ if (inWord) words.push(current);
213
+ return words;
214
+ };
215
+ var normalizeProjectPath = (input) => {
216
+ if (posix.isAbsolute(input)) return void 0;
217
+ const normalized = posix.normalize(input.replace(/\\/g, "/")).replace(/\/+$/, "") || ".";
218
+ if (normalized === ".." || normalized.startsWith("../")) return void 0;
219
+ return normalized;
220
+ };
221
+ var resolveProjectRoot = (projectFolder) => Effect2.flatMap(
222
+ FileSystem2.FileSystem,
223
+ (fs) => fs.realPath(projectFolder).pipe(Effect2.orElseSucceed(() => projectFolder))
224
+ );
225
+ var resolveInsideProject = (projectRoot, projectRelative, subject, context) => Effect2.gen(function* () {
226
+ const fs = yield* FileSystem2.FileSystem;
227
+ const pathSvc = yield* Path2.Path;
228
+ const resolved = yield* fs.realPath(pathSvc.join(projectRoot, projectRelative)).pipe(Effect2.option);
229
+ if (Option2.isNone(resolved)) return resolved;
230
+ const relative = pathSvc.relative(projectRoot, resolved.value);
231
+ if (relative === ".." || relative.startsWith(`..${pathSvc.sep}`) || pathSvc.isAbsolute(relative)) {
232
+ return yield* new ContainerConfigError({
233
+ message: `${subject} leads through a symbolic link to a location outside the project. Only files inside the project can be deployed.`,
234
+ ...context
235
+ });
236
+ }
237
+ return resolved;
238
+ });
239
+ var readInsideProject = (projectRoot, projectRelative, subject, context) => Effect2.gen(function* () {
240
+ const fs = yield* FileSystem2.FileSystem;
241
+ const resolved = yield* resolveInsideProject(projectRoot, projectRelative, subject, context);
242
+ if (Option2.isNone(resolved)) return Option2.none();
243
+ return yield* fs.readFileString(resolved.value).pipe(Effect2.option);
244
+ });
245
+ var toArgv = (name, key, value) => value === void 0 ? Effect2.succeed(void 0) : Array.isArray(value) ? Effect2.succeed(value) : Effect2.try({
246
+ try: () => splitCommandWords(value),
247
+ catch: (error) => new ContainerConfigError({
248
+ message: `Container '${name}' has an invalid ${key}: ${error instanceof Error ? error.message : String(error)}`,
249
+ containerName: name
250
+ })
251
+ });
252
+ var isIntegerBetween = (value, min, max) => Number.isInteger(value) && value >= min && value <= max;
253
+ var validateContainer = (name, container) => {
254
+ const build = typeof container.build === "object" ? container.build : void 0;
255
+ const buildArgs = Object.values(build?.args ?? {});
256
+ const env = Object.entries(container.env ?? {});
257
+ const user = container.user === void 0 ? void 0 : String(container.user);
258
+ const badEnvKey = env.find(([key]) => key === "" || key.includes("=") || key.includes("\0"))?.[0];
259
+ const longEnvKey = env.find(([, value]) => value.length > MAX_CONTAINER_ENV_VALUE_LENGTH)?.[0];
260
+ const problem = [
261
+ [
262
+ container.image !== void 0 && !/^\S{1,512}$/.test(container.image),
263
+ "has an invalid image reference. Use 1 to 512 characters without whitespace."
264
+ ],
265
+ [
266
+ container.port !== void 0 && !isIntegerBetween(container.port, 1, 65535),
267
+ `listens on port ${container.port}. Use a whole number from 1 to 65535.`
268
+ ],
269
+ [
270
+ container.memory !== void 0 && !isIntegerBetween(container.memory, MIN_CONTAINER_MEMORY_MB, MAX_CONTAINER_MEMORY_MB),
271
+ `asks for ${container.memory} MB of memory. Use a whole number from ${MIN_CONTAINER_MEMORY_MB} to ${MAX_CONTAINER_MEMORY_MB}.`
272
+ ],
273
+ [
274
+ container.working_dir !== void 0 && !(container.working_dir.startsWith("/") && container.working_dir.length <= 1024),
275
+ `sets working_dir '${container.working_dir}', which must be an absolute path of at most 1024 characters.`
276
+ ],
277
+ [
278
+ typeof container.user === "number" && !isIntegerBetween(container.user, 0, Number.MAX_SAFE_INTEGER),
279
+ `sets user ${container.user}. A numeric user must be a non-negative whole number.`
280
+ ],
281
+ [
282
+ user !== void 0 && (user.length === 0 || user.length > 256),
283
+ "sets an empty or overlong user. Use 1 to 256 characters: a name, a uid, name:group or uid:gid."
284
+ ],
285
+ [
286
+ build?.target !== void 0 && (build.target.length === 0 || build.target.length > 128),
287
+ "has an empty build target or one longer than 128 characters."
288
+ ],
289
+ [
290
+ buildArgs.length > MAX_CONTAINER_ENV_VARS || buildArgs.some((value) => value.length > MAX_CONTAINER_ENV_VALUE_LENGTH),
291
+ `sets more than ${MAX_CONTAINER_ENV_VARS} build args, or one longer than ${MAX_CONTAINER_ENV_VALUE_LENGTH} characters.`
292
+ ],
293
+ [
294
+ env.length > MAX_CONTAINER_ENV_VARS,
295
+ `sets ${env.length} environment variables. At most ${MAX_CONTAINER_ENV_VARS} are allowed.`
296
+ ],
297
+ [
298
+ badEnvKey !== void 0,
299
+ `sets an environment variable named '${badEnvKey}'. Names must be non-empty and contain no '='.`
300
+ ],
301
+ [
302
+ longEnvKey !== void 0,
303
+ `sets environment variable '${longEnvKey}' to a value longer than ${MAX_CONTAINER_ENV_VALUE_LENGTH} characters.`
304
+ ]
305
+ ].find(([failed]) => failed)?.[1];
306
+ return problem === void 0 ? Effect2.void : Effect2.fail(new ContainerConfigError({ message: `Container '${name}' ${problem}`, containerName: name }));
307
+ };
308
+ var toContainerSpec = Effect2.fn("toContainerSpec")(function* (name, container, projectFolder) {
309
+ yield* validateContainer(name, container);
310
+ let source;
311
+ if (container.image !== void 0 && container.build === void 0) {
312
+ source = { type: "registry", reference: container.image };
313
+ } else if (container.build !== void 0 && container.image === void 0) {
314
+ const build = typeof container.build === "string" ? { context: container.build } : container.build;
315
+ const dockerfile = build.dockerfile ?? "Dockerfile";
316
+ const context = normalizeProjectPath(build.context ?? ".");
317
+ const dockerfilePath = context === void 0 || posix.isAbsolute(dockerfile) ? void 0 : normalizeProjectPath(posix.join(context, dockerfile));
318
+ if (context === void 0 || dockerfilePath === void 0) {
319
+ return yield* new ContainerConfigError({
320
+ message: `Container '${name}' has a build context or Dockerfile outside the project.`,
321
+ containerName: name
322
+ });
323
+ }
324
+ const projectRoot = yield* resolveProjectRoot(projectFolder);
325
+ yield* resolveInsideProject(projectRoot, context, `The build context '${context}' of container '${name}'`, {
326
+ containerName: name
327
+ });
328
+ const dockerfileReal = yield* resolveInsideProject(
329
+ projectRoot,
330
+ dockerfilePath,
331
+ `The Dockerfile '${dockerfilePath}' of container '${name}'`,
332
+ { containerName: name }
333
+ );
334
+ if (Option2.isNone(dockerfileReal)) {
335
+ return yield* new ContainerConfigError({
336
+ message: `Container '${name}' builds '${dockerfilePath}', which does not exist.`,
337
+ containerName: name
338
+ });
339
+ }
340
+ source = {
341
+ type: "dockerfile",
342
+ context,
343
+ dockerfile: posix.relative(context, dockerfilePath),
344
+ ...build.target !== void 0 && { target: build.target },
345
+ ...build.args !== void 0 && Object.keys(build.args).length > 0 && { buildArgs: { ...build.args } }
346
+ };
347
+ } else {
348
+ return yield* new ContainerConfigError({
349
+ message: `Container '${name}' must set exactly one of 'image' and 'build'.`,
350
+ containerName: name
351
+ });
352
+ }
353
+ const entrypoint = yield* toArgv(name, "entrypoint", container.entrypoint);
354
+ const command = yield* toArgv(name, "command", container.command);
355
+ const spec = {
356
+ name,
357
+ source,
358
+ ...container.port !== void 0 && { port: container.port },
359
+ ...entrypoint !== void 0 && { entrypoint },
360
+ ...command !== void 0 && { command },
361
+ ...container.working_dir !== void 0 && { workingDirectory: container.working_dir },
362
+ ...container.user !== void 0 && { user: String(container.user) },
363
+ ...container.env !== void 0 && Object.keys(container.env).length > 0 && { environmentVariables: { ...container.env } }
364
+ };
365
+ return spec;
366
+ });
367
+ var normalizeContainers = Effect2.fn("normalizeContainers")(function* (containers, projectFolder) {
368
+ const entrypoints = [];
369
+ const sidecars = [];
370
+ const warnings = [];
371
+ for (const [name, container] of Object.entries(containers)) {
372
+ if (!CONTAINER_NAME_PATTERN.test(name) || name === "localhost") {
373
+ return yield* new ContainerConfigError({
374
+ 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)}'.`,
375
+ containerName: name
376
+ });
377
+ }
378
+ const spec = yield* toContainerSpec(name, container, projectFolder);
379
+ if (container.sidecar === true) {
380
+ const ignored = ["max_duration", "streaming", "schedule"].filter(
381
+ (key) => container[key] !== void 0
382
+ );
383
+ if (ignored.length > 0) {
384
+ warnings.push(
385
+ `Sidecar '${name}' ignores ${ignored.join(", ")}: those settings apply to container functions only.`
386
+ );
387
+ }
388
+ sidecars.push({ ...spec, memory: container.memory ?? DEFAULT_SIDECAR_MEMORY_MB });
389
+ continue;
390
+ }
391
+ entrypoints.push({
392
+ path: containerEntrypointPath(name),
393
+ displayName: name,
394
+ runtime: CONTAINER_RUNTIME,
395
+ memory: container.memory ?? DEFAULT_CONTAINER_MEMORY_MB,
396
+ maxDuration: container.max_duration ?? DEFAULT_FUNCTION_DURATION_SECONDS,
397
+ streaming: container.streaming ?? true,
398
+ ...container.schedule !== void 0 && { schedule: container.schedule },
399
+ container: spec
400
+ });
401
+ }
402
+ if (sidecars.length > MAX_SIDECARS) {
403
+ return yield* new ContainerConfigError({
404
+ message: `A deployment may declare at most ${MAX_SIDECARS} sidecars; this one declares ${sidecars.length}.`
405
+ });
406
+ }
407
+ const sidecarPorts = /* @__PURE__ */ new Map();
408
+ for (const sidecar of sidecars) {
409
+ if (sidecar.port === void 0) continue;
410
+ const owner = sidecarPorts.get(sidecar.port);
411
+ if (owner !== void 0) {
412
+ return yield* new ContainerConfigError({
413
+ message: `Sidecars '${owner}' and '${sidecar.name}' both listen on port ${sidecar.port}. Sidecars share one network namespace, so their ports must differ.`,
414
+ containerName: sidecar.name
415
+ });
416
+ }
417
+ sidecarPorts.set(sidecar.port, sidecar.name);
418
+ }
419
+ for (const entrypoint of entrypoints) {
420
+ const port = entrypoint.container?.port;
421
+ const sidecar = port === void 0 ? void 0 : sidecarPorts.get(port);
422
+ if (sidecar !== void 0) {
423
+ return yield* new ContainerConfigError({
424
+ message: `Container '${entrypoint.displayName}' and sidecar '${sidecar}' both listen on port ${port}. Sidecars run inside the function's microVM, so their ports must differ.`,
425
+ containerName: sidecar
426
+ });
427
+ }
428
+ }
429
+ return { entrypoints, sidecars, warnings };
430
+ });
431
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
432
+ var isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
433
+ var scalarToString = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : void 0;
434
+ var gigadriveExtension = (service) => isRecord(service["x-gigadrive"]) ? service["x-gigadrive"] : {};
435
+ var COMPOSE_OVERRIDE_FILE_NAMES = [
436
+ "compose.override.yaml",
437
+ "compose.override.yml",
438
+ "docker-compose.override.yaml",
439
+ "docker-compose.override.yml"
440
+ ];
441
+ var findClosingQuote = (body, quote) => {
442
+ for (let index = 0; index < body.length; index += 1) {
443
+ if (quote === '"' && body[index] === "\\") index += 1;
444
+ else if (body[index] === quote) return index;
445
+ }
446
+ return -1;
447
+ };
448
+ var DOUBLE_QUOTE_ESCAPES = { n: "\n", r: "\r", t: " " };
449
+ var parseDotEnv = (content) => {
450
+ const values = {};
451
+ const lines = content.split(/\r?\n/);
452
+ for (let index = 0; index < lines.length; index += 1) {
453
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=(.*)$/.exec(lines[index]);
454
+ if (!match) continue;
455
+ const [, key, rest] = match;
456
+ const quote = rest.trimStart()[0];
457
+ if (quote === '"' || quote === "'") {
458
+ let body = rest.trimStart().slice(1);
459
+ let end = index;
460
+ let close = findClosingQuote(body, quote);
461
+ while (close === -1 && end + 1 < lines.length) {
462
+ end += 1;
463
+ body += `
464
+ ${lines[end]}`;
465
+ close = findClosingQuote(body, quote);
466
+ }
467
+ if (close !== -1) {
468
+ const raw = body.slice(0, close);
469
+ values[key] = quote === '"' ? raw.replace(/\\([nrt"\\])/g, (_match, char) => DOUBLE_QUOTE_ESCAPES[char] ?? char) : raw;
470
+ index = end;
471
+ continue;
472
+ }
473
+ }
474
+ values[key] = rest.replace(/\s+#.*$/, "").trim();
475
+ }
476
+ return values;
477
+ };
478
+ var InterpolationError = class extends Error {
479
+ };
480
+ var findClosingBrace = (value, start) => {
481
+ let depth = 0;
482
+ for (let index = start; index < value.length; index += 1) {
483
+ if (value[index] === "$" && (value[index + 1] === "$" || value[index + 1] === "{")) {
484
+ if (value[index + 1] === "{") depth += 1;
485
+ index += 1;
486
+ } else if (value[index] === "}") {
487
+ if (depth === 0) return index;
488
+ depth -= 1;
489
+ }
490
+ }
491
+ return -1;
492
+ };
493
+ var interpolate = (value, variables, missing, where) => {
494
+ let result = "";
495
+ let index = 0;
496
+ while (index < value.length) {
497
+ const dollar = value.indexOf("$", index);
498
+ if (dollar === -1) {
499
+ result += value.slice(index);
500
+ break;
501
+ }
502
+ result += value.slice(index, dollar);
503
+ const next = value[dollar + 1];
504
+ if (next === "$") {
505
+ result += "$";
506
+ index = dollar + 2;
507
+ continue;
508
+ }
509
+ if (next === "{") {
510
+ const close = findClosingBrace(value, dollar + 2);
511
+ const expression = close === -1 ? null : /^([A-Za-z_][A-Za-z0-9_]*)(?:(:?[-?+])([\s\S]*))?$/.exec(value.slice(dollar + 2, close));
512
+ if (expression === null) {
513
+ throw new InterpolationError(`has an invalid variable reference in ${where}. Write '$$' for a literal '$'.`);
514
+ }
515
+ const [, name, operator, operand = ""] = expression;
516
+ const current = variables[name];
517
+ const isSet = current !== void 0 && !(operator?.startsWith(":") === true && current === "");
518
+ if (operator === void 0) {
519
+ if (current === void 0) missing.add(name);
520
+ result += current ?? "";
521
+ } else if (operator.endsWith("-")) {
522
+ result += isSet ? current : interpolate(operand, variables, missing, where);
523
+ } else if (operator.endsWith("+")) {
524
+ result += isSet ? interpolate(operand, variables, missing, where) : "";
525
+ } else if (isSet) {
526
+ result += current;
527
+ } else {
528
+ const reason = interpolate(operand, variables, missing, where);
529
+ throw new InterpolationError(
530
+ `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.`
531
+ );
532
+ }
533
+ index = close + 1;
534
+ continue;
535
+ }
536
+ const bare = /^[A-Za-z_][A-Za-z0-9_]*/.exec(value.slice(dollar + 1))?.[0];
537
+ if (bare === void 0) {
538
+ result += "$";
539
+ index = dollar + 1;
540
+ continue;
541
+ }
542
+ if (variables[bare] === void 0) missing.add(bare);
543
+ result += variables[bare] ?? "";
544
+ index = dollar + 1 + bare.length;
545
+ }
546
+ return result;
547
+ };
548
+ var interpolateDeep = (value, variables, missing, where) => {
549
+ if (typeof value === "string") return interpolate(value, variables, missing, where);
550
+ if (Array.isArray(value)) {
551
+ return value.map((item, index) => interpolateDeep(item, variables, missing, `${where}[${index}]`));
552
+ }
553
+ if (isRecord(value)) {
554
+ return Object.fromEntries(
555
+ Object.entries(value).map(([key, item]) => [
556
+ key,
557
+ interpolateDeep(item, variables, missing, where === "" ? key : `${where}.${key}`)
558
+ ])
559
+ );
560
+ }
561
+ return value;
562
+ };
563
+ var parseComposeMemory = (value) => {
564
+ if (typeof value === "number") {
565
+ return Number.isFinite(value) && value >= 0 ? Math.ceil(value / 1024 ** 2) : void 0;
566
+ }
567
+ if (typeof value !== "string") return void 0;
568
+ const match = /^(\d+(?:\.\d+)?) ?([kmgtp])?i?b?$/i.exec(value.trim());
569
+ if (!match) return void 0;
570
+ const exponent = " kmgtp".indexOf((match[2] ?? " ").toLowerCase());
571
+ return Math.ceil(Number(match[1]) * 1024 ** exponent / 1024 ** 2);
572
+ };
573
+ var parseComposePort = (entry) => {
574
+ let port = entry;
575
+ if (isRecord(entry)) {
576
+ if (typeof entry.protocol === "string" && entry.protocol.toLowerCase() !== "tcp") return void 0;
577
+ port = entry.target;
578
+ } else if (typeof entry === "string") {
579
+ const [withoutProtocol, protocol] = entry.split("/");
580
+ if (protocol !== void 0 && protocol.toLowerCase() !== "tcp") return void 0;
581
+ port = (withoutProtocol.split(":").at(-1) ?? "").split("-")[0];
582
+ }
583
+ const number = typeof port === "number" ? port : typeof port === "string" && /^\d+$/.test(port) ? Number(port) : NaN;
584
+ return isIntegerBetween(number, 1, 65535) ? number : void 0;
585
+ };
586
+ var parseComposeEnvironment = (serviceName, value, warnings) => {
587
+ if (value === void 0 || value === null) return void 0;
588
+ const environment = {};
589
+ if (Array.isArray(value)) {
590
+ for (const item of value) {
591
+ if (typeof item !== "string") continue;
592
+ const separator = item.indexOf("=");
593
+ if (separator === -1) {
594
+ warnings.push(
595
+ `Compose service '${serviceName}' takes '${item}' from the host environment, which deployments do not have. Set it in the console instead.`
596
+ );
597
+ continue;
598
+ }
599
+ environment[item.slice(0, separator)] = item.slice(separator + 1);
600
+ }
601
+ } else if (isRecord(value)) {
602
+ for (const [key, item] of Object.entries(value)) {
603
+ const scalar = scalarToString(item);
604
+ if (scalar === void 0) {
605
+ warnings.push(
606
+ `Compose service '${serviceName}' takes '${key}' from the host environment, which deployments do not have. Set it in the console instead.`
607
+ );
608
+ continue;
609
+ }
610
+ environment[key] = scalar;
611
+ }
612
+ }
613
+ return environment;
614
+ };
615
+ var isPublicMarked = (service) => gigadriveExtension(service).public === true;
616
+ var buildsFromSource = (service) => service.build !== void 0 && gigadriveExtension(service).sidecar !== true;
617
+ var loadComposeServices = Effect2.fn("loadComposeServices")(function* (composePath, projectFolder) {
618
+ const relativePath = normalizeProjectPath(composePath);
619
+ if (relativePath === void 0) {
620
+ return yield* new ContainerConfigError({
621
+ message: `Compose file '${composePath}' is outside the project.`,
622
+ filePath: composePath
623
+ });
624
+ }
625
+ const projectRoot = yield* resolveProjectRoot(projectFolder);
626
+ const content = yield* readInsideProject(projectRoot, relativePath, `Compose file '${relativePath}'`, {
627
+ filePath: relativePath
628
+ });
629
+ if (Option2.isNone(content)) {
630
+ return yield* new ContainerConfigError({
631
+ message: `Compose file '${relativePath}' could not be read.`,
632
+ filePath: relativePath
633
+ });
634
+ }
635
+ const document = yield* Effect2.try({
636
+ try: () => parseYaml(content.value),
637
+ catch: (error) => {
638
+ const position = error instanceof YAMLError ? error.linePos?.[0] : void 0;
639
+ return new ContainerConfigError({
640
+ message: `Compose file '${relativePath}' is not valid YAML${position ? ` (line ${position.line}, column ${position.col})` : ""}.`,
641
+ filePath: relativePath
642
+ });
643
+ }
644
+ });
645
+ if (!isRecord(document) || !isRecord(document.services) || Object.keys(document.services).length === 0) {
646
+ return yield* new ContainerConfigError({
647
+ message: `Compose file '${relativePath}' declares no services.`,
648
+ filePath: relativePath
649
+ });
650
+ }
651
+ const services = Object.entries(document.services).filter(
652
+ (entry) => isRecord(entry[1]) && !(isStringArray(entry[1].profiles) && entry[1].profiles.length > 0) && gigadriveExtension(entry[1]).skip !== true
653
+ );
654
+ return { relativePath, projectRoot, services };
655
+ });
656
+ var composeDeclaresApp = Effect2.fn("composeDeclaresApp")(function* (composePath, projectFolder) {
657
+ const { services } = yield* loadComposeServices(composePath, projectFolder);
658
+ return services.some(([, service]) => isPublicMarked(service) || buildsFromSource(service));
659
+ });
660
+ var readComposeContainers = Effect2.fn("readComposeContainers")(function* (composePath, projectFolder) {
661
+ const fs = yield* FileSystem2.FileSystem;
662
+ const pathSvc = yield* Path2.Path;
663
+ const { relativePath, projectRoot, services } = yield* loadComposeServices(composePath, projectFolder);
664
+ const composeDirectory = posix.dirname(relativePath);
665
+ const warnings = [];
666
+ for (const [name] of services) {
667
+ if (!CONTAINER_NAME_PATTERN.test(name) || name === "localhost") {
668
+ return yield* new ContainerConfigError({
669
+ 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}'.`,
670
+ containerName: name,
671
+ filePath: relativePath
672
+ });
673
+ }
674
+ }
675
+ const marked = services.filter(([, service]) => isPublicMarked(service)).map(([name]) => name);
676
+ const built = services.filter(([, service]) => buildsFromSource(service)).map(([name]) => name);
677
+ if (marked.length > 1) {
678
+ return yield* new ContainerConfigError({
679
+ 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.`,
680
+ filePath: relativePath
681
+ });
682
+ }
683
+ const publicService = marked[0] ?? (built.length === 1 ? built[0] : void 0);
684
+ if (publicService === void 0) {
685
+ return yield* new ContainerConfigError({
686
+ 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 }'.`,
687
+ filePath: relativePath
688
+ });
689
+ }
690
+ if (COMPOSE_FILE_NAMES.includes(posix.basename(relativePath))) {
691
+ for (const overrideName of COMPOSE_OVERRIDE_FILE_NAMES) {
692
+ const overridePath = posix.join(composeDirectory, overrideName);
693
+ const overrideExists = yield* fs.exists(pathSvc.join(projectRoot, overridePath)).pipe(Effect2.orElseSucceed(() => false));
694
+ if (overrideExists) {
695
+ warnings.push(
696
+ `Compose file '${relativePath}' has the override file '${overridePath}' next to it, which is not merged. Move its settings into '${relativePath}' or gigadrive.yaml.`
697
+ );
698
+ }
699
+ }
700
+ }
701
+ const dotEnv = yield* readInsideProject(
702
+ projectRoot,
703
+ posix.join(composeDirectory, ".env"),
704
+ `The .env file next to '${relativePath}'`,
705
+ { filePath: relativePath }
706
+ );
707
+ const variables = Option2.isSome(dotEnv) ? parseDotEnv(dotEnv.value) : {};
708
+ const missing = /* @__PURE__ */ new Set();
709
+ const containers = {};
710
+ for (const [name, rawService] of services) {
711
+ const isPublic = name === publicService;
712
+ const service = yield* Effect2.try({
713
+ try: () => interpolateDeep(rawService, variables, missing, ""),
714
+ catch: (error) => new ContainerConfigError({
715
+ message: `Compose service '${name}' ${error instanceof InterpolationError ? error.message : "could not be interpolated."}`,
716
+ containerName: name,
717
+ filePath: relativePath
718
+ })
719
+ });
720
+ const container = {};
721
+ if (service.extends !== void 0) {
722
+ warnings.push(
723
+ `Compose service '${name}' uses 'extends', which is not supported. The service is deployed as written, without the settings it would inherit.`
724
+ );
725
+ }
726
+ if (typeof service.image === "string" && service.build === void 0) {
727
+ container.image = service.image;
728
+ } else if (typeof service.build === "string" || isRecord(service.build)) {
729
+ const build = typeof service.build === "string" ? { context: service.build } : service.build;
730
+ if (build.dockerfile_inline !== void 0) {
731
+ return yield* new ContainerConfigError({
732
+ message: `Compose service '${name}' uses build.dockerfile_inline, which is not supported. Commit the Dockerfile and point build.dockerfile at it.`,
733
+ containerName: name,
734
+ filePath: relativePath
735
+ });
736
+ }
737
+ const rawContext = typeof build.context === "string" ? build.context : ".";
738
+ const context = posix.isAbsolute(rawContext) ? rawContext : posix.join(composeDirectory, rawContext);
739
+ const args = Array.isArray(build.args) ? Object.fromEntries(
740
+ build.args.filter((item) => typeof item === "string" && item.includes("=")).map((item) => [item.slice(0, item.indexOf("=")), item.slice(item.indexOf("=") + 1)])
741
+ ) : isRecord(build.args) ? Object.fromEntries(Object.entries(build.args).map(([key, item]) => [key, scalarToString(item) ?? ""])) : void 0;
742
+ container.build = {
743
+ context,
744
+ ...typeof build.dockerfile === "string" && { dockerfile: build.dockerfile },
745
+ ...typeof build.target === "string" && { target: build.target },
746
+ ...args !== void 0 && Object.keys(args).length > 0 && { args }
747
+ };
748
+ } else {
749
+ return yield* new ContainerConfigError({
750
+ message: `Compose service '${name}' has neither an image nor a build.`,
751
+ containerName: name,
752
+ filePath: relativePath
753
+ });
754
+ }
755
+ if (!isPublic) container.sidecar = true;
756
+ const ports = [
757
+ ...isPublic && Array.isArray(service.ports) ? service.ports : [],
758
+ ...Array.isArray(service.expose) ? service.expose : [],
759
+ ...!isPublic && Array.isArray(service.ports) ? service.ports : []
760
+ ];
761
+ const port = ports.map(parseComposePort).find((value) => value !== void 0);
762
+ if (port !== void 0) container.port = port;
763
+ for (const key of ["entrypoint", "command"]) {
764
+ const value = service[key];
765
+ if (typeof value === "string" || isStringArray(value)) container[key] = value;
766
+ }
767
+ const environment = {};
768
+ const envFiles = (typeof service.env_file === "string" ? [service.env_file] : Array.isArray(service.env_file) ? service.env_file : []).flatMap(
769
+ (entry) => typeof entry === "string" ? [{ path: entry }] : isRecord(entry) && typeof entry.path === "string" ? [{ path: entry.path, ...typeof entry.required === "boolean" && { required: entry.required } }] : []
770
+ );
771
+ for (const envFile of envFiles) {
772
+ const envFilePath = posix.isAbsolute(envFile.path) ? void 0 : normalizeProjectPath(posix.join(composeDirectory, envFile.path));
773
+ const envContent = envFilePath === void 0 ? Option2.none() : yield* readInsideProject(
774
+ projectRoot,
775
+ envFilePath,
776
+ `The env_file '${envFile.path}' of Compose service '${name}'`,
777
+ { containerName: name, filePath: relativePath }
778
+ );
779
+ if (Option2.isSome(envContent)) {
780
+ Object.assign(environment, parseDotEnv(envContent.value));
781
+ } else if (envFile.required === true) {
782
+ return yield* new ContainerConfigError({
783
+ 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.`,
784
+ containerName: name,
785
+ filePath: relativePath
786
+ });
787
+ } else if (envFile.required === void 0) {
788
+ warnings.push(
789
+ `Compose service '${name}' reads env_file '${envFile.path}', which is not in the deployed source. Set those variables in the console instead.`
790
+ );
791
+ }
792
+ }
793
+ Object.assign(environment, parseComposeEnvironment(name, service.environment, warnings));
794
+ if (Object.keys(environment).length > 0) container.env = environment;
795
+ if (typeof service.working_dir === "string") container.working_dir = service.working_dir;
796
+ if (typeof service.user === "string" || typeof service.user === "number") container.user = String(service.user);
797
+ const deploy = isRecord(service.deploy) ? service.deploy : {};
798
+ const resources = isRecord(deploy.resources) ? deploy.resources : {};
799
+ const limits = isRecord(resources.limits) ? resources.limits : {};
800
+ const memoryLimit = limits.memory ?? service.mem_limit;
801
+ if (memoryLimit !== void 0 && memoryLimit !== null) {
802
+ const memory = parseComposeMemory(memoryLimit);
803
+ if (memory === void 0) {
804
+ warnings.push(
805
+ `Compose service '${name}' sets the memory limit ${JSON.stringify(memoryLimit)}, which is not a size Docker accepts. The default is used instead.`
806
+ );
807
+ } else if (memory >= MIN_CONTAINER_MEMORY_MB && memory <= MAX_CONTAINER_MEMORY_MB) {
808
+ container.memory = memory;
809
+ } else if (memory > 0) {
810
+ warnings.push(
811
+ `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.`
812
+ );
813
+ }
814
+ }
815
+ if (service.volumes !== void 0) {
816
+ warnings.push(
817
+ `Compose service '${name}' declares volumes. Containers on Gigadrive Network have no persistent volumes, so anything written there is lost when the instance stops.`
818
+ );
819
+ }
820
+ containers[name] = container;
821
+ }
822
+ if (missing.size > 0) {
823
+ warnings.push(
824
+ `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.`
825
+ );
826
+ }
827
+ return { containers, publicService, warnings };
828
+ });
829
+
96
830
  // src/define-config.ts
97
831
  var defineConfig = (config) => config;
98
832
 
833
+ // src/detection/detect-container-project.ts
834
+ import { FileSystem as FileSystem3, Path as Path3 } from "@effect/platform";
835
+ import { Effect as Effect3 } from "effect";
836
+
837
+ // src/regions.ts
838
+ var REGION_DEFINITIONS = [
839
+ "us-east-1",
840
+ "us-east-2",
841
+ "us-west-1",
842
+ "us-west-2",
843
+ "ap-south-1",
844
+ "ap-northeast-3",
845
+ "ap-northeast-2",
846
+ "ap-southeast-1",
847
+ "ap-southeast-2",
848
+ "ap-northeast-1",
849
+ "ca-central-1",
850
+ "eu-central-1",
851
+ "eu-west-1",
852
+ "eu-west-2",
853
+ "eu-west-3",
854
+ "eu-north-1",
855
+ "sa-east-1"
856
+ ];
857
+ var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
858
+
859
+ // src/detection/detect-container-project.ts
860
+ var detectContainerProject = Effect3.fn("detectContainerProject")(function* (projectFolder) {
861
+ const fs = yield* FileSystem3.FileSystem;
862
+ const pathSvc = yield* Path3.Path;
863
+ const exists = (file) => fs.exists(pathSvc.join(projectFolder, file)).pipe(Effect3.catchAll(() => Effect3.succeed(false)));
864
+ let detection;
865
+ const warnings = [];
866
+ const composeFile = yield* Effect3.findFirst(COMPOSE_FILE_NAMES, exists);
867
+ if (composeFile._tag === "Some" && (yield* composeDeclaresApp(composeFile.value, projectFolder))) {
868
+ const imported = yield* readComposeContainers(composeFile.value, projectFolder);
869
+ warnings.push(...imported.warnings);
870
+ detection = { kind: "compose", file: composeFile.value, containers: imported.containers };
871
+ } else if (yield* exists("Dockerfile")) {
872
+ detection = { kind: "dockerfile", file: "Dockerfile", containers: { app: { build: "." } } };
873
+ } else {
874
+ return void 0;
875
+ }
876
+ const {
877
+ entrypoints,
878
+ sidecars,
879
+ warnings: containerWarnings
880
+ } = yield* normalizeContainers(detection.containers, projectFolder);
881
+ const [publicContainer] = entrypoints;
882
+ const config = {
883
+ regions: [...AVAILABLE_REGIONS],
884
+ environmentVariables: {},
885
+ commands: [],
886
+ entrypoints,
887
+ ...sidecars.length > 0 && { sidecars },
888
+ routes: publicContainer ? [
889
+ {
890
+ path: "/*",
891
+ destination: publicContainer.path,
892
+ handler: "SERVERLESS_FUNCTION_STREAMING",
893
+ methods: ["ANY"],
894
+ headers: {}
895
+ }
896
+ ] : [],
897
+ warnings: [
898
+ `Auto-detected ${detection.kind === "compose" ? "Docker Compose" : "Dockerfile"} project (${detection.file}). Create a gigadrive.yaml to customize.`,
899
+ ...warnings,
900
+ ...containerWarnings
901
+ ],
902
+ errors: []
903
+ };
904
+ return { kind: detection.kind, file: detection.file, config };
905
+ });
906
+
99
907
  // src/detection/detect-framework.ts
100
- import { FileSystem as FileSystem9, Path as Path7 } from "@effect/platform";
101
- import { Effect as Effect11 } from "effect";
908
+ import { FileSystem as FileSystem11, Path as Path9 } from "@effect/platform";
909
+ import { Effect as Effect13 } from "effect";
102
910
 
103
911
  // src/detection/detect-package-manager.ts
104
- import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
105
- import { Effect as Effect2 } from "effect";
912
+ import { FileSystem as FileSystem4, Path as Path4 } from "@effect/platform";
913
+ import { Effect as Effect4 } from "effect";
106
914
  var LOCKFILE_MAP = [
107
915
  { files: ["bun.lockb", "bun.lock"], pm: "bun" },
108
916
  { files: ["pnpm-lock.yaml"], pm: "pnpm" },
@@ -110,19 +918,19 @@ var LOCKFILE_MAP = [
110
918
  { files: ["package-lock.json"], pm: "npm" },
111
919
  { files: ["composer.lock"], pm: "composer" }
112
920
  ];
113
- var detectPackageManager = Effect2.fn("detectPackageManager")(function* (projectFolder) {
114
- const fs = yield* FileSystem2.FileSystem;
115
- const pathService = yield* Path2.Path;
921
+ var detectPackageManager = Effect4.fn("detectPackageManager")(function* (projectFolder) {
922
+ const fs = yield* FileSystem4.FileSystem;
923
+ const pathService = yield* Path4.Path;
116
924
  for (const { files, pm } of LOCKFILE_MAP) {
117
925
  for (const file of files) {
118
- const exists = yield* fs.exists(pathService.join(projectFolder, file)).pipe(Effect2.catchAll(() => Effect2.succeed(false)));
926
+ const exists = yield* fs.exists(pathService.join(projectFolder, file)).pipe(Effect4.catchAll(() => Effect4.succeed(false)));
119
927
  if (exists) {
120
- yield* Effect2.logDebug(`Detected package manager: ${pm} (via ${file})`);
928
+ yield* Effect4.logDebug(`Detected package manager: ${pm} (via ${file})`);
121
929
  return pm;
122
930
  }
123
931
  }
124
932
  }
125
- yield* Effect2.logDebug("No lockfile found, defaulting to npm");
933
+ yield* Effect4.logDebug("No lockfile found, defaulting to npm");
126
934
  return "npm";
127
935
  });
128
936
 
@@ -148,8 +956,8 @@ var astro = {
148
956
  };
149
957
 
150
958
  // src/detection/frameworks/resolve-node-entrypoint.ts
151
- import { FileSystem as FileSystem3, Path as Path3 } from "@effect/platform";
152
- import { Effect as Effect3 } from "effect";
959
+ import { FileSystem as FileSystem5, Path as Path5 } from "@effect/platform";
960
+ import { Effect as Effect5 } from "effect";
153
961
  var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "tsx", "ts-node"]);
154
962
  var COMMON_ENTRYPOINT_CANDIDATES = [
155
963
  "index.js",
@@ -165,11 +973,11 @@ var COMMON_ENTRYPOINT_CANDIDATES = [
165
973
  "server.ts",
166
974
  "src/server.ts"
167
975
  ];
168
- var isRecord = (value) => typeof value === "object" && value !== null;
976
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
169
977
  var parsePackageJson = (content) => {
170
978
  try {
171
979
  const parsed = JSON.parse(content);
172
- if (!isRecord(parsed)) return void 0;
980
+ if (!isRecord2(parsed)) return void 0;
173
981
  return parsed;
174
982
  } catch {
175
983
  return void 0;
@@ -197,14 +1005,14 @@ var applyEntrypoint = (defaults, entrypoint) => ({
197
1005
  (route) => route.destination === defaults.entrypoint ? { ...route, destination: entrypoint } : route
198
1006
  )
199
1007
  });
200
- var resolveNodeServerEntrypoint = (defaults, projectFolder) => Effect3.gen(function* () {
201
- const fs = yield* FileSystem3.FileSystem;
202
- const pathService = yield* Path3.Path;
1008
+ var resolveNodeServerEntrypoint = (defaults, projectFolder) => Effect5.gen(function* () {
1009
+ const fs = yield* FileSystem5.FileSystem;
1010
+ const pathService = yield* Path5.Path;
203
1011
  const isFile = (relativePath) => fs.stat(pathService.join(projectFolder, relativePath)).pipe(
204
- Effect3.map((info) => info.type === "File"),
205
- Effect3.catchAll(() => Effect3.succeed(false))
1012
+ Effect5.map((info) => info.type === "File"),
1013
+ Effect5.catchAll(() => Effect5.succeed(false))
206
1014
  );
207
- const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(Effect3.catchAll(() => Effect3.succeed(void 0)));
1015
+ const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(Effect5.catchAll(() => Effect5.succeed(void 0)));
208
1016
  const manifest = content ? parsePackageJson(content) : void 0;
209
1017
  const startScript = typeof manifest?.scripts?.start === "string" ? manifest.scripts.start : void 0;
210
1018
  const candidates = [
@@ -336,13 +1144,13 @@ var laravel = {
336
1144
  };
337
1145
 
338
1146
  // src/detection/frameworks/nestjs.ts
339
- import { FileSystem as FileSystem4 } from "@effect/platform";
340
- import { Effect as Effect4 } from "effect";
341
- var isRecord2 = (value) => typeof value === "object" && value !== null;
1147
+ import { FileSystem as FileSystem6 } from "@effect/platform";
1148
+ import { Effect as Effect6 } from "effect";
1149
+ var isRecord3 = (value) => typeof value === "object" && value !== null;
342
1150
  var parseNestCliJson = (content) => {
343
1151
  try {
344
1152
  const parsed = JSON.parse(content);
345
- if (!isRecord2(parsed)) return void 0;
1153
+ if (!isRecord3(parsed)) return void 0;
346
1154
  return parsed;
347
1155
  } catch {
348
1156
  return void 0;
@@ -388,9 +1196,9 @@ var nestjs = {
388
1196
  detectors: [{ matchPackage: "@nestjs/core" }],
389
1197
  priority: 90,
390
1198
  getDefaultConfig: () => createNestConfig("dist"),
391
- refineDefaultConfig: (defaults, projectFolder) => Effect4.gen(function* () {
392
- const fs = yield* FileSystem4.FileSystem;
393
- const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(Effect4.catchAll(() => Effect4.succeed(void 0)));
1199
+ refineDefaultConfig: (defaults, projectFolder) => Effect6.gen(function* () {
1200
+ const fs = yield* FileSystem6.FileSystem;
1201
+ const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(Effect6.catchAll(() => Effect6.succeed(void 0)));
394
1202
  if (!content) return defaults;
395
1203
  const nestConfig = parseNestCliJson(content);
396
1204
  const outputPath = nestConfig ? getProjectOutputPath(nestConfig) : void 0;
@@ -399,28 +1207,28 @@ var nestjs = {
399
1207
  };
400
1208
 
401
1209
  // src/detection/frameworks/nextjs.ts
402
- import { FileSystem as FileSystem6, Path as Path5 } from "@effect/platform";
403
- import { Effect as Effect6 } from "effect";
1210
+ import { FileSystem as FileSystem8, Path as Path7 } from "@effect/platform";
1211
+ import { Effect as Effect8 } from "effect";
404
1212
 
405
1213
  // src/build-output-v3/get-default-path-map.ts
406
- import { FileSystem as FileSystem5, Path as Path4 } from "@effect/platform";
407
- import { Effect as Effect5 } from "effect";
408
- var getDefaultPathMap = Effect5.fn("getDefaultPathMap")(function* (directory) {
409
- const fs = yield* FileSystem5.FileSystem;
410
- const pathService = yield* Path4.Path;
1214
+ import { FileSystem as FileSystem7, Path as Path6 } from "@effect/platform";
1215
+ import { Effect as Effect7 } from "effect";
1216
+ var getDefaultPathMap = Effect7.fn("getDefaultPathMap")(function* (directory) {
1217
+ const fs = yield* FileSystem7.FileSystem;
1218
+ const pathService = yield* Path6.Path;
411
1219
  const result = {};
412
- const exists = yield* fs.exists(directory).pipe(Effect5.catchAll(() => Effect5.succeed(false)));
1220
+ const exists = yield* fs.exists(directory).pipe(Effect7.catchAll(() => Effect7.succeed(false)));
413
1221
  if (!exists) {
414
1222
  return result;
415
1223
  }
416
1224
  const MAX_DEPTH = 100;
417
- const processDirectory = (currentPath, basePath = "", depth = 0) => Effect5.gen(function* () {
1225
+ const processDirectory = (currentPath, basePath = "", depth = 0) => Effect7.gen(function* () {
418
1226
  if (depth > MAX_DEPTH) return;
419
- const entries = yield* fs.readDirectory(currentPath).pipe(Effect5.catchAll(() => Effect5.succeed([])));
1227
+ const entries = yield* fs.readDirectory(currentPath).pipe(Effect7.catchAll(() => Effect7.succeed([])));
420
1228
  for (const name of entries) {
421
1229
  const fullPath = pathService.join(currentPath, name);
422
1230
  const relativePath = basePath ? pathService.join(basePath, name) : name;
423
- const stat = yield* fs.stat(fullPath).pipe(Effect5.catchAll(() => Effect5.succeed(null)));
1231
+ const stat = yield* fs.stat(fullPath).pipe(Effect7.catchAll(() => Effect7.succeed(null)));
424
1232
  if (!stat) continue;
425
1233
  if (stat.type === "Directory") {
426
1234
  yield* processDirectory(fullPath, relativePath, depth + 1);
@@ -437,291 +1245,269 @@ var getDefaultPathMap = Effect5.fn("getDefaultPathMap")(function* (directory) {
437
1245
  var MAXIMUM_ENTRY_PAGE_PATHS = 50;
438
1246
 
439
1247
  // src/nextjs-manifest.ts
440
- import { Schema as Schema3 } from "effect";
441
- var StringArraySchema = Schema3.mutable(Schema3.Array(Schema3.String));
442
- var StringRecordSchema = Schema3.mutable(Schema3.Record({ key: Schema3.String, value: Schema3.String }));
443
- var JsonValueSchema = Schema3.suspend(
444
- () => Schema3.Union(
445
- Schema3.Null,
446
- Schema3.Boolean,
447
- Schema3.Number,
448
- Schema3.String,
449
- Schema3.mutable(Schema3.Array(JsonValueSchema)),
450
- Schema3.mutable(Schema3.Record({ key: Schema3.String, value: JsonValueSchema }))
1248
+ import { Schema as Schema4 } from "effect";
1249
+ var StringArraySchema = Schema4.mutable(Schema4.Array(Schema4.String));
1250
+ var StringRecordSchema = Schema4.mutable(Schema4.Record({ key: Schema4.String, value: Schema4.String }));
1251
+ var JsonValueSchema = Schema4.suspend(
1252
+ () => Schema4.Union(
1253
+ Schema4.Null,
1254
+ Schema4.Boolean,
1255
+ Schema4.Number,
1256
+ Schema4.String,
1257
+ Schema4.mutable(Schema4.Array(JsonValueSchema)),
1258
+ Schema4.mutable(Schema4.Record({ key: Schema4.String, value: JsonValueSchema }))
451
1259
  )
452
1260
  );
453
- var ImageLocalPatternSchema = Schema3.mutable(
454
- Schema3.Struct({
455
- pathname: Schema3.optional(Schema3.String),
456
- search: Schema3.optional(Schema3.String)
1261
+ var ImageLocalPatternSchema = Schema4.mutable(
1262
+ Schema4.Struct({
1263
+ pathname: Schema4.optional(Schema4.String),
1264
+ search: Schema4.optional(Schema4.String)
457
1265
  })
458
1266
  );
459
- var ImageRemotePatternSchema = Schema3.mutable(
460
- Schema3.Struct({
461
- protocol: Schema3.optional(Schema3.Literal("http", "https")),
462
- hostname: Schema3.String,
463
- port: Schema3.optional(Schema3.String),
464
- pathname: Schema3.optional(Schema3.String),
465
- search: Schema3.optional(Schema3.String)
1267
+ var ImageRemotePatternSchema = Schema4.mutable(
1268
+ Schema4.Struct({
1269
+ protocol: Schema4.optional(Schema4.Literal("http", "https")),
1270
+ hostname: Schema4.String,
1271
+ port: Schema4.optional(Schema4.String),
1272
+ pathname: Schema4.optional(Schema4.String),
1273
+ search: Schema4.optional(Schema4.String)
466
1274
  })
467
1275
  );
468
- var ImageVariantSchema = Schema3.mutable(
469
- Schema3.Struct({
470
- width: Schema3.optional(Schema3.Number),
471
- height: Schema3.optional(Schema3.Number),
472
- quality: Schema3.optional(Schema3.Number),
473
- format: Schema3.optional(Schema3.Literal("image/avif", "image/webp", "image/jpeg", "image/png")),
474
- fit: Schema3.optional(Schema3.Literal("contain", "cover", "fill", "inside", "outside"))
1276
+ var ImageVariantSchema = Schema4.mutable(
1277
+ Schema4.Struct({
1278
+ width: Schema4.optional(Schema4.Number),
1279
+ height: Schema4.optional(Schema4.Number),
1280
+ quality: Schema4.optional(Schema4.Number),
1281
+ format: Schema4.optional(Schema4.Literal("image/avif", "image/webp", "image/jpeg", "image/png")),
1282
+ fit: Schema4.optional(Schema4.Literal("contain", "cover", "fill", "inside", "outside"))
475
1283
  })
476
1284
  );
477
- var PositiveIntSchema = Schema3.Int.pipe(Schema3.greaterThan(0));
478
- var ImagePolicySchema = Schema3.mutable(
479
- Schema3.Struct({
480
- localPatterns: Schema3.mutable(Schema3.Array(ImageLocalPatternSchema)),
481
- remotePatterns: Schema3.mutable(Schema3.Array(ImageRemotePatternSchema)),
482
- widths: Schema3.mutable(Schema3.Array(PositiveIntSchema)),
483
- heights: Schema3.mutable(Schema3.Array(PositiveIntSchema)),
484
- qualities: Schema3.mutable(Schema3.Array(Schema3.Number.pipe(Schema3.between(1, 100)))),
485
- formats: Schema3.mutable(Schema3.Array(Schema3.Literal("image/avif", "image/webp", "image/jpeg", "image/png"))),
486
- minimumCacheTTL: Schema3.Number,
487
- dangerouslyAllowSVG: Schema3.Boolean,
488
- contentSecurityPolicy: Schema3.String,
489
- contentDispositionType: Schema3.Literal("inline", "attachment"),
490
- maximumRedirects: Schema3.Number,
491
- maximumResponseBody: Schema3.Number,
492
- variants: Schema3.mutable(Schema3.Record({ key: Schema3.String, value: ImageVariantSchema }))
1285
+ var PositiveIntSchema = Schema4.Int.pipe(Schema4.greaterThan(0));
1286
+ var ImagePolicySchema = Schema4.mutable(
1287
+ Schema4.Struct({
1288
+ localPatterns: Schema4.mutable(Schema4.Array(ImageLocalPatternSchema)),
1289
+ remotePatterns: Schema4.mutable(Schema4.Array(ImageRemotePatternSchema)),
1290
+ widths: Schema4.mutable(Schema4.Array(PositiveIntSchema)),
1291
+ heights: Schema4.mutable(Schema4.Array(PositiveIntSchema)),
1292
+ qualities: Schema4.mutable(Schema4.Array(Schema4.Number.pipe(Schema4.between(1, 100)))),
1293
+ formats: Schema4.mutable(Schema4.Array(Schema4.Literal("image/avif", "image/webp", "image/jpeg", "image/png"))),
1294
+ minimumCacheTTL: Schema4.Number,
1295
+ dangerouslyAllowSVG: Schema4.Boolean,
1296
+ contentSecurityPolicy: Schema4.String,
1297
+ contentDispositionType: Schema4.Literal("inline", "attachment"),
1298
+ maximumRedirects: Schema4.Number,
1299
+ maximumResponseBody: Schema4.Number,
1300
+ variants: Schema4.mutable(Schema4.Record({ key: Schema4.String, value: ImageVariantSchema }))
493
1301
  })
494
1302
  );
495
1303
  var GigadriveNextPrerenderOutputFields = {
496
- id: Schema3.String,
497
- type: Schema3.Literal("PRERENDER"),
1304
+ id: Schema4.String,
1305
+ type: Schema4.Literal("PRERENDER"),
498
1306
  pathname: UrlPathnameSchema,
499
- route: Schema3.optional(UrlPathnameSchema),
500
- parentOutputId: Schema3.String,
501
- groupId: Schema3.Number,
502
- pprChain: Schema3.optional(Schema3.mutable(Schema3.Struct({ headers: HttpSingleValueHeadersSchema }))),
503
- parentFallbackMode: Schema3.optional(JsonValueSchema),
504
- fallback: Schema3.optional(
505
- Schema3.mutable(
506
- Schema3.Struct({
507
- filePath: Schema3.optional(PortableRelativePathSchema),
508
- initialStatus: Schema3.optional(Schema3.Int.pipe(Schema3.between(100, 599))),
509
- initialHeaders: Schema3.optional(HttpHeadersSchema),
510
- initialExpiration: Schema3.optional(Schema3.Number),
511
- initialRevalidate: Schema3.optional(Schema3.Union(Schema3.Number, Schema3.Literal(false))),
512
- postponedState: Schema3.optional(Schema3.String)
1307
+ route: Schema4.optional(UrlPathnameSchema),
1308
+ parentOutputId: Schema4.String,
1309
+ groupId: Schema4.Number,
1310
+ pprChain: Schema4.optional(Schema4.mutable(Schema4.Struct({ headers: HttpSingleValueHeadersSchema }))),
1311
+ parentFallbackMode: Schema4.optional(JsonValueSchema),
1312
+ fallback: Schema4.optional(
1313
+ Schema4.mutable(
1314
+ Schema4.Struct({
1315
+ filePath: Schema4.optional(PortableRelativePathSchema),
1316
+ initialStatus: Schema4.optional(Schema4.Int.pipe(Schema4.between(100, 599))),
1317
+ initialHeaders: Schema4.optional(HttpHeadersSchema),
1318
+ initialExpiration: Schema4.optional(Schema4.Number),
1319
+ initialRevalidate: Schema4.optional(Schema4.Union(Schema4.Number, Schema4.Literal(false))),
1320
+ postponedState: Schema4.optional(Schema4.String)
513
1321
  })
514
1322
  )
515
1323
  ),
516
- config: Schema3.mutable(
517
- Schema3.Struct({
518
- allowQuery: Schema3.optional(StringArraySchema),
519
- allowHeader: Schema3.optional(StringArraySchema),
520
- bypassFor: Schema3.optional(Schema3.mutable(Schema3.Array(JsonValueSchema))),
521
- renderingMode: Schema3.optional(Schema3.String),
522
- partialFallback: Schema3.optional(Schema3.Boolean),
523
- bypassToken: Schema3.optional(Schema3.String)
1324
+ config: Schema4.mutable(
1325
+ Schema4.Struct({
1326
+ allowQuery: Schema4.optional(StringArraySchema),
1327
+ allowHeader: Schema4.optional(StringArraySchema),
1328
+ bypassFor: Schema4.optional(Schema4.mutable(Schema4.Array(JsonValueSchema))),
1329
+ renderingMode: Schema4.optional(Schema4.String),
1330
+ partialFallback: Schema4.optional(Schema4.Boolean),
1331
+ bypassToken: Schema4.optional(Schema4.String)
524
1332
  })
525
1333
  )
526
1334
  };
527
- var GigadriveNextPrerenderClassificationSchema = Schema3.Struct({
528
- routeType: Schema3.Literal("route", "fallback", "shell", "page"),
529
- response: Schema3.Literal("empty", "initial", "complete"),
530
- compute: Schema3.Literal("blocking", "resuming", "static"),
531
- htmlSize: Schema3.optional(Schema3.Number)
1335
+ var GigadriveNextPrerenderClassificationSchema = Schema4.Struct({
1336
+ routeType: Schema4.Literal("route", "fallback", "shell", "page"),
1337
+ response: Schema4.Literal("empty", "initial", "complete"),
1338
+ compute: Schema4.Literal("blocking", "resuming", "static"),
1339
+ htmlSize: Schema4.optional(Schema4.Number)
532
1340
  });
533
- var GigadriveNextPrerenderWithoutClassificationSchema = Schema3.Struct({
534
- routeType: Schema3.optional(Schema3.Never),
535
- response: Schema3.optional(Schema3.Never),
536
- compute: Schema3.optional(Schema3.Never),
537
- htmlSize: Schema3.optional(Schema3.Never)
1341
+ var GigadriveNextPrerenderWithoutClassificationSchema = Schema4.Struct({
1342
+ routeType: Schema4.optional(Schema4.Never),
1343
+ response: Schema4.optional(Schema4.Never),
1344
+ compute: Schema4.optional(Schema4.Never),
1345
+ htmlSize: Schema4.optional(Schema4.Never)
538
1346
  });
539
- var GigadriveNextPrerenderOutputSchema = Schema3.Union(
540
- Schema3.mutable(
541
- Schema3.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
1347
+ var GigadriveNextPrerenderOutputSchema = Schema4.Union(
1348
+ Schema4.mutable(
1349
+ Schema4.Struct({ ...GigadriveNextPrerenderOutputFields, ...GigadriveNextPrerenderClassificationSchema.fields })
542
1350
  ),
543
- Schema3.mutable(
544
- Schema3.Struct({
1351
+ Schema4.mutable(
1352
+ Schema4.Struct({
545
1353
  ...GigadriveNextPrerenderOutputFields,
546
1354
  ...GigadriveNextPrerenderWithoutClassificationSchema.fields
547
1355
  })
548
1356
  )
549
1357
  );
550
- var GigadriveNextStaticAssetPrefixSchema = Schema3.mutable(
551
- Schema3.Struct({
1358
+ var GigadriveNextStaticAssetPrefixSchema = Schema4.mutable(
1359
+ Schema4.Struct({
552
1360
  sourceDir: PortableRelativePathSchema,
553
1361
  urlPrefix: PortableRelativePathSchema,
554
- immutable: Schema3.Boolean
1362
+ immutable: Schema4.Boolean
555
1363
  })
556
1364
  );
557
- var GigadriveNextRouteConditionSchema = Schema3.Union(
558
- Schema3.mutable(
559
- Schema3.Struct({
560
- type: Schema3.Literal("header", "cookie", "query"),
561
- key: Schema3.String,
562
- value: Schema3.optional(Schema3.String)
1365
+ var GigadriveNextRouteConditionSchema = Schema4.Union(
1366
+ Schema4.mutable(
1367
+ Schema4.Struct({
1368
+ type: Schema4.Literal("header", "cookie", "query"),
1369
+ key: Schema4.String,
1370
+ value: Schema4.optional(Schema4.String)
563
1371
  })
564
1372
  ),
565
- Schema3.mutable(
566
- Schema3.Struct({
567
- type: Schema3.Literal("host"),
568
- value: Schema3.String
1373
+ Schema4.mutable(
1374
+ Schema4.Struct({
1375
+ type: Schema4.Literal("host"),
1376
+ value: Schema4.String
569
1377
  })
570
1378
  )
571
1379
  );
572
- var GigadriveNextMiddlewareMatcherSchema = Schema3.mutable(
573
- Schema3.Struct({
574
- source: Schema3.String,
575
- sourceRegex: Schema3.String,
576
- has: Schema3.optional(Schema3.mutable(Schema3.Array(GigadriveNextRouteConditionSchema))),
577
- missing: Schema3.optional(Schema3.mutable(Schema3.Array(GigadriveNextRouteConditionSchema)))
1380
+ var GigadriveNextMiddlewareMatcherSchema = Schema4.mutable(
1381
+ Schema4.Struct({
1382
+ source: Schema4.String,
1383
+ sourceRegex: Schema4.String,
1384
+ has: Schema4.optional(Schema4.mutable(Schema4.Array(GigadriveNextRouteConditionSchema))),
1385
+ missing: Schema4.optional(Schema4.mutable(Schema4.Array(GigadriveNextRouteConditionSchema)))
578
1386
  })
579
1387
  );
580
- var GigadriveNextMiddlewareDescriptorSchema = Schema3.Union(
581
- Schema3.mutable(
582
- Schema3.Struct({
583
- present: Schema3.Literal(true),
584
- matchers: Schema3.mutable(Schema3.Array(GigadriveNextMiddlewareMatcherSchema))
1388
+ var GigadriveNextMiddlewareDescriptorSchema = Schema4.Union(
1389
+ Schema4.mutable(
1390
+ Schema4.Struct({
1391
+ present: Schema4.Literal(true),
1392
+ matchers: Schema4.mutable(Schema4.Array(GigadriveNextMiddlewareMatcherSchema))
585
1393
  })
586
1394
  ),
587
- Schema3.mutable(
588
- Schema3.Struct({
589
- present: Schema3.Literal(false),
590
- matchers: Schema3.mutable(Schema3.Tuple())
1395
+ Schema4.mutable(
1396
+ Schema4.Struct({
1397
+ present: Schema4.Literal(false),
1398
+ matchers: Schema4.mutable(Schema4.Tuple())
591
1399
  })
592
1400
  )
593
1401
  );
594
- var GigadriveNextServerDescriptorSchema = Schema3.mutable(
595
- Schema3.Struct({
596
- maxDuration: Schema3.optional(Schema3.Number),
597
- preferredRegion: Schema3.optional(Schema3.Union(Schema3.String, StringArraySchema)),
598
- env: Schema3.optional(StringRecordSchema)
1402
+ var GigadriveNextServerDescriptorSchema = Schema4.mutable(
1403
+ Schema4.Struct({
1404
+ maxDuration: Schema4.optional(Schema4.Number),
1405
+ preferredRegion: Schema4.optional(Schema4.Union(Schema4.String, StringArraySchema)),
1406
+ env: Schema4.optional(StringRecordSchema)
599
1407
  })
600
1408
  );
601
- var PrerenderArraySchema = Schema3.mutable(Schema3.Array(GigadriveNextPrerenderOutputSchema));
602
- var StaticAssetArraySchema = Schema3.mutable(Schema3.Array(GigadriveNextStaticAssetPrefixSchema));
603
- var JsonValueArraySchema = Schema3.mutable(Schema3.Array(JsonValueSchema));
604
- var GigadriveNextPrerenderSeedMethodsSchema = Schema3.mutable(
605
- Schema3.Tuple(Schema3.Literal("GET"), Schema3.Literal("HEAD"))
1409
+ var PrerenderArraySchema = Schema4.mutable(Schema4.Array(GigadriveNextPrerenderOutputSchema));
1410
+ var StaticAssetArraySchema = Schema4.mutable(Schema4.Array(GigadriveNextStaticAssetPrefixSchema));
1411
+ var JsonValueArraySchema = Schema4.mutable(Schema4.Array(JsonValueSchema));
1412
+ var GigadriveNextPrerenderSeedMethodsSchema = Schema4.mutable(
1413
+ Schema4.Tuple(Schema4.Literal("GET"), Schema4.Literal("HEAD"))
606
1414
  );
607
- var GigadriveNextBuildManifestV1Schema = Schema3.mutable(
608
- Schema3.Struct({
609
- version: Schema3.Literal(1),
610
- output: Schema3.Literal("standalone", "export"),
1415
+ var GigadriveNextBuildManifestV1Schema = Schema4.mutable(
1416
+ Schema4.Struct({
1417
+ version: Schema4.Literal(1),
1418
+ output: Schema4.Literal("standalone", "export"),
611
1419
  distDir: PortableRelativePathSchema,
612
1420
  repoRootToProject: RepositoryRelativePathSchema,
613
- nextVersion: Schema3.String,
614
- buildId: Schema3.String
1421
+ nextVersion: Schema4.String,
1422
+ buildId: Schema4.String
615
1423
  })
616
1424
  );
617
- var GigadriveNextBuildManifestV2ExportSchema = Schema3.mutable(
618
- Schema3.Struct({
619
- version: Schema3.Literal(2),
620
- mode: Schema3.Literal("export"),
1425
+ var GigadriveNextBuildManifestV2ExportSchema = Schema4.mutable(
1426
+ Schema4.Struct({
1427
+ version: Schema4.Literal(2),
1428
+ mode: Schema4.Literal("export"),
621
1429
  distDir: PortableRelativePathSchema,
622
1430
  repoRootToProject: RepositoryRelativePathSchema,
623
- nextVersion: Schema3.String,
624
- buildId: Schema3.String
1431
+ nextVersion: Schema4.String,
1432
+ buildId: Schema4.String
625
1433
  })
626
1434
  );
627
- var GigadriveNextBuildManifestV2StandaloneSchema = Schema3.mutable(
628
- Schema3.Struct({
629
- version: Schema3.Literal(2),
630
- mode: Schema3.Literal("standalone-v2"),
1435
+ var GigadriveNextBuildManifestV2StandaloneSchema = Schema4.mutable(
1436
+ Schema4.Struct({
1437
+ version: Schema4.Literal(2),
1438
+ mode: Schema4.Literal("standalone-v2"),
631
1439
  distDir: PortableRelativePathSchema,
632
1440
  repoRootToProject: RepositoryRelativePathSchema,
633
- nextVersion: Schema3.String,
634
- buildId: Schema3.String,
1441
+ nextVersion: Schema4.String,
1442
+ buildId: Schema4.String,
635
1443
  server: GigadriveNextServerDescriptorSchema,
636
- config: Schema3.mutable(
637
- Schema3.Struct({
638
- basePath: Schema3.String,
639
- trailingSlash: Schema3.Boolean,
640
- cacheComponents: Schema3.Boolean,
641
- i18n: Schema3.optional(JsonValueSchema),
642
- images: Schema3.optional(ImagePolicySchema)
1444
+ config: Schema4.mutable(
1445
+ Schema4.Struct({
1446
+ basePath: Schema4.String,
1447
+ trailingSlash: Schema4.Boolean,
1448
+ cacheComponents: Schema4.Boolean,
1449
+ i18n: Schema4.optional(JsonValueSchema),
1450
+ images: Schema4.optional(ImagePolicySchema)
643
1451
  })
644
1452
  ),
645
- routing: Schema3.mutable(
646
- Schema3.Struct({
1453
+ routing: Schema4.mutable(
1454
+ Schema4.Struct({
647
1455
  beforeMiddleware: JsonValueArraySchema,
648
1456
  beforeFiles: JsonValueArraySchema,
649
1457
  afterFiles: JsonValueArraySchema,
650
1458
  dynamicRoutes: JsonValueArraySchema,
651
1459
  onMatch: JsonValueArraySchema,
652
1460
  fallback: JsonValueArraySchema,
653
- shouldNormalizeNextData: Schema3.Boolean,
1461
+ shouldNormalizeNextData: Schema4.Boolean,
654
1462
  rsc: JsonValueSchema
655
1463
  })
656
1464
  ),
657
- outputs: Schema3.mutable(
658
- Schema3.Struct({
1465
+ outputs: Schema4.mutable(
1466
+ Schema4.Struct({
659
1467
  prerenders: PrerenderArraySchema,
660
- prerenderManifest: Schema3.optional(PortableRelativePathSchema),
661
- assetManifest: Schema3.optional(PortableRelativePathSchema),
662
- entryPagePaths: Schema3.optional(Schema3.mutable(Schema3.Array(UrlPathnameSchema))),
1468
+ prerenderManifest: Schema4.optional(PortableRelativePathSchema),
1469
+ assetManifest: Schema4.optional(PortableRelativePathSchema),
1470
+ entryPagePaths: Schema4.optional(Schema4.mutable(Schema4.Array(UrlPathnameSchema))),
663
1471
  staticAssets: StaticAssetArraySchema,
664
- middleware: Schema3.optional(GigadriveNextMiddlewareDescriptorSchema)
1472
+ middleware: Schema4.optional(GigadriveNextMiddlewareDescriptorSchema)
665
1473
  })
666
1474
  )
667
1475
  })
668
1476
  );
669
- var GigadriveNextBuildManifestSchema = Schema3.Union(
1477
+ var GigadriveNextBuildManifestSchema = Schema4.Union(
670
1478
  GigadriveNextBuildManifestV1Schema,
671
1479
  GigadriveNextBuildManifestV2ExportSchema,
672
1480
  GigadriveNextBuildManifestV2StandaloneSchema
673
1481
  );
674
- var GigadriveNextPrerenderManifestV1Schema = Schema3.mutable(
675
- Schema3.Struct({
676
- version: Schema3.Literal(1),
677
- seedMethods: Schema3.optional(GigadriveNextPrerenderSeedMethodsSchema),
1482
+ var GigadriveNextPrerenderManifestV1Schema = Schema4.mutable(
1483
+ Schema4.Struct({
1484
+ version: Schema4.Literal(1),
1485
+ seedMethods: Schema4.optional(GigadriveNextPrerenderSeedMethodsSchema),
678
1486
  prerenders: PrerenderArraySchema
679
1487
  })
680
1488
  );
681
1489
  var parseGigadriveNextBuildManifest = (content) => decodeJson(GigadriveNextBuildManifestSchema, content);
682
1490
  var parseGigadriveNextPrerenderManifest = (content) => decodeJson(GigadriveNextPrerenderManifestV1Schema, content);
683
1491
 
684
- // src/regions.ts
685
- var REGION_DEFINITIONS = [
686
- "us-east-1",
687
- "us-east-2",
688
- "us-west-1",
689
- "us-west-2",
690
- "ap-south-1",
691
- "ap-northeast-3",
692
- "ap-northeast-2",
693
- "ap-southeast-1",
694
- "ap-southeast-2",
695
- "ap-northeast-1",
696
- "ca-central-1",
697
- "eu-central-1",
698
- "eu-west-1",
699
- "eu-west-2",
700
- "eu-west-3",
701
- "eu-north-1",
702
- "sa-east-1"
703
- ];
704
- var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
705
-
706
1492
  // src/detection/frameworks/nextjs.ts
707
1493
  var toPortablePath = (value) => value.replaceAll("\\", "/");
708
1494
  var STANDALONE_SERVER_MEMORY_MB = 1024;
709
- var resolveStandaloneServer = Effect6.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
710
- const fs = yield* FileSystem6.FileSystem;
711
- const pathService = yield* Path5.Path;
1495
+ var resolveStandaloneServer = Effect8.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
1496
+ const fs = yield* FileSystem8.FileSystem;
1497
+ const pathService = yield* Path7.Path;
712
1498
  const standaloneRoot = pathService.join(projectFolder, distDir, "standalone");
713
1499
  const monorepoServer = pathService.join(standaloneRoot, repoRootToProject, "server.js");
714
1500
  const rootServer = pathService.join(standaloneRoot, "server.js");
715
- const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(Effect6.catchAll(() => Effect6.succeed(false)));
1501
+ const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
716
1502
  const serverPath = monorepoServerExists ? monorepoServer : rootServer;
717
- const serverExists = yield* fs.exists(serverPath).pipe(Effect6.catchAll(() => Effect6.succeed(false)));
1503
+ const serverExists = yield* fs.exists(serverPath).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
718
1504
  if (!serverExists) return void 0;
719
1505
  const serverDirectory = pathService.dirname(serverPath);
720
1506
  const entrypoint = toPortablePath(pathService.relative(standaloneRoot, serverPath));
721
1507
  return { standaloneRoot, serverDirectory, entrypoint };
722
1508
  });
723
- var buildStandalonePublicAssets = Effect6.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
724
- const pathService = yield* Path5.Path;
1509
+ var buildStandalonePublicAssets = Effect8.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
1510
+ const pathService = yield* Path7.Path;
725
1511
  const publicFiles = yield* getDefaultPathMap(pathService.join(projectFolder, "public"));
726
1512
  const assetPaths = [];
727
1513
  const assetOverrides = {};
@@ -737,8 +1523,8 @@ var buildStandalonePublicAssets = Effect6.fn("nextjs.buildStandalonePublicAssets
737
1523
  }
738
1524
  return { assetPaths, assetOverrides, filePathMap };
739
1525
  });
740
- var createStaticExportConfig = Effect6.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
741
- const pathService = yield* Path5.Path;
1526
+ var createStaticExportConfig = Effect8.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
1527
+ const pathService = yield* Path7.Path;
742
1528
  const outputDirectory = pathService.join(projectFolder, "out");
743
1529
  const files = yield* getDefaultPathMap(outputDirectory);
744
1530
  const assetPaths = [];
@@ -761,8 +1547,8 @@ var createStaticExportConfig = Effect6.fn("nextjs.createStaticExportConfig")(fun
761
1547
  package: void 0
762
1548
  };
763
1549
  });
764
- var createStandaloneConfig = Effect6.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
765
- const pathService = yield* Path5.Path;
1550
+ var createStandaloneConfig = Effect8.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
1551
+ const pathService = yield* Path7.Path;
766
1552
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
767
1553
  if (!resolved) return defaults;
768
1554
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -795,7 +1581,7 @@ var createStandaloneConfig = Effect6.fn("nextjs.createStandaloneConfig")(functio
795
1581
  }
796
1582
  };
797
1583
  });
798
- var createStandaloneV2Config = Effect6.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
1584
+ var createStandaloneV2Config = Effect8.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
799
1585
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
800
1586
  if (!resolved) return defaults;
801
1587
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -903,9 +1689,9 @@ var nextjs = {
903
1689
  routes: [{ source: "/*", destination: ".next/standalone/server.js" }],
904
1690
  environmentVariables: { NODE_ENV: "production" }
905
1691
  }),
906
- refineDefaultConfig: (defaults, projectFolder) => Effect6.gen(function* () {
907
- const fs = yield* FileSystem6.FileSystem;
908
- const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(Effect6.catchAll(() => Effect6.succeed(void 0)));
1692
+ refineDefaultConfig: (defaults, projectFolder) => Effect8.gen(function* () {
1693
+ const fs = yield* FileSystem8.FileSystem;
1694
+ const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(Effect8.catchAll(() => Effect8.succeed(void 0)));
909
1695
  const manifest = manifestContent ? parseGigadriveNextBuildManifest(manifestContent) : void 0;
910
1696
  if (!manifest) return defaults;
911
1697
  if (manifest.version === 2) {
@@ -916,8 +1702,8 @@ var nextjs = {
916
1702
  };
917
1703
 
918
1704
  // src/detection/frameworks/nuxt.ts
919
- import { FileSystem as FileSystem7 } from "@effect/platform";
920
- import { Effect as Effect7 } from "effect";
1705
+ import { FileSystem as FileSystem9 } from "@effect/platform";
1706
+ import { Effect as Effect9 } from "effect";
921
1707
  var isVercelBuildOutputV3 = (content) => {
922
1708
  try {
923
1709
  const config = JSON.parse(content);
@@ -944,9 +1730,9 @@ var nuxt = {
944
1730
  routes: [{ source: "/*", destination: ".output/server/index.mjs" }],
945
1731
  environmentVariables: { NODE_ENV: "production", NITRO_PRESET: "node-server" }
946
1732
  }),
947
- refineDefaultConfig: (defaults, projectFolder) => Effect7.gen(function* () {
948
- const fs = yield* FileSystem7.FileSystem;
949
- const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(Effect7.catchAll(() => Effect7.succeed(void 0)));
1733
+ refineDefaultConfig: (defaults, projectFolder) => Effect9.gen(function* () {
1734
+ const fs = yield* FileSystem9.FileSystem;
1735
+ const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(Effect9.catchAll(() => Effect9.succeed(void 0)));
950
1736
  if (!buildOutputConfig || !isVercelBuildOutputV3(buildOutputConfig)) {
951
1737
  return defaults;
952
1738
  }
@@ -1069,7 +1855,7 @@ var FRAMEWORK_DEFINITIONS = [
1069
1855
  ].sort((a, b) => b.priority - a.priority);
1070
1856
 
1071
1857
  // src/detection/generate-config.ts
1072
- import { Effect as Effect8 } from "effect";
1858
+ import { Effect as Effect10 } from "effect";
1073
1859
  var getInstallCommand = (pm) => {
1074
1860
  switch (pm) {
1075
1861
  case "bun":
@@ -1085,11 +1871,11 @@ var getInstallCommand = (pm) => {
1085
1871
  return "npm install";
1086
1872
  }
1087
1873
  };
1088
- var generateConfig = Effect8.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
1874
+ var generateConfig = Effect10.fn("generateConfig")(function* (framework, packageManager, projectFolder, refinedDefaults) {
1089
1875
  const defaults = refinedDefaults ?? framework.getDefaultConfig(packageManager);
1090
1876
  const installCmd = defaults.installCommand ?? getInstallCommand(packageManager);
1091
1877
  const commands = [installCmd, ...defaults.commands];
1092
- yield* Effect8.logDebug(`Generating config for ${framework.name}`, {
1878
+ yield* Effect10.logDebug(`Generating config for ${framework.name}`, {
1093
1879
  runtime: defaults.runtime,
1094
1880
  entrypoint: defaults.entrypoint,
1095
1881
  commands
@@ -1154,31 +1940,31 @@ var generateConfig = Effect8.fn("generateConfig")(function* (framework, packageM
1154
1940
  });
1155
1941
 
1156
1942
  // src/detection/read-dependencies.ts
1157
- import { FileSystem as FileSystem8, Path as Path6 } from "@effect/platform";
1158
- import { Effect as Effect10 } from "effect";
1943
+ import { FileSystem as FileSystem10, Path as Path8 } from "@effect/platform";
1944
+ import { Effect as Effect12 } from "effect";
1159
1945
 
1160
1946
  // src/detection/types.ts
1161
- import { Schema as Schema4 } from "effect";
1162
- var ManifestNotFoundError = class extends Schema4.TaggedError()("ManifestNotFoundError", {
1163
- message: Schema4.String,
1164
- filePath: Schema4.String
1947
+ import { Schema as Schema5 } from "effect";
1948
+ var ManifestNotFoundError = class extends Schema5.TaggedError()("ManifestNotFoundError", {
1949
+ message: Schema5.String,
1950
+ filePath: Schema5.String
1165
1951
  }) {
1166
1952
  };
1167
- var ManifestReadError = class extends Schema4.TaggedError()("ManifestReadError", {
1168
- message: Schema4.String,
1169
- filePath: Schema4.String
1953
+ var ManifestReadError = class extends Schema5.TaggedError()("ManifestReadError", {
1954
+ message: Schema5.String,
1955
+ filePath: Schema5.String
1170
1956
  }) {
1171
1957
  };
1172
- var ManifestParseError = class extends Schema4.TaggedError()("ManifestParseError", {
1173
- message: Schema4.String,
1174
- filePath: Schema4.String
1958
+ var ManifestParseError = class extends Schema5.TaggedError()("ManifestParseError", {
1959
+ message: Schema5.String,
1960
+ filePath: Schema5.String
1175
1961
  }) {
1176
1962
  };
1177
- var FrameworkNotDetectedError = class extends Schema4.TaggedError()(
1963
+ var FrameworkNotDetectedError = class extends Schema5.TaggedError()(
1178
1964
  "FrameworkNotDetectedError",
1179
1965
  {
1180
- message: Schema4.String,
1181
- directory: Schema4.String
1966
+ message: Schema5.String,
1967
+ directory: Schema5.String
1182
1968
  }
1183
1969
  ) {
1184
1970
  };
@@ -1188,25 +1974,25 @@ var MANIFEST_CONFIG = {
1188
1974
  node: { file: "package.json", depKeys: ["dependencies", "devDependencies"] },
1189
1975
  php: { file: "composer.json", depKeys: ["require", "require-dev"] }
1190
1976
  };
1191
- var readManifest = Effect10.fn("readManifest")(function* (filePath) {
1192
- const fs = yield* FileSystem8.FileSystem;
1193
- const exists = yield* fs.exists(filePath).pipe(Effect10.catchAll(() => Effect10.succeed(false)));
1977
+ var readManifest = Effect12.fn("readManifest")(function* (filePath) {
1978
+ const fs = yield* FileSystem10.FileSystem;
1979
+ const exists = yield* fs.exists(filePath).pipe(Effect12.catchAll(() => Effect12.succeed(false)));
1194
1980
  if (!exists) {
1195
- return yield* Effect10.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
1981
+ return yield* Effect12.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
1196
1982
  }
1197
- const content = yield* fs.readFileString(filePath).pipe(Effect10.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
1198
- const parsed = yield* Effect10.try({
1983
+ const content = yield* fs.readFileString(filePath).pipe(Effect12.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
1984
+ const parsed = yield* Effect12.try({
1199
1985
  try: () => JSON.parse(content),
1200
1986
  catch: () => new ManifestParseError({ message: "Failed to parse manifest JSON", filePath })
1201
1987
  });
1202
1988
  return parsed;
1203
1989
  });
1204
- var readDependencies = Effect10.fn("readDependencies")(function* (projectFolder, language) {
1205
- const pathService = yield* Path6.Path;
1990
+ var readDependencies = Effect12.fn("readDependencies")(function* (projectFolder, language) {
1991
+ const pathService = yield* Path8.Path;
1206
1992
  const config = MANIFEST_CONFIG[language];
1207
1993
  const filePath = pathService.join(projectFolder, config.file);
1208
1994
  const manifest = yield* readManifest(filePath).pipe(
1209
- Effect10.catchTag("ManifestNotFoundError", () => Effect10.succeed(null))
1995
+ Effect12.catchTag("ManifestNotFoundError", () => Effect12.succeed(null))
1210
1996
  );
1211
1997
  if (!manifest) {
1212
1998
  return /* @__PURE__ */ new Set();
@@ -1224,9 +2010,9 @@ var readDependencies = Effect10.fn("readDependencies")(function* (projectFolder,
1224
2010
  });
1225
2011
 
1226
2012
  // src/detection/detect-framework.ts
1227
- var matchesDetectionItem = Effect11.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
1228
- const fs = yield* FileSystem9.FileSystem;
1229
- const pathService = yield* Path7.Path;
2013
+ var matchesDetectionItem = Effect13.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
2014
+ const fs = yield* FileSystem11.FileSystem;
2015
+ const pathService = yield* Path9.Path;
1230
2016
  if (item.matchPackage != null) {
1231
2017
  const deps = yield* cachedReadDeps(`${projectFolder}\0${framework.language}`);
1232
2018
  if (!deps.has(item.matchPackage)) {
@@ -1235,12 +2021,12 @@ var matchesDetectionItem = Effect11.fn("matchesDetectionItem")(function* (item,
1235
2021
  }
1236
2022
  if (item.path != null) {
1237
2023
  const filePath = pathService.join(projectFolder, item.path);
1238
- const exists = yield* fs.exists(filePath).pipe(Effect11.catchAll(() => Effect11.succeed(false)));
2024
+ const exists = yield* fs.exists(filePath).pipe(Effect13.catchAll(() => Effect13.succeed(false)));
1239
2025
  if (!exists) {
1240
2026
  return false;
1241
2027
  }
1242
2028
  if (item.matchContent != null) {
1243
- const content = yield* fs.readFileString(filePath).pipe(Effect11.catchAll(() => Effect11.succeed("")));
2029
+ const content = yield* fs.readFileString(filePath).pipe(Effect13.catchAll(() => Effect13.succeed("")));
1244
2030
  let regexMatch = false;
1245
2031
  try {
1246
2032
  regexMatch = new RegExp(item.matchContent).test(content);
@@ -1253,7 +2039,7 @@ var matchesDetectionItem = Effect11.fn("matchesDetectionItem")(function* (item,
1253
2039
  }
1254
2040
  return true;
1255
2041
  });
1256
- var matchesFramework = Effect11.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
2042
+ var matchesFramework = Effect13.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
1257
2043
  for (const detector of framework.detectors) {
1258
2044
  const matches = yield* matchesDetectionItem(detector, projectFolder, framework, cachedReadDeps);
1259
2045
  if (!matches) {
@@ -1262,9 +2048,9 @@ var matchesFramework = Effect11.fn("matchesFramework")(function* (framework, pro
1262
2048
  }
1263
2049
  return true;
1264
2050
  });
1265
- var detectFramework = Effect11.fn("detectFramework")(function* (projectFolder) {
1266
- yield* Effect11.logDebug(`Detecting framework in ${projectFolder}`);
1267
- const cachedReadDeps = yield* Effect11.cachedFunction((key) => {
2051
+ var detectFramework = Effect13.fn("detectFramework")(function* (projectFolder) {
2052
+ yield* Effect13.logDebug(`Detecting framework in ${projectFolder}`);
2053
+ const cachedReadDeps = yield* Effect13.cachedFunction((key) => {
1268
2054
  const sepIndex = key.lastIndexOf("\0");
1269
2055
  const folder = key.slice(0, sepIndex);
1270
2056
  const language = key.slice(sepIndex + 1);
@@ -1273,7 +2059,7 @@ var detectFramework = Effect11.fn("detectFramework")(function* (projectFolder) {
1273
2059
  for (const framework of FRAMEWORK_DEFINITIONS) {
1274
2060
  const matches = yield* matchesFramework(framework, projectFolder, cachedReadDeps);
1275
2061
  if (matches) {
1276
- yield* Effect11.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
2062
+ yield* Effect13.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
1277
2063
  const detectedPackageManager = yield* detectPackageManager(projectFolder);
1278
2064
  const packageManager = framework.language === "php" ? "composer" : detectedPackageManager;
1279
2065
  const defaults = framework.getDefaultConfig(packageManager);
@@ -1282,7 +2068,7 @@ var detectFramework = Effect11.fn("detectFramework")(function* (projectFolder) {
1282
2068
  return { framework, packageManager, config };
1283
2069
  }
1284
2070
  }
1285
- return yield* Effect11.fail(
2071
+ return yield* Effect13.fail(
1286
2072
  new FrameworkNotDetectedError({
1287
2073
  message: `No framework detected in project directory`,
1288
2074
  directory: projectFolder
@@ -1291,15 +2077,22 @@ var detectFramework = Effect11.fn("detectFramework")(function* (projectFolder) {
1291
2077
  });
1292
2078
 
1293
2079
  // src/detection/merge-config.ts
1294
- import { Effect as Effect12 } from "effect";
1295
- var mergeWithFrameworkDefaults = Effect12.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
1296
- yield* Effect12.logDebug("Merging user config with framework defaults");
2080
+ import { Effect as Effect14 } from "effect";
2081
+ var mergeWithFrameworkDefaults = Effect14.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
2082
+ yield* Effect14.logDebug("Merging user config with framework defaults");
1297
2083
  const userHasAssets = (userConfig.assets?.paths?.length ?? 0) > 0 || (userConfig.assets?.prefixes?.length ?? 0) > 0 || (userConfig.assets?.manifests?.length ?? 0) > 0;
2084
+ const isContainer = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME;
2085
+ const userFileFunctions = userConfig.entrypoints.filter((entrypoint) => !isContainer(entrypoint));
2086
+ const containerPaths = new Set(userConfig.entrypoints.filter(isContainer).map((entrypoint) => entrypoint.path));
2087
+ const targetsContainer = (route) => containerPaths.has(route.destination.replace(/^\/+/, ""));
1298
2088
  const merged = {
1299
2089
  regions: userConfig.regions,
1300
2090
  commands: userConfig.commands.length > 0 ? userConfig.commands : frameworkConfig.commands,
1301
- entrypoints: userConfig.entrypoints.length > 0 ? userConfig.entrypoints : frameworkConfig.entrypoints,
1302
- routes: userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
2091
+ entrypoints: [
2092
+ ...userFileFunctions.length > 0 ? userFileFunctions : frameworkConfig.entrypoints,
2093
+ ...userConfig.entrypoints.filter(isContainer)
2094
+ ],
2095
+ routes: containerPaths.size > 0 && userConfig.routes.every(targetsContainer) ? [...userConfig.routes, ...frameworkConfig.routes] : userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
1303
2096
  assets: userHasAssets ? userConfig.assets : frameworkConfig.assets,
1304
2097
  environmentVariables: {
1305
2098
  ...frameworkConfig.environmentVariables,
@@ -1307,6 +2100,7 @@ var mergeWithFrameworkDefaults = Effect12.fn("mergeWithFrameworkDefaults")(funct
1307
2100
  },
1308
2101
  excludeFiles: userConfig.excludeFiles && userConfig.excludeFiles.length > 0 ? userConfig.excludeFiles : frameworkConfig.excludeFiles,
1309
2102
  services: userConfig.services,
2103
+ ...userConfig.sidecars !== void 0 && { sidecars: userConfig.sidecars },
1310
2104
  userArchive: userConfig.userArchive,
1311
2105
  warnings: [...frameworkConfig.warnings, ...userConfig.warnings],
1312
2106
  errors: [...frameworkConfig.errors, ...userConfig.errors]
@@ -1314,51 +2108,6 @@ var mergeWithFrameworkDefaults = Effect12.fn("mergeWithFrameworkDefaults")(funct
1314
2108
  return merged;
1315
2109
  });
1316
2110
 
1317
- // src/errors.ts
1318
- import { Schema as Schema5 } from "effect";
1319
- var ConfigFileNotFoundError = class extends Schema5.TaggedError()("ConfigFileNotFoundError", {
1320
- message: Schema5.String,
1321
- filePath: Schema5.String
1322
- }) {
1323
- };
1324
- var ConfigFileEmptyError = class extends Schema5.TaggedError()("ConfigFileEmptyError", {
1325
- message: Schema5.String,
1326
- filePath: Schema5.String
1327
- }) {
1328
- };
1329
- var ConfigFileParseError = class extends Schema5.TaggedError()("ConfigFileParseError", {
1330
- message: Schema5.String,
1331
- filePath: Schema5.String,
1332
- cause: Schema5.optional(Schema5.String)
1333
- }) {
1334
- };
1335
- var ConfigModuleLoadError = class extends Schema5.TaggedError()("ConfigModuleLoadError", {
1336
- message: Schema5.String,
1337
- filePath: Schema5.String,
1338
- cause: Schema5.optional(Schema5.String)
1339
- }) {
1340
- };
1341
- var ConfigVersionError = class extends Schema5.TaggedError()("ConfigVersionError", {
1342
- message: Schema5.String,
1343
- filePath: Schema5.String,
1344
- version: Schema5.optional(Schema5.Number)
1345
- }) {
1346
- };
1347
- var ConfigSchemaValidationError = class extends Schema5.TaggedError()(
1348
- "ConfigSchemaValidationError",
1349
- {
1350
- message: Schema5.String,
1351
- filePath: Schema5.String,
1352
- validationErrors: Schema5.Array(Schema5.String)
1353
- }
1354
- ) {
1355
- };
1356
- var FunctionConfigError = class extends Schema5.TaggedError()("FunctionConfigError", {
1357
- message: Schema5.String,
1358
- functionPath: Schema5.String
1359
- }) {
1360
- };
1361
-
1362
2111
  // src/filter-functions-from-assets.ts
1363
2112
  var filterFunctionsFromAssets = (config) => {
1364
2113
  return {
@@ -1370,28 +2119,24 @@ var filterFunctionsFromAssets = (config) => {
1370
2119
  };
1371
2120
  };
1372
2121
 
1373
- // src/normalized-config.ts
1374
- var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
1375
- var MAX_FUNCTION_DURATION_SECONDS = 28800;
1376
-
1377
2122
  // src/parse-config.ts
1378
- import { Effect as Effect18 } from "effect";
2123
+ import { Effect as Effect20 } from "effect";
1379
2124
 
1380
2125
  // src/services/raw-config-reader.ts
1381
- import { FileSystem as FileSystem10, Path as Path8 } from "@effect/platform";
1382
- import { Effect as Effect14 } from "effect";
1383
- import { parse as parseYaml } from "yaml";
2126
+ import { FileSystem as FileSystem12, Path as Path10 } from "@effect/platform";
2127
+ import { Effect as Effect16 } from "effect";
2128
+ import { parse as parseYaml2 } from "yaml";
1384
2129
 
1385
2130
  // src/services/config-module-loader.ts
1386
- import { Effect as Effect13 } from "effect";
2131
+ import { Effect as Effect15 } from "effect";
1387
2132
  import { createJiti } from "jiti";
1388
2133
  var MISSING_DEFAULT_EXPORT = /* @__PURE__ */ Symbol("MissingDefaultExport");
1389
2134
  var STRICT_DEFAULT_EXPORT_EXTENSIONS = [".ts", ".mts", ".mjs"];
1390
- var ConfigModuleLoader = class extends Effect13.Service()("ConfigModuleLoader", {
1391
- effect: Effect13.gen(function* () {
1392
- const loadConfigModule = Effect13.fn("ConfigModuleLoader.loadConfigModule")(function* (filePath) {
2135
+ var ConfigModuleLoader = class extends Effect15.Service()("ConfigModuleLoader", {
2136
+ effect: Effect15.gen(function* () {
2137
+ const loadConfigModule = Effect15.fn("ConfigModuleLoader.loadConfigModule")(function* (filePath) {
1393
2138
  const extension = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
1394
- const mod = yield* Effect13.tryPromise({
2139
+ const mod = yield* Effect15.tryPromise({
1395
2140
  try: async () => {
1396
2141
  const jiti = createJiti(filePath, { fsCache: false, interopDefault: true, moduleCache: false });
1397
2142
  const namespace = await jiti.import(filePath);
@@ -1410,7 +2155,7 @@ var ConfigModuleLoader = class extends Effect13.Service()("ConfigModuleLoader",
1410
2155
  })
1411
2156
  });
1412
2157
  if (mod === MISSING_DEFAULT_EXPORT) {
1413
- return yield* Effect13.fail(
2158
+ return yield* Effect15.fail(
1414
2159
  new ConfigModuleLoadError({
1415
2160
  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.`,
1416
2161
  filePath
@@ -1418,7 +2163,7 @@ var ConfigModuleLoader = class extends Effect13.Service()("ConfigModuleLoader",
1418
2163
  );
1419
2164
  }
1420
2165
  if (typeof mod === "function") {
1421
- return yield* Effect13.fail(
2166
+ return yield* Effect15.fail(
1422
2167
  new ConfigModuleLoadError({
1423
2168
  message: `Config module at ${filePath} default-exports a function; config factories are not supported. Export a config object instead (optionally wrapped in defineConfig).`,
1424
2169
  filePath
@@ -1426,7 +2171,7 @@ var ConfigModuleLoader = class extends Effect13.Service()("ConfigModuleLoader",
1426
2171
  );
1427
2172
  }
1428
2173
  if (typeof mod !== "object" || mod === null || Array.isArray(mod)) {
1429
- return yield* Effect13.fail(
2174
+ return yield* Effect15.fail(
1430
2175
  new ConfigModuleLoadError({
1431
2176
  message: `Config module at ${filePath} must default-export a config object.`,
1432
2177
  filePath
@@ -1456,36 +2201,36 @@ var ALLOWED_CONFIG_NAMES = [
1456
2201
  "nebula.json"
1457
2202
  ];
1458
2203
  var MODULE_EXTENSIONS = [".ts", ".mts", ".cts", ".js", ".mjs", ".cjs"];
1459
- var RawConfigReader = class extends Effect14.Service()("RawConfigReader", {
2204
+ var RawConfigReader = class extends Effect16.Service()("RawConfigReader", {
1460
2205
  dependencies: [ConfigModuleLoader.Default],
1461
- effect: Effect14.gen(function* () {
1462
- const fs = yield* FileSystem10.FileSystem;
1463
- const pathService = yield* Path8.Path;
2206
+ effect: Effect16.gen(function* () {
2207
+ const fs = yield* FileSystem12.FileSystem;
2208
+ const pathService = yield* Path10.Path;
1464
2209
  const moduleLoader = yield* ConfigModuleLoader;
1465
- const findConfig = Effect14.fn("RawConfigReader.findConfig")(function* (projectFolder) {
2210
+ const findConfig = Effect16.fn("RawConfigReader.findConfig")(function* (projectFolder) {
1466
2211
  for (const name of ALLOWED_CONFIG_NAMES) {
1467
2212
  const filePath = pathService.join(projectFolder, name);
1468
- const exists = yield* fs.exists(filePath).pipe(Effect14.catchAll(() => Effect14.succeed(false)));
2213
+ const exists = yield* fs.exists(filePath).pipe(Effect16.catchAll(() => Effect16.succeed(false)));
1469
2214
  if (exists) {
1470
2215
  return filePath;
1471
2216
  }
1472
2217
  }
1473
2218
  return null;
1474
2219
  });
1475
- const readRawConfig = Effect14.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
1476
- const exists = yield* fs.exists(filePath).pipe(Effect14.catchAll(() => Effect14.succeed(false)));
2220
+ const readRawConfig = Effect16.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
2221
+ const exists = yield* fs.exists(filePath).pipe(Effect16.catchAll(() => Effect16.succeed(false)));
1477
2222
  if (!exists) {
1478
- return yield* Effect14.fail(
2223
+ return yield* Effect16.fail(
1479
2224
  new ConfigFileNotFoundError({ message: `Config file not found at ${filePath}`, filePath })
1480
2225
  );
1481
2226
  }
1482
2227
  const fileContents = yield* fs.readFileString(filePath).pipe(
1483
- Effect14.mapError(
2228
+ Effect16.mapError(
1484
2229
  () => new ConfigFileParseError({ message: `Failed to read config file at ${filePath}`, filePath })
1485
2230
  )
1486
2231
  );
1487
2232
  if (fileContents.length === 0) {
1488
- return yield* Effect14.fail(
2233
+ return yield* Effect16.fail(
1489
2234
  new ConfigFileEmptyError({ message: `Config file is empty at ${filePath}`, filePath })
1490
2235
  );
1491
2236
  }
@@ -1498,10 +2243,10 @@ var RawConfigReader = class extends Effect14.Service()("RawConfigReader", {
1498
2243
  if (fileExtension === ".json") {
1499
2244
  parsed = JSON.parse(fileContents);
1500
2245
  } else {
1501
- parsed = parseYaml(fileContents);
2246
+ parsed = parseYaml2(fileContents);
1502
2247
  }
1503
2248
  } catch (error) {
1504
- return yield* Effect14.fail(
2249
+ return yield* Effect16.fail(
1505
2250
  new ConfigFileParseError({
1506
2251
  message: `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
1507
2252
  filePath,
@@ -1511,21 +2256,21 @@ var RawConfigReader = class extends Effect14.Service()("RawConfigReader", {
1511
2256
  }
1512
2257
  }
1513
2258
  if (parsed == null) {
1514
- return yield* Effect14.fail(
2259
+ return yield* Effect16.fail(
1515
2260
  new ConfigFileParseError({ message: `Config file could not be parsed at ${filePath}`, filePath })
1516
2261
  );
1517
2262
  }
1518
2263
  if (!options?.disableVersionCheck && typeof parsed.version !== "number") {
1519
- return yield* Effect14.fail(
2264
+ return yield* Effect16.fail(
1520
2265
  new ConfigVersionError({ message: `Config file is missing version at ${filePath}`, filePath })
1521
2266
  );
1522
2267
  }
1523
2268
  return parsed;
1524
2269
  });
1525
2270
  const readConfigFile = (configFilePath) => readRawConfig(configFilePath, { disableVersionCheck: true }).pipe(
1526
- Effect14.map((parsed) => parsed),
1527
- Effect14.catchTag("ConfigFileNotFoundError", () => Effect14.succeed(null)),
1528
- Effect14.withSpan("RawConfigReader.readConfigFile")
2271
+ Effect16.map((parsed) => parsed),
2272
+ Effect16.catchTag("ConfigFileNotFoundError", () => Effect16.succeed(null)),
2273
+ Effect16.withSpan("RawConfigReader.readConfigFile")
1529
2274
  );
1530
2275
  return { findConfig, readRawConfig, readConfigFile };
1531
2276
  })
@@ -1535,17 +2280,17 @@ var RawConfigReader = class extends Effect14.Service()("RawConfigReader", {
1535
2280
  // src/services/schema-validator.ts
1536
2281
  import Ajv from "ajv";
1537
2282
  import addFormats from "ajv-formats";
1538
- import { Effect as Effect15 } from "effect";
1539
- var SchemaValidator = class extends Effect15.Service()("SchemaValidator", {
1540
- effect: Effect15.gen(function* () {
2283
+ import { Effect as Effect17 } from "effect";
2284
+ var SchemaValidator = class extends Effect17.Service()("SchemaValidator", {
2285
+ effect: Effect17.gen(function* () {
1541
2286
  const ajv = new Ajv();
1542
2287
  addFormats(ajv);
1543
- const validate = Effect15.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
2288
+ const validate = Effect17.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
1544
2289
  const compiledValidate = ajv.compile(schema);
1545
2290
  const valid = compiledValidate(config);
1546
2291
  if (!valid) {
1547
2292
  const errors = compiledValidate.errors?.map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`.trim()) ?? [];
1548
- return yield* Effect15.fail(
2293
+ return yield* Effect17.fail(
1549
2294
  new ConfigSchemaValidationError({
1550
2295
  message: `Config file at ${filePath} is invalid: ${ajv.errorsText(compiledValidate.errors)}`,
1551
2296
  filePath,
@@ -1601,8 +2346,9 @@ var getFilesForPattern = async (pattern, projectFolder, excludeFiles = []) => {
1601
2346
  };
1602
2347
 
1603
2348
  // src/services/v4-config-parser.ts
1604
- import { Effect as Effect16 } from "effect";
2349
+ import { Effect as Effect18 } from "effect";
1605
2350
  import { minimatch as minimatch2 } from "minimatch";
2351
+ import { posix as posix2 } from "path";
1606
2352
  import safeRegex from "safe-regex2";
1607
2353
  var DEFAULT_FUNCTION_SETTINGS = {
1608
2354
  memory: 128,
@@ -1635,6 +2381,46 @@ var normalizeImagePolicy = (images) => {
1635
2381
  variants: images.variants ?? {}
1636
2382
  };
1637
2383
  };
2384
+ var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
2385
+ var queueDurationSeconds = (value) => {
2386
+ if (value === void 0) return void 0;
2387
+ if (typeof value === "number") return value;
2388
+ const match = /^(\d+(?:\.\d+)?)(s|m|h|d)$/.exec(value);
2389
+ if (!match)
2390
+ throw new Error(`Invalid queue duration "${value}". Use seconds or a string such as 30s, 10m, 1.5h or 4d.`);
2391
+ return Math.ceil(Number(match[1]) * DURATION_UNIT_SECONDS[match[2]]);
2392
+ };
2393
+ var byName = (left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
2394
+ var definedOnly = (value) => Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
2395
+ var normalizeQueue = (name, queue) => {
2396
+ if (queue === null) return { name };
2397
+ const schedules = queue.schedules ? Object.entries(queue.schedules).map(
2398
+ ([scheduleName, schedule]) => definedOnly({
2399
+ name: scheduleName,
2400
+ cron: schedule.cron,
2401
+ timezone: schedule.timezone,
2402
+ body: schedule.body === void 0 || typeof schedule.body === "string" ? schedule.body : JSON.stringify(schedule.body),
2403
+ // A plain-text body labelled JSON would fail to decode on every fire.
2404
+ contentType: schedule.contentType ?? (typeof schedule.body === "string" ? "text/plain; charset=utf-8" : void 0),
2405
+ headers: schedule.headers,
2406
+ enabled: schedule.enabled
2407
+ })
2408
+ ).sort(byName) : void 0;
2409
+ return definedOnly({
2410
+ name,
2411
+ consumer: queue.consumer,
2412
+ visibilityTimeoutSeconds: queueDurationSeconds(queue.visibilityTimeout),
2413
+ retentionSeconds: queueDurationSeconds(queue.retention),
2414
+ maxAttempts: queue.maxAttempts,
2415
+ retryBackoffMinSeconds: queueDurationSeconds(queue.retryBackoff?.min),
2416
+ retryBackoffMaxSeconds: queueDurationSeconds(queue.retryBackoff?.max),
2417
+ deduplicationWindowSeconds: queueDurationSeconds(queue.deduplicationWindow),
2418
+ concurrency: queue.concurrency,
2419
+ rateLimit: queue.rateLimit == null ? queue.rateLimit : { count: queue.rateLimit.count, periodSeconds: queueDurationSeconds(queue.rateLimit.period) ?? 1 },
2420
+ deadLetter: queue.deadLetter,
2421
+ schedules
2422
+ });
2423
+ };
1638
2424
  var normalizeServices = (services) => {
1639
2425
  if (services == null) return void 0;
1640
2426
  const normalized = [];
@@ -1647,7 +2433,13 @@ var normalizeServices = (services) => {
1647
2433
  if (services.storage !== void 0) {
1648
2434
  normalized.push({
1649
2435
  type: "storage",
1650
- 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)
2436
+ buckets: Object.entries(services.storage.buckets).map(([name, bucket]) => ({ name, visibility: bucket?.visibility ?? "private" })).sort(byName)
2437
+ });
2438
+ }
2439
+ if (services.queues !== void 0) {
2440
+ normalized.push({
2441
+ type: "queues",
2442
+ queues: Object.entries(services.queues).map(([name, queue]) => normalizeQueue(name, queue)).sort(byName)
1651
2443
  });
1652
2444
  }
1653
2445
  return normalized.length > 0 ? normalized : void 0;
@@ -1705,9 +2497,10 @@ var mapRoute = (route, entrypoints) => ({
1705
2497
  negativeRequirements: route.missing,
1706
2498
  status: route.statusCode
1707
2499
  });
1708
- var parseEntrypoints = Effect16.fn("parseEntrypoints")(function* (config, projectFolder) {
2500
+ var parseEntrypoints = Effect18.fn("parseEntrypoints")(function* (config, projectFolder) {
1709
2501
  const entrypoints = [];
1710
- if (config.functions == null) return entrypoints;
2502
+ const dockerFunctions = [];
2503
+ if (config.functions == null) return { entrypoints, dockerFunctions };
1711
2504
  for (const [fnPath, func] of Object.entries(config.functions)) {
1712
2505
  if (getFunctionSettings(fnPath, config) == null) {
1713
2506
  return yield* new FunctionConfigError({
@@ -1715,7 +2508,7 @@ var parseEntrypoints = Effect16.fn("parseEntrypoints")(function* (config, projec
1715
2508
  functionPath: fnPath
1716
2509
  });
1717
2510
  }
1718
- const matchedFiles = yield* Effect16.tryPromise({
2511
+ const matchedFiles = yield* Effect18.tryPromise({
1719
2512
  try: () => getFilesForPattern(fnPath, projectFolder, func.excludeFiles),
1720
2513
  catch: (error) => new FunctionConfigError({
1721
2514
  message: `Failed to resolve files for function pattern '${fnPath}': ${error instanceof Error ? error.message : String(error)}`,
@@ -1732,6 +2525,35 @@ var parseEntrypoints = Effect16.fn("parseEntrypoints")(function* (config, projec
1732
2525
  functionPath: file
1733
2526
  });
1734
2527
  }
2528
+ if (settings.runtime === CONTAINER_RUNTIME) {
2529
+ const unsupported = ["symlinks", "includeFiles"].filter((key) => settings[key] !== void 0);
2530
+ if (unsupported.length > 0) {
2531
+ return yield* new FunctionConfigError({
2532
+ message: `Function '${file}' runs a Dockerfile, so ${unsupported.join(" and ")} do not apply. Copy files in the Dockerfile instead.`,
2533
+ functionPath: file
2534
+ });
2535
+ }
2536
+ const declared = Object.entries(config.functions).filter(([pattern]) => matchesPattern(file, pattern)).reduce((merged, [, value]) => ({ ...merged, ...value }), {});
2537
+ dockerFunctions.push({
2538
+ file,
2539
+ name: dockerFunctionContainerName(file),
2540
+ container: {
2541
+ build: { context: posix2.dirname(file), dockerfile: posix2.basename(file) },
2542
+ ...declared.port !== void 0 && { port: declared.port },
2543
+ ...declared.memory !== void 0 && { memory: declared.memory },
2544
+ ...declared.max_duration !== void 0 && { max_duration: declared.max_duration },
2545
+ ...declared.streaming !== void 0 && { streaming: declared.streaming },
2546
+ ...func.schedule !== void 0 && { schedule: func.schedule }
2547
+ }
2548
+ });
2549
+ continue;
2550
+ }
2551
+ if (settings.port !== void 0) {
2552
+ return yield* new FunctionConfigError({
2553
+ message: `Function '${file}' sets port, which only applies to 'runtime: docker'.`,
2554
+ functionPath: file
2555
+ });
2556
+ }
1735
2557
  const runtime = settings.runtime ?? "node-20";
1736
2558
  const streaming = settings.streaming ?? runtimeStreamsByDefault(runtime);
1737
2559
  entrypoints.push({
@@ -1749,25 +2571,84 @@ var parseEntrypoints = Effect16.fn("parseEntrypoints")(function* (config, projec
1749
2571
  });
1750
2572
  }
1751
2573
  }
1752
- return entrypoints;
2574
+ dockerFunctions.sort((left, right) => left.file < right.file ? -1 : left.file > right.file ? 1 : 0);
2575
+ for (const [index, { file, name }] of dockerFunctions.entries()) {
2576
+ const clash = dockerFunctions.slice(0, index).find((candidate) => candidate.name === name);
2577
+ if (clash !== void 0) {
2578
+ return yield* new FunctionConfigError({
2579
+ message: `Functions '${clash.file}' and '${file}' would both be named '${name}'. Declare one of them under 'containers' with an explicit name.`,
2580
+ functionPath: file
2581
+ });
2582
+ }
2583
+ }
2584
+ return { entrypoints, dockerFunctions };
1753
2585
  });
1754
- var collectAssets = Effect16.fn("collectAssets")(function* (config, projectFolder) {
2586
+ var collectAssets = Effect18.fn("collectAssets")(function* (config, projectFolder) {
1755
2587
  if (config.assets == null) return [];
1756
2588
  const assetNames = yield* collectAssetFiles(projectFolder, config.assets);
1757
2589
  return assetNames.filter((assetName) => getFunctionSettings(`${config.assets}/${assetName}`, config) == null).map((assetName) => `${config.assets}/${assetName}`);
1758
2590
  });
1759
- var V4ConfigParser = class extends Effect16.Service()("V4ConfigParser", {
2591
+ var reservedRuntimePorts = (entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME ? [] : entrypoint.runtime.startsWith("php-") ? [8080, 9e3] : [8080];
2592
+ var parseContainers = Effect18.fn("parseContainers")(function* (config, projectFolder, fileEntrypoints, dockerFunctions) {
2593
+ const warnings = [];
2594
+ let containers = {};
2595
+ if (config.compose != null) {
2596
+ const imported = yield* readComposeContainers(config.compose, projectFolder);
2597
+ containers = imported.containers;
2598
+ warnings.push(...imported.warnings);
2599
+ }
2600
+ containers = { ...containers, ...config.containers ?? {} };
2601
+ for (const { file, name, container } of dockerFunctions) {
2602
+ if (containers[name] !== void 0) {
2603
+ return yield* new ContainerConfigError({
2604
+ message: `Function '${file}' runs as container '${name}', which 'containers' or the Compose file already declares. Rename one of them.`,
2605
+ containerName: name
2606
+ });
2607
+ }
2608
+ containers[name] = container;
2609
+ }
2610
+ const result = yield* normalizeContainers(containers, projectFolder);
2611
+ warnings.push(...result.warnings);
2612
+ for (const entrypoint of fileEntrypoints) {
2613
+ for (const port of reservedRuntimePorts(entrypoint)) {
2614
+ const sidecar = result.sidecars.find((candidate) => candidate.port === port);
2615
+ if (sidecar !== void 0) {
2616
+ return yield* new ContainerConfigError({
2617
+ message: `Sidecar '${sidecar.name}' listens on port ${port}, which the ${entrypoint.runtime} runtime of '${entrypoint.path}' uses inside the same microVM. Choose another port.`,
2618
+ containerName: sidecar.name
2619
+ });
2620
+ }
2621
+ }
2622
+ }
2623
+ return { ...result, warnings };
2624
+ });
2625
+ var V4ConfigParser = class extends Effect18.Service()("V4ConfigParser", {
1760
2626
  accessors: true,
1761
- effect: Effect16.succeed({
2627
+ effect: Effect18.succeed({
1762
2628
  /**
1763
2629
  * Parses a ConfigV4 into a NormalizedConfig.
1764
2630
  *
1765
2631
  * @param config - The raw V4 config object
1766
2632
  * @param projectFolder - Absolute path to the project root
1767
2633
  */
1768
- parse: Effect16.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
1769
- const entrypoints = yield* parseEntrypoints(config, projectFolder);
2634
+ parse: Effect18.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
2635
+ const { entrypoints: fileEntrypoints, dockerFunctions } = yield* parseEntrypoints(config, projectFolder);
2636
+ const containers = yield* parseContainers(config, projectFolder, fileEntrypoints, dockerFunctions);
2637
+ const entrypoints = [...fileEntrypoints, ...containers.entrypoints];
1770
2638
  const assets = yield* collectAssets(config, projectFolder);
2639
+ const dockerFunctionPaths = new Map(
2640
+ dockerFunctions.map(({ file, name }) => [file, containerEntrypointPath(name)])
2641
+ );
2642
+ const routes = (config.routes ?? []).map(
2643
+ (route) => mapRoute(
2644
+ {
2645
+ ...route,
2646
+ destination: dockerFunctionPaths.get(normalizeRouteDestination(route.destination)) ?? route.destination
2647
+ },
2648
+ entrypoints
2649
+ )
2650
+ );
2651
+ const warnings = [...containers.warnings];
1771
2652
  return {
1772
2653
  regions: resolveRegions(config.regions),
1773
2654
  assets: {
@@ -1780,10 +2661,11 @@ var V4ConfigParser = class extends Effect16.Service()("V4ConfigParser", {
1780
2661
  commands: config.build_commands ?? [],
1781
2662
  images: normalizeImagePolicy(config.images),
1782
2663
  services: normalizeServices(config.services),
2664
+ ...containers.sidecars.length > 0 && { sidecars: containers.sidecars },
1783
2665
  entrypoints,
1784
2666
  errors: [],
1785
- warnings: [],
1786
- routes: (config.routes ?? []).map((route) => mapRoute(route, entrypoints))
2667
+ warnings,
2668
+ routes
1787
2669
  };
1788
2670
  })
1789
2671
  })
@@ -1791,7 +2673,7 @@ var V4ConfigParser = class extends Effect16.Service()("V4ConfigParser", {
1791
2673
  };
1792
2674
 
1793
2675
  // src/services/vercel-build-output-parser.ts
1794
- import { Path as Path9 } from "@effect/platform";
2676
+ import { Path as Path11 } from "@effect/platform";
1795
2677
 
1796
2678
  // ../commons/src/deep-merge.ts
1797
2679
  var deepMerge = (target, ...partials) => {
@@ -1820,7 +2702,7 @@ var deepMerge = (target, ...partials) => {
1820
2702
  };
1821
2703
 
1822
2704
  // src/services/vercel-build-output-parser.ts
1823
- import { Effect as Effect17 } from "effect";
2705
+ import { Effect as Effect19 } from "effect";
1824
2706
 
1825
2707
  // src/build-output-v3/determine-repo-root.ts
1826
2708
  import path from "path";
@@ -1916,11 +2798,11 @@ var translateVercelRuntime = (runtime) => {
1916
2798
  var CONFIG_PATH = ".vercel/output/config.json";
1917
2799
  var FUNCTIONS_FOLDER = ".vercel/output/functions";
1918
2800
  var FUNCTION_CONFIGS_PATTERN = `${FUNCTIONS_FOLDER}/**/*.func/.vc-config.json`;
1919
- var VercelBuildOutputParser = class extends Effect17.Service()("VercelBuildOutputParser", {
1920
- effect: Effect17.gen(function* () {
2801
+ var VercelBuildOutputParser = class extends Effect19.Service()("VercelBuildOutputParser", {
2802
+ effect: Effect19.gen(function* () {
1921
2803
  const rawConfigReader = yield* RawConfigReader;
1922
- const pathService = yield* Path9.Path;
1923
- const parse = Effect17.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
2804
+ const pathService = yield* Path11.Path;
2805
+ const parse = Effect19.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
1924
2806
  const config = yield* rawConfigReader.readConfigFile(pathService.join(projectFolder, CONFIG_PATH));
1925
2807
  if (!config || typeof config.version !== "number" || config.version < 3) {
1926
2808
  return parseResult;
@@ -1932,8 +2814,8 @@ var VercelBuildOutputParser = class extends Effect17.Service()("VercelBuildOutpu
1932
2814
  merged.regions = [...new Set(merged.regions)];
1933
2815
  return merged;
1934
2816
  });
1935
- const loadFunctions = Effect17.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
1936
- const functionConfigs = yield* Effect17.tryPromise({
2817
+ const loadFunctions = Effect19.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
2818
+ const functionConfigs = yield* Effect19.tryPromise({
1937
2819
  try: () => getFilesForPattern(FUNCTION_CONFIGS_PATTERN, projectFolder),
1938
2820
  catch: (error) => new ConfigFileParseError({
1939
2821
  message: `Failed to scan for function configs: ${error instanceof Error ? error.message : String(error)}`,
@@ -2219,17 +3101,22 @@ var schema_default = {
2219
3101
  minimum: 1
2220
3102
  },
2221
3103
  runtime: {
2222
- $ref: "#/$defs/runtimes",
2223
- description: "The runtime to use for the function."
3104
+ anyOf: [{ $ref: "#/$defs/runtimes" }, { const: "docker" }],
3105
+ 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."
3106
+ },
3107
+ port: {
3108
+ type: "integer",
3109
+ minimum: 1,
3110
+ maximum: 65535,
3111
+ description: "TCP port the image listens on. Only for `runtime: docker`. Defaults to the first exposed port of the image, then 8080."
2224
3112
  },
2225
3113
  streaming: {
2226
3114
  type: "boolean",
2227
3115
  description: "Enable function response streaming. When omitted, Node and Bun runtimes stream by default."
2228
3116
  },
2229
3117
  schedule: {
2230
- type: "string",
2231
- description: "An expression to schedule the function to run at specific times.",
2232
- 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]+))?)*)[)])$"
3118
+ $ref: "#/$defs/schedule",
3119
+ description: "An expression to schedule the function to run at specific times."
2233
3120
  },
2234
3121
  symlinks: {
2235
3122
  type: "object",
@@ -2321,6 +3208,151 @@ var schema_default = {
2321
3208
  }
2322
3209
  }
2323
3210
  },
3211
+ compose: {
3212
+ type: ["string", "null"],
3213
+ minLength: 1,
3214
+ maxLength: 1024,
3215
+ 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."
3216
+ },
3217
+ containers: {
3218
+ type: ["object", "null"],
3219
+ 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>`.",
3220
+ maxProperties: 16,
3221
+ propertyNames: {
3222
+ pattern: "^[a-z][a-z0-9_-]{0,62}$",
3223
+ not: {
3224
+ enum: ["localhost"]
3225
+ }
3226
+ },
3227
+ additionalProperties: {
3228
+ type: "object",
3229
+ additionalProperties: false,
3230
+ oneOf: [
3231
+ {
3232
+ required: ["image"]
3233
+ },
3234
+ {
3235
+ required: ["build"]
3236
+ }
3237
+ ],
3238
+ properties: {
3239
+ image: {
3240
+ type: "string",
3241
+ minLength: 1,
3242
+ maxLength: 512,
3243
+ pattern: "^[^\\s]+$",
3244
+ description: "Registry image reference, e.g. `redis:7-alpine` or `ghcr.io/acme/api:1.4`."
3245
+ },
3246
+ build: {
3247
+ description: "Build context directory, or the full build settings, for a Dockerfile in the repository.",
3248
+ oneOf: [
3249
+ {
3250
+ type: "string",
3251
+ minLength: 1,
3252
+ maxLength: 1024
3253
+ },
3254
+ {
3255
+ type: "object",
3256
+ additionalProperties: false,
3257
+ properties: {
3258
+ context: {
3259
+ type: "string",
3260
+ minLength: 1,
3261
+ maxLength: 1024
3262
+ },
3263
+ dockerfile: {
3264
+ type: "string",
3265
+ minLength: 1,
3266
+ maxLength: 1024
3267
+ },
3268
+ target: {
3269
+ type: "string",
3270
+ minLength: 1,
3271
+ maxLength: 128
3272
+ },
3273
+ args: {
3274
+ type: "object",
3275
+ maxProperties: 100,
3276
+ additionalProperties: {
3277
+ type: "string",
3278
+ maxLength: 65536
3279
+ }
3280
+ }
3281
+ }
3282
+ }
3283
+ ]
3284
+ },
3285
+ sidecar: {
3286
+ type: "boolean",
3287
+ description: "Run next to every function instance instead of serving routes. Defaults to false."
3288
+ },
3289
+ port: {
3290
+ type: "integer",
3291
+ minimum: 1,
3292
+ maximum: 65535,
3293
+ description: "TCP port the container listens on. Defaults to the first exposed port of the image, then 8080."
3294
+ },
3295
+ entrypoint: {
3296
+ $ref: "#/$defs/stringOrStringArray",
3297
+ description: "Replaces the image ENTRYPOINT. A string is split into words the way Compose splits it."
3298
+ },
3299
+ command: {
3300
+ $ref: "#/$defs/stringOrStringArray",
3301
+ description: "Replaces the image CMD. A string is split into words the way Compose splits it."
3302
+ },
3303
+ env: {
3304
+ type: "object",
3305
+ maxProperties: 100,
3306
+ additionalProperties: {
3307
+ type: "string",
3308
+ maxLength: 65536
3309
+ },
3310
+ description: "Environment variables baked into the container, merged over the image ENV."
3311
+ },
3312
+ working_dir: {
3313
+ type: "string",
3314
+ minLength: 1,
3315
+ maxLength: 1024,
3316
+ pattern: "^/",
3317
+ description: "Replaces the image WORKDIR. Must be absolute."
3318
+ },
3319
+ user: {
3320
+ oneOf: [
3321
+ {
3322
+ type: "string",
3323
+ minLength: 1,
3324
+ maxLength: 256
3325
+ },
3326
+ {
3327
+ type: "integer",
3328
+ minimum: 0
3329
+ }
3330
+ ],
3331
+ description: "Replaces the image USER: a name, a uid, `name:group` or `uid:gid`. A bare uid may be a number."
3332
+ },
3333
+ memory: {
3334
+ type: "integer",
3335
+ minimum: 128,
3336
+ maximum: 3009,
3337
+ description: "Memory in MB. Defaults to 512 for a container function and 256 for a sidecar."
3338
+ },
3339
+ max_duration: {
3340
+ type: "number",
3341
+ minimum: 1,
3342
+ maximum: 28800,
3343
+ description: "Maximum lifetime in seconds of one request. Container functions only."
3344
+ },
3345
+ streaming: {
3346
+ type: "boolean",
3347
+ description: "Stream responses. Defaults to true. Container functions only."
3348
+ },
3349
+ schedule: {
3350
+ $ref: "#/$defs/schedule",
3351
+ description: "An expression to run the container function at specific times. Container functions only."
3352
+ }
3353
+ }
3354
+ }
3355
+ },
2324
3356
  services: {
2325
3357
  type: "object",
2326
3358
  description: "Managed services to provision for the deployment environment.",
@@ -2471,11 +3503,179 @@ var schema_default = {
2471
3503
  }
2472
3504
  }
2473
3505
  }
3506
+ },
3507
+ queues: {
3508
+ type: "object",
3509
+ 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.",
3510
+ propertyNames: {
3511
+ maxLength: 255,
3512
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.~:@+=-]*$"
3513
+ },
3514
+ additionalProperties: {
3515
+ oneOf: [
3516
+ {
3517
+ type: "null"
3518
+ },
3519
+ {
3520
+ type: "object",
3521
+ additionalProperties: false,
3522
+ properties: {
3523
+ consumer: {
3524
+ type: ["string", "null"],
3525
+ pattern: "^/(?!/)\\S*$",
3526
+ maxLength: 2048,
3527
+ 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."
3528
+ },
3529
+ visibilityTimeout: {
3530
+ $ref: "#/$defs/queueDuration",
3531
+ description: "How long a push delivery may run, or how long a received message stays leased. Defaults to 60 seconds, at most 12 hours."
3532
+ },
3533
+ retention: {
3534
+ $ref: "#/$defs/queueDuration",
3535
+ description: "How long an undelivered message is kept. Defaults to 4 days, at most 14 days."
3536
+ },
3537
+ maxAttempts: {
3538
+ type: "integer",
3539
+ minimum: 1,
3540
+ maximum: 100,
3541
+ description: "Deliveries per message before it moves to the dead-letter list. Defaults to 10."
3542
+ },
3543
+ retryBackoff: {
3544
+ type: "object",
3545
+ additionalProperties: false,
3546
+ description: "Exponential backoff between failed deliveries. Defaults to 5 seconds, doubling up to 15 minutes.",
3547
+ properties: {
3548
+ min: {
3549
+ $ref: "#/$defs/queueDuration"
3550
+ },
3551
+ max: {
3552
+ $ref: "#/$defs/queueDuration"
3553
+ }
3554
+ }
3555
+ },
3556
+ deduplicationWindow: {
3557
+ $ref: "#/$defs/queueDuration",
3558
+ description: "How long a deduplication key is remembered. Defaults to 24 hours, at most 7 days."
3559
+ },
3560
+ concurrency: {
3561
+ type: ["integer", "null"],
3562
+ minimum: 1,
3563
+ maximum: 1e3,
3564
+ description: "Most push deliveries in flight at once. Defaults to 100."
3565
+ },
3566
+ rateLimit: {
3567
+ oneOf: [
3568
+ {
3569
+ type: "null"
3570
+ },
3571
+ {
3572
+ type: "object",
3573
+ required: ["count"],
3574
+ additionalProperties: false,
3575
+ properties: {
3576
+ count: {
3577
+ type: "integer",
3578
+ minimum: 1,
3579
+ maximum: 1e6
3580
+ },
3581
+ period: {
3582
+ $ref: "#/$defs/queueDuration"
3583
+ }
3584
+ }
3585
+ }
3586
+ ],
3587
+ description: "Most deliveries started per period. The period defaults to one second."
3588
+ },
3589
+ deadLetter: {
3590
+ type: "boolean",
3591
+ description: "Keep messages that run out of attempts so you can inspect and redrive them. Defaults to true."
3592
+ },
3593
+ schedules: {
3594
+ type: "object",
3595
+ description: "Cron schedules that send a message into this queue, keyed by schedule name.",
3596
+ propertyNames: {
3597
+ maxLength: 128,
3598
+ pattern: "^[A-Za-z0-9_][A-Za-z0-9_.-]*$"
3599
+ },
3600
+ additionalProperties: {
3601
+ type: "object",
3602
+ required: ["cron"],
3603
+ additionalProperties: false,
3604
+ properties: {
3605
+ cron: {
3606
+ type: "string",
3607
+ minLength: 1,
3608
+ maxLength: 255,
3609
+ description: "Five-field cron expression, or a macro such as @hourly or @daily."
3610
+ },
3611
+ timezone: {
3612
+ type: "string",
3613
+ description: "IANA time zone the expression is evaluated in. Defaults to UTC."
3614
+ },
3615
+ body: {
3616
+ description: "Message body. Strings are sent as is; any other value is sent as JSON."
3617
+ },
3618
+ contentType: {
3619
+ type: "string",
3620
+ description: "Content type of the body. Defaults to text/plain for a string body and application/json otherwise."
3621
+ },
3622
+ headers: {
3623
+ type: "object",
3624
+ additionalProperties: {
3625
+ type: "string"
3626
+ },
3627
+ description: "Headers delivered with every message from this schedule."
3628
+ },
3629
+ enabled: {
3630
+ type: "boolean",
3631
+ description: "Set to false to keep the schedule without sending. Defaults to true."
3632
+ }
3633
+ }
3634
+ }
3635
+ }
3636
+ }
3637
+ }
3638
+ ]
3639
+ }
2474
3640
  }
2475
3641
  }
2476
3642
  }
2477
3643
  },
2478
3644
  $defs: {
3645
+ schedule: {
3646
+ type: "string",
3647
+ 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]+))?)*)[)])$"
3648
+ },
3649
+ stringOrStringArray: {
3650
+ oneOf: [
3651
+ {
3652
+ type: "string",
3653
+ minLength: 1,
3654
+ maxLength: 4096
3655
+ },
3656
+ {
3657
+ type: "array",
3658
+ maxItems: 256,
3659
+ items: {
3660
+ type: "string",
3661
+ maxLength: 4096
3662
+ }
3663
+ }
3664
+ ]
3665
+ },
3666
+ queueDuration: {
3667
+ oneOf: [
3668
+ {
3669
+ type: "integer",
3670
+ minimum: 0
3671
+ },
3672
+ {
3673
+ type: "string",
3674
+ pattern: "^[0-9]+(\\.[0-9]+)?(s|m|h|d)$"
3675
+ }
3676
+ ],
3677
+ description: "A duration in seconds, or a string such as 30s, 10m, 1.5h or 4d."
3678
+ },
2479
3679
  runtimes: {
2480
3680
  type: "string",
2481
3681
  enum: ["php-84", "php-83", "php-81", "node-22", "node-20", "node-18", "node-16", "bun-1"]
@@ -2532,7 +3732,7 @@ var schema_default = {
2532
3732
  };
2533
3733
 
2534
3734
  // src/parse-config.ts
2535
- var parseConfigRaw = Effect18.fn("parseConfigRaw")(function* (filePath, projectFolder) {
3735
+ var parseConfigRaw = Effect20.fn("parseConfigRaw")(function* (filePath, projectFolder) {
2536
3736
  const rawReader = yield* RawConfigReader;
2537
3737
  const validator = yield* SchemaValidator;
2538
3738
  const v4Parser = yield* V4ConfigParser;
@@ -2543,7 +3743,7 @@ var parseConfigRaw = Effect18.fn("parseConfigRaw")(function* (filePath, projectF
2543
3743
  };
2544
3744
  const schema = schemaMap[version];
2545
3745
  if (!schema) {
2546
- return yield* Effect18.fail(
3746
+ return yield* Effect20.fail(
2547
3747
  new ConfigVersionError({
2548
3748
  message: `Config file at ${filePath} has an unsupported version: ${version}`,
2549
3749
  filePath,
@@ -2554,14 +3754,42 @@ var parseConfigRaw = Effect18.fn("parseConfigRaw")(function* (filePath, projectF
2554
3754
  yield* validator.validate(parsed, schema, filePath);
2555
3755
  return yield* v4Parser.parse(parsed, projectFolder);
2556
3756
  });
2557
- var parseConfig = Effect18.fn("parseConfig")(function* (filePath, projectFolder) {
3757
+ var parseConfig = Effect20.fn("parseConfig")(function* (filePath, projectFolder) {
2558
3758
  const parseResult = yield* parseConfigRaw(filePath, projectFolder);
2559
3759
  return yield* postProcessConfig(parseResult, projectFolder);
2560
3760
  });
2561
- var postProcessConfig = Effect18.fn("postProcessConfig")(function* (config, projectFolder) {
3761
+ var postProcessConfig = Effect20.fn("postProcessConfig")(function* (config, projectFolder) {
2562
3762
  const vercelParser = yield* VercelBuildOutputParser;
2563
3763
  let result = yield* vercelParser.parse(config, projectFolder);
2564
3764
  const hasAssets = (result.assets?.paths?.length ?? 0) > 0 || (result.assets?.prefixes?.length ?? 0) > 0 || (result.assets?.manifests?.length ?? 0) > 0;
3765
+ const containerFunctions = result.entrypoints.filter((entrypoint) => entrypoint.runtime === CONTAINER_RUNTIME);
3766
+ const [onlyFunction] = result.entrypoints;
3767
+ if (onlyFunction !== void 0 && onlyFunction.runtime === CONTAINER_RUNTIME && result.entrypoints.length === 1 && result.routes.length === 0 && !hasAssets) {
3768
+ result = {
3769
+ ...result,
3770
+ routes: [
3771
+ {
3772
+ path: "/*",
3773
+ destination: onlyFunction.path,
3774
+ handler: onlyFunction.streaming === true ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION",
3775
+ methods: ["ANY"],
3776
+ headers: {}
3777
+ }
3778
+ ]
3779
+ };
3780
+ } else {
3781
+ for (const entrypoint of containerFunctions) {
3782
+ const routed = result.routes.some((route) => route.destination.replace(/^\/+/, "") === entrypoint.path);
3783
+ if (!routed && entrypoint.schedule === void 0) {
3784
+ result.warnings.push(
3785
+ `Container function '${entrypoint.displayName ?? entrypoint.path}' has no route and no schedule, so nothing reaches it. Add a route with destination '${entrypoint.path}'.`
3786
+ );
3787
+ }
3788
+ }
3789
+ }
3790
+ if ((result.sidecars?.length ?? 0) > 0 && result.entrypoints.length === 0) {
3791
+ result.warnings.push("Sidecars run next to functions, and this deployment has none, so no sidecar will start.");
3792
+ }
2565
3793
  if (result.entrypoints.length === 0 && !hasAssets && result.routes.length === 0) {
2566
3794
  result.errors.push(
2567
3795
  "The current project config does not resolve to any functions, assets or routes and can not be deployed."
@@ -2599,6 +3827,9 @@ export {
2599
3827
  ALLOWED_CONFIG_NAMES,
2600
3828
  AVAILABLE_REGIONS,
2601
3829
  AVAILABLE_RUNTIMES,
3830
+ COMPOSE_FILE_NAMES,
3831
+ CONTAINER_ENTRYPOINT_PREFIX,
3832
+ CONTAINER_RUNTIME,
2602
3833
  ConfigFileEmptyError,
2603
3834
  ConfigFileNotFoundError,
2604
3835
  ConfigFileParseError,
@@ -2606,7 +3837,10 @@ export {
2606
3837
  ConfigModuleLoader,
2607
3838
  ConfigSchemaValidationError,
2608
3839
  ConfigVersionError,
3840
+ ContainerConfigError,
3841
+ DEFAULT_CONTAINER_MEMORY_MB,
2609
3842
  DEFAULT_FUNCTION_DURATION_SECONDS,
3843
+ DEFAULT_SIDECAR_MEMORY_MB,
2610
3844
  FRAMEWORK_DEFINITIONS,
2611
3845
  FrameworkNotDetectedError,
2612
3846
  FunctionConfigError,
@@ -2623,6 +3857,7 @@ export {
2623
3857
  GigadriveNextServerDescriptorSchema,
2624
3858
  GigadriveNextStaticAssetPrefixSchema,
2625
3859
  MAX_FUNCTION_DURATION_SECONDS,
3860
+ MAX_SIDECARS,
2626
3861
  ManifestNotFoundError,
2627
3862
  ManifestParseError,
2628
3863
  ManifestReadError,
@@ -2635,19 +3870,26 @@ export {
2635
3870
  V4ConfigParser,
2636
3871
  VercelBuildOutputParser,
2637
3872
  collectAssetFiles,
3873
+ composeDeclaresApp,
3874
+ containerEntrypointPath,
2638
3875
  defineConfig,
3876
+ detectContainerProject,
2639
3877
  detectFramework,
2640
3878
  detectPackageManager,
3879
+ dockerFunctionContainerName,
2641
3880
  filterFunctionsFromAssets,
2642
3881
  generateConfig,
2643
3882
  getFunctionSettings,
2644
3883
  mergeWithFrameworkDefaults,
3884
+ normalizeContainers,
2645
3885
  parseConfig,
2646
3886
  parseConfigRaw,
2647
3887
  parseGigadriveNextBuildManifest,
2648
3888
  parseGigadriveNextPrerenderManifest,
2649
3889
  parseStaticAssetManifest,
2650
3890
  postProcessConfig,
2651
- readDependencies
3891
+ readComposeContainers,
3892
+ readDependencies,
3893
+ splitCommandWords
2652
3894
  };
2653
3895
  //# sourceMappingURL=index.mjs.map