@gigadrive/network-config 4.3.6 → 4.4.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
@@ -1,6 +1,6 @@
1
1
  // src/detection/detect-framework.ts
2
- import { FileSystem as FileSystem7, Path as Path5 } from "@effect/platform";
3
- import { Effect as Effect9 } from "effect";
2
+ import { FileSystem as FileSystem8, Path as Path6 } from "@effect/platform";
3
+ import { Effect as Effect10 } from "effect";
4
4
 
5
5
  // src/detection/detect-package-manager.ts
6
6
  import { FileSystem, Path } from "@effect/platform";
@@ -49,6 +49,83 @@ var astro = {
49
49
  })
50
50
  };
51
51
 
52
+ // src/detection/frameworks/resolve-node-entrypoint.ts
53
+ import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
54
+ import { Effect as Effect2 } from "effect";
55
+ var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["node", "bun", "tsx", "ts-node"]);
56
+ var COMMON_ENTRYPOINT_CANDIDATES = [
57
+ "index.js",
58
+ "src/index.js",
59
+ "index.ts",
60
+ "src/index.ts",
61
+ "app.js",
62
+ "src/app.js",
63
+ "server.js",
64
+ "src/server.js",
65
+ "app.ts",
66
+ "src/app.ts",
67
+ "server.ts",
68
+ "src/server.ts"
69
+ ];
70
+ var isRecord = (value) => typeof value === "object" && value !== null;
71
+ var parsePackageJson = (content) => {
72
+ try {
73
+ const parsed = JSON.parse(content);
74
+ if (!isRecord(parsed)) return void 0;
75
+ return parsed;
76
+ } catch {
77
+ return void 0;
78
+ }
79
+ };
80
+ var normalizeEntrypointPath = (entrypoint) => {
81
+ const normalized = entrypoint.replace(/\\/g, "/").replace(/^\.\//, "");
82
+ if (!normalized || normalized === "." || normalized.startsWith("/") || normalized.includes(":") || normalized.split("/").some((segment) => segment === "..")) {
83
+ return void 0;
84
+ }
85
+ return normalized;
86
+ };
87
+ var parseStartScript = (script) => {
88
+ const tokens = script.trim().split(/\s+/);
89
+ if (tokens.length < 2 || !SCRIPT_RUNNERS.has(tokens[0])) return void 0;
90
+ let index = 1;
91
+ if (tokens[0] === "bun" && tokens[index] === "run") index++;
92
+ while (index < tokens.length && tokens[index].startsWith("-")) index++;
93
+ return tokens[index];
94
+ };
95
+ var applyEntrypoint = (defaults, entrypoint) => ({
96
+ ...defaults,
97
+ entrypoint,
98
+ routes: defaults.routes.map(
99
+ (route) => route.destination === defaults.entrypoint ? { ...route, destination: entrypoint } : route
100
+ )
101
+ });
102
+ var resolveNodeServerEntrypoint = (defaults, projectFolder) => Effect2.gen(function* () {
103
+ const fs = yield* FileSystem2.FileSystem;
104
+ const pathService = yield* Path2.Path;
105
+ const isFile = (relativePath) => fs.stat(pathService.join(projectFolder, relativePath)).pipe(
106
+ Effect2.map((info) => info.type === "File"),
107
+ Effect2.catchAll(() => Effect2.succeed(false))
108
+ );
109
+ const content = yield* fs.readFileString(pathService.join(projectFolder, "package.json")).pipe(Effect2.catchAll(() => Effect2.succeed(void 0)));
110
+ const manifest = content ? parsePackageJson(content) : void 0;
111
+ const startScript = typeof manifest?.scripts?.start === "string" ? manifest.scripts.start : void 0;
112
+ const candidates = [
113
+ typeof manifest?.main === "string" ? manifest.main : void 0,
114
+ startScript ? parseStartScript(startScript) : void 0,
115
+ defaults.entrypoint,
116
+ ...COMMON_ENTRYPOINT_CANDIDATES
117
+ ];
118
+ for (const candidate of candidates) {
119
+ if (!candidate) continue;
120
+ const normalized = normalizeEntrypointPath(candidate);
121
+ if (!normalized) continue;
122
+ if (yield* isFile(normalized)) {
123
+ return applyEntrypoint(defaults, normalized);
124
+ }
125
+ }
126
+ return defaults;
127
+ });
128
+
52
129
  // src/detection/frameworks/elysia.ts
53
130
  var elysia = {
54
131
  slug: "elysia",
@@ -65,7 +142,8 @@ var elysia = {
65
142
  entrypoint: "src/index.ts",
66
143
  routes: [{ source: "/*", destination: "src/index.ts" }],
67
144
  environmentVariables: { NODE_ENV: "production" }
68
- })
145
+ }),
146
+ refineDefaultConfig: resolveNodeServerEntrypoint
69
147
  };
70
148
 
71
149
  // src/detection/frameworks/express.ts
@@ -84,7 +162,8 @@ var express = {
84
162
  entrypoint: "index.js",
85
163
  routes: [{ source: "/*", destination: "index.js" }],
86
164
  environmentVariables: { NODE_ENV: "production" }
87
- })
165
+ }),
166
+ refineDefaultConfig: resolveNodeServerEntrypoint
88
167
  };
89
168
 
90
169
  // src/detection/frameworks/fastify.ts
@@ -103,7 +182,8 @@ var fastify = {
103
182
  entrypoint: "src/index.ts",
104
183
  routes: [{ source: "/*", destination: "src/index.ts" }],
105
184
  environmentVariables: { NODE_ENV: "production" }
106
- })
185
+ }),
186
+ refineDefaultConfig: resolveNodeServerEntrypoint
107
187
  };
108
188
 
109
189
  // src/detection/frameworks/hono.ts
@@ -122,7 +202,8 @@ var hono = {
122
202
  entrypoint: "src/index.ts",
123
203
  routes: [{ source: "/*", destination: "src/index.ts" }],
124
204
  environmentVariables: { NODE_ENV: "production" }
125
- })
205
+ }),
206
+ refineDefaultConfig: resolveNodeServerEntrypoint
126
207
  };
127
208
 
128
209
  // src/detection/frameworks/laravel.ts
@@ -157,13 +238,13 @@ var laravel = {
157
238
  };
158
239
 
159
240
  // src/detection/frameworks/nestjs.ts
160
- import { FileSystem as FileSystem2 } from "@effect/platform";
161
- import { Effect as Effect2 } from "effect";
162
- var isRecord = (value) => typeof value === "object" && value !== null;
241
+ import { FileSystem as FileSystem3 } from "@effect/platform";
242
+ import { Effect as Effect3 } from "effect";
243
+ var isRecord2 = (value) => typeof value === "object" && value !== null;
163
244
  var parseNestCliJson = (content) => {
164
245
  try {
165
246
  const parsed = JSON.parse(content);
166
- if (!isRecord(parsed)) return void 0;
247
+ if (!isRecord2(parsed)) return void 0;
167
248
  return parsed;
168
249
  } catch {
169
250
  return void 0;
@@ -209,9 +290,9 @@ var nestjs = {
209
290
  detectors: [{ matchPackage: "@nestjs/core" }],
210
291
  priority: 90,
211
292
  getDefaultConfig: () => createNestConfig("dist"),
212
- refineDefaultConfig: (defaults, projectFolder) => Effect2.gen(function* () {
213
- const fs = yield* FileSystem2.FileSystem;
214
- const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(Effect2.catchAll(() => Effect2.succeed(void 0)));
293
+ refineDefaultConfig: (defaults, projectFolder) => Effect3.gen(function* () {
294
+ const fs = yield* FileSystem3.FileSystem;
295
+ const content = yield* fs.readFileString(`${projectFolder}/nest-cli.json`).pipe(Effect3.catchAll(() => Effect3.succeed(void 0)));
215
296
  if (!content) return defaults;
216
297
  const nestConfig = parseNestCliJson(content);
217
298
  const outputPath = nestConfig ? getProjectOutputPath(nestConfig) : void 0;
@@ -220,28 +301,28 @@ var nestjs = {
220
301
  };
221
302
 
222
303
  // src/detection/frameworks/nextjs.ts
223
- import { FileSystem as FileSystem4, Path as Path3 } from "@effect/platform";
224
- import { Effect as Effect4 } from "effect";
304
+ import { FileSystem as FileSystem5, Path as Path4 } from "@effect/platform";
305
+ import { Effect as Effect5 } from "effect";
225
306
 
226
307
  // src/build-output-v3/get-default-path-map.ts
227
- import { FileSystem as FileSystem3, Path as Path2 } from "@effect/platform";
228
- import { Effect as Effect3 } from "effect";
229
- var getDefaultPathMap = Effect3.fn("getDefaultPathMap")(function* (directory) {
230
- const fs = yield* FileSystem3.FileSystem;
231
- const pathService = yield* Path2.Path;
308
+ import { FileSystem as FileSystem4, Path as Path3 } from "@effect/platform";
309
+ import { Effect as Effect4 } from "effect";
310
+ var getDefaultPathMap = Effect4.fn("getDefaultPathMap")(function* (directory) {
311
+ const fs = yield* FileSystem4.FileSystem;
312
+ const pathService = yield* Path3.Path;
232
313
  const result = {};
233
- const exists = yield* fs.exists(directory).pipe(Effect3.catchAll(() => Effect3.succeed(false)));
314
+ const exists = yield* fs.exists(directory).pipe(Effect4.catchAll(() => Effect4.succeed(false)));
234
315
  if (!exists) {
235
316
  return result;
236
317
  }
237
318
  const MAX_DEPTH = 100;
238
- const processDirectory = (currentPath, basePath = "", depth = 0) => Effect3.gen(function* () {
319
+ const processDirectory = (currentPath, basePath = "", depth = 0) => Effect4.gen(function* () {
239
320
  if (depth > MAX_DEPTH) return;
240
- const entries = yield* fs.readDirectory(currentPath).pipe(Effect3.catchAll(() => Effect3.succeed([])));
321
+ const entries = yield* fs.readDirectory(currentPath).pipe(Effect4.catchAll(() => Effect4.succeed([])));
241
322
  for (const name of entries) {
242
323
  const fullPath = pathService.join(currentPath, name);
243
324
  const relativePath = basePath ? pathService.join(basePath, name) : name;
244
- const stat = yield* fs.stat(fullPath).pipe(Effect3.catchAll(() => Effect3.succeed(null)));
325
+ const stat = yield* fs.stat(fullPath).pipe(Effect4.catchAll(() => Effect4.succeed(null)));
245
326
  if (!stat) continue;
246
327
  if (stat.type === "Directory") {
247
328
  yield* processDirectory(fullPath, relativePath, depth + 1);
@@ -255,27 +336,27 @@ var getDefaultPathMap = Effect3.fn("getDefaultPathMap")(function* (directory) {
255
336
  });
256
337
 
257
338
  // src/nextjs-manifest.ts
258
- var isRecord2 = (value) => typeof value === "object" && value !== null;
339
+ var isRecord3 = (value) => typeof value === "object" && value !== null;
259
340
  var isPortableRelativePath = (value, allowCurrentDirectory = false) => {
260
341
  if (typeof value !== "string") return false;
261
342
  const normalized = value.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/+$/, "");
262
343
  return (allowCurrentDirectory || normalized !== "" && normalized !== ".") && !normalized.startsWith("/") && !/^[A-Za-z]:/.test(normalized) && !normalized.includes(":") && !normalized.split("/").some((segment) => segment === "..");
263
344
  };
264
- var isStringRecord = (value) => isRecord2(value) && Object.values(value).every((item) => typeof item === "string");
345
+ var isStringRecord = (value) => isRecord3(value) && Object.values(value).every((item) => typeof item === "string");
265
346
  var isJsonValue = (value) => {
266
347
  if (value === null || typeof value === "string" || typeof value === "boolean") return true;
267
348
  if (typeof value === "number") return Number.isFinite(value);
268
349
  if (Array.isArray(value)) return value.every(isJsonValue);
269
- return isRecord2(value) && Object.values(value).every(isJsonValue);
350
+ return isRecord3(value) && Object.values(value).every(isJsonValue);
270
351
  };
271
- var isPrerenderOutput = (value) => isRecord2(value) && value.type === "PRERENDER" && typeof value.id === "string" && typeof value.pathname === "string" && typeof value.parentOutputId === "string" && typeof value.groupId === "number" && isRecord2(value.config) && (value.fallback === void 0 || isRecord2(value.fallback) && (value.fallback.filePath === void 0 || isPortableRelativePath(value.fallback.filePath)));
272
- var isStaticAssetPrefix = (value) => isRecord2(value) && isPortableRelativePath(value.sourceDir) && isPortableRelativePath(value.urlPrefix) && typeof value.immutable === "boolean";
273
- var isServerDescriptor = (value) => isRecord2(value) && (value.maxDuration === void 0 || typeof value.maxDuration === "number") && (value.preferredRegion === void 0 || typeof value.preferredRegion === "string" || Array.isArray(value.preferredRegion) && value.preferredRegion.every((region) => typeof region === "string")) && (value.env === void 0 || isStringRecord(value.env));
274
- var isImagePolicy = (value) => isRecord2(value) && Array.isArray(value.localPatterns) && Array.isArray(value.remotePatterns) && value.remotePatterns.every((pattern) => isRecord2(pattern) && typeof pattern.hostname === "string") && Array.isArray(value.widths) && value.widths.every((item) => typeof item === "number" && Number.isInteger(item) && item > 0) && Array.isArray(value.heights) && value.heights.every((item) => typeof item === "number" && Number.isInteger(item) && item > 0) && Array.isArray(value.qualities) && value.qualities.every((item) => typeof item === "number" && item >= 1 && item <= 100) && Array.isArray(value.formats) && value.formats.every((item) => ["image/avif", "image/webp", "image/jpeg", "image/png"].includes(String(item))) && typeof value.minimumCacheTTL === "number" && typeof value.dangerouslyAllowSVG === "boolean" && typeof value.contentSecurityPolicy === "string" && (value.contentDispositionType === "inline" || value.contentDispositionType === "attachment") && typeof value.maximumRedirects === "number" && typeof value.maximumResponseBody === "number" && isRecord2(value.variants);
352
+ var isPrerenderOutput = (value) => isRecord3(value) && value.type === "PRERENDER" && typeof value.id === "string" && typeof value.pathname === "string" && typeof value.parentOutputId === "string" && typeof value.groupId === "number" && isRecord3(value.config) && (value.fallback === void 0 || isRecord3(value.fallback) && (value.fallback.filePath === void 0 || isPortableRelativePath(value.fallback.filePath)));
353
+ var isStaticAssetPrefix = (value) => isRecord3(value) && isPortableRelativePath(value.sourceDir) && isPortableRelativePath(value.urlPrefix) && typeof value.immutable === "boolean";
354
+ var isServerDescriptor = (value) => isRecord3(value) && (value.maxDuration === void 0 || typeof value.maxDuration === "number") && (value.preferredRegion === void 0 || typeof value.preferredRegion === "string" || Array.isArray(value.preferredRegion) && value.preferredRegion.every((region) => typeof region === "string")) && (value.env === void 0 || isStringRecord(value.env));
355
+ var isImagePolicy = (value) => isRecord3(value) && Array.isArray(value.localPatterns) && Array.isArray(value.remotePatterns) && value.remotePatterns.every((pattern) => isRecord3(pattern) && typeof pattern.hostname === "string") && Array.isArray(value.widths) && value.widths.every((item) => typeof item === "number" && Number.isInteger(item) && item > 0) && Array.isArray(value.heights) && value.heights.every((item) => typeof item === "number" && Number.isInteger(item) && item > 0) && Array.isArray(value.qualities) && value.qualities.every((item) => typeof item === "number" && item >= 1 && item <= 100) && Array.isArray(value.formats) && value.formats.every((item) => ["image/avif", "image/webp", "image/jpeg", "image/png"].includes(String(item))) && typeof value.minimumCacheTTL === "number" && typeof value.dangerouslyAllowSVG === "boolean" && typeof value.contentSecurityPolicy === "string" && (value.contentDispositionType === "inline" || value.contentDispositionType === "attachment") && typeof value.maximumRedirects === "number" && typeof value.maximumResponseBody === "number" && isRecord3(value.variants);
275
356
  var parseGigadriveNextBuildManifest = (content) => {
276
357
  try {
277
358
  const value = JSON.parse(content);
278
- if (!isRecord2(value)) return void 0;
359
+ if (!isRecord3(value)) return void 0;
279
360
  if (value.version === 1) {
280
361
  if (value.output !== "standalone" && value.output !== "export" || !isPortableRelativePath(value.distDir) || !isPortableRelativePath(value.repoRootToProject, true) || typeof value.nextVersion !== "string" || typeof value.buildId !== "string") {
281
362
  return void 0;
@@ -290,7 +371,7 @@ var parseGigadriveNextBuildManifest = (content) => {
290
371
  return value;
291
372
  }
292
373
  if (value.mode === "standalone-v2") {
293
- if (!isServerDescriptor(value.server) || !isRecord2(value.config) || typeof value.config.basePath !== "string" || typeof value.config.trailingSlash !== "boolean" || typeof value.config.cacheComponents !== "boolean" || value.config.images !== void 0 && !isImagePolicy(value.config.images) || !isRecord2(value.routing) || !isJsonValue(value.routing) || !isRecord2(value.outputs) || !Array.isArray(value.outputs.prerenders) || !value.outputs.prerenders.every(isPrerenderOutput) || !Array.isArray(value.outputs.staticAssets) || !value.outputs.staticAssets.every(isStaticAssetPrefix)) {
374
+ if (!isServerDescriptor(value.server) || !isRecord3(value.config) || typeof value.config.basePath !== "string" || typeof value.config.trailingSlash !== "boolean" || typeof value.config.cacheComponents !== "boolean" || value.config.images !== void 0 && !isImagePolicy(value.config.images) || !isRecord3(value.routing) || !isJsonValue(value.routing) || !isRecord3(value.outputs) || !Array.isArray(value.outputs.prerenders) || !value.outputs.prerenders.every(isPrerenderOutput) || !Array.isArray(value.outputs.staticAssets) || !value.outputs.staticAssets.every(isStaticAssetPrefix)) {
294
375
  return void 0;
295
376
  }
296
377
  return value;
@@ -329,22 +410,22 @@ var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
329
410
  var toPortablePath = (value) => value.replaceAll("\\", "/");
330
411
  var MAXIMUM_ENTRY_PAGE_PATHS = 50;
331
412
  var STANDALONE_SERVER_MEMORY_MB = 1024;
332
- var resolveStandaloneServer = Effect4.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
333
- const fs = yield* FileSystem4.FileSystem;
334
- const pathService = yield* Path3.Path;
413
+ var resolveStandaloneServer = Effect5.fn("nextjs.resolveStandaloneServer")(function* (projectFolder, distDir, repoRootToProject) {
414
+ const fs = yield* FileSystem5.FileSystem;
415
+ const pathService = yield* Path4.Path;
335
416
  const standaloneRoot = pathService.join(projectFolder, distDir, "standalone");
336
417
  const monorepoServer = pathService.join(standaloneRoot, repoRootToProject, "server.js");
337
418
  const rootServer = pathService.join(standaloneRoot, "server.js");
338
- const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(Effect4.catchAll(() => Effect4.succeed(false)));
419
+ const monorepoServerExists = yield* fs.exists(monorepoServer).pipe(Effect5.catchAll(() => Effect5.succeed(false)));
339
420
  const serverPath = monorepoServerExists ? monorepoServer : rootServer;
340
- const serverExists = yield* fs.exists(serverPath).pipe(Effect4.catchAll(() => Effect4.succeed(false)));
421
+ const serverExists = yield* fs.exists(serverPath).pipe(Effect5.catchAll(() => Effect5.succeed(false)));
341
422
  if (!serverExists) return void 0;
342
423
  const serverDirectory = pathService.dirname(serverPath);
343
424
  const entrypoint = toPortablePath(pathService.relative(standaloneRoot, serverPath));
344
425
  return { standaloneRoot, serverDirectory, entrypoint };
345
426
  });
346
- var buildStandalonePublicAssets = Effect4.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
347
- const pathService = yield* Path3.Path;
427
+ var buildStandalonePublicAssets = Effect5.fn("nextjs.buildStandalonePublicAssets")(function* (projectFolder, standaloneRoot, serverDirectory) {
428
+ const pathService = yield* Path4.Path;
348
429
  const publicFiles = yield* getDefaultPathMap(pathService.join(projectFolder, "public"));
349
430
  const assetPaths = [];
350
431
  const assetOverrides = {};
@@ -360,8 +441,8 @@ var buildStandalonePublicAssets = Effect4.fn("nextjs.buildStandalonePublicAssets
360
441
  }
361
442
  return { assetPaths, assetOverrides, filePathMap };
362
443
  });
363
- var createStaticExportConfig = Effect4.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
364
- const pathService = yield* Path3.Path;
444
+ var createStaticExportConfig = Effect5.fn("nextjs.createStaticExportConfig")(function* (defaults, projectFolder) {
445
+ const pathService = yield* Path4.Path;
365
446
  const outputDirectory = pathService.join(projectFolder, "out");
366
447
  const files = yield* getDefaultPathMap(outputDirectory);
367
448
  const assetPaths = [];
@@ -384,8 +465,8 @@ var createStaticExportConfig = Effect4.fn("nextjs.createStaticExportConfig")(fun
384
465
  package: void 0
385
466
  };
386
467
  });
387
- var createStandaloneConfig = Effect4.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
388
- const pathService = yield* Path3.Path;
468
+ var createStandaloneConfig = Effect5.fn("nextjs.createStandaloneConfig")(function* (defaults, projectFolder, manifest) {
469
+ const pathService = yield* Path4.Path;
389
470
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
390
471
  if (!resolved) return defaults;
391
472
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -418,7 +499,7 @@ var createStandaloneConfig = Effect4.fn("nextjs.createStandaloneConfig")(functio
418
499
  }
419
500
  };
420
501
  });
421
- var createStandaloneV2Config = Effect4.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
502
+ var createStandaloneV2Config = Effect5.fn("nextjs.createStandaloneV2Config")(function* (defaults, projectFolder, manifest) {
422
503
  const resolved = yield* resolveStandaloneServer(projectFolder, manifest.distDir, manifest.repoRootToProject);
423
504
  if (!resolved) return defaults;
424
505
  const { standaloneRoot, serverDirectory, entrypoint } = resolved;
@@ -519,9 +600,9 @@ var nextjs = {
519
600
  routes: [{ source: "/*", destination: ".next/standalone/server.js" }],
520
601
  environmentVariables: { NODE_ENV: "production" }
521
602
  }),
522
- refineDefaultConfig: (defaults, projectFolder) => Effect4.gen(function* () {
523
- const fs = yield* FileSystem4.FileSystem;
524
- const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(Effect4.catchAll(() => Effect4.succeed(void 0)));
603
+ refineDefaultConfig: (defaults, projectFolder) => Effect5.gen(function* () {
604
+ const fs = yield* FileSystem5.FileSystem;
605
+ const manifestContent = yield* fs.readFileString(`${projectFolder}/.gigadrive/nextjs.json`).pipe(Effect5.catchAll(() => Effect5.succeed(void 0)));
525
606
  const manifest = manifestContent ? parseGigadriveNextBuildManifest(manifestContent) : void 0;
526
607
  if (!manifest) return defaults;
527
608
  if (manifest.version === 2) {
@@ -532,8 +613,8 @@ var nextjs = {
532
613
  };
533
614
 
534
615
  // src/detection/frameworks/nuxt.ts
535
- import { FileSystem as FileSystem5 } from "@effect/platform";
536
- import { Effect as Effect5 } from "effect";
616
+ import { FileSystem as FileSystem6 } from "@effect/platform";
617
+ import { Effect as Effect6 } from "effect";
537
618
  var isVercelBuildOutputV3 = (content) => {
538
619
  try {
539
620
  const config = JSON.parse(content);
@@ -560,9 +641,9 @@ var nuxt = {
560
641
  routes: [{ source: "/*", destination: ".output/server/index.mjs" }],
561
642
  environmentVariables: { NODE_ENV: "production", NITRO_PRESET: "node-server" }
562
643
  }),
563
- refineDefaultConfig: (defaults, projectFolder) => Effect5.gen(function* () {
564
- const fs = yield* FileSystem5.FileSystem;
565
- const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(Effect5.catchAll(() => Effect5.succeed(void 0)));
644
+ refineDefaultConfig: (defaults, projectFolder) => Effect6.gen(function* () {
645
+ const fs = yield* FileSystem6.FileSystem;
646
+ const buildOutputConfig = yield* fs.readFileString(`${projectFolder}/.vercel/output/config.json`).pipe(Effect6.catchAll(() => Effect6.succeed(void 0)));
566
647
  if (!buildOutputConfig || !isVercelBuildOutputV3(buildOutputConfig)) {
567
648
  return defaults;
568
649
  }
@@ -685,7 +766,7 @@ var FRAMEWORK_DEFINITIONS = [
685
766
  ].sort((a, b) => b.priority - a.priority);
686
767
 
687
768
  // src/detection/generate-config.ts
688
- import { Effect as Effect6 } from "effect";
769
+ import { Effect as Effect7 } from "effect";
689
770
  var getInstallCommand = (pm) => {
690
771
  switch (pm) {
691
772
  case "bun":
@@ -701,11 +782,11 @@ var getInstallCommand = (pm) => {
701
782
  return "npm install";
702
783
  }
703
784
  };
704
- var generateConfig = Effect6.fn("generateConfig")(function* (framework, packageManager, refinedDefaults) {
785
+ var generateConfig = Effect7.fn("generateConfig")(function* (framework, packageManager, refinedDefaults) {
705
786
  const defaults = refinedDefaults ?? framework.getDefaultConfig(packageManager);
706
787
  const installCmd = defaults.installCommand ?? getInstallCommand(packageManager);
707
788
  const commands = [installCmd, ...defaults.commands];
708
- yield* Effect6.logDebug(`Generating config for ${framework.name}`, {
789
+ yield* Effect7.logDebug(`Generating config for ${framework.name}`, {
709
790
  runtime: defaults.runtime,
710
791
  entrypoint: defaults.entrypoint,
711
792
  commands
@@ -753,8 +834,8 @@ var generateConfig = Effect6.fn("generateConfig")(function* (framework, packageM
753
834
  });
754
835
 
755
836
  // src/detection/read-dependencies.ts
756
- import { FileSystem as FileSystem6, Path as Path4 } from "@effect/platform";
757
- import { Effect as Effect8 } from "effect";
837
+ import { FileSystem as FileSystem7, Path as Path5 } from "@effect/platform";
838
+ import { Effect as Effect9 } from "effect";
758
839
 
759
840
  // src/detection/types.ts
760
841
  import { Schema } from "effect";
@@ -787,25 +868,25 @@ var MANIFEST_CONFIG = {
787
868
  node: { file: "package.json", depKeys: ["dependencies", "devDependencies"] },
788
869
  php: { file: "composer.json", depKeys: ["require", "require-dev"] }
789
870
  };
790
- var readManifest = Effect8.fn("readManifest")(function* (filePath) {
791
- const fs = yield* FileSystem6.FileSystem;
792
- const exists = yield* fs.exists(filePath).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
871
+ var readManifest = Effect9.fn("readManifest")(function* (filePath) {
872
+ const fs = yield* FileSystem7.FileSystem;
873
+ const exists = yield* fs.exists(filePath).pipe(Effect9.catchAll(() => Effect9.succeed(false)));
793
874
  if (!exists) {
794
- return yield* Effect8.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
875
+ return yield* Effect9.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
795
876
  }
796
- const content = yield* fs.readFileString(filePath).pipe(Effect8.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
797
- const parsed = yield* Effect8.try({
877
+ const content = yield* fs.readFileString(filePath).pipe(Effect9.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
878
+ const parsed = yield* Effect9.try({
798
879
  try: () => JSON.parse(content),
799
880
  catch: () => new ManifestParseError({ message: "Failed to parse manifest JSON", filePath })
800
881
  });
801
882
  return parsed;
802
883
  });
803
- var readDependencies = Effect8.fn("readDependencies")(function* (projectFolder, language) {
804
- const pathService = yield* Path4.Path;
884
+ var readDependencies = Effect9.fn("readDependencies")(function* (projectFolder, language) {
885
+ const pathService = yield* Path5.Path;
805
886
  const config = MANIFEST_CONFIG[language];
806
887
  const filePath = pathService.join(projectFolder, config.file);
807
888
  const manifest = yield* readManifest(filePath).pipe(
808
- Effect8.catchTag("ManifestNotFoundError", () => Effect8.succeed(null))
889
+ Effect9.catchTag("ManifestNotFoundError", () => Effect9.succeed(null))
809
890
  );
810
891
  if (!manifest) {
811
892
  return /* @__PURE__ */ new Set();
@@ -823,9 +904,9 @@ var readDependencies = Effect8.fn("readDependencies")(function* (projectFolder,
823
904
  });
824
905
 
825
906
  // src/detection/detect-framework.ts
826
- var matchesDetectionItem = Effect9.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
827
- const fs = yield* FileSystem7.FileSystem;
828
- const pathService = yield* Path5.Path;
907
+ var matchesDetectionItem = Effect10.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
908
+ const fs = yield* FileSystem8.FileSystem;
909
+ const pathService = yield* Path6.Path;
829
910
  if (item.matchPackage != null) {
830
911
  const deps = yield* cachedReadDeps(`${projectFolder}\0${framework.language}`);
831
912
  if (!deps.has(item.matchPackage)) {
@@ -834,12 +915,12 @@ var matchesDetectionItem = Effect9.fn("matchesDetectionItem")(function* (item, p
834
915
  }
835
916
  if (item.path != null) {
836
917
  const filePath = pathService.join(projectFolder, item.path);
837
- const exists = yield* fs.exists(filePath).pipe(Effect9.catchAll(() => Effect9.succeed(false)));
918
+ const exists = yield* fs.exists(filePath).pipe(Effect10.catchAll(() => Effect10.succeed(false)));
838
919
  if (!exists) {
839
920
  return false;
840
921
  }
841
922
  if (item.matchContent != null) {
842
- const content = yield* fs.readFileString(filePath).pipe(Effect9.catchAll(() => Effect9.succeed("")));
923
+ const content = yield* fs.readFileString(filePath).pipe(Effect10.catchAll(() => Effect10.succeed("")));
843
924
  let regexMatch = false;
844
925
  try {
845
926
  regexMatch = new RegExp(item.matchContent).test(content);
@@ -852,7 +933,7 @@ var matchesDetectionItem = Effect9.fn("matchesDetectionItem")(function* (item, p
852
933
  }
853
934
  return true;
854
935
  });
855
- var matchesFramework = Effect9.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
936
+ var matchesFramework = Effect10.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
856
937
  for (const detector of framework.detectors) {
857
938
  const matches = yield* matchesDetectionItem(detector, projectFolder, framework, cachedReadDeps);
858
939
  if (!matches) {
@@ -861,9 +942,9 @@ var matchesFramework = Effect9.fn("matchesFramework")(function* (framework, proj
861
942
  }
862
943
  return true;
863
944
  });
864
- var detectFramework = Effect9.fn("detectFramework")(function* (projectFolder) {
865
- yield* Effect9.logDebug(`Detecting framework in ${projectFolder}`);
866
- const cachedReadDeps = yield* Effect9.cachedFunction((key) => {
945
+ var detectFramework = Effect10.fn("detectFramework")(function* (projectFolder) {
946
+ yield* Effect10.logDebug(`Detecting framework in ${projectFolder}`);
947
+ const cachedReadDeps = yield* Effect10.cachedFunction((key) => {
867
948
  const sepIndex = key.lastIndexOf("\0");
868
949
  const folder = key.slice(0, sepIndex);
869
950
  const language = key.slice(sepIndex + 1);
@@ -872,7 +953,7 @@ var detectFramework = Effect9.fn("detectFramework")(function* (projectFolder) {
872
953
  for (const framework of FRAMEWORK_DEFINITIONS) {
873
954
  const matches = yield* matchesFramework(framework, projectFolder, cachedReadDeps);
874
955
  if (matches) {
875
- yield* Effect9.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
956
+ yield* Effect10.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
876
957
  const detectedPackageManager = yield* detectPackageManager(projectFolder);
877
958
  const packageManager = framework.language === "php" ? "composer" : detectedPackageManager;
878
959
  const defaults = framework.getDefaultConfig(packageManager);
@@ -881,7 +962,7 @@ var detectFramework = Effect9.fn("detectFramework")(function* (projectFolder) {
881
962
  return { framework, packageManager, config };
882
963
  }
883
964
  }
884
- return yield* Effect9.fail(
965
+ return yield* Effect10.fail(
885
966
  new FrameworkNotDetectedError({
886
967
  message: `No framework detected in project directory`,
887
968
  directory: projectFolder
@@ -890,9 +971,9 @@ var detectFramework = Effect9.fn("detectFramework")(function* (projectFolder) {
890
971
  });
891
972
 
892
973
  // src/detection/merge-config.ts
893
- import { Effect as Effect10 } from "effect";
894
- var mergeWithFrameworkDefaults = Effect10.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
895
- yield* Effect10.logDebug("Merging user config with framework defaults");
974
+ import { Effect as Effect11 } from "effect";
975
+ var mergeWithFrameworkDefaults = Effect11.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
976
+ yield* Effect11.logDebug("Merging user config with framework defaults");
896
977
  const merged = {
897
978
  regions: userConfig.regions,
898
979
  commands: userConfig.commands.length > 0 ? userConfig.commands : frameworkConfig.commands,
@@ -967,11 +1048,11 @@ var DEFAULT_FUNCTION_DURATION_SECONDS = 30;
967
1048
  var MAX_FUNCTION_DURATION_SECONDS = 28800;
968
1049
 
969
1050
  // src/parse-config.ts
970
- import { Effect as Effect15 } from "effect";
1051
+ import { Effect as Effect16 } from "effect";
971
1052
 
972
1053
  // src/services/raw-config-reader.ts
973
- import { FileSystem as FileSystem8, Path as Path6 } from "@effect/platform";
974
- import { Effect as Effect11 } from "effect";
1054
+ import { FileSystem as FileSystem9, Path as Path7 } from "@effect/platform";
1055
+ import { Effect as Effect12 } from "effect";
975
1056
  import { parse as parseYaml } from "yaml";
976
1057
  var ALLOWED_CONFIG_NAMES = [
977
1058
  "gigadrive.yaml",
@@ -980,34 +1061,34 @@ var ALLOWED_CONFIG_NAMES = [
980
1061
  "nebula.yml",
981
1062
  "nebula.json"
982
1063
  ];
983
- var RawConfigReader = class extends Effect11.Service()("RawConfigReader", {
984
- effect: Effect11.gen(function* () {
985
- const fs = yield* FileSystem8.FileSystem;
986
- const pathService = yield* Path6.Path;
987
- const findConfig = Effect11.fn("RawConfigReader.findConfig")(function* (projectFolder) {
1064
+ var RawConfigReader = class extends Effect12.Service()("RawConfigReader", {
1065
+ effect: Effect12.gen(function* () {
1066
+ const fs = yield* FileSystem9.FileSystem;
1067
+ const pathService = yield* Path7.Path;
1068
+ const findConfig = Effect12.fn("RawConfigReader.findConfig")(function* (projectFolder) {
988
1069
  for (const name of ALLOWED_CONFIG_NAMES) {
989
1070
  const filePath = pathService.join(projectFolder, name);
990
- const exists = yield* fs.exists(filePath).pipe(Effect11.catchAll(() => Effect11.succeed(false)));
1071
+ const exists = yield* fs.exists(filePath).pipe(Effect12.catchAll(() => Effect12.succeed(false)));
991
1072
  if (exists) {
992
1073
  return filePath;
993
1074
  }
994
1075
  }
995
1076
  return null;
996
1077
  });
997
- const readRawConfig = Effect11.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
998
- const exists = yield* fs.exists(filePath).pipe(Effect11.catchAll(() => Effect11.succeed(false)));
1078
+ const readRawConfig = Effect12.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
1079
+ const exists = yield* fs.exists(filePath).pipe(Effect12.catchAll(() => Effect12.succeed(false)));
999
1080
  if (!exists) {
1000
- return yield* Effect11.fail(
1081
+ return yield* Effect12.fail(
1001
1082
  new ConfigFileNotFoundError({ message: `Config file not found at ${filePath}`, filePath })
1002
1083
  );
1003
1084
  }
1004
1085
  const fileContents = yield* fs.readFileString(filePath).pipe(
1005
- Effect11.mapError(
1086
+ Effect12.mapError(
1006
1087
  () => new ConfigFileParseError({ message: `Failed to read config file at ${filePath}`, filePath })
1007
1088
  )
1008
1089
  );
1009
1090
  if (fileContents.length === 0) {
1010
- return yield* Effect11.fail(
1091
+ return yield* Effect12.fail(
1011
1092
  new ConfigFileEmptyError({ message: `Config file is empty at ${filePath}`, filePath })
1012
1093
  );
1013
1094
  }
@@ -1022,7 +1103,7 @@ var RawConfigReader = class extends Effect11.Service()("RawConfigReader", {
1022
1103
  parsed = parseYaml(fileContents);
1023
1104
  }
1024
1105
  } catch (error) {
1025
- return yield* Effect11.fail(
1106
+ return yield* Effect12.fail(
1026
1107
  new ConfigFileParseError({
1027
1108
  message: `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
1028
1109
  filePath,
@@ -1031,21 +1112,21 @@ var RawConfigReader = class extends Effect11.Service()("RawConfigReader", {
1031
1112
  );
1032
1113
  }
1033
1114
  if (parsed == null) {
1034
- return yield* Effect11.fail(
1115
+ return yield* Effect12.fail(
1035
1116
  new ConfigFileParseError({ message: `Config file could not be parsed at ${filePath}`, filePath })
1036
1117
  );
1037
1118
  }
1038
1119
  if (!options?.disableVersionCheck && typeof parsed.version !== "number") {
1039
- return yield* Effect11.fail(
1120
+ return yield* Effect12.fail(
1040
1121
  new ConfigVersionError({ message: `Config file is missing version at ${filePath}`, filePath })
1041
1122
  );
1042
1123
  }
1043
1124
  return parsed;
1044
1125
  });
1045
1126
  const readConfigFile = (configFilePath) => readRawConfig(configFilePath, { disableVersionCheck: true }).pipe(
1046
- Effect11.map((parsed) => parsed),
1047
- Effect11.catchTag("ConfigFileNotFoundError", () => Effect11.succeed(null)),
1048
- Effect11.withSpan("RawConfigReader.readConfigFile")
1127
+ Effect12.map((parsed) => parsed),
1128
+ Effect12.catchTag("ConfigFileNotFoundError", () => Effect12.succeed(null)),
1129
+ Effect12.withSpan("RawConfigReader.readConfigFile")
1049
1130
  );
1050
1131
  return { findConfig, readRawConfig, readConfigFile };
1051
1132
  })
@@ -1055,17 +1136,17 @@ var RawConfigReader = class extends Effect11.Service()("RawConfigReader", {
1055
1136
  // src/services/schema-validator.ts
1056
1137
  import Ajv from "ajv";
1057
1138
  import addFormats from "ajv-formats";
1058
- import { Effect as Effect12 } from "effect";
1059
- var SchemaValidator = class extends Effect12.Service()("SchemaValidator", {
1060
- effect: Effect12.gen(function* () {
1139
+ import { Effect as Effect13 } from "effect";
1140
+ var SchemaValidator = class extends Effect13.Service()("SchemaValidator", {
1141
+ effect: Effect13.gen(function* () {
1061
1142
  const ajv = new Ajv();
1062
1143
  addFormats(ajv);
1063
- const validate = Effect12.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
1144
+ const validate = Effect13.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
1064
1145
  const compiledValidate = ajv.compile(schema);
1065
1146
  const valid = compiledValidate(config);
1066
1147
  if (!valid) {
1067
1148
  const errors = compiledValidate.errors?.map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`.trim()) ?? [];
1068
- return yield* Effect12.fail(
1149
+ return yield* Effect13.fail(
1069
1150
  new ConfigSchemaValidationError({
1070
1151
  message: `Config file at ${filePath} is invalid: ${ajv.errorsText(compiledValidate.errors)}`,
1071
1152
  filePath,
@@ -1080,9 +1161,9 @@ var SchemaValidator = class extends Effect12.Service()("SchemaValidator", {
1080
1161
  };
1081
1162
 
1082
1163
  // src/services/v4-config-parser.ts
1083
- import { FileSystem as FileSystem9, Path as Path7 } from "@effect/platform";
1164
+ import { FileSystem as FileSystem10, Path as Path8 } from "@effect/platform";
1084
1165
  import { getFilesForPattern } from "@gigadrive/build-utils";
1085
- import { Effect as Effect13 } from "effect";
1166
+ import { Effect as Effect14 } from "effect";
1086
1167
  import { minimatch } from "minimatch";
1087
1168
  import safeRegex from "safe-regex2";
1088
1169
  var MAX_ASSET_DEPTH = 100;
@@ -1118,6 +1199,23 @@ var normalizeImagePolicy = (images) => {
1118
1199
  variants: images.variants ?? {}
1119
1200
  };
1120
1201
  };
1202
+ var normalizeServices = (services) => {
1203
+ if (services == null) return void 0;
1204
+ const normalized = [];
1205
+ if (services.redis !== void 0) {
1206
+ normalized.push({ type: "redis", ...services.redis ?? {} });
1207
+ }
1208
+ if (services.postgres !== void 0) {
1209
+ normalized.push({ type: "postgres", ...services.postgres ?? {} });
1210
+ }
1211
+ if (services.storage !== void 0) {
1212
+ normalized.push({
1213
+ type: "storage",
1214
+ 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)
1215
+ });
1216
+ }
1217
+ return normalized.length > 0 ? normalized : void 0;
1218
+ };
1121
1219
  var normalizeRouteDestination = (destination) => {
1122
1220
  const [withoutHash] = destination.split("#", 1);
1123
1221
  const [withoutQuery] = withoutHash.split("?", 1);
@@ -1171,7 +1269,7 @@ var mapRoute = (route, entrypoints) => ({
1171
1269
  negativeRequirements: route.missing,
1172
1270
  status: route.statusCode
1173
1271
  });
1174
- var parseEntrypoints = Effect13.fn("parseEntrypoints")(function* (config, projectFolder) {
1272
+ var parseEntrypoints = Effect14.fn("parseEntrypoints")(function* (config, projectFolder) {
1175
1273
  const entrypoints = [];
1176
1274
  if (config.functions == null) return entrypoints;
1177
1275
  for (const [fnPath, func] of Object.entries(config.functions)) {
@@ -1181,7 +1279,7 @@ var parseEntrypoints = Effect13.fn("parseEntrypoints")(function* (config, projec
1181
1279
  functionPath: fnPath
1182
1280
  });
1183
1281
  }
1184
- const matchedFiles = yield* Effect13.tryPromise({
1282
+ const matchedFiles = yield* Effect14.tryPromise({
1185
1283
  try: () => getFilesForPattern(fnPath, projectFolder, func.excludeFiles),
1186
1284
  catch: (error) => new FunctionConfigError({
1187
1285
  message: `Failed to resolve files for function pattern '${fnPath}': ${error instanceof Error ? error.message : String(error)}`,
@@ -1217,14 +1315,14 @@ var parseEntrypoints = Effect13.fn("parseEntrypoints")(function* (config, projec
1217
1315
  }
1218
1316
  return entrypoints;
1219
1317
  });
1220
- var collectAssets = Effect13.fn("collectAssets")(function* (config, projectFolder) {
1318
+ var collectAssets = Effect14.fn("collectAssets")(function* (config, projectFolder) {
1221
1319
  if (config.assets == null) return [];
1222
- const fs = yield* FileSystem9.FileSystem;
1223
- const pathService = yield* Path7.Path;
1320
+ const fs = yield* FileSystem10.FileSystem;
1321
+ const pathService = yield* Path8.Path;
1224
1322
  const assetsPath = pathService.join(projectFolder, config.assets);
1225
- const assetsExist = yield* fs.exists(assetsPath).pipe(Effect13.catchAll(() => Effect13.succeed(false)));
1323
+ const assetsExist = yield* fs.exists(assetsPath).pipe(Effect14.catchAll(() => Effect14.succeed(false)));
1226
1324
  if (!assetsExist) return [];
1227
- const stat = yield* fs.stat(assetsPath).pipe(Effect13.catchAll(() => Effect13.succeed(null)));
1325
+ const stat = yield* fs.stat(assetsPath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1228
1326
  if (!stat || stat.type !== "Directory") return [];
1229
1327
  const allFiles = yield* collectFilesRecursively(assetsPath);
1230
1328
  const assets = [];
@@ -1235,18 +1333,18 @@ var collectAssets = Effect13.fn("collectAssets")(function* (config, projectFolde
1235
1333
  }
1236
1334
  return assets;
1237
1335
  });
1238
- var collectFilesRecursively = Effect13.fn("collectFilesRecursively")(
1336
+ var collectFilesRecursively = Effect14.fn("collectFilesRecursively")(
1239
1337
  function* (basePath, relativePath = "", depth = 0) {
1240
1338
  if (depth > MAX_ASSET_DEPTH) return [];
1241
- const fs = yield* FileSystem9.FileSystem;
1242
- const pathSvc = yield* Path7.Path;
1339
+ const fs = yield* FileSystem10.FileSystem;
1340
+ const pathSvc = yield* Path8.Path;
1243
1341
  const currentPath = relativePath ? pathSvc.join(basePath, relativePath) : basePath;
1244
- const entries = yield* fs.readDirectory(currentPath).pipe(Effect13.catchAll(() => Effect13.succeed([])));
1342
+ const entries = yield* fs.readDirectory(currentPath).pipe(Effect14.catchAll(() => Effect14.succeed([])));
1245
1343
  const result = [];
1246
1344
  for (const name of entries) {
1247
1345
  const fullPath = pathSvc.join(currentPath, name);
1248
1346
  const entryRelative = relativePath ? pathSvc.join(relativePath, name) : name;
1249
- const stat = yield* fs.stat(fullPath).pipe(Effect13.catchAll(() => Effect13.succeed(null)));
1347
+ const stat = yield* fs.stat(fullPath).pipe(Effect14.catchAll(() => Effect14.succeed(null)));
1250
1348
  if (!stat) continue;
1251
1349
  if (stat.type === "Directory") {
1252
1350
  const nested = yield* collectFilesRecursively(basePath, entryRelative, depth + 1);
@@ -1258,16 +1356,16 @@ var collectFilesRecursively = Effect13.fn("collectFilesRecursively")(
1258
1356
  return result;
1259
1357
  }
1260
1358
  );
1261
- var V4ConfigParser = class extends Effect13.Service()("V4ConfigParser", {
1359
+ var V4ConfigParser = class extends Effect14.Service()("V4ConfigParser", {
1262
1360
  accessors: true,
1263
- effect: Effect13.succeed({
1361
+ effect: Effect14.succeed({
1264
1362
  /**
1265
1363
  * Parses a ConfigV4 into a NormalizedConfig.
1266
1364
  *
1267
1365
  * @param config - The raw V4 config object
1268
1366
  * @param projectFolder - Absolute path to the project root
1269
1367
  */
1270
- parse: Effect13.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
1368
+ parse: Effect14.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
1271
1369
  const entrypoints = yield* parseEntrypoints(config, projectFolder);
1272
1370
  const assets = yield* collectAssets(config, projectFolder);
1273
1371
  return {
@@ -1281,6 +1379,7 @@ var V4ConfigParser = class extends Effect13.Service()("V4ConfigParser", {
1281
1379
  environmentVariables: config.env ?? {},
1282
1380
  commands: config.build_commands ?? [],
1283
1381
  images: normalizeImagePolicy(config.images),
1382
+ services: normalizeServices(config.services),
1284
1383
  entrypoints,
1285
1384
  errors: [],
1286
1385
  warnings: [],
@@ -1292,10 +1391,10 @@ var V4ConfigParser = class extends Effect13.Service()("V4ConfigParser", {
1292
1391
  };
1293
1392
 
1294
1393
  // src/services/vercel-build-output-parser.ts
1295
- import { Path as Path8 } from "@effect/platform";
1394
+ import { Path as Path9 } from "@effect/platform";
1296
1395
  import { getFilesForPattern as getFilesForPattern2 } from "@gigadrive/build-utils";
1297
1396
  import { deepMerge } from "@gigadrive/commons";
1298
- import { Effect as Effect14 } from "effect";
1397
+ import { Effect as Effect15 } from "effect";
1299
1398
 
1300
1399
  // src/build-output-v3/determine-repo-root.ts
1301
1400
  import path from "path";
@@ -1391,11 +1490,11 @@ var translateVercelRuntime = (runtime) => {
1391
1490
  var CONFIG_PATH = ".vercel/output/config.json";
1392
1491
  var FUNCTIONS_FOLDER = ".vercel/output/functions";
1393
1492
  var FUNCTION_CONFIGS_PATTERN = `${FUNCTIONS_FOLDER}/**/*.func/.vc-config.json`;
1394
- var VercelBuildOutputParser = class extends Effect14.Service()("VercelBuildOutputParser", {
1395
- effect: Effect14.gen(function* () {
1493
+ var VercelBuildOutputParser = class extends Effect15.Service()("VercelBuildOutputParser", {
1494
+ effect: Effect15.gen(function* () {
1396
1495
  const rawConfigReader = yield* RawConfigReader;
1397
- const pathService = yield* Path8.Path;
1398
- const parse = Effect14.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
1496
+ const pathService = yield* Path9.Path;
1497
+ const parse = Effect15.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
1399
1498
  const config = yield* rawConfigReader.readConfigFile(pathService.join(projectFolder, CONFIG_PATH));
1400
1499
  if (!config || typeof config.version !== "number" || config.version < 3) {
1401
1500
  return parseResult;
@@ -1407,8 +1506,8 @@ var VercelBuildOutputParser = class extends Effect14.Service()("VercelBuildOutpu
1407
1506
  merged.regions = [...new Set(merged.regions)];
1408
1507
  return merged;
1409
1508
  });
1410
- const loadFunctions = Effect14.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
1411
- const functionConfigs = yield* Effect14.tryPromise({
1509
+ const loadFunctions = Effect15.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
1510
+ const functionConfigs = yield* Effect15.tryPromise({
1412
1511
  try: () => getFilesForPattern2(FUNCTION_CONFIGS_PATTERN, projectFolder),
1413
1512
  catch: (error) => new ConfigFileParseError({
1414
1513
  message: `Failed to scan for function configs: ${error instanceof Error ? error.message : String(error)}`,
@@ -1798,7 +1897,8 @@ var schema_default = {
1798
1897
  },
1799
1898
  services: {
1800
1899
  type: "object",
1801
- description: "Optionally, specify additional services like databases to deploy.",
1900
+ description: "Managed services to provision for the deployment environment.",
1901
+ additionalProperties: false,
1802
1902
  properties: {
1803
1903
  redis: {
1804
1904
  oneOf: [
@@ -1912,6 +2012,39 @@ var schema_default = {
1912
2012
  }
1913
2013
  }
1914
2014
  ]
2015
+ },
2016
+ storage: {
2017
+ type: "object",
2018
+ required: ["buckets"],
2019
+ additionalProperties: false,
2020
+ description: "File Storage buckets to provision in the deployment environment. The environment and global delivery/S3 slug are assigned by the platform.",
2021
+ properties: {
2022
+ buckets: {
2023
+ type: "object",
2024
+ description: "Buckets keyed by immutable environment-scoped name. Use null or an empty object for the default private visibility.",
2025
+ propertyNames: {
2026
+ minLength: 3,
2027
+ maxLength: 63,
2028
+ pattern: "^(?![0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$"
2029
+ },
2030
+ additionalProperties: {
2031
+ oneOf: [
2032
+ { type: "null" },
2033
+ {
2034
+ type: "object",
2035
+ additionalProperties: false,
2036
+ properties: {
2037
+ visibility: {
2038
+ type: "string",
2039
+ enum: ["public", "private"],
2040
+ description: "Bucket access policy. Defaults to private when omitted. Public buckets can serve objects through their CDN hostname."
2041
+ }
2042
+ }
2043
+ }
2044
+ ]
2045
+ }
2046
+ }
2047
+ }
1915
2048
  }
1916
2049
  }
1917
2050
  }
@@ -1973,7 +2106,7 @@ var schema_default = {
1973
2106
  };
1974
2107
 
1975
2108
  // src/parse-config.ts
1976
- var parseConfigRaw = Effect15.fn("parseConfigRaw")(function* (filePath, projectFolder) {
2109
+ var parseConfigRaw = Effect16.fn("parseConfigRaw")(function* (filePath, projectFolder) {
1977
2110
  const rawReader = yield* RawConfigReader;
1978
2111
  const validator = yield* SchemaValidator;
1979
2112
  const v4Parser = yield* V4ConfigParser;
@@ -1984,7 +2117,7 @@ var parseConfigRaw = Effect15.fn("parseConfigRaw")(function* (filePath, projectF
1984
2117
  };
1985
2118
  const schema = schemaMap[version];
1986
2119
  if (!schema) {
1987
- return yield* Effect15.fail(
2120
+ return yield* Effect16.fail(
1988
2121
  new ConfigVersionError({
1989
2122
  message: `Config file at ${filePath} has an unsupported version: ${version}`,
1990
2123
  filePath,
@@ -1995,11 +2128,11 @@ var parseConfigRaw = Effect15.fn("parseConfigRaw")(function* (filePath, projectF
1995
2128
  yield* validator.validate(parsed, schema, filePath);
1996
2129
  return yield* v4Parser.parse(parsed, projectFolder);
1997
2130
  });
1998
- var parseConfig = Effect15.fn("parseConfig")(function* (filePath, projectFolder) {
2131
+ var parseConfig = Effect16.fn("parseConfig")(function* (filePath, projectFolder) {
1999
2132
  const parseResult = yield* parseConfigRaw(filePath, projectFolder);
2000
2133
  return yield* postProcessConfig(parseResult, projectFolder);
2001
2134
  });
2002
- var postProcessConfig = Effect15.fn("postProcessConfig")(function* (config, projectFolder) {
2135
+ var postProcessConfig = Effect16.fn("postProcessConfig")(function* (config, projectFolder) {
2003
2136
  const vercelParser = yield* VercelBuildOutputParser;
2004
2137
  let result = yield* vercelParser.parse(config, projectFolder);
2005
2138
  if (result.entrypoints.length === 0 && (result.assets?.paths?.length || 0) === 0 && result.routes.length === 0) {