@gigadrive/network-config 2.1.2 → 3.0.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,28 +1,908 @@
1
- // src/find-config.ts
2
- import fs from "fs";
3
- import path from "path";
4
- var ALLOWED_CONFIG_NAMES = ["gigadrive.yaml", "gigadrive.yml", "nebula.yaml", "nebula.yml", "nebula.json"];
5
- var findConfig = (projectFolder) => {
6
- for (const name of ALLOWED_CONFIG_NAMES) {
7
- const filePath = path.join(projectFolder, name);
8
- if (fs.existsSync(filePath)) {
9
- return filePath;
1
+ // src/detection/detect-framework.ts
2
+ import { FileSystem as FileSystem3, Path as Path3 } from "@effect/platform";
3
+ import { Effect as Effect4 } from "effect";
4
+
5
+ // src/detection/detect-package-manager.ts
6
+ import { FileSystem, Path } from "@effect/platform";
7
+ import { Effect } from "effect";
8
+ var LOCKFILE_MAP = [
9
+ { files: ["bun.lockb", "bun.lock"], pm: "bun" },
10
+ { files: ["pnpm-lock.yaml"], pm: "pnpm" },
11
+ { files: ["yarn.lock"], pm: "yarn" },
12
+ { files: ["package-lock.json"], pm: "npm" },
13
+ { files: ["composer.lock"], pm: "composer" }
14
+ ];
15
+ var detectPackageManager = Effect.fn("detectPackageManager")(function* (projectFolder) {
16
+ const fs = yield* FileSystem.FileSystem;
17
+ const pathService = yield* Path.Path;
18
+ for (const { files, pm } of LOCKFILE_MAP) {
19
+ for (const file of files) {
20
+ const exists = yield* fs.exists(pathService.join(projectFolder, file)).pipe(Effect.catchAll(() => Effect.succeed(false)));
21
+ if (exists) {
22
+ yield* Effect.logDebug(`Detected package manager: ${pm} (via ${file})`);
23
+ return pm;
24
+ }
25
+ }
26
+ }
27
+ yield* Effect.logDebug("No lockfile found, defaulting to npm");
28
+ return "npm";
29
+ });
30
+
31
+ // src/detection/frameworks/astro.ts
32
+ var astro = {
33
+ slug: "astro",
34
+ name: "Astro",
35
+ language: "node",
36
+ detectors: [{ matchPackage: "astro" }],
37
+ priority: 85,
38
+ getDefaultConfig: () => ({
39
+ runtime: "node-22",
40
+ memory: 256,
41
+ maxDuration: 30,
42
+ streaming: true,
43
+ commands: ["astro build"],
44
+ entrypoint: "dist/server/entry.mjs",
45
+ assetsDir: "dist/client",
46
+ populateAssetCache: true,
47
+ routes: [{ source: "/*", destination: "dist/server/entry.mjs" }],
48
+ environmentVariables: { NODE_ENV: "production" }
49
+ })
50
+ };
51
+
52
+ // src/detection/frameworks/elysia.ts
53
+ var elysia = {
54
+ slug: "elysia",
55
+ name: "Elysia",
56
+ language: "node",
57
+ detectors: [{ matchPackage: "elysia" }],
58
+ priority: 45,
59
+ getDefaultConfig: () => ({
60
+ runtime: "bun-1",
61
+ memory: 128,
62
+ maxDuration: 30,
63
+ streaming: true,
64
+ commands: [],
65
+ entrypoint: "src/index.ts",
66
+ routes: [{ source: "/*", destination: "src/index.ts" }],
67
+ environmentVariables: { NODE_ENV: "production" }
68
+ })
69
+ };
70
+
71
+ // src/detection/frameworks/express.ts
72
+ var express = {
73
+ slug: "express",
74
+ name: "Express",
75
+ language: "node",
76
+ detectors: [{ matchPackage: "express" }],
77
+ priority: 10,
78
+ getDefaultConfig: () => ({
79
+ runtime: "node-22",
80
+ memory: 128,
81
+ maxDuration: 30,
82
+ streaming: true,
83
+ commands: [],
84
+ entrypoint: "index.js",
85
+ routes: [{ source: "/*", destination: "index.js" }],
86
+ environmentVariables: { NODE_ENV: "production" }
87
+ })
88
+ };
89
+
90
+ // src/detection/frameworks/fastify.ts
91
+ var fastify = {
92
+ slug: "fastify",
93
+ name: "Fastify",
94
+ language: "node",
95
+ detectors: [{ matchPackage: "fastify" }],
96
+ priority: 40,
97
+ getDefaultConfig: () => ({
98
+ runtime: "node-22",
99
+ memory: 128,
100
+ maxDuration: 30,
101
+ streaming: true,
102
+ commands: [],
103
+ entrypoint: "src/index.ts",
104
+ routes: [{ source: "/*", destination: "src/index.ts" }],
105
+ environmentVariables: { NODE_ENV: "production" }
106
+ })
107
+ };
108
+
109
+ // src/detection/frameworks/hono.ts
110
+ var hono = {
111
+ slug: "hono",
112
+ name: "Hono",
113
+ language: "node",
114
+ detectors: [{ matchPackage: "hono" }],
115
+ priority: 50,
116
+ getDefaultConfig: () => ({
117
+ runtime: "node-22",
118
+ memory: 128,
119
+ maxDuration: 30,
120
+ streaming: true,
121
+ commands: [],
122
+ entrypoint: "src/index.ts",
123
+ routes: [{ source: "/*", destination: "src/index.ts" }],
124
+ environmentVariables: { NODE_ENV: "production" }
125
+ })
126
+ };
127
+
128
+ // src/detection/frameworks/laravel.ts
129
+ var laravel = {
130
+ slug: "laravel",
131
+ name: "Laravel",
132
+ language: "php",
133
+ detectors: [{ matchPackage: "laravel/framework" }, { path: "artisan" }],
134
+ priority: 80,
135
+ getDefaultConfig: () => ({
136
+ runtime: "php-84",
137
+ memory: 256,
138
+ maxDuration: 30,
139
+ streaming: false,
140
+ commands: ["composer require bref/laravel-bridge --update-with-dependencies", "bun install", "bun run build"],
141
+ entrypoint: "public/index.php",
142
+ assetsDir: "public",
143
+ populateAssetCache: true,
144
+ excludeFiles: ["tests/", "storage/", ".ddev", "node_modules/"],
145
+ routes: [{ source: "/*", destination: "public/index.php" }],
146
+ environmentVariables: {
147
+ APP_NAME: "Laravel",
148
+ APP_ENV: "production",
149
+ APP_DEBUG: "false",
150
+ LOG_CHANNEL: "stderr",
151
+ LOG_DEPRECATIONS_CHANNEL: "stderr",
152
+ LOG_LEVEL: "debug",
153
+ BROADCAST_DRIVER: "log",
154
+ SESSION_DRIVER: "cookie"
155
+ }
156
+ })
157
+ };
158
+
159
+ // src/detection/frameworks/nestjs.ts
160
+ var nestjs = {
161
+ slug: "nestjs",
162
+ name: "NestJS",
163
+ language: "node",
164
+ detectors: [{ matchPackage: "@nestjs/core" }],
165
+ priority: 90,
166
+ getDefaultConfig: () => ({
167
+ runtime: "node-22",
168
+ memory: 256,
169
+ maxDuration: 30,
170
+ streaming: true,
171
+ commands: ["nest build"],
172
+ entrypoint: "dist/main.js",
173
+ routes: [{ source: "/*", destination: "dist/main.js" }],
174
+ environmentVariables: { NODE_ENV: "production" }
175
+ })
176
+ };
177
+
178
+ // src/detection/frameworks/nextjs.ts
179
+ var nextjs = {
180
+ slug: "nextjs",
181
+ name: "Next.js",
182
+ language: "node",
183
+ detectors: [{ matchPackage: "next" }],
184
+ priority: 100,
185
+ getDefaultConfig: () => ({
186
+ runtime: "node-22",
187
+ memory: 256,
188
+ maxDuration: 30,
189
+ streaming: true,
190
+ commands: ["next build"],
191
+ entrypoint: ".next/standalone/server.js",
192
+ assetsDir: ".next/static",
193
+ populateAssetCache: true,
194
+ routes: [{ source: "/*", destination: ".next/standalone/server.js" }],
195
+ environmentVariables: { NODE_ENV: "production" }
196
+ })
197
+ };
198
+
199
+ // src/detection/frameworks/nuxt.ts
200
+ var nuxt = {
201
+ slug: "nuxt",
202
+ name: "Nuxt",
203
+ language: "node",
204
+ detectors: [{ matchPackage: "nuxt" }],
205
+ priority: 95,
206
+ getDefaultConfig: () => ({
207
+ runtime: "node-22",
208
+ memory: 256,
209
+ maxDuration: 30,
210
+ streaming: true,
211
+ commands: ["nuxt build"],
212
+ entrypoint: ".output/server/index.mjs",
213
+ assetsDir: ".output/public",
214
+ populateAssetCache: true,
215
+ routes: [{ source: "/*", destination: ".output/server/index.mjs" }],
216
+ environmentVariables: { NODE_ENV: "production", NITRO_PRESET: "node-server" }
217
+ })
218
+ };
219
+
220
+ // src/detection/frameworks/remix.ts
221
+ var remix = {
222
+ slug: "remix",
223
+ name: "Remix",
224
+ language: "node",
225
+ detectors: [{ matchPackage: "@remix-run/dev" }],
226
+ priority: 90,
227
+ getDefaultConfig: () => ({
228
+ runtime: "node-22",
229
+ memory: 256,
230
+ maxDuration: 30,
231
+ streaming: true,
232
+ commands: ["remix build"],
233
+ entrypoint: "build/server/index.js",
234
+ assetsDir: "build/client",
235
+ populateAssetCache: true,
236
+ routes: [{ source: "/*", destination: "build/server/index.js" }],
237
+ environmentVariables: { NODE_ENV: "production" }
238
+ })
239
+ };
240
+
241
+ // src/detection/frameworks/sveltekit.ts
242
+ var sveltekit = {
243
+ slug: "sveltekit",
244
+ name: "SvelteKit",
245
+ language: "node",
246
+ detectors: [{ matchPackage: "@sveltejs/kit" }],
247
+ priority: 90,
248
+ getDefaultConfig: () => ({
249
+ runtime: "node-22",
250
+ memory: 256,
251
+ maxDuration: 30,
252
+ streaming: true,
253
+ commands: ["vite build"],
254
+ entrypoint: "build/index.js",
255
+ assetsDir: "build/client",
256
+ populateAssetCache: true,
257
+ routes: [{ source: "/*", destination: "build/index.js" }],
258
+ environmentVariables: { NODE_ENV: "production" }
259
+ })
260
+ };
261
+
262
+ // src/detection/frameworks/symfony.ts
263
+ var symfony = {
264
+ slug: "symfony",
265
+ name: "Symfony",
266
+ language: "php",
267
+ detectors: [{ matchPackage: "symfony/framework-bundle" }, { path: "bin/console" }],
268
+ priority: 80,
269
+ getDefaultConfig: () => ({
270
+ runtime: "php-84",
271
+ memory: 512,
272
+ maxDuration: 30,
273
+ streaming: false,
274
+ installCommand: "composer install --prefer-dist --optimize-autoloader --no-dev",
275
+ commands: ["bun install", "bun run build"],
276
+ entrypoint: "public/index.php",
277
+ assetsDir: "public",
278
+ populateAssetCache: true,
279
+ excludeFiles: ["tests/*", ".ddev", "var"],
280
+ symlinks: { var: "/tmp" },
281
+ routes: [{ source: "/*", destination: "public/index.php" }],
282
+ environmentVariables: {
283
+ APP_ENV: "prod",
284
+ APP_DEBUG: "false",
285
+ APP_SECRET: "please_change_this_value_in_your_settings",
286
+ MESSENGER_TRANSPORT_DSN: "doctrine://default?auto_setup=0"
287
+ }
288
+ })
289
+ };
290
+
291
+ // src/detection/frameworks/vite.ts
292
+ var vite = {
293
+ slug: "vite",
294
+ name: "Vite",
295
+ language: "node",
296
+ detectors: [{ matchPackage: "vite" }],
297
+ priority: 5,
298
+ getDefaultConfig: () => ({
299
+ runtime: "node-22",
300
+ memory: 128,
301
+ maxDuration: 30,
302
+ streaming: false,
303
+ commands: ["vite build"],
304
+ assetsDir: "dist",
305
+ populateAssetCache: true,
306
+ routes: [],
307
+ environmentVariables: { NODE_ENV: "production" }
308
+ })
309
+ };
310
+
311
+ // src/detection/frameworks/index.ts
312
+ var FRAMEWORK_DEFINITIONS = [
313
+ nextjs,
314
+ nuxt,
315
+ nestjs,
316
+ remix,
317
+ sveltekit,
318
+ astro,
319
+ laravel,
320
+ symfony,
321
+ hono,
322
+ elysia,
323
+ fastify,
324
+ express,
325
+ vite
326
+ ].sort((a, b) => b.priority - a.priority);
327
+
328
+ // src/detection/generate-config.ts
329
+ import { Effect as Effect2 } from "effect";
330
+
331
+ // src/regions.ts
332
+ var REGION_DEFINITIONS = [
333
+ "us-east-1",
334
+ "us-east-2",
335
+ "us-west-1",
336
+ "us-west-2",
337
+ "ap-south-1",
338
+ "ap-northeast-3",
339
+ "ap-northeast-2",
340
+ "ap-southeast-1",
341
+ "ap-southeast-2",
342
+ "ap-northeast-1",
343
+ "ca-central-1",
344
+ "eu-central-1",
345
+ "eu-west-1",
346
+ "eu-west-2",
347
+ "eu-west-3",
348
+ "eu-north-1",
349
+ "sa-east-1"
350
+ ];
351
+ var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
352
+
353
+ // src/detection/generate-config.ts
354
+ var getInstallCommand = (pm) => {
355
+ switch (pm) {
356
+ case "bun":
357
+ return "bun install";
358
+ case "pnpm":
359
+ return "pnpm install";
360
+ case "yarn":
361
+ return "yarn install";
362
+ case "composer":
363
+ return "composer install";
364
+ case "npm":
365
+ default:
366
+ return "npm install";
367
+ }
368
+ };
369
+ var generateConfig = Effect2.fn("generateConfig")(function* (framework, packageManager) {
370
+ const defaults = framework.getDefaultConfig(packageManager);
371
+ const installCmd = defaults.installCommand ?? getInstallCommand(packageManager);
372
+ const commands = [installCmd, ...defaults.commands];
373
+ yield* Effect2.logDebug(`Generating config for ${framework.name}`, {
374
+ runtime: defaults.runtime,
375
+ entrypoint: defaults.entrypoint,
376
+ commands
377
+ });
378
+ const entrypoints = defaults.entrypoint ? [
379
+ {
380
+ path: defaults.entrypoint,
381
+ runtime: defaults.runtime,
382
+ memory: defaults.memory,
383
+ maxDuration: defaults.maxDuration,
384
+ streaming: defaults.streaming,
385
+ symlinks: defaults.symlinks
386
+ }
387
+ ] : [];
388
+ const routes = defaults.entrypoint ? defaults.routes.map((route) => ({
389
+ path: route.source,
390
+ destination: route.destination,
391
+ handler: defaults.streaming ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION",
392
+ methods: ["ANY"],
393
+ headers: {}
394
+ })) : [];
395
+ const config = {
396
+ regions: [...AVAILABLE_REGIONS],
397
+ environmentVariables: { ...defaults.environmentVariables },
398
+ commands,
399
+ entrypoints,
400
+ routes,
401
+ excludeFiles: defaults.excludeFiles && defaults.excludeFiles.length > 0 ? [...defaults.excludeFiles] : void 0,
402
+ warnings: [`Auto-detected framework: ${framework.name}. Create a gigadrive.yaml to customize.`],
403
+ errors: []
404
+ };
405
+ if (defaults.assetsDir) {
406
+ config.assets = {
407
+ paths: [],
408
+ prefixToStrip: defaults.assetsDir + "/",
409
+ dynamicRoutes: true,
410
+ populateCache: defaults.populateAssetCache ?? false
411
+ };
412
+ }
413
+ return config;
414
+ });
415
+
416
+ // src/detection/read-dependencies.ts
417
+ import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
418
+ import { Effect as Effect3 } from "effect";
419
+
420
+ // src/detection/types.ts
421
+ import { Schema } from "effect";
422
+ var ManifestNotFoundError = class extends Schema.TaggedError()("ManifestNotFoundError", {
423
+ message: Schema.String,
424
+ filePath: Schema.String
425
+ }) {
426
+ };
427
+ var ManifestReadError = class extends Schema.TaggedError()("ManifestReadError", {
428
+ message: Schema.String,
429
+ filePath: Schema.String
430
+ }) {
431
+ };
432
+ var ManifestParseError = class extends Schema.TaggedError()("ManifestParseError", {
433
+ message: Schema.String,
434
+ filePath: Schema.String
435
+ }) {
436
+ };
437
+ var FrameworkNotDetectedError = class extends Schema.TaggedError()(
438
+ "FrameworkNotDetectedError",
439
+ {
440
+ message: Schema.String,
441
+ directory: Schema.String
442
+ }
443
+ ) {
444
+ };
445
+
446
+ // src/detection/read-dependencies.ts
447
+ var MANIFEST_CONFIG = {
448
+ node: { file: "package.json", depKeys: ["dependencies", "devDependencies"] },
449
+ php: { file: "composer.json", depKeys: ["require", "require-dev"] }
450
+ };
451
+ var readManifest = Effect3.fn("readManifest")(function* (filePath) {
452
+ const fs = yield* FileSystem2.FileSystem;
453
+ const exists = yield* fs.exists(filePath).pipe(Effect3.catchAll(() => Effect3.succeed(false)));
454
+ if (!exists) {
455
+ return yield* Effect3.fail(new ManifestNotFoundError({ message: "Manifest not found", filePath }));
456
+ }
457
+ const content = yield* fs.readFileString(filePath).pipe(Effect3.mapError(() => new ManifestReadError({ message: "Failed to read manifest", filePath })));
458
+ const parsed = yield* Effect3.try({
459
+ try: () => JSON.parse(content),
460
+ catch: () => new ManifestParseError({ message: "Failed to parse manifest JSON", filePath })
461
+ });
462
+ return parsed;
463
+ });
464
+ var readDependencies = Effect3.fn("readDependencies")(function* (projectFolder, language) {
465
+ const pathService = yield* Path2.Path;
466
+ const config = MANIFEST_CONFIG[language];
467
+ const filePath = pathService.join(projectFolder, config.file);
468
+ const manifest = yield* readManifest(filePath).pipe(
469
+ Effect3.catchTag("ManifestNotFoundError", () => Effect3.succeed(null))
470
+ );
471
+ if (!manifest) {
472
+ return /* @__PURE__ */ new Set();
473
+ }
474
+ const deps = /* @__PURE__ */ new Set();
475
+ for (const key of config.depKeys) {
476
+ const section = manifest[key];
477
+ if (section && typeof section === "object" && !Array.isArray(section)) {
478
+ for (const name of Object.keys(section)) {
479
+ deps.add(name);
480
+ }
481
+ }
482
+ }
483
+ return deps;
484
+ });
485
+
486
+ // src/detection/detect-framework.ts
487
+ var matchesDetectionItem = Effect4.fn("matchesDetectionItem")(function* (item, projectFolder, framework, cachedReadDeps) {
488
+ const fs = yield* FileSystem3.FileSystem;
489
+ const pathService = yield* Path3.Path;
490
+ if (item.matchPackage != null) {
491
+ const deps = yield* cachedReadDeps(`${projectFolder}\0${framework.language}`);
492
+ if (!deps.has(item.matchPackage)) {
493
+ return false;
494
+ }
495
+ }
496
+ if (item.path != null) {
497
+ const filePath = pathService.join(projectFolder, item.path);
498
+ const exists = yield* fs.exists(filePath).pipe(Effect4.catchAll(() => Effect4.succeed(false)));
499
+ if (!exists) {
500
+ return false;
10
501
  }
502
+ if (item.matchContent != null) {
503
+ const content = yield* fs.readFileString(filePath).pipe(Effect4.catchAll(() => Effect4.succeed("")));
504
+ let regexMatch = false;
505
+ try {
506
+ regexMatch = new RegExp(item.matchContent).test(content);
507
+ } catch {
508
+ }
509
+ if (!regexMatch) {
510
+ return false;
511
+ }
512
+ }
513
+ }
514
+ return true;
515
+ });
516
+ var matchesFramework = Effect4.fn("matchesFramework")(function* (framework, projectFolder, cachedReadDeps) {
517
+ for (const detector of framework.detectors) {
518
+ const matches = yield* matchesDetectionItem(detector, projectFolder, framework, cachedReadDeps);
519
+ if (!matches) {
520
+ return false;
521
+ }
522
+ }
523
+ return true;
524
+ });
525
+ var detectFramework = Effect4.fn("detectFramework")(function* (projectFolder) {
526
+ yield* Effect4.logDebug(`Detecting framework in ${projectFolder}`);
527
+ const cachedReadDeps = yield* Effect4.cachedFunction((key) => {
528
+ const sepIndex = key.lastIndexOf("\0");
529
+ const folder = key.slice(0, sepIndex);
530
+ const language = key.slice(sepIndex + 1);
531
+ return readDependencies(folder, language);
532
+ });
533
+ for (const framework of FRAMEWORK_DEFINITIONS) {
534
+ const matches = yield* matchesFramework(framework, projectFolder, cachedReadDeps);
535
+ if (matches) {
536
+ yield* Effect4.log(`Detected framework: ${framework.name}`, { slug: framework.slug });
537
+ const packageManager = yield* detectPackageManager(projectFolder);
538
+ const config = yield* generateConfig(framework, packageManager);
539
+ return { framework, packageManager, config };
540
+ }
541
+ }
542
+ return yield* Effect4.fail(
543
+ new FrameworkNotDetectedError({
544
+ message: `No framework detected in project directory`,
545
+ directory: projectFolder
546
+ })
547
+ );
548
+ });
549
+
550
+ // src/detection/merge-config.ts
551
+ import { Effect as Effect5 } from "effect";
552
+ var mergeWithFrameworkDefaults = Effect5.fn("mergeWithFrameworkDefaults")(function* (userConfig, frameworkConfig) {
553
+ yield* Effect5.logDebug("Merging user config with framework defaults");
554
+ const merged = {
555
+ regions: userConfig.regions,
556
+ commands: userConfig.commands.length > 0 ? userConfig.commands : frameworkConfig.commands,
557
+ entrypoints: userConfig.entrypoints.length > 0 ? userConfig.entrypoints : frameworkConfig.entrypoints,
558
+ routes: userConfig.routes.length > 0 ? userConfig.routes : frameworkConfig.routes,
559
+ assets: userConfig.assets?.paths && userConfig.assets.paths.length > 0 ? userConfig.assets : frameworkConfig.assets,
560
+ environmentVariables: {
561
+ ...frameworkConfig.environmentVariables,
562
+ ...userConfig.environmentVariables
563
+ },
564
+ excludeFiles: userConfig.excludeFiles && userConfig.excludeFiles.length > 0 ? userConfig.excludeFiles : frameworkConfig.excludeFiles,
565
+ services: userConfig.services,
566
+ userArchive: userConfig.userArchive,
567
+ warnings: [...frameworkConfig.warnings, ...userConfig.warnings],
568
+ errors: [...frameworkConfig.errors, ...userConfig.errors]
569
+ };
570
+ return merged;
571
+ });
572
+
573
+ // src/errors.ts
574
+ import { Schema as Schema2 } from "effect";
575
+ var ConfigFileNotFoundError = class extends Schema2.TaggedError()("ConfigFileNotFoundError", {
576
+ message: Schema2.String,
577
+ filePath: Schema2.String
578
+ }) {
579
+ };
580
+ var ConfigFileEmptyError = class extends Schema2.TaggedError()("ConfigFileEmptyError", {
581
+ message: Schema2.String,
582
+ filePath: Schema2.String
583
+ }) {
584
+ };
585
+ var ConfigFileParseError = class extends Schema2.TaggedError()("ConfigFileParseError", {
586
+ message: Schema2.String,
587
+ filePath: Schema2.String,
588
+ cause: Schema2.optional(Schema2.String)
589
+ }) {
590
+ };
591
+ var ConfigVersionError = class extends Schema2.TaggedError()("ConfigVersionError", {
592
+ message: Schema2.String,
593
+ filePath: Schema2.String,
594
+ version: Schema2.optional(Schema2.Number)
595
+ }) {
596
+ };
597
+ var ConfigSchemaValidationError = class extends Schema2.TaggedError()(
598
+ "ConfigSchemaValidationError",
599
+ {
600
+ message: Schema2.String,
601
+ filePath: Schema2.String,
602
+ validationErrors: Schema2.Array(Schema2.String)
11
603
  }
12
- return null;
604
+ ) {
605
+ };
606
+ var FunctionConfigError = class extends Schema2.TaggedError()("FunctionConfigError", {
607
+ message: Schema2.String,
608
+ functionPath: Schema2.String
609
+ }) {
610
+ };
611
+
612
+ // src/filter-functions-from-assets.ts
613
+ var filterFunctionsFromAssets = (config) => {
614
+ return {
615
+ ...config,
616
+ assets: {
617
+ ...config.assets,
618
+ paths: config.assets?.paths?.filter((path2) => !config.entrypoints.some((entrypoint) => entrypoint.path === path2))
619
+ }
620
+ };
13
621
  };
14
622
 
15
623
  // src/parse-config.ts
624
+ import { Effect as Effect11 } from "effect";
625
+
626
+ // src/services/raw-config-reader.ts
627
+ import { FileSystem as FileSystem4, Path as Path4 } from "@effect/platform";
628
+ import { Effect as Effect6 } from "effect";
629
+ import { parse as parseYaml } from "yaml";
630
+ var ALLOWED_CONFIG_NAMES = [
631
+ "gigadrive.yaml",
632
+ "gigadrive.yml",
633
+ "nebula.yaml",
634
+ "nebula.yml",
635
+ "nebula.json"
636
+ ];
637
+ var RawConfigReader = class extends Effect6.Service()("RawConfigReader", {
638
+ effect: Effect6.gen(function* () {
639
+ const fs = yield* FileSystem4.FileSystem;
640
+ const pathService = yield* Path4.Path;
641
+ const findConfig = Effect6.fn("RawConfigReader.findConfig")(function* (projectFolder) {
642
+ for (const name of ALLOWED_CONFIG_NAMES) {
643
+ const filePath = pathService.join(projectFolder, name);
644
+ const exists = yield* fs.exists(filePath).pipe(Effect6.catchAll(() => Effect6.succeed(false)));
645
+ if (exists) {
646
+ return filePath;
647
+ }
648
+ }
649
+ return null;
650
+ });
651
+ const readRawConfig = Effect6.fn("RawConfigReader.readRawConfig")(function* (filePath, options) {
652
+ const exists = yield* fs.exists(filePath).pipe(Effect6.catchAll(() => Effect6.succeed(false)));
653
+ if (!exists) {
654
+ return yield* Effect6.fail(
655
+ new ConfigFileNotFoundError({ message: `Config file not found at ${filePath}`, filePath })
656
+ );
657
+ }
658
+ const fileContents = yield* fs.readFileString(filePath).pipe(
659
+ Effect6.mapError(
660
+ () => new ConfigFileParseError({ message: `Failed to read config file at ${filePath}`, filePath })
661
+ )
662
+ );
663
+ if (fileContents.length === 0) {
664
+ return yield* Effect6.fail(
665
+ new ConfigFileEmptyError({ message: `Config file is empty at ${filePath}`, filePath })
666
+ );
667
+ }
668
+ const fileExtension = pathService.extname(filePath).toLowerCase();
669
+ let parsed;
670
+ try {
671
+ if (fileExtension === ".json") {
672
+ parsed = JSON.parse(fileContents);
673
+ } else if ([".yml", ".yaml"].includes(fileExtension)) {
674
+ parsed = parseYaml(fileContents);
675
+ } else {
676
+ parsed = parseYaml(fileContents);
677
+ }
678
+ } catch (error) {
679
+ return yield* Effect6.fail(
680
+ new ConfigFileParseError({
681
+ message: `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`,
682
+ filePath,
683
+ cause: error instanceof Error ? error.message : void 0
684
+ })
685
+ );
686
+ }
687
+ if (parsed == null) {
688
+ return yield* Effect6.fail(
689
+ new ConfigFileParseError({ message: `Config file could not be parsed at ${filePath}`, filePath })
690
+ );
691
+ }
692
+ if (!options?.disableVersionCheck && typeof parsed.version !== "number") {
693
+ return yield* Effect6.fail(
694
+ new ConfigVersionError({ message: `Config file is missing version at ${filePath}`, filePath })
695
+ );
696
+ }
697
+ return parsed;
698
+ });
699
+ const readConfigFile = (configFilePath) => readRawConfig(configFilePath, { disableVersionCheck: true }).pipe(
700
+ Effect6.map((parsed) => parsed),
701
+ Effect6.catchTag("ConfigFileNotFoundError", () => Effect6.succeed(null)),
702
+ Effect6.withSpan("RawConfigReader.readConfigFile")
703
+ );
704
+ return { findConfig, readRawConfig, readConfigFile };
705
+ })
706
+ }) {
707
+ };
708
+
709
+ // src/services/schema-validator.ts
16
710
  import Ajv from "ajv";
17
711
  import addFormats from "ajv-formats";
712
+ import { Effect as Effect7 } from "effect";
713
+ var SchemaValidator = class extends Effect7.Service()("SchemaValidator", {
714
+ effect: Effect7.gen(function* () {
715
+ const ajv = new Ajv();
716
+ addFormats(ajv);
717
+ const validate = Effect7.fn("SchemaValidator.validate")(function* (config, schema, filePath) {
718
+ const compiledValidate = ajv.compile(schema);
719
+ const valid = compiledValidate(config);
720
+ if (!valid) {
721
+ const errors = compiledValidate.errors?.map((e) => `${e.instancePath || "/"} ${e.message ?? ""}`.trim()) ?? [];
722
+ return yield* Effect7.fail(
723
+ new ConfigSchemaValidationError({
724
+ message: `Config file at ${filePath} is invalid: ${ajv.errorsText(compiledValidate.errors)}`,
725
+ filePath,
726
+ validationErrors: errors
727
+ })
728
+ );
729
+ }
730
+ });
731
+ return { validate };
732
+ })
733
+ }) {
734
+ };
18
735
 
19
- // src/build-output-v3/parse.ts
736
+ // src/services/v4-config-parser.ts
737
+ import { FileSystem as FileSystem5, Path as Path5 } from "@effect/platform";
20
738
  import { getFilesForPattern } from "@gigadrive/build-utils";
739
+ import { Effect as Effect8 } from "effect";
740
+ import { minimatch } from "minimatch";
741
+ import safeRegex from "safe-regex2";
742
+ var MAX_ASSET_DEPTH = 100;
743
+ var DISALLOWED_ASSET_EXTENSIONS = [".htaccess", ".htpasswd"];
744
+ var DEFAULT_FUNCTION_SETTINGS = {
745
+ memory: 128,
746
+ max_duration: 30,
747
+ schedule: void 0,
748
+ symlinks: void 0,
749
+ excludeFiles: void 0,
750
+ includeFiles: void 0
751
+ };
752
+ var matchesPattern = (path2, pattern) => {
753
+ if (minimatch(path2, pattern)) return true;
754
+ try {
755
+ if (safeRegex(pattern)) {
756
+ return new RegExp(pattern).test(path2);
757
+ }
758
+ } catch {
759
+ }
760
+ return false;
761
+ };
762
+ var getFunctionSettings = (path2, config) => {
763
+ let settings;
764
+ for (const [pattern, value] of Object.entries(config.functions ?? {})) {
765
+ if (!matchesPattern(path2, pattern)) continue;
766
+ settings = { ...DEFAULT_FUNCTION_SETTINGS, ...settings, ...value };
767
+ }
768
+ return settings;
769
+ };
770
+ var resolveRouteHandler = (destination, redirect) => {
771
+ const isExternal = destination.toLowerCase().startsWith("http://") || destination.toLowerCase().startsWith("https://");
772
+ if (redirect === true) return "HTTP_REDIRECT";
773
+ return isExternal ? "HTTP_PROXY" : "SERVERLESS_FUNCTION";
774
+ };
775
+ var isRegion = (r) => AVAILABLE_REGIONS.includes(r);
776
+ var resolveRegions = (configRegions) => {
777
+ if (configRegions?.includes("global") === true) return AVAILABLE_REGIONS;
778
+ const valid = configRegions?.filter(isRegion);
779
+ return valid && valid.length > 0 ? valid : AVAILABLE_REGIONS;
780
+ };
781
+ var mapRoute = (route) => ({
782
+ path: route.source,
783
+ destination: route.destination,
784
+ handler: resolveRouteHandler(route.destination, route.redirect),
785
+ headers: route.headers ?? {},
786
+ methods: route.methods ?? ["ANY"],
787
+ positiveRequirements: route.has,
788
+ negativeRequirements: route.missing,
789
+ status: route.statusCode
790
+ });
791
+ var parseEntrypoints = Effect8.fn("parseEntrypoints")(function* (config, projectFolder) {
792
+ const entrypoints = [];
793
+ if (config.functions == null) return entrypoints;
794
+ for (const [fnPath, func] of Object.entries(config.functions)) {
795
+ const settings = getFunctionSettings(fnPath, config);
796
+ if (settings == null) {
797
+ yield* new FunctionConfigError({
798
+ message: `Settings invalid for function at path '${fnPath}'`,
799
+ functionPath: fnPath
800
+ });
801
+ }
802
+ const matchedFiles = yield* Effect8.tryPromise({
803
+ try: () => getFilesForPattern(fnPath, projectFolder, func.excludeFiles),
804
+ catch: (error) => new FunctionConfigError({
805
+ message: `Failed to resolve files for function pattern '${fnPath}': ${error instanceof Error ? error.message : String(error)}`,
806
+ functionPath: fnPath
807
+ })
808
+ });
809
+ for (const file of matchedFiles) {
810
+ if (entrypoints.some((ep) => ep.path === file)) continue;
811
+ if (Object.keys(config.functions).some((f) => f === file && f !== fnPath)) continue;
812
+ entrypoints.push({
813
+ path: file,
814
+ runtime: settings.runtime ?? "node-20",
815
+ memory: settings.memory ?? 128,
816
+ maxDuration: settings.max_duration ?? 30,
817
+ schedule: func.schedule,
818
+ symlinks: func.symlinks,
819
+ streaming: settings.runtime == null || settings.runtime.startsWith("node-") || settings.runtime.startsWith("bun-")
820
+ });
821
+ }
822
+ }
823
+ return entrypoints;
824
+ });
825
+ var collectAssets = Effect8.fn("collectAssets")(function* (config, projectFolder) {
826
+ if (config.assets == null) return [];
827
+ const fs = yield* FileSystem5.FileSystem;
828
+ const pathService = yield* Path5.Path;
829
+ const assetsPath = pathService.join(projectFolder, config.assets);
830
+ const assetsExist = yield* fs.exists(assetsPath).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
831
+ if (!assetsExist) return [];
832
+ const stat = yield* fs.stat(assetsPath).pipe(Effect8.catchAll(() => Effect8.succeed(null)));
833
+ if (!stat || stat.type !== "Directory") return [];
834
+ const allFiles = yield* collectFilesRecursively(assetsPath);
835
+ const assets = [];
836
+ for (const assetName of allFiles) {
837
+ if (DISALLOWED_ASSET_EXTENSIONS.some((ext) => assetName.toLowerCase().endsWith(ext.toLowerCase()))) continue;
838
+ if (getFunctionSettings(`${config.assets}/${assetName}`, config) != null) continue;
839
+ assets.push(`${config.assets}/${assetName}`);
840
+ }
841
+ return assets;
842
+ });
843
+ var collectFilesRecursively = Effect8.fn("collectFilesRecursively")(
844
+ function* (basePath, relativePath = "", depth = 0) {
845
+ if (depth > MAX_ASSET_DEPTH) return [];
846
+ const fs = yield* FileSystem5.FileSystem;
847
+ const pathSvc = yield* Path5.Path;
848
+ const currentPath = relativePath ? pathSvc.join(basePath, relativePath) : basePath;
849
+ const entries = yield* fs.readDirectory(currentPath).pipe(Effect8.catchAll(() => Effect8.succeed([])));
850
+ const result = [];
851
+ for (const name of entries) {
852
+ const fullPath = pathSvc.join(currentPath, name);
853
+ const entryRelative = relativePath ? pathSvc.join(relativePath, name) : name;
854
+ const stat = yield* fs.stat(fullPath).pipe(Effect8.catchAll(() => Effect8.succeed(null)));
855
+ if (!stat) continue;
856
+ if (stat.type === "Directory") {
857
+ const nested = yield* collectFilesRecursively(basePath, entryRelative, depth + 1);
858
+ result.push(...nested);
859
+ } else {
860
+ result.push(entryRelative);
861
+ }
862
+ }
863
+ return result;
864
+ }
865
+ );
866
+ var V4ConfigParser = class extends Effect8.Service()("V4ConfigParser", {
867
+ accessors: true,
868
+ effect: Effect8.succeed({
869
+ /**
870
+ * Parses a ConfigV4 into a NormalizedConfig.
871
+ *
872
+ * @param config - The raw V4 config object
873
+ * @param projectFolder - Absolute path to the project root
874
+ */
875
+ parse: Effect8.fn("V4ConfigParser.parse")(function* (config, projectFolder) {
876
+ const entrypoints = yield* parseEntrypoints(config, projectFolder);
877
+ const assets = yield* collectAssets(config, projectFolder);
878
+ return {
879
+ regions: resolveRegions(config.regions),
880
+ assets: {
881
+ paths: assets.sort(),
882
+ prefixToStrip: (config.assets ?? "") + "/",
883
+ dynamicRoutes: true,
884
+ populateCache: config.populateAssetCache ?? false
885
+ },
886
+ environmentVariables: config.env ?? {},
887
+ commands: config.build_commands ?? [],
888
+ entrypoints,
889
+ errors: [],
890
+ warnings: [],
891
+ routes: (config.routes ?? []).map(mapRoute)
892
+ };
893
+ })
894
+ })
895
+ }) {
896
+ };
897
+
898
+ // src/services/vercel-build-output-parser.ts
899
+ import { Path as Path7 } from "@effect/platform";
900
+ import { getFilesForPattern as getFilesForPattern2 } from "@gigadrive/build-utils";
21
901
  import { deepMerge } from "@gigadrive/commons";
22
- import path5 from "path";
902
+ import { Effect as Effect10 } from "effect";
23
903
 
24
904
  // src/build-output-v3/determine-repo-root.ts
25
- import path2 from "path";
905
+ import path from "path";
26
906
  var determineRepoRoot = (projectFolder, config) => {
27
907
  if (!config.entrypoints?.length) {
28
908
  return projectFolder;
@@ -36,7 +916,7 @@ var determineRepoRoot = (projectFolder, config) => {
36
916
  if (filePaths.length === 0) {
37
917
  return projectFolder;
38
918
  }
39
- const pathParts = filePaths.map((p) => p.split(path2.sep));
919
+ const pathParts = filePaths.map((p) => p.split(path.sep));
40
920
  const firstPath = pathParts[0];
41
921
  const commonParts = [];
42
922
  for (let i = 0; i < firstPath.length; i++) {
@@ -47,7 +927,7 @@ var determineRepoRoot = (projectFolder, config) => {
47
927
  break;
48
928
  }
49
929
  }
50
- const commonRoot = commonParts.join(path2.sep);
930
+ const commonRoot = commonParts.join(path.sep);
51
931
  if (commonRoot && projectFolder.startsWith(commonRoot) && commonRoot !== projectFolder) {
52
932
  return commonRoot;
53
933
  }
@@ -55,28 +935,35 @@ var determineRepoRoot = (projectFolder, config) => {
55
935
  };
56
936
 
57
937
  // src/build-output-v3/get-default-path-map.ts
58
- import fs2 from "fs";
59
- import path3 from "path";
60
- var getDefaultPathMap = (directory) => {
938
+ import { FileSystem as FileSystem6, Path as Path6 } from "@effect/platform";
939
+ import { Effect as Effect9 } from "effect";
940
+ var getDefaultPathMap = Effect9.fn("getDefaultPathMap")(function* (directory) {
941
+ const fs = yield* FileSystem6.FileSystem;
942
+ const pathService = yield* Path6.Path;
61
943
  const result = {};
62
- if (!fs2.existsSync(directory)) {
944
+ const exists = yield* fs.exists(directory).pipe(Effect9.catchAll(() => Effect9.succeed(false)));
945
+ if (!exists) {
63
946
  return result;
64
947
  }
65
- const processDirectory = (currentPath, basePath = "") => {
66
- const files = fs2.readdirSync(currentPath, { withFileTypes: true });
67
- for (const file of files) {
68
- const fullPath = path3.join(currentPath, file.name);
69
- const relativePath = path3.join(basePath, file.name);
70
- if (file.isDirectory()) {
71
- processDirectory(fullPath, relativePath);
948
+ const MAX_DEPTH = 100;
949
+ const processDirectory = (currentPath, basePath = "", depth = 0) => Effect9.gen(function* () {
950
+ if (depth > MAX_DEPTH) return;
951
+ const entries = yield* fs.readDirectory(currentPath).pipe(Effect9.catchAll(() => Effect9.succeed([])));
952
+ for (const name of entries) {
953
+ const fullPath = pathService.join(currentPath, name);
954
+ const relativePath = basePath ? pathService.join(basePath, name) : name;
955
+ const stat = yield* fs.stat(fullPath).pipe(Effect9.catchAll(() => Effect9.succeed(null)));
956
+ if (!stat) continue;
957
+ if (stat.type === "Directory") {
958
+ yield* processDirectory(fullPath, relativePath, depth + 1);
72
959
  } else {
73
960
  result[fullPath] = relativePath;
74
961
  }
75
962
  }
76
- };
77
- processDirectory(directory);
963
+ });
964
+ yield* processDirectory(directory);
78
965
  return result;
79
- };
966
+ });
80
967
 
81
968
  // src/build-output-v3/get-monorepo-files.ts
82
969
  function getMonorepoFiles(directory, filePathMaps) {
@@ -94,56 +981,6 @@ function getMonorepoFiles(directory, filePathMaps) {
94
981
  return result;
95
982
  }
96
983
 
97
- // src/build-output-v3/read-config-file.ts
98
- import fs4 from "fs";
99
-
100
- // src/parse-raw-config.ts
101
- import fs3 from "fs/promises";
102
- import path4 from "path";
103
- import { parse as parseYaml } from "yaml";
104
- var parseRawConfig = async (filePath, { disableVersionCheck = false } = {}) => {
105
- try {
106
- await fs3.access(filePath);
107
- } catch {
108
- throw new Error(`Config file not found at ${filePath}`);
109
- }
110
- const fileContents = await fs3.readFile(filePath, "utf8");
111
- if (fileContents.length === 0) {
112
- throw new Error(`Config file is empty at ${filePath}`);
113
- }
114
- const fileExtension = path4.extname(filePath).toLowerCase();
115
- let parsed;
116
- try {
117
- if (fileExtension === ".json") {
118
- parsed = JSON.parse(fileContents);
119
- } else if ([".yml", ".yaml"].includes(fileExtension)) {
120
- parsed = parseYaml(fileContents);
121
- } else {
122
- parsed = parseYaml(fileContents);
123
- }
124
- } catch (error) {
125
- throw new Error(
126
- `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
127
- );
128
- }
129
- if (parsed === void 0) {
130
- throw new Error(`Config file could not be parsed at ${filePath}`);
131
- }
132
- if (!disableVersionCheck && typeof parsed.version !== "number") {
133
- throw new Error(`Config file is missing version at ${filePath}`);
134
- }
135
- return parsed;
136
- };
137
-
138
- // src/build-output-v3/read-config-file.ts
139
- var readConfigFile = async (configFilePath) => {
140
- if (!fs4.existsSync(configFilePath)) {
141
- return null;
142
- }
143
- const parsed = await parseRawConfig(configFilePath, { disableVersionCheck: true });
144
- return parsed;
145
- };
146
-
147
984
  // src/build-output-v3/translate-vercel-region.ts
148
985
  var translateVercelRegion = (region) => {
149
986
  const regionMap = {
@@ -185,284 +1022,141 @@ var translateVercelRuntime = (runtime) => {
185
1022
  }
186
1023
  };
187
1024
 
188
- // src/build-output-v3/parse.ts
189
- var configPath = ".vercel/output/config.json";
190
- var functionsFolder = ".vercel/output/functions";
191
- var functionConfigsPattern = `${functionsFolder}/**/*.func/.vc-config.json`;
192
- var parseVercelBuildOutputV3 = async (parseResult, projectFolder) => {
193
- const config = await readConfigFile(path5.join(projectFolder, configPath));
194
- if (!config || config.version < 3) {
195
- return parseResult;
196
- }
197
- const functionData = await loadFunctions(config, parseResult, projectFolder);
198
- const assetOverrides = loadAssetOverrides(config, parseResult);
199
- return deepMerge(parseResult, functionData, assetOverrides);
200
- };
201
- var loadAssetOverrides = (config, parseResult) => {
202
- return {
203
- assets: {
204
- overrides: {
205
- ...parseResult.assets?.overrides ?? {},
206
- ...config.overrides
207
- }
208
- }
209
- };
210
- };
211
- var loadFunctions = async (config, parseResult, projectFolder) => {
212
- const functionConfigs = await getFilesForPattern(functionConfigsPattern, projectFolder);
213
- const result = {
214
- entrypoints: [],
215
- routes: [],
216
- regions: []
217
- };
218
- for (const functionConfigPath of functionConfigs) {
219
- const functionConfig = await readConfigFile(path5.join(projectFolder, functionConfigPath));
220
- if (!functionConfig) {
221
- parseResult.errors.push(`Failed to read function config at ${functionConfigPath}`);
222
- continue;
223
- }
224
- const functionPathRelative = path5.dirname(functionConfigPath).replace(functionsFolder, "");
225
- const functionName = functionPathRelative.replace(/^\/|\.func$/g, "");
226
- const runtime = translateVercelRuntime(functionConfig.runtime);
227
- if (!runtime) {
228
- parseResult.errors.push(`Unsupported runtime for function ${functionName}: ${functionConfig.runtime}`);
229
- continue;
230
- }
231
- const functionDirectory = `.vercel/output/functions/${functionName}.func`;
232
- const {
233
- handler,
234
- maxDuration,
235
- memory,
236
- regions = [],
237
- environment,
238
- filePathMap: configFilePathMap = {}
239
- } = functionConfig;
240
- const translatedRegions = regions.length > 0 ? regions.map(translateVercelRegion) : ["us-east-1"];
241
- result.regions = Array.from(/* @__PURE__ */ new Set([...parseResult.regions, ...translatedRegions]));
242
- const environmentVariables = {};
243
- if (environment && typeof environment === "object") {
244
- for (const env of Object.values(environment)) {
245
- Object.assign(environmentVariables, env);
246
- }
247
- }
248
- const streaming = runtime.startsWith("node-") || runtime.startsWith("bun-");
249
- let filePathMap = {};
250
- for (const [key, value] of Object.entries(configFilePathMap)) {
251
- filePathMap[path5.join(projectFolder, key)] = value;
252
- }
253
- if (Object.keys(filePathMap).length === 0) {
254
- filePathMap = getDefaultPathMap(path5.join(projectFolder, functionDirectory));
255
- }
256
- if (!result.entrypoints) {
257
- result.entrypoints = [];
258
- }
259
- result.entrypoints.push({
260
- displayName: functionName,
261
- runtime,
262
- path: path5.join(functionDirectory, handler),
263
- memory: memory ?? 1024,
264
- maxDuration: maxDuration ?? 15,
265
- environmentVariables,
266
- streaming,
267
- package: {
268
- filePathMap
1025
+ // src/services/vercel-build-output-parser.ts
1026
+ var CONFIG_PATH = ".vercel/output/config.json";
1027
+ var FUNCTIONS_FOLDER = ".vercel/output/functions";
1028
+ var FUNCTION_CONFIGS_PATTERN = `${FUNCTIONS_FOLDER}/**/*.func/.vc-config.json`;
1029
+ var VercelBuildOutputParser = class extends Effect10.Service()("VercelBuildOutputParser", {
1030
+ effect: Effect10.gen(function* () {
1031
+ const rawConfigReader = yield* RawConfigReader;
1032
+ const pathService = yield* Path7.Path;
1033
+ const parse = Effect10.fn("VercelBuildOutputParser.parse")(function* (parseResult, projectFolder) {
1034
+ const config = yield* rawConfigReader.readConfigFile(pathService.join(projectFolder, CONFIG_PATH));
1035
+ if (!config || typeof config.version !== "number" || config.version < 3) {
1036
+ return parseResult;
269
1037
  }
1038
+ const functionData = yield* loadFunctions(config, parseResult, projectFolder);
1039
+ const assetOverrides = loadAssetOverrides(config, parseResult);
1040
+ const merged = deepMerge(parseResult, functionData, assetOverrides);
1041
+ merged.regions = [...new Set(merged.regions)];
1042
+ return merged;
270
1043
  });
271
- const handlerType = streaming ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION";
272
- const defaultRoute = {
273
- path: `/${functionName}`,
274
- methods: ["ANY"],
275
- headers: {},
276
- destination: handler,
277
- handler: handlerType
278
- };
279
- const configRoutes = config.routes?.filter((r) => r.dest === `/${functionName}`).map(
280
- (r) => ({
281
- path: r.src ?? `/${functionName}`,
282
- methods: ["ANY"],
283
- headers: {},
284
- destination: handler,
285
- handler: handlerType
286
- })
287
- ) ?? [];
288
- if (!result.routes) {
289
- result.routes = [];
290
- }
291
- result.routes.push(defaultRoute, ...configRoutes);
292
- }
293
- return {
294
- ...result,
295
- userArchive: {
296
- rootOverwrite: determineRepoRoot(projectFolder, result),
297
- fileWhitelist: [
298
- `${projectFolder}/*`,
299
- ...getMonorepoFiles(projectFolder, result.entrypoints?.map((e) => e.package?.filePathMap ?? {}) ?? [])
300
- ]
301
- }
302
- };
303
- };
304
-
305
- // src/filter-functions-from-assets.ts
306
- var filterFunctionsFromAssets = (config) => {
307
- return {
308
- ...config,
309
- assets: {
310
- ...config.assets,
311
- paths: config.assets?.paths?.filter((path6) => !config.entrypoints.some((entrypoint) => entrypoint.path === path6))
312
- }
313
- };
314
- };
315
-
316
- // src/v4/parse.ts
317
- import { getFilesForPattern as getFilesForPattern2 } from "@gigadrive/build-utils";
318
- import fs5 from "fs";
319
- import { minimatch } from "minimatch";
320
-
321
- // src/regions.ts
322
- var REGION_DEFINITIONS = [
323
- "us-east-1",
324
- "us-east-2",
325
- "us-west-1",
326
- "us-west-2",
327
- "ap-south-1",
328
- "ap-northeast-3",
329
- "ap-northeast-2",
330
- "ap-southeast-1",
331
- "ap-southeast-2",
332
- "ap-northeast-1",
333
- "ca-central-1",
334
- "eu-central-1",
335
- "eu-west-1",
336
- "eu-west-2",
337
- "eu-west-3",
338
- "eu-north-1",
339
- "sa-east-1"
340
- ];
341
- var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
342
-
343
- // src/v4/parse.ts
344
- var parseConfigV4 = async (config, projectFolder) => {
345
- const regions = config.regions?.includes("global") === true ? AVAILABLE_REGIONS : config.regions?.filter((region) => region !== "global") ?? ["global"];
346
- const populateAssetCache = config.populateAssetCache ?? false;
347
- const environmentVariables = config.env ?? {};
348
- const commands = config.build_commands ?? [];
349
- const entrypoints = [];
350
- const errors = [];
351
- const warnings = [];
352
- if (config.functions != null) {
353
- for (const [path6, func] of Object.entries(config.functions)) {
354
- const settings = getFunctionSettings(path6, config);
355
- if (settings == null) {
356
- throw new Error(`Settings invalid for function at path '${path6}'`);
357
- }
358
- (await getFilesForPattern2(path6, projectFolder, func.excludeFiles)).forEach((file) => {
359
- if (entrypoints.find((entrypoint) => entrypoint.path === file)) {
360
- return;
361
- }
362
- if (config.functions != null && Object.keys(config.functions).find((func2) => func2 === file && func2 !== path6)) {
363
- return;
364
- }
365
- entrypoints.push({
366
- path: file,
367
- runtime: settings.runtime ?? "node-20",
368
- memory: settings.memory ?? 128,
369
- maxDuration: settings.max_duration ?? 30,
370
- schedule: func.schedule,
371
- symlinks: func.symlinks,
372
- streaming: settings.runtime == null || settings.runtime.startsWith("node-") || settings.runtime.startsWith("bun-")
373
- });
1044
+ const loadFunctions = Effect10.fn("VercelBuildOutputParser.loadFunctions")(function* (config, parseResult, projectFolder) {
1045
+ const functionConfigs = yield* Effect10.tryPromise({
1046
+ try: () => getFilesForPattern2(FUNCTION_CONFIGS_PATTERN, projectFolder),
1047
+ catch: (error) => new ConfigFileParseError({
1048
+ message: `Failed to scan for function configs: ${error instanceof Error ? error.message : String(error)}`,
1049
+ filePath: projectFolder
1050
+ })
374
1051
  });
375
- }
376
- }
377
- const assets = [];
378
- if (config.assets != null) {
379
- const assetsPath = `${projectFolder}/${config.assets}`;
380
- if (fs5.existsSync(assetsPath) && fs5.statSync(assetsPath).isDirectory()) {
381
- for (const asset of fs5.readdirSync(`${projectFolder}/${config.assets}`, {
382
- recursive: true
383
- })) {
384
- if (fs5.statSync(`${projectFolder}/${config.assets}/${asset.toString()}`).isDirectory()) {
1052
+ const result = {
1053
+ entrypoints: [],
1054
+ routes: [],
1055
+ regions: [],
1056
+ errors: []
1057
+ };
1058
+ for (const functionConfigPath of functionConfigs) {
1059
+ const functionConfig = yield* rawConfigReader.readConfigFile(
1060
+ pathService.join(projectFolder, functionConfigPath)
1061
+ );
1062
+ if (!functionConfig) {
1063
+ result.errors.push(`Failed to read function config at ${functionConfigPath}`);
385
1064
  continue;
386
1065
  }
387
- const disallowedExtensions = [".htaccess", ".htpasswd"];
388
- const assetName = asset.toString();
389
- if (disallowedExtensions.some((ext) => assetName.toLowerCase().endsWith(ext.toLowerCase()))) {
1066
+ const functionPathRelative = pathService.dirname(functionConfigPath).replace(FUNCTIONS_FOLDER, "");
1067
+ const functionName = functionPathRelative.replace(/^\/|\.func$/g, "");
1068
+ const runtime = translateVercelRuntime(functionConfig.runtime);
1069
+ if (!runtime) {
1070
+ result.errors.push(`Unsupported runtime for function ${functionName}: ${functionConfig.runtime}`);
390
1071
  continue;
391
1072
  }
392
- if (getFunctionSettings(`${config.assets}/${assetName}`, config) != null) {
393
- continue;
1073
+ const functionDirectory = `.vercel/output/functions/${functionName}.func`;
1074
+ const {
1075
+ handler,
1076
+ maxDuration,
1077
+ memory,
1078
+ regions = [],
1079
+ environment,
1080
+ filePathMap: configFilePathMap = {}
1081
+ } = functionConfig;
1082
+ const functionEntrypointPath = pathService.join(functionDirectory, handler);
1083
+ const translatedRegions = regions.length > 0 ? regions.map(translateVercelRegion) : ["us-east-1"];
1084
+ result.regions = Array.from(/* @__PURE__ */ new Set([...result.regions ?? [], ...translatedRegions]));
1085
+ const environmentVariables = {};
1086
+ if (environment && typeof environment === "object") {
1087
+ for (const env of Object.values(environment)) {
1088
+ Object.assign(environmentVariables, env);
1089
+ }
1090
+ }
1091
+ const streaming = runtime.startsWith("node-") || runtime.startsWith("bun-");
1092
+ let filePathMap = {};
1093
+ for (const [key, value] of Object.entries(configFilePathMap)) {
1094
+ filePathMap[pathService.join(projectFolder, key)] = value;
1095
+ }
1096
+ if (Object.keys(filePathMap).length === 0) {
1097
+ filePathMap = yield* getDefaultPathMap(pathService.join(projectFolder, functionDirectory));
1098
+ }
1099
+ if (!result.entrypoints) {
1100
+ result.entrypoints = [];
1101
+ }
1102
+ result.entrypoints.push({
1103
+ displayName: functionName,
1104
+ runtime,
1105
+ path: functionEntrypointPath,
1106
+ memory: memory ?? 1024,
1107
+ maxDuration: maxDuration ?? 15,
1108
+ environmentVariables,
1109
+ streaming,
1110
+ package: {
1111
+ filePathMap
1112
+ }
1113
+ });
1114
+ const handlerType = streaming ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION";
1115
+ const defaultRoute = {
1116
+ path: `/${functionName}`,
1117
+ methods: ["ANY"],
1118
+ headers: {},
1119
+ destination: functionEntrypointPath,
1120
+ handler: handlerType
1121
+ };
1122
+ const configRoutes = config.routes?.filter((r) => "dest" in r && r.dest === `/${functionName}`).map(
1123
+ (r) => ({
1124
+ path: ("src" in r ? r.src : void 0) ?? `/${functionName}`,
1125
+ methods: ["ANY"],
1126
+ headers: {},
1127
+ destination: functionEntrypointPath,
1128
+ handler: handlerType
1129
+ })
1130
+ ) ?? [];
1131
+ if (!result.routes) {
1132
+ result.routes = [];
394
1133
  }
395
- assets.push(`${config.assets}/${assetName}`);
1134
+ result.routes.push(defaultRoute, ...configRoutes);
396
1135
  }
397
- }
398
- }
399
- const routes = config.routes ?? [];
400
- return {
401
- regions,
402
- assets: {
403
- paths: assets.sort(),
404
- prefixToStrip: (config.assets ?? "") + "/",
405
- dynamicRoutes: true,
406
- populateCache: populateAssetCache
407
- },
408
- environmentVariables,
409
- commands,
410
- entrypoints,
411
- errors,
412
- warnings,
413
- routes: routes.map((route) => {
414
- const handler = route.destination.toLowerCase().startsWith("http://") || route.destination.toLowerCase().startsWith("https://") ? route.redirect === true ? "HTTP_REDIRECT" : "HTTP_PROXY" : route.redirect === true ? "HTTP_REDIRECT" : "SERVERLESS_FUNCTION";
415
1136
  return {
416
- path: route.source,
417
- destination: route.destination,
418
- handler,
419
- headers: route.headers ?? {},
420
- methods: route.methods ?? ["ANY"],
421
- positiveRequirements: route.has,
422
- negativeRequirements: route.missing,
423
- status: route.statusCode
1137
+ ...result,
1138
+ userArchive: {
1139
+ rootOverwrite: determineRepoRoot(projectFolder, result),
1140
+ fileWhitelist: [
1141
+ `${projectFolder}/*`,
1142
+ ...getMonorepoFiles(projectFolder, result.entrypoints?.map((e) => e.package?.filePathMap ?? {}) ?? [])
1143
+ ]
1144
+ }
424
1145
  };
425
- })
426
- };
1146
+ });
1147
+ return { parse };
1148
+ })
1149
+ }) {
427
1150
  };
428
- var getFunctionSettings = (path6, config) => {
429
- let functionSettings;
430
- for (const [key, value] of Object.entries(config.functions ?? {})) {
431
- if (minimatch(path6, key) || new RegExp(key).test(path6)) {
432
- if (functionSettings == null) {
433
- functionSettings = {
434
- memory: 128,
435
- max_duration: 30,
436
- schedule: void 0,
437
- symlinks: void 0,
438
- excludeFiles: void 0,
439
- includeFiles: void 0
440
- };
441
- }
442
- if (value.runtime != null) {
443
- functionSettings.runtime = value.runtime;
444
- }
445
- if (value.memory != null) {
446
- functionSettings.memory = value.memory;
447
- }
448
- if (value.max_duration != null) {
449
- functionSettings.max_duration = value.max_duration;
450
- }
451
- if (value.schedule != null) {
452
- functionSettings.schedule = value.schedule;
453
- }
454
- if (value.symlinks != null) {
455
- functionSettings.symlinks = value.symlinks;
456
- }
457
- if (value.excludeFiles != null) {
458
- functionSettings.excludeFiles = value.excludeFiles;
459
- }
460
- if (value.includeFiles != null) {
461
- functionSettings.includeFiles = value.includeFiles;
1151
+ var loadAssetOverrides = (config, parseResult) => {
1152
+ return {
1153
+ assets: {
1154
+ overrides: {
1155
+ ...parseResult.assets?.overrides ?? {},
1156
+ ...config.overrides
462
1157
  }
463
1158
  }
464
- }
465
- return functionSettings;
1159
+ };
466
1160
  };
467
1161
 
468
1162
  // src/v4/schema.json
@@ -748,7 +1442,7 @@ var schema_default = {
748
1442
  $defs: {
749
1443
  runtimes: {
750
1444
  type: "string",
751
- enum: ["php-83", "php-82", "php-81", "php-80", "node-20", "node-18", "node-16", "bun-1"]
1445
+ enum: ["php-84", "php-83", "php-81", "node-22", "node-20", "node-18", "node-16", "bun-1"]
752
1446
  },
753
1447
  matchRequirements: {
754
1448
  description: "An array of requirements that are needed to match",
@@ -802,45 +1496,45 @@ var schema_default = {
802
1496
  };
803
1497
 
804
1498
  // src/parse-config.ts
805
- var parseConfig = async (filePath, projectFolder) => {
806
- const parsed = await parseRawConfig(filePath);
1499
+ var parseConfigRaw = Effect11.fn("parseConfigRaw")(function* (filePath, projectFolder) {
1500
+ const rawReader = yield* RawConfigReader;
1501
+ const validator = yield* SchemaValidator;
1502
+ const v4Parser = yield* V4ConfigParser;
1503
+ const parsed = yield* rawReader.readRawConfig(filePath);
807
1504
  const version = parsed.version;
808
1505
  const schemaMap = {
809
- /*1: schemaV1,
810
- 2: schemaV2,
811
- 3: schemaV3,*/
812
1506
  4: schema_default
813
1507
  };
814
- const relativePath = filePath.replace("/tmp/unzipped/", "");
815
1508
  const schema = schemaMap[version];
816
1509
  if (!schema) {
817
- throw new Error(`Config file at ${relativePath} has an unsupported version: ${version}`);
818
- }
819
- const ajv = new Ajv();
820
- addFormats(ajv);
821
- const validate = ajv.compile(schema);
822
- const valid = validate(parsed);
823
- if (!valid) {
824
- throw new Error(`Config file at ${relativePath} is invalid: ${ajv.errorsText(validate.errors)}`);
1510
+ return yield* Effect11.fail(
1511
+ new ConfigVersionError({
1512
+ message: `Config file at ${filePath} has an unsupported version: ${version}`,
1513
+ filePath,
1514
+ version
1515
+ })
1516
+ );
825
1517
  }
826
- const parserMap = {
827
- /*1: parseConfigV1,
828
- 2: parseConfigV2,
829
- 3: parseConfigV3,*/
830
- 4: parseConfigV4
831
- };
832
- let parseResult = await parserMap[version](parsed, projectFolder);
833
- parseResult = await parseVercelBuildOutputV3(parseResult, projectFolder);
834
- if (parseResult.entrypoints.length === 0 && (parseResult.assets?.paths?.length || 0) === 0) {
835
- parseResult.warnings.push("The current project does not have any functions or assets to deploy.");
836
- } else if (parseResult.entrypoints.length === 0 && (parseResult.assets?.paths?.length || 0) === 0 && parseResult.routes.length === 0) {
837
- parseResult.errors.push(
1518
+ yield* validator.validate(parsed, schema, filePath);
1519
+ return yield* v4Parser.parse(parsed, projectFolder);
1520
+ });
1521
+ var parseConfig = Effect11.fn("parseConfig")(function* (filePath, projectFolder) {
1522
+ const parseResult = yield* parseConfigRaw(filePath, projectFolder);
1523
+ return yield* postProcessConfig(parseResult, projectFolder);
1524
+ });
1525
+ var postProcessConfig = Effect11.fn("postProcessConfig")(function* (config, projectFolder) {
1526
+ const vercelParser = yield* VercelBuildOutputParser;
1527
+ let result = yield* vercelParser.parse(config, projectFolder);
1528
+ if (result.entrypoints.length === 0 && (result.assets?.paths?.length || 0) === 0 && result.routes.length === 0) {
1529
+ result.errors.push(
838
1530
  "The current project config does not resolve to any functions, assets or routes and can not be deployed."
839
1531
  );
1532
+ } else if (result.entrypoints.length === 0 && (result.assets?.paths?.length || 0) === 0) {
1533
+ result.warnings.push("The current project does not have any functions or assets to deploy.");
840
1534
  }
841
- parseResult = filterFunctionsFromAssets(parseResult);
842
- return parseResult;
843
- };
1535
+ result = filterFunctionsFromAssets(result);
1536
+ return result;
1537
+ });
844
1538
 
845
1539
  // src/runtime.ts
846
1540
  var RUNTIME_DEFINITIONS = [
@@ -854,12 +1548,40 @@ var RUNTIME_DEFINITIONS = [
854
1548
  "bun-1"
855
1549
  ];
856
1550
  var AVAILABLE_RUNTIMES = [...RUNTIME_DEFINITIONS];
1551
+
1552
+ // src/services/index.ts
1553
+ import { Layer } from "effect";
1554
+ var BaseLayers = Layer.mergeAll(RawConfigReader.Default, SchemaValidator.Default, V4ConfigParser.Default);
1555
+ var NetworkConfigLive = VercelBuildOutputParser.Default.pipe(Layer.provideMerge(BaseLayers));
857
1556
  export {
858
1557
  ALLOWED_CONFIG_NAMES,
859
1558
  AVAILABLE_REGIONS,
860
1559
  AVAILABLE_RUNTIMES,
861
- findConfig,
1560
+ ConfigFileEmptyError,
1561
+ ConfigFileNotFoundError,
1562
+ ConfigFileParseError,
1563
+ ConfigSchemaValidationError,
1564
+ ConfigVersionError,
1565
+ FRAMEWORK_DEFINITIONS,
1566
+ FrameworkNotDetectedError,
1567
+ FunctionConfigError,
1568
+ ManifestNotFoundError,
1569
+ ManifestParseError,
1570
+ ManifestReadError,
1571
+ NetworkConfigLive,
1572
+ RawConfigReader,
1573
+ SchemaValidator,
1574
+ V4ConfigParser,
1575
+ VercelBuildOutputParser,
1576
+ detectFramework,
1577
+ detectPackageManager,
1578
+ filterFunctionsFromAssets,
1579
+ generateConfig,
1580
+ getFunctionSettings,
1581
+ mergeWithFrameworkDefaults,
862
1582
  parseConfig,
863
- parseRawConfig
1583
+ parseConfigRaw,
1584
+ postProcessConfig,
1585
+ readDependencies
864
1586
  };
865
1587
  //# sourceMappingURL=index.mjs.map