@gigadrive/network-config 2.1.1 → 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;
10
494
  }
11
495
  }
12
- return null;
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;
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)
603
+ }
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,9 +916,9 @@ 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
- let commonParts = [];
921
+ const commonParts = [];
42
922
  for (let i = 0; i < firstPath.length; i++) {
43
923
  const part = firstPath[i];
44
924
  if (pathParts.every((parts) => parts[i] === part)) {
@@ -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,54 +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";
102
- import path4 from "path";
103
- import { parse as parseYaml } from "yaml";
104
- var parseRawConfig = async (filePath, { disableVersionCheck = false } = {}) => {
105
- if (!fs3.existsSync(filePath)) {
106
- throw new Error(`Config file not found at ${filePath}`);
107
- }
108
- const fileContents = fs3.readFileSync(filePath, "utf8");
109
- if (fileContents.length === 0) {
110
- throw new Error(`Config file is empty at ${filePath}`);
111
- }
112
- const fileExtension = path4.extname(filePath).toLowerCase();
113
- let parsed;
114
- try {
115
- if (fileExtension === ".json") {
116
- parsed = JSON.parse(fileContents);
117
- } else if ([".yml", ".yaml"].includes(fileExtension)) {
118
- parsed = parseYaml(fileContents);
119
- } else {
120
- parsed = parseYaml(fileContents);
121
- }
122
- } catch (error) {
123
- throw new Error(
124
- `Failed to parse config file at ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`
125
- );
126
- }
127
- if (parsed === void 0) {
128
- throw new Error(`Config file could not be parsed at ${filePath}`);
129
- }
130
- if (!disableVersionCheck && typeof parsed.version !== "number") {
131
- throw new Error(`Config file is missing version at ${filePath}`);
132
- }
133
- return parsed;
134
- };
135
-
136
- // src/build-output-v3/read-config-file.ts
137
- var readConfigFile = async (configFilePath) => {
138
- if (!fs4.existsSync(configFilePath)) {
139
- return null;
140
- }
141
- const parsed = await parseRawConfig(configFilePath, { disableVersionCheck: true });
142
- return parsed;
143
- };
144
-
145
984
  // src/build-output-v3/translate-vercel-region.ts
146
985
  var translateVercelRegion = (region) => {
147
986
  const regionMap = {
@@ -183,284 +1022,141 @@ var translateVercelRuntime = (runtime) => {
183
1022
  }
184
1023
  };
185
1024
 
186
- // src/build-output-v3/parse.ts
187
- var configPath = ".vercel/output/config.json";
188
- var functionsFolder = ".vercel/output/functions";
189
- var functionConfigsPattern = `${functionsFolder}/**/*.func/.vc-config.json`;
190
- var parseVercelBuildOutputV3 = async (parseResult, projectFolder) => {
191
- const config = await readConfigFile(path5.join(projectFolder, configPath));
192
- if (!config || config.version < 3) {
193
- return parseResult;
194
- }
195
- const functionData = await loadFunctions(config, parseResult, projectFolder);
196
- const assetOverrides = await loadAssetOverrides(config, parseResult);
197
- return deepMerge(parseResult, functionData, assetOverrides);
198
- };
199
- var loadAssetOverrides = async (config, parseResult) => {
200
- return {
201
- assets: {
202
- overrides: {
203
- ...parseResult.assets?.overrides ?? {},
204
- ...config.overrides
205
- }
206
- }
207
- };
208
- };
209
- var loadFunctions = async (config, parseResult, projectFolder) => {
210
- const functionConfigs = await getFilesForPattern(functionConfigsPattern, projectFolder);
211
- const result = {
212
- entrypoints: [],
213
- routes: [],
214
- regions: []
215
- };
216
- for (const functionConfigPath of functionConfigs) {
217
- const functionConfig = await readConfigFile(path5.join(projectFolder, functionConfigPath));
218
- if (!functionConfig) {
219
- parseResult.errors.push(`Failed to read function config at ${functionConfigPath}`);
220
- continue;
221
- }
222
- const functionPathRelative = path5.dirname(functionConfigPath).replace(functionsFolder, "");
223
- const functionName = functionPathRelative.replace(/^\/|\.func$/g, "");
224
- const runtime = translateVercelRuntime(functionConfig.runtime);
225
- if (!runtime) {
226
- parseResult.errors.push(`Unsupported runtime for function ${functionName}: ${functionConfig.runtime}`);
227
- continue;
228
- }
229
- const functionDirectory = `.vercel/output/functions/${functionName}.func`;
230
- const {
231
- handler,
232
- maxDuration,
233
- memory,
234
- regions = [],
235
- environment,
236
- filePathMap: configFilePathMap = {}
237
- } = functionConfig;
238
- const translatedRegions = regions.length > 0 ? regions.map(translateVercelRegion) : ["us-east-1"];
239
- result.regions = Array.from(/* @__PURE__ */ new Set([...parseResult.regions, ...translatedRegions]));
240
- const environmentVariables = {};
241
- if (environment && typeof environment === "object") {
242
- for (const env of Object.values(environment)) {
243
- Object.assign(environmentVariables, env);
244
- }
245
- }
246
- const streaming = runtime.startsWith("node-") || runtime.startsWith("bun-");
247
- let filePathMap = {};
248
- for (const [key, value] of Object.entries(configFilePathMap)) {
249
- filePathMap[path5.join(projectFolder, key)] = value;
250
- }
251
- if (Object.keys(filePathMap).length === 0) {
252
- filePathMap = getDefaultPathMap(path5.join(projectFolder, functionDirectory));
253
- }
254
- if (!result.entrypoints) {
255
- result.entrypoints = [];
256
- }
257
- result.entrypoints.push({
258
- displayName: functionName,
259
- runtime,
260
- path: path5.join(functionDirectory, handler),
261
- memory: memory ?? 1024,
262
- maxDuration: maxDuration ?? 15,
263
- environmentVariables,
264
- streaming,
265
- package: {
266
- 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;
267
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;
268
1043
  });
269
- const handlerType = streaming ? "SERVERLESS_FUNCTION_STREAMING" : "SERVERLESS_FUNCTION";
270
- const defaultRoute = {
271
- path: `/${functionName}`,
272
- methods: ["ANY"],
273
- headers: {},
274
- destination: handler,
275
- handler: handlerType
276
- };
277
- const configRoutes = config.routes?.filter((r) => r.dest === `/${functionName}`).map(
278
- (r) => ({
279
- path: r.src ?? `/${functionName}`,
280
- methods: ["ANY"],
281
- headers: {},
282
- destination: handler,
283
- handler: handlerType
284
- })
285
- ) ?? [];
286
- if (!result.routes) {
287
- result.routes = [];
288
- }
289
- result.routes.push(defaultRoute, ...configRoutes);
290
- }
291
- return {
292
- ...result,
293
- userArchive: {
294
- rootOverwrite: determineRepoRoot(projectFolder, result),
295
- fileWhitelist: [
296
- `${projectFolder}/*`,
297
- ...getMonorepoFiles(projectFolder, result.entrypoints?.map((e) => e.package?.filePathMap ?? {}) ?? [])
298
- ]
299
- }
300
- };
301
- };
302
-
303
- // src/filter-functions-from-assets.ts
304
- var filterFunctionsFromAssets = async (config) => {
305
- return {
306
- ...config,
307
- assets: {
308
- ...config.assets,
309
- paths: config.assets?.paths?.filter((path6) => !config.entrypoints.some((entrypoint) => entrypoint.path === path6))
310
- }
311
- };
312
- };
313
-
314
- // src/v4/parse.ts
315
- import { getFilesForPattern as getFilesForPattern2 } from "@gigadrive/build-utils";
316
- import fs5 from "fs";
317
- import { minimatch } from "minimatch";
318
-
319
- // src/regions.ts
320
- var REGION_DEFINITIONS = [
321
- "us-east-1",
322
- "us-east-2",
323
- "us-west-1",
324
- "us-west-2",
325
- "ap-south-1",
326
- "ap-northeast-3",
327
- "ap-northeast-2",
328
- "ap-southeast-1",
329
- "ap-southeast-2",
330
- "ap-northeast-1",
331
- "ca-central-1",
332
- "eu-central-1",
333
- "eu-west-1",
334
- "eu-west-2",
335
- "eu-west-3",
336
- "eu-north-1",
337
- "sa-east-1"
338
- ];
339
- var AVAILABLE_REGIONS = [...REGION_DEFINITIONS];
340
-
341
- // src/v4/parse.ts
342
- var parseConfigV4 = async (config, projectFolder) => {
343
- const regions = config.regions?.includes("global") === true ? AVAILABLE_REGIONS : config.regions?.filter((region) => region !== "global") ?? ["global"];
344
- const populateAssetCache = config.populateAssetCache ?? false;
345
- const environmentVariables = config.env ?? {};
346
- const commands = config.build_commands ?? [];
347
- const entrypoints = [];
348
- const errors = [];
349
- const warnings = [];
350
- if (config.functions != null) {
351
- for (const [path6, func] of Object.entries(config.functions)) {
352
- const settings = getFunctionSettings(path6, config);
353
- if (settings == null) {
354
- throw new Error(`Settings invalid for function at path '${path6}'`);
355
- }
356
- (await getFilesForPattern2(path6, projectFolder, func.excludeFiles)).forEach((file) => {
357
- if (entrypoints.find((entrypoint) => entrypoint.path === file)) {
358
- return;
359
- }
360
- if (config.functions != null && Object.keys(config.functions).find((func2) => func2 === file && func2 !== path6)) {
361
- return;
362
- }
363
- entrypoints.push({
364
- path: file,
365
- runtime: settings.runtime ?? "node-20",
366
- memory: settings.memory ?? 128,
367
- maxDuration: settings.max_duration ?? 30,
368
- schedule: func.schedule,
369
- symlinks: func.symlinks,
370
- streaming: settings.runtime == null || settings.runtime.startsWith("node-") || settings.runtime.startsWith("bun-")
371
- });
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
+ })
372
1051
  });
373
- }
374
- }
375
- const assets = [];
376
- if (config.assets != null) {
377
- const assetsPath = `${projectFolder}/${config.assets}`;
378
- if (fs5.existsSync(assetsPath) && fs5.statSync(assetsPath).isDirectory()) {
379
- for (const asset of fs5.readdirSync(`${projectFolder}/${config.assets}`, {
380
- recursive: true
381
- })) {
382
- if (fs5.statSync(`${projectFolder}/${config.assets}/${asset}`).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}`);
383
1064
  continue;
384
1065
  }
385
- const disallowedExtensions = [".htaccess", ".htpasswd"];
386
- const assetName = asset.toString();
387
- 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}`);
388
1071
  continue;
389
1072
  }
390
- if (getFunctionSettings(`${config.assets}/${assetName}`, config) != null) {
391
- 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
+ }
392
1090
  }
393
- assets.push(`${config.assets}/${assetName}`);
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 = [];
1133
+ }
1134
+ result.routes.push(defaultRoute, ...configRoutes);
394
1135
  }
395
- }
396
- }
397
- const routes = config.routes ?? [];
398
- return {
399
- regions,
400
- assets: {
401
- paths: assets.sort(),
402
- prefixToStrip: (config.assets ?? "") + "/",
403
- dynamicRoutes: true,
404
- populateCache: populateAssetCache
405
- },
406
- environmentVariables,
407
- commands,
408
- entrypoints,
409
- errors,
410
- warnings,
411
- routes: routes.map((route) => {
412
- 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";
413
1136
  return {
414
- path: route.source,
415
- destination: route.destination,
416
- handler,
417
- headers: route.headers ?? {},
418
- methods: route.methods ?? ["ANY"],
419
- positiveRequirements: route.has,
420
- negativeRequirements: route.missing,
421
- 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
+ }
422
1145
  };
423
- })
424
- };
1146
+ });
1147
+ return { parse };
1148
+ })
1149
+ }) {
425
1150
  };
426
- var getFunctionSettings = (path6, config) => {
427
- let functionSettings;
428
- for (const [key, value] of Object.entries(config.functions ?? {})) {
429
- if (minimatch(path6, key) || new RegExp(key).test(path6)) {
430
- if (functionSettings == null) {
431
- functionSettings = {
432
- memory: 128,
433
- max_duration: 30,
434
- schedule: void 0,
435
- symlinks: void 0,
436
- excludeFiles: void 0,
437
- includeFiles: void 0
438
- };
439
- }
440
- if (value.runtime != null) {
441
- functionSettings.runtime = value.runtime;
442
- }
443
- if (value.memory != null) {
444
- functionSettings.memory = value.memory;
445
- }
446
- if (value.max_duration != null) {
447
- functionSettings.max_duration = value.max_duration;
448
- }
449
- if (value.schedule != null) {
450
- functionSettings.schedule = value.schedule;
451
- }
452
- if (value.symlinks != null) {
453
- functionSettings.symlinks = value.symlinks;
454
- }
455
- if (value.excludeFiles != null) {
456
- functionSettings.excludeFiles = value.excludeFiles;
457
- }
458
- if (value.includeFiles != null) {
459
- functionSettings.includeFiles = value.includeFiles;
1151
+ var loadAssetOverrides = (config, parseResult) => {
1152
+ return {
1153
+ assets: {
1154
+ overrides: {
1155
+ ...parseResult.assets?.overrides ?? {},
1156
+ ...config.overrides
460
1157
  }
461
1158
  }
462
- }
463
- return functionSettings;
1159
+ };
464
1160
  };
465
1161
 
466
1162
  // src/v4/schema.json
@@ -746,7 +1442,7 @@ var schema_default = {
746
1442
  $defs: {
747
1443
  runtimes: {
748
1444
  type: "string",
749
- 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"]
750
1446
  },
751
1447
  matchRequirements: {
752
1448
  description: "An array of requirements that are needed to match",
@@ -800,45 +1496,45 @@ var schema_default = {
800
1496
  };
801
1497
 
802
1498
  // src/parse-config.ts
803
- var parseConfig = async (filePath, projectFolder) => {
804
- 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);
805
1504
  const version = parsed.version;
806
1505
  const schemaMap = {
807
- /*1: schemaV1,
808
- 2: schemaV2,
809
- 3: schemaV3,*/
810
1506
  4: schema_default
811
1507
  };
812
- const relativePath = filePath.replace("/tmp/unzipped/", "");
813
1508
  const schema = schemaMap[version];
814
1509
  if (!schema) {
815
- throw new Error(`Config file at ${relativePath} has an unsupported version: ${version}`);
816
- }
817
- const ajv = new Ajv();
818
- addFormats(ajv);
819
- const validate = ajv.compile(schema);
820
- const valid = validate(parsed);
821
- if (!valid) {
822
- 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
+ );
823
1517
  }
824
- const parserMap = {
825
- /*1: parseConfigV1,
826
- 2: parseConfigV2,
827
- 3: parseConfigV3,*/
828
- 4: parseConfigV4
829
- };
830
- let parseResult = await parserMap[version](parsed, projectFolder);
831
- parseResult = await parseVercelBuildOutputV3(parseResult, projectFolder);
832
- if (parseResult.entrypoints.length === 0 && (parseResult.assets?.paths?.length || 0) === 0) {
833
- parseResult.warnings.push("The current project does not have any functions or assets to deploy.");
834
- } else if (parseResult.entrypoints.length === 0 && (parseResult.assets?.paths?.length || 0) === 0 && parseResult.routes.length === 0) {
835
- 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(
836
1530
  "The current project config does not resolve to any functions, assets or routes and can not be deployed."
837
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.");
838
1534
  }
839
- parseResult = await filterFunctionsFromAssets(parseResult);
840
- return parseResult;
841
- };
1535
+ result = filterFunctionsFromAssets(result);
1536
+ return result;
1537
+ });
842
1538
 
843
1539
  // src/runtime.ts
844
1540
  var RUNTIME_DEFINITIONS = [
@@ -852,12 +1548,40 @@ var RUNTIME_DEFINITIONS = [
852
1548
  "bun-1"
853
1549
  ];
854
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));
855
1556
  export {
856
1557
  ALLOWED_CONFIG_NAMES,
857
1558
  AVAILABLE_REGIONS,
858
1559
  AVAILABLE_RUNTIMES,
859
- 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,
860
1582
  parseConfig,
861
- parseRawConfig
1583
+ parseConfigRaw,
1584
+ postProcessConfig,
1585
+ readDependencies
862
1586
  };
863
1587
  //# sourceMappingURL=index.mjs.map