@marko/run 0.2.3 → 0.2.5

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.
@@ -29,7 +29,7 @@ import sade from "sade";
29
29
 
30
30
  // src/cli/commands.ts
31
31
  import path3 from "path";
32
- import fs3 from "fs";
32
+ import fs4 from "fs";
33
33
  import { fileURLToPath as fileURLToPath2 } from "url";
34
34
  import { build as viteBuild, resolveConfig } from "vite";
35
35
 
@@ -45,23 +45,58 @@ function setConfig(obj, key, value) {
45
45
  }
46
46
  var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
47
47
  var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
48
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
48
49
  var setExternalAdapterOptions = (viteConfig, value) => setConfig(viteConfig, AdapterConfigKey, value);
49
50
 
50
51
  // src/cli/commands.ts
51
52
  import { MemoryStore } from "@marko/vite";
52
53
 
54
+ // src/vite/utils/server.ts
55
+ import net from "net";
56
+ import cp from "child_process";
57
+ import { parse, config } from "dotenv";
58
+ import fs from "fs";
59
+ import cluster from "cluster";
60
+ async function getConnection(port) {
61
+ return new Promise((resolve) => {
62
+ const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
63
+ connection.end();
64
+ resolve(null);
65
+ }).on("connect", () => {
66
+ resolve(connection);
67
+ });
68
+ });
69
+ }
70
+ async function isPortInUse(port) {
71
+ return Boolean(await getConnection(port));
72
+ }
73
+ async function getAvailablePort(port) {
74
+ if (port && !await isPortInUse(port)) {
75
+ return port;
76
+ }
77
+ return new Promise((resolve) => {
78
+ const server = net.createServer().listen(0, () => {
79
+ const { port: port2 } = server.address();
80
+ server.close(() => resolve(port2));
81
+ });
82
+ });
83
+ }
84
+
53
85
  // src/vite/plugin.ts
54
86
  import path2 from "path";
55
87
  import crypto from "crypto";
56
- import fs2 from "fs";
88
+ import fs3 from "fs";
57
89
  import glob from "glob";
58
90
  import { mergeConfig } from "vite";
59
91
  import markoVitePlugin, { FileStore } from "@marko/vite";
60
92
 
61
93
  // src/vite/constants.ts
94
+ var markoRunFilePrefix = "__marko-run__";
62
95
  var virtualFilePrefix = "virtual:marko-run";
63
96
  var virtualRoutesPrefix = `${virtualFilePrefix}/routes`;
64
97
  var virtualRuntimePrefix = `${virtualFilePrefix}/internal`;
98
+ var httpVerbs = ["get", "post", "put", "delete"];
99
+ var serverEntryQuery = "?marko-server-entry";
65
100
  var RoutableFileTypes = {
66
101
  Page: "page",
67
102
  Layout: "layout",
@@ -215,6 +250,144 @@ var VDir = _VDir;
215
250
  _dirs = new WeakMap();
216
251
  _pathlessDirs = new WeakMap();
217
252
 
253
+ // src/vite/routes/parse.ts
254
+ function parseFlatRoute(pattern) {
255
+ if (!pattern)
256
+ throw new Error("Empty pattern");
257
+ const len = pattern.length;
258
+ let i = 0;
259
+ return parse2([
260
+ {
261
+ id: "/",
262
+ segments: [],
263
+ source: pattern
264
+ }
265
+ ]);
266
+ function parse2(basePaths, group) {
267
+ const pathMap = /* @__PURE__ */ new Map();
268
+ const delimiters = group ? ").," : ".,";
269
+ let charCode;
270
+ let segmentStart = i;
271
+ let type;
272
+ let current;
273
+ do {
274
+ charCode = pattern.charCodeAt(i);
275
+ if (charCode === 41 && group) {
276
+ break;
277
+ } else if (charCode === 44) {
278
+ if (!current) {
279
+ segmentEnd(
280
+ basePaths.map((path4) => ({
281
+ ...path4,
282
+ segments: path4.segments.slice()
283
+ })),
284
+ "",
285
+ "_",
286
+ pathMap
287
+ );
288
+ } else {
289
+ segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
290
+ }
291
+ current = void 0;
292
+ type = void 0;
293
+ segmentStart = ++i;
294
+ } else if (charCode === 46) {
295
+ if (current) {
296
+ segmentEnd(current, pattern.slice(segmentStart, i), type);
297
+ }
298
+ type = void 0;
299
+ segmentStart = ++i;
300
+ } else if (charCode === 40) {
301
+ const groupPaths = parse2(current || basePaths, ++i);
302
+ if (groupPaths.length) {
303
+ current = groupPaths;
304
+ }
305
+ segmentStart = ++i;
306
+ } else {
307
+ if (charCode === 95) {
308
+ type = "_";
309
+ } else if (charCode === 36) {
310
+ type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
311
+ }
312
+ current ?? (current = basePaths.map((path4) => ({
313
+ ...path4,
314
+ segments: path4.segments.slice()
315
+ })));
316
+ i = len;
317
+ for (const char of delimiters) {
318
+ const index = pattern.indexOf(char, segmentStart);
319
+ if (index >= 0 && index < i) {
320
+ i = index;
321
+ }
322
+ }
323
+ }
324
+ } while (i < len);
325
+ if (group && charCode !== 41) {
326
+ throw new Error(
327
+ `Invalid route pattern: group was not closed '${pattern.slice(
328
+ group
329
+ )}' in '${pattern}'`
330
+ );
331
+ }
332
+ if (!current) {
333
+ segmentEnd(
334
+ basePaths.map((path4) => ({
335
+ ...path4,
336
+ segments: path4.segments.slice()
337
+ })),
338
+ "",
339
+ "_",
340
+ pathMap
341
+ );
342
+ } else {
343
+ segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
344
+ }
345
+ return [...pathMap.values()];
346
+ }
347
+ function segmentEnd(paths, raw, type, map) {
348
+ let segment;
349
+ if (raw) {
350
+ segment = {
351
+ raw,
352
+ name: raw,
353
+ type
354
+ };
355
+ if (type === "$" || type === "$$") {
356
+ segment.name = type;
357
+ segment.param = raw.slice(type.length);
358
+ }
359
+ }
360
+ for (const path4 of paths) {
361
+ if (segment) {
362
+ if (path4.isCatchall) {
363
+ throw new Error(
364
+ `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
365
+ 0,
366
+ i
367
+ )}' in '${pattern}'.`
368
+ );
369
+ }
370
+ path4.segments.push(segment);
371
+ path4.id += path4.id === "/" ? segment.name : `/${segment.name}`;
372
+ if (type === "$$") {
373
+ path4.isCatchall = true;
374
+ }
375
+ }
376
+ if (map) {
377
+ if (map.has(path4.id)) {
378
+ const existing = map.get(path4.id);
379
+ const existingExpansion = existing.segments.map((s) => s.raw).join(".");
380
+ const currentExpansion = path4.segments.map((s) => s.raw).join(".");
381
+ throw new Error(
382
+ `Invalid route pattern: route '${path4.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
383
+ );
384
+ }
385
+ map.set(path4.id, path4);
386
+ }
387
+ }
388
+ }
389
+ }
390
+
218
391
  // src/vite/routes/builder.ts
219
392
  var markoFiles = `(${RoutableFileTypes.Layout}|${RoutableFileTypes.Page}|${RoutableFileTypes.NotFound}|${RoutableFileTypes.Error})\\.(?:.*\\.)?(marko)`;
220
393
  var nonMarkoFiles = `(${RoutableFileTypes.Middleware}|${RoutableFileTypes.Handler}|${RoutableFileTypes.Meta})\\.(?:.*\\.)?(.+)`;
@@ -222,13 +395,1104 @@ var routeableFileRegex = new RegExp(
222
395
  `[+](?:${markoFiles}|${nonMarkoFiles})$`,
223
396
  "i"
224
397
  );
398
+ function matchRoutableFile(filename) {
399
+ const match = filename.match(routeableFileRegex);
400
+ return match && (match[1] || match[3]).toLowerCase();
401
+ }
402
+ function isSpecialType(type) {
403
+ return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
404
+ }
405
+ async function buildRoutes(walk, basePath = "") {
406
+ if (basePath) {
407
+ basePath = basePath.replace(/^\/+|\/+$/g, "");
408
+ }
409
+ const uniqueRoutes = /* @__PURE__ */ new Map();
410
+ const routes = [];
411
+ const special = {};
412
+ const middlewares = /* @__PURE__ */ new Set();
413
+ const unusedFiles = /* @__PURE__ */ new Set();
414
+ const currentLayouts = /* @__PURE__ */ new Set();
415
+ const currentMiddleware = /* @__PURE__ */ new Set();
416
+ const root = new VDir();
417
+ const dirStack = [];
418
+ let activeDirs = [root];
419
+ let nextFileId = 1;
420
+ let nextRouteIndex = 1;
421
+ let isBaseDir = true;
422
+ await walk({
423
+ onEnter({ name }) {
424
+ if (!name || isBaseDir) {
425
+ isBaseDir = false;
426
+ return;
427
+ }
428
+ dirStack.push(name);
429
+ const previousDirs = activeDirs;
430
+ const paths = parseFlatRoute(name);
431
+ activeDirs = VDir.addPaths(previousDirs, paths);
432
+ return () => {
433
+ activeDirs = previousDirs;
434
+ dirStack.pop();
435
+ };
436
+ },
437
+ onFile({ name, path: path4 }) {
438
+ const match = name.match(routeableFileRegex);
439
+ if (!match) {
440
+ return;
441
+ }
442
+ const type = (match[1] || match[3]).toLowerCase();
443
+ if (dirStack.length && isSpecialType(type)) {
444
+ console.warn(
445
+ `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path4}`
446
+ );
447
+ return;
448
+ }
449
+ let dirs = activeDirs;
450
+ if (match.index) {
451
+ const paths = parseFlatRoute(name.slice(0, match.index));
452
+ dirs = VDir.addPaths(activeDirs, paths);
453
+ }
454
+ const dirPath = dirStack.join("/");
455
+ const relativePath = dirPath ? `${dirPath}/${name}` : name;
456
+ const file = {
457
+ id: String(nextFileId++),
458
+ name,
459
+ type,
460
+ filePath: path4,
461
+ relativePath,
462
+ importPath: `${basePath}/${relativePath}`,
463
+ verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
464
+ };
465
+ for (const dir of dirs) {
466
+ dir.addFile(file);
467
+ }
468
+ }
469
+ });
470
+ traverse(root);
471
+ return {
472
+ list: routes,
473
+ middleware: [...middlewares],
474
+ special
475
+ };
476
+ function traverse(dir) {
477
+ let middleware;
478
+ let layout;
479
+ if (dir.files) {
480
+ middleware = dir.files.get(RoutableFileTypes.Middleware);
481
+ layout = dir.files.get(RoutableFileTypes.Layout);
482
+ const handler = dir.files.get(RoutableFileTypes.Handler);
483
+ const page = dir.files.get(RoutableFileTypes.Page);
484
+ let hasSpecial = false;
485
+ if (middleware) {
486
+ if (currentMiddleware.has(middleware)) {
487
+ middleware = void 0;
488
+ } else {
489
+ currentMiddleware.add(middleware);
490
+ unusedFiles.add(middleware);
491
+ }
492
+ }
493
+ if (layout) {
494
+ if (currentLayouts.has(layout)) {
495
+ layout = void 0;
496
+ } else {
497
+ currentLayouts.add(layout);
498
+ unusedFiles.add(layout);
499
+ }
500
+ }
501
+ if (page || handler) {
502
+ const path4 = dir.pathInfo;
503
+ if (uniqueRoutes.has(path4.id)) {
504
+ const existing = uniqueRoutes.get(path4.id);
505
+ const route = routes[existing.index];
506
+ const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
507
+ const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
508
+ throw new Error(`Duplicate routes for path '${path4.path}' were defined. A route established by:
509
+ ${existingFiles.join(" and ")} via '${existing.dir.path}'
510
+ collides with
511
+ ${currentFiles.join(" and ")} via '${dir.path}'
512
+ `);
513
+ }
514
+ uniqueRoutes.set(path4.id, { dir, index: routes.length });
515
+ routes.push({
516
+ index: nextRouteIndex++,
517
+ key: dir.fullPath,
518
+ paths: [path4],
519
+ middleware: [...currentMiddleware],
520
+ layouts: page ? [...currentLayouts] : [],
521
+ meta: dir.files.get(RoutableFileTypes.Meta),
522
+ page,
523
+ handler,
524
+ entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
525
+ });
526
+ }
527
+ if (dir === root) {
528
+ for (const [type, file] of dir.files) {
529
+ if (isSpecialType(type)) {
530
+ hasSpecial = true;
531
+ special[type] = {
532
+ index: 0,
533
+ key: type,
534
+ paths: [],
535
+ middleware: [],
536
+ layouts: [...currentLayouts],
537
+ page: file,
538
+ entryName: `${markoRunFilePrefix}special.${type}`
539
+ };
540
+ }
541
+ }
542
+ }
543
+ if (handler || page) {
544
+ for (const middleware2 of currentMiddleware) {
545
+ middlewares.add(middleware2);
546
+ unusedFiles.delete(middleware2);
547
+ }
548
+ }
549
+ if (page || hasSpecial) {
550
+ for (const layout2 of currentLayouts) {
551
+ unusedFiles.delete(layout2);
552
+ }
553
+ }
554
+ }
555
+ if (dir.dirs) {
556
+ for (const child of dir.dirs()) {
557
+ traverse(child);
558
+ }
559
+ }
560
+ if (middleware) {
561
+ currentMiddleware.delete(middleware);
562
+ }
563
+ if (layout) {
564
+ currentLayouts.delete(layout);
565
+ }
566
+ }
567
+ }
225
568
 
226
569
  // src/vite/routes/walk.ts
227
- import fs from "fs";
570
+ import fs2 from "fs";
228
571
  import path from "path";
572
+ function createFSWalker(dir) {
573
+ return async function walkFS({
574
+ onEnter,
575
+ onFile,
576
+ onDir,
577
+ maxDepth = 50
578
+ }) {
579
+ async function walk(dir2, depth) {
580
+ const onExit = onEnter == null ? void 0 : onEnter(dir2);
581
+ if (onExit !== false) {
582
+ const dirs = [];
583
+ const entries = await fs2.promises.readdir(dir2.path, {
584
+ withFileTypes: true
585
+ });
586
+ const prefix = dir2.path + path.sep;
587
+ for (const entry of entries) {
588
+ const walkEntry = {
589
+ name: entry.name,
590
+ path: prefix + entry.name
591
+ };
592
+ if (entry.isDirectory()) {
593
+ dirs.push(walkEntry);
594
+ } else {
595
+ onFile == null ? void 0 : onFile(walkEntry);
596
+ }
597
+ }
598
+ if ((onDir == null ? void 0 : onDir()) !== false && --depth > 0) {
599
+ for (const entry of dirs) {
600
+ await walk(entry, depth);
601
+ }
602
+ }
603
+ onExit == null ? void 0 : onExit();
604
+ }
605
+ }
606
+ await walk(
607
+ {
608
+ path: dir,
609
+ name: path.basename(dir)
610
+ },
611
+ maxDepth
612
+ );
613
+ };
614
+ }
615
+
616
+ // src/vite/codegen/writer.ts
617
+ function createWriter(sink, options) {
618
+ let buffer = "";
619
+ let indentLevel = 0;
620
+ let indentString = "";
621
+ let firstOpenIndex = 0;
622
+ const branches = [];
623
+ const openWriters = /* @__PURE__ */ new Map();
624
+ function write(data) {
625
+ if (!writer.__isActive) {
626
+ throw new Error("Cannot write to branch that has been joined");
627
+ }
628
+ if (openWriters.size) {
629
+ buffer += data;
630
+ } else {
631
+ sink(data);
632
+ }
633
+ return writer;
634
+ }
635
+ const writer = {
636
+ __isActive: true,
637
+ get indent() {
638
+ return indentLevel;
639
+ },
640
+ set indent(value) {
641
+ if (options == null ? void 0 : options.indentWith) {
642
+ if (value < 0) {
643
+ value = 0;
644
+ }
645
+ if (value !== indentLevel) {
646
+ indentLevel = value;
647
+ indentString = options.indentWith.repeat(indentLevel);
648
+ }
649
+ }
650
+ },
651
+ write(data, indent = false) {
652
+ if (indent && indentString) {
653
+ write(indentString);
654
+ }
655
+ return write(data);
656
+ },
657
+ writeLines(...lines) {
658
+ for (const line of lines) {
659
+ if (line) {
660
+ writer.write(line, true);
661
+ }
662
+ writer.write("\n");
663
+ }
664
+ return writer;
665
+ },
666
+ writeBlockStart(data) {
667
+ writer.writeLines(data).indent++;
668
+ return writer;
669
+ },
670
+ writeBlockEnd(data = "}") {
671
+ writer.indent--;
672
+ writer.writeLines(data);
673
+ return writer;
674
+ },
675
+ writeBlock(start, lines, end) {
676
+ return writer.writeBlockStart(start).writeLines(...lines).writeBlockEnd(end);
677
+ },
678
+ branch(name) {
679
+ let existing = openWriters.get(name);
680
+ if (existing) {
681
+ return existing;
682
+ }
683
+ const branch = {
684
+ buffer,
685
+ writer: createWriter(
686
+ (data) => {
687
+ branch.buffer += data;
688
+ },
689
+ {
690
+ ...options,
691
+ onJoin() {
692
+ openWriters.delete(name);
693
+ for (let i = firstOpenIndex; i < branches.length; i++) {
694
+ const b = branches[i];
695
+ if (!b) {
696
+ continue;
697
+ } else if (b.writer.__isActive) {
698
+ break;
699
+ }
700
+ sink(b.buffer);
701
+ branches[i] = null;
702
+ firstOpenIndex++;
703
+ }
704
+ if (!openWriters.size) {
705
+ sink(buffer);
706
+ buffer = "";
707
+ }
708
+ }
709
+ }
710
+ )
711
+ };
712
+ branch.writer.indent = indentLevel;
713
+ openWriters.set(name, branch.writer);
714
+ branches.push(branch);
715
+ buffer = "";
716
+ return branch.writer;
717
+ },
718
+ join(recursive) {
719
+ var _a;
720
+ if (writer.__isActive) {
721
+ if (openWriters.size) {
722
+ if (recursive) {
723
+ for (const branch of openWriters.values()) {
724
+ branch.join(true);
725
+ }
726
+ } else {
727
+ throw new Error(
728
+ `Cannot join a Writer with un-joined branches - use the \`recursive\` argument to join all open branches`
729
+ );
730
+ }
731
+ }
732
+ buffer && sink(buffer);
733
+ writer.__isActive = false;
734
+ (_a = options == null ? void 0 : options.onJoin) == null ? void 0 : _a.call(options, writer);
735
+ }
736
+ }
737
+ };
738
+ return writer;
739
+ }
740
+ function createStringWriter(opts) {
741
+ let code = "";
742
+ const writer = createWriter((data) => {
743
+ code += data;
744
+ }, { indentWith: " ", ...opts });
745
+ return Object.assign(writer, {
746
+ end() {
747
+ writer.join(true);
748
+ return code;
749
+ }
750
+ });
751
+ }
752
+
753
+ // src/vite/utils/route.ts
754
+ function getVerbs(route) {
755
+ var _a, _b;
756
+ const verbs = ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.slice()) || [];
757
+ if (route.page && !verbs.includes("get")) {
758
+ verbs.unshift("get");
759
+ }
760
+ return verbs;
761
+ }
762
+ function hasVerb(route, verb) {
763
+ var _a, _b;
764
+ return verb === "get" && route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes(verb));
765
+ }
766
+
767
+ // src/vite/codegen/index.ts
768
+ function renderRouteTemplate(route) {
769
+ if (!route.page) {
770
+ throw new Error(`Route ${route.key} has no page to render`);
771
+ }
772
+ const writer = createStringWriter();
773
+ writer.writeLines(`// ${virtualFilePrefix}/${route.entryName}.marko`);
774
+ writer.branch("imports");
775
+ writer.writeLines("");
776
+ writeRouteTemplateTag(writer, [...route.layouts, route.page]);
777
+ return writer.end();
778
+ }
779
+ function writeRouteTemplateTag(writer, [file, ...rest], index = 1) {
780
+ if (file) {
781
+ const isLast = !rest.length;
782
+ const tag = isLast ? "page" : `layout${index}`;
783
+ writer.branch("imports").writeLines(`import ${tag} from './${file.importPath}';`);
784
+ if (isLast) {
785
+ writer.writeLines(`<${tag} ...input />`);
786
+ } else {
787
+ writer.writeBlockStart(`<${tag} ...input>`);
788
+ writeRouteTemplateTag(writer, rest, index + 1);
789
+ writer.writeBlockEnd(`</>`);
790
+ }
791
+ }
792
+ }
793
+ function renderRouteEntry(route) {
794
+ var _a;
795
+ const { key, index, handler, page, middleware, meta, entryName } = route;
796
+ const verbs = getVerbs(route);
797
+ if (!verbs) {
798
+ throw new Error(
799
+ `Route ${key} doesn't have a handler or page for any HTTP verbs`
800
+ );
801
+ }
802
+ const writer = createStringWriter();
803
+ writer.writeLines(`// ${virtualFilePrefix}/${entryName}.js`);
804
+ const imports = writer.branch("imports");
805
+ const runtimeImports = [];
806
+ if (handler) {
807
+ runtimeImports.push("normalize");
808
+ }
809
+ if (handler || middleware.length) {
810
+ runtimeImports.push("call");
811
+ }
812
+ if (!page || verbs.length > 1) {
813
+ runtimeImports.push("noContent");
814
+ }
815
+ if (page) {
816
+ runtimeImports.push("pageResponse");
817
+ }
818
+ if (runtimeImports.length) {
819
+ imports.writeLines(
820
+ `import { ${runtimeImports.join(", ")} } from '${virtualRuntimePrefix}';`
821
+ );
822
+ }
823
+ if (middleware.length) {
824
+ const names = middleware.map((m) => `mware${m.id}`);
825
+ imports.writeLines(
826
+ `import { ${names.join(
827
+ ", "
828
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
829
+ );
830
+ }
831
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
832
+ writer.writeLines("");
833
+ const names = [];
834
+ for (const verb of handler.verbs) {
835
+ const importName = verb.toUpperCase();
836
+ names.push(importName);
837
+ writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
838
+ }
839
+ imports.writeLines(
840
+ `import { ${names.join(", ")} } from './${handler.importPath}';`
841
+ );
842
+ }
843
+ if (page) {
844
+ imports.writeLines(
845
+ `import page from '${virtualFilePrefix}/${entryName}.marko${serverEntryQuery}';`
846
+ );
847
+ }
848
+ if (meta) {
849
+ imports.writeLines(
850
+ `export { default as meta${index} } from './${meta.importPath}';`
851
+ );
852
+ }
853
+ for (const verb of verbs) {
854
+ writeRouteEntryHandler(writer, route, verb);
855
+ }
856
+ return writer.end();
857
+ }
858
+ function writePageResponse(writer, wrapFn) {
859
+ writer.writeLines(
860
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} pageResponse(page, buildInput());`
861
+ );
862
+ }
863
+ function writeMiddleware(writer, middleware, next, wrapFn) {
864
+ if (wrapFn) {
865
+ writer.writeLines(
866
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
867
+ );
868
+ } else {
869
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
870
+ }
871
+ }
872
+ function writeRouteEntryHandler(writer, route, verb) {
873
+ var _a;
874
+ const { key, index, page, handler, middleware } = route;
875
+ const len = middleware.length;
876
+ let nextName;
877
+ let currentName;
878
+ let hasBody = false;
879
+ writer.writeLines("");
880
+ if (page) {
881
+ writer.writeBlockStart(
882
+ `export async function ${verb}${index}(context, buildInput) {`
883
+ );
884
+ } else {
885
+ writer.writeBlockStart(`export async function ${verb}${index}(context) {`);
886
+ }
887
+ const continuations = writer.branch("cont");
888
+ if (page && verb === "get") {
889
+ currentName = "__page";
890
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
891
+ const name = `${verb}Handler`;
892
+ writePageResponse(continuations, currentName);
893
+ if (len) {
894
+ nextName = currentName;
895
+ currentName = `__${name}`;
896
+ writeMiddleware(continuations, name, nextName, currentName);
897
+ } else {
898
+ writeMiddleware(writer, name, currentName);
899
+ hasBody = true;
900
+ }
901
+ } else if (len) {
902
+ writePageResponse(continuations, currentName);
903
+ nextName = currentName;
904
+ } else {
905
+ writePageResponse(continuations);
906
+ hasBody = true;
907
+ }
908
+ } else if (handler) {
909
+ const name = `${verb}Handler`;
910
+ currentName = `__${name}`;
911
+ nextName = "noContent";
912
+ if (len) {
913
+ writeMiddleware(continuations, name, nextName, currentName);
914
+ } else {
915
+ writeMiddleware(writer, name, nextName);
916
+ hasBody = true;
917
+ }
918
+ } else {
919
+ throw new Error(`Route ${key} has no handler for ${verb} requests`);
920
+ }
921
+ if (!hasBody) {
922
+ let i = len;
923
+ while (i--) {
924
+ const { id } = middleware[i];
925
+ const name = `mware${id}`;
926
+ nextName = currentName;
927
+ currentName = i ? `__${name}` : "";
928
+ writeMiddleware(continuations, name, nextName, currentName);
929
+ }
930
+ }
931
+ continuations.join();
932
+ writer.writeBlockEnd("}");
933
+ }
934
+ function renderRouter(routes, options = {
935
+ trailingSlashes: "RedirectWithout"
936
+ }) {
937
+ const writer = createStringWriter();
938
+ writer.writeLines(`// @marko/run/router`);
939
+ const imports = writer.branch("imports");
940
+ imports.writeLines(
941
+ `import { NotHandled, NotMatched, createContext } from 'virtual:marko-run/internal';`
942
+ );
943
+ for (const route of routes.list) {
944
+ const verbs = getVerbs(route);
945
+ const names = verbs.map((verb) => `${verb}${route.index}`);
946
+ route.meta && names.push(`meta${route.index}`);
947
+ imports.writeLines(
948
+ `import { ${names.join(", ")} } from '${virtualFilePrefix}/${route.entryName}.js';`
949
+ );
950
+ }
951
+ for (const { key, entryName } of Object.values(routes.special)) {
952
+ imports.writeLines(
953
+ `import page${key} from '${virtualFilePrefix}/${entryName}.marko${serverEntryQuery}';`
954
+ );
955
+ }
956
+ writer.writeLines(
957
+ `
958
+ globalThis.__marko_run__ = { match, fetch, invoke };
959
+ `
960
+ ).writeBlockStart(`export function match(method, pathname) {`).writeLines(
961
+ `if (!pathname) {
962
+ pathname = '/';
963
+ } else if (pathname.charAt(0) !== '/') {
964
+ pathname = '/' + pathname;
965
+ }`
966
+ ).writeBlockStart(`switch (method.toLowerCase()) {`);
967
+ for (const verb of httpVerbs) {
968
+ const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
969
+ if (filteredRoutes.length) {
970
+ const trie = createRouteTrie(filteredRoutes);
971
+ writer.writeBlockStart(`case '${verb}': {`);
972
+ writeRouterVerb(writer, trie, verb);
973
+ writer.writeBlockEnd("}");
974
+ }
975
+ }
976
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
977
+ writer.write(`
978
+ export async function invoke(route, request, platform, url) {
979
+ const [context, buildInput] = createContext(route, request, platform, url);
980
+ try {
981
+ if (route) {
982
+ try {
983
+ const response = await route.handler(context, buildInput);
984
+ if (response) return response;
985
+ } catch (error) {
986
+ if (error === NotHandled) {
987
+ return;
988
+ } else if (error !== NotMatched) {
989
+ throw error;
990
+ }
991
+ }
992
+ `);
993
+ if (routes.special[RoutableFileTypes.NotFound]) {
994
+ writer.indent = 2;
995
+ imports.writeLines(
996
+ `
997
+ const page404ResponseInit = {
998
+ status: 404,
999
+ headers: { "content-type": "text/html;charset=UTF-8" },
1000
+ };`
1001
+ );
1002
+ writer.write(
1003
+ ` } else {
1004
+ }
1005
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
1006
+ return new Response(page404.stream(buildInput()), page404ResponseInit);
1007
+ }
1008
+ `
1009
+ );
1010
+ } else {
1011
+ writer.indent = 3;
1012
+ writer.writeBlockEnd("}");
1013
+ }
1014
+ writer.indent--;
1015
+ writer.writeBlockStart(`} catch (error) {`);
1016
+ if (routes.special[RoutableFileTypes.Error]) {
1017
+ imports.writeLines(`
1018
+ const page500ResponseInit = {
1019
+ status: 500,
1020
+ headers: { "content-type": "text/html;charset=UTF-8" },
1021
+ };`);
1022
+ writer.writeBlockStart(
1023
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
1024
+ ).writeLines(
1025
+ `return new Response(page500.stream(buildInput({ error })), page500ResponseInit);`
1026
+ ).writeBlockEnd("}");
1027
+ }
1028
+ writer.writeLines(`throw error;`).writeBlockEnd("}").writeBlockEnd("}").write(`
1029
+ export async function fetch(request, platform) {
1030
+ try {
1031
+ const url = new URL(request.url);
1032
+ let { pathname } = url;`);
1033
+ switch (options.trailingSlashes) {
1034
+ case "RedirectWithout":
1035
+ writer.write(`
1036
+ if (pathname !== '/' && pathname.endsWith('/')) {
1037
+ url.pathname = pathname.slice(0, -1);
1038
+ return Response.redirect(url);
1039
+ }`);
1040
+ break;
1041
+ case "RedirectWith":
1042
+ writer.write(`
1043
+ if (pathname !== '/' && !pathname.endsWith('/')) {
1044
+ url.pathname = pathname + '/';
1045
+ return Response.redirect(url);
1046
+ }`);
1047
+ break;
1048
+ case "RewriteWithout":
1049
+ writer.write(`
1050
+ if (pathname !== '/' && pathname.endsWith('/')) {
1051
+ url.pathname = pathname = pathname.slice(0, -1);
1052
+ }`);
1053
+ break;
1054
+ case "RewriteWith":
1055
+ writer.write(`
1056
+ if (pathname !== '/' && !pathname.endsWith('/')) {
1057
+ url.pathname = pathname = pathname + '/';
1058
+ }`);
1059
+ break;
1060
+ }
1061
+ writer.write(`
1062
+
1063
+ const route = match(request.method, pathname);
1064
+ return await invoke(route, request, platform, url);
1065
+ } catch (error) {
1066
+ const body = import.meta.env.DEV
1067
+ ? error.stack || error.message || "Internal Server Error"
1068
+ : null;
1069
+ return new Response(body, {
1070
+ status: 500
1071
+ });
1072
+ }
1073
+ }`);
1074
+ return writer.end();
1075
+ }
1076
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
1077
+ const { route, dynamic, catchAll } = trie;
1078
+ let closeCount = 0;
1079
+ if (level === 0) {
1080
+ writer.writeLines(`const len = pathname.length;`);
1081
+ if (route) {
1082
+ writer.writeLines(
1083
+ `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
1084
+ );
1085
+ } else if (trie.static || dynamic) {
1086
+ writer.writeBlockStart(`if (len > 1) {`);
1087
+ closeCount++;
1088
+ }
1089
+ }
1090
+ if (trie.static || dynamic) {
1091
+ const next = level + 1;
1092
+ const index = `i${next}`;
1093
+ let terminal;
1094
+ let children;
1095
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
1096
+ if (trie.static) {
1097
+ for (const child of trie.static.values()) {
1098
+ if (child.route) {
1099
+ (terminal ?? (terminal = [])).push(child);
1100
+ }
1101
+ if (child.static || child.dynamic || child.catchAll) {
1102
+ (children ?? (children = [])).push(child);
1103
+ }
1104
+ }
1105
+ }
1106
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
1107
+ closeCount++;
1108
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
1109
+ let value = `decodeURIComponent(pathname.slice(${offset}, ${index} ? -1 : len))`;
1110
+ if (dynamic == null ? void 0 : dynamic.route) {
1111
+ const segment = `s${next}`;
1112
+ writer.writeLines(`const ${segment} = ${value};`);
1113
+ value = segment;
1114
+ }
1115
+ if (terminal) {
1116
+ const useSwitch = terminal.length > 1;
1117
+ if (useSwitch) {
1118
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
1119
+ }
1120
+ for (const { key, path: path4, route: route2 } of terminal) {
1121
+ if (useSwitch) {
1122
+ writer.write(`case '${key}': `, true);
1123
+ } else {
1124
+ writer.write(`if (${value}.toLowerCase() === '${key}') `, true);
1125
+ }
1126
+ writer.write(
1127
+ `return ${renderMatch(verb, route2, path4)}; // ${path4.path}
1128
+ `
1129
+ );
1130
+ }
1131
+ if (useSwitch) {
1132
+ writer.writeBlockEnd("}");
1133
+ }
1134
+ }
1135
+ if (dynamic == null ? void 0 : dynamic.route) {
1136
+ writer.writeLines(
1137
+ `if (${value}) return ${renderMatch(
1138
+ verb,
1139
+ dynamic.route,
1140
+ dynamic.path
1141
+ )}; // ${dynamic.path.path}`
1142
+ );
1143
+ }
1144
+ }
1145
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
1146
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
1147
+ writer.writeBlockEnd("} else {").indent++;
1148
+ } else {
1149
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
1150
+ closeCount++;
1151
+ }
1152
+ let value = `decodeURIComponent(pathname.slice(${offset}, ${index} - 1))`;
1153
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic)) {
1154
+ const segment = `s${next}`;
1155
+ writer.writeLines(`const ${segment} = ${value};`);
1156
+ value = segment;
1157
+ }
1158
+ if (children) {
1159
+ const useSwitch = children.length > 1;
1160
+ if (useSwitch) {
1161
+ writer.writeBlockStart(`switch (${value}.toLowerCase()) {`);
1162
+ }
1163
+ for (const child of children) {
1164
+ const decodedKey = decodeURIComponent(child.key);
1165
+ if (useSwitch) {
1166
+ writer.writeBlockStart(`case '${decodedKey}': {`);
1167
+ } else {
1168
+ writer.writeBlockStart(
1169
+ `if (${value}.toLowerCase() === '${decodedKey}') {`
1170
+ );
1171
+ }
1172
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
1173
+ writeRouterVerb(writer, child, verb, next, nextOffset);
1174
+ if (useSwitch) {
1175
+ writer.writeBlockEnd("} break;");
1176
+ } else {
1177
+ writer.writeBlockEnd("}");
1178
+ }
1179
+ }
1180
+ if (useSwitch) {
1181
+ writer.writeBlockEnd("}");
1182
+ }
1183
+ }
1184
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
1185
+ writer.writeBlockStart(`if (${value}) {`);
1186
+ writeRouterVerb(writer, dynamic, verb, next, index);
1187
+ writer.writeBlockEnd(`}`);
1188
+ }
1189
+ }
1190
+ }
1191
+ while (closeCount--) {
1192
+ writer.writeBlockEnd("}");
1193
+ }
1194
+ if (catchAll) {
1195
+ writer.writeLines(
1196
+ `return ${renderMatch(verb, catchAll.route, catchAll.path, String(offset))}; // ${catchAll.path.path}`
1197
+ );
1198
+ } else if (level === 0) {
1199
+ writer.writeLines("return null;");
1200
+ }
1201
+ }
1202
+ function wrapPropertyName(name) {
1203
+ name = decodeURIComponent(name);
1204
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
1205
+ }
1206
+ function renderParams(params, pathIndex) {
1207
+ let result = "";
1208
+ let catchAll = "";
1209
+ let sep = "{";
1210
+ for (const [name, index] of Object.entries(params)) {
1211
+ if (typeof index === "number") {
1212
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
1213
+ sep = ",";
1214
+ } else if (pathIndex) {
1215
+ catchAll = name;
1216
+ }
1217
+ }
1218
+ if (catchAll) {
1219
+ result += `${sep} ${wrapPropertyName(
1220
+ catchAll
1221
+ )}: pathname.slice(${pathIndex})`;
1222
+ }
1223
+ return result ? result + " }" : "{}";
1224
+ }
1225
+ function renderMatch(verb, route, path4, pathIndex) {
1226
+ const handler = `${verb}${route.index}`;
1227
+ const params = path4.params ? renderParams(path4.params, pathIndex) : "{}";
1228
+ const meta = route.meta ? `meta${route.index}` : "{}";
1229
+ const pathPattern = pathToURLPatternString(path4.path);
1230
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
1231
+ }
1232
+ function renderMiddleware(middleware) {
1233
+ const writer = createStringWriter();
1234
+ writer.writeLines(
1235
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
1236
+ );
1237
+ const imports = writer.branch("imports");
1238
+ imports.writeLines(`import { normalize } from 'virtual:marko-run/internal';`);
1239
+ writer.writeLines("");
1240
+ for (const { id, importPath } of middleware) {
1241
+ const importName = `middleware${id}`;
1242
+ imports.writeLines(`import ${importName} from './${importPath}';`);
1243
+ writer.writeLines(`export const mware${id} = normalize(${importName});`);
1244
+ }
1245
+ imports.join();
1246
+ return writer.end();
1247
+ }
1248
+ function stripTsExtension(path4) {
1249
+ const index = path4.lastIndexOf(".");
1250
+ if (index !== -1) {
1251
+ const ext = path4.slice(index + 1);
1252
+ if (ext.toLowerCase() === "ts") {
1253
+ return path4.slice(0, index);
1254
+ }
1255
+ }
1256
+ return path4;
1257
+ }
1258
+ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
1259
+ var _a, _b;
1260
+ const writer = createStringWriter();
1261
+ writer.writeLines(
1262
+ `/*
1263
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
1264
+ Do NOT manually edit this file or your changes will be lost.
1265
+ */
1266
+ `,
1267
+ `import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";`,
1268
+ `import type Run from "@marko/run";`
1269
+ );
1270
+ const headWriter = writer.branch("head");
1271
+ writer.writeLines("\n").writeBlockStart(`declare module "@marko/run" {`);
1272
+ if (adapter && adapter.typeInfo) {
1273
+ const platformType = await adapter.typeInfo(
1274
+ (data) => headWriter.write(data)
1275
+ );
1276
+ if (platformType) {
1277
+ writer.writeLines(`interface Platform extends ${platformType} {}
1278
+ `);
1279
+ }
1280
+ }
1281
+ headWriter.join();
1282
+ writer.writeBlockStart(`interface AppData extends Run.DefineApp<{`).writeBlockStart("routes: {");
1283
+ const routesWriter = writer.branch("routes");
1284
+ writer.writeBlockEnd("}").writeBlockEnd(`}> {}`).writeBlockEnd(`}`);
1285
+ const routeTypes = /* @__PURE__ */ new Map();
1286
+ for (const route of routes.list) {
1287
+ let routeType = "";
1288
+ for (const path4 of route.paths) {
1289
+ const pathType = `"${pathToURLPatternString(path4.path)}"`;
1290
+ routeType += routeType ? " | " + pathType : pathType;
1291
+ routesWriter.writeLines(`${pathType}: Routes["${route.key}"];`);
1292
+ }
1293
+ for (const file of [route.handler, route.page]) {
1294
+ if (file) {
1295
+ const existing = routeTypes.get(file);
1296
+ if (!existing) {
1297
+ routeTypes.set(file, [routeType]);
1298
+ } else {
1299
+ existing.push(routeType);
1300
+ }
1301
+ }
1302
+ }
1303
+ for (const files of [route.middleware, route.layouts]) {
1304
+ if (files) {
1305
+ for (const file of files) {
1306
+ const existing = routeTypes.get(file);
1307
+ if (!existing) {
1308
+ routeTypes.set(file, [routeType]);
1309
+ } else {
1310
+ existing.push(routeType);
1311
+ }
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+ for (const special of Object.values(routes.special)) {
1317
+ routeTypes.set(special.page, []);
1318
+ }
1319
+ routesWriter.join();
1320
+ const handlerWriter = writer.branch("handler");
1321
+ const middlewareWriter = writer.branch("middleware");
1322
+ const pageWriter = writer.branch("page");
1323
+ const layoutWriter = writer.branch("layout");
1324
+ for (const [file, types] of routeTypes) {
1325
+ const path4 = `${pathPrefix}/${file.relativePath}`;
1326
+ const routeType = `Run.Routes[${types.join(" | ")}]`;
1327
+ switch (file.type) {
1328
+ case RoutableFileTypes.Handler:
1329
+ writeModuleDeclaration(handlerWriter, path4, routeType);
1330
+ break;
1331
+ case RoutableFileTypes.Middleware:
1332
+ writeModuleDeclaration(middlewareWriter, path4, routeType);
1333
+ break;
1334
+ case RoutableFileTypes.Page:
1335
+ writeModuleDeclaration(pageWriter, path4, routeType);
1336
+ break;
1337
+ case RoutableFileTypes.Layout:
1338
+ writeModuleDeclaration(
1339
+ layoutWriter,
1340
+ path4,
1341
+ routeType,
1342
+ `
1343
+ export interface Input {
1344
+ renderBody: Marko.Body;
1345
+ }`
1346
+ );
1347
+ break;
1348
+ case RoutableFileTypes.Error:
1349
+ writeModuleDeclaration(
1350
+ writer,
1351
+ path4,
1352
+ "globalThis.MarkoRun.Route",
1353
+ `
1354
+ export interface Input {
1355
+ error: unknown;
1356
+ }`
1357
+ );
1358
+ break;
1359
+ case RoutableFileTypes.NotFound:
1360
+ writeModuleDeclaration(writer, path4, "Run.Route");
1361
+ break;
1362
+ }
1363
+ }
1364
+ handlerWriter.join();
1365
+ middlewareWriter.join();
1366
+ pageWriter.join();
1367
+ layoutWriter.join();
1368
+ writer.writeBlockStart(`
1369
+ type Routes = {`);
1370
+ for (const route of routes.list) {
1371
+ const { meta, handler, page } = route;
1372
+ if (page || handler) {
1373
+ const verbs = [];
1374
+ if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
1375
+ verbs.push(`"get"`);
1376
+ }
1377
+ if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
1378
+ verbs.push(`"post"`);
1379
+ }
1380
+ let routeType = `{ verb: ${verbs.join(" | ")};`;
1381
+ if (meta) {
1382
+ const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
1383
+ let metaType = `typeof import("${metaPath}")`;
1384
+ if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
1385
+ metaType += `["default"]`;
1386
+ }
1387
+ routeType += ` meta: ${metaType};`;
1388
+ }
1389
+ writer.writeLines(`"${route.key}": ${routeType} };`);
1390
+ }
1391
+ }
1392
+ writer.writeBlockEnd("}");
1393
+ return writer.end();
1394
+ }
1395
+ function writeModuleDeclaration(writer, path4, routeType, moduleTypes) {
1396
+ writer.writeLines("").write(`declare module "${stripTsExtension(path4)}" {`);
1397
+ if (moduleTypes) {
1398
+ writer.write(moduleTypes);
1399
+ }
1400
+ if (routeType) {
1401
+ const isMarko = path4.endsWith(".marko");
1402
+ writer.write(`
1403
+ namespace MarkoRun {
1404
+ export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
1405
+ export type Route = ${routeType};
1406
+ export type Context = Run.MultiRouteContext<Route>${isMarko ? " & Marko.Global" : ""};
1407
+ export type Handler = Run.HandlerLike<Route>;
1408
+ export const route: Run.HandlerTypeFn<Route>;
1409
+ }`);
1410
+ }
1411
+ writer.writeLines(`
1412
+ }`);
1413
+ }
1414
+ function pathToURLPatternString(path4) {
1415
+ return path4.replace(/\/\$(\$?)([^\/]*)/g, (_, catchAll, name) => {
1416
+ name = decodeURIComponent(name);
1417
+ return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
1418
+ });
1419
+ }
1420
+ function createRouteTrie(routes) {
1421
+ const root = {
1422
+ key: ""
1423
+ };
1424
+ function insert(path4, route) {
1425
+ let node = root;
1426
+ for (const segment of path4.segments) {
1427
+ if (segment === "$$") {
1428
+ node.catchAll ?? (node.catchAll = { route, path: path4 });
1429
+ return;
1430
+ } else if (segment === "$") {
1431
+ node = node.dynamic ?? (node.dynamic = {
1432
+ key: ""
1433
+ });
1434
+ } else {
1435
+ node.static ?? (node.static = /* @__PURE__ */ new Map());
1436
+ let next = node.static.get(segment);
1437
+ if (!next) {
1438
+ next = {
1439
+ key: segment
1440
+ };
1441
+ node.static.set(segment, next);
1442
+ }
1443
+ node = next;
1444
+ }
1445
+ }
1446
+ node.path ?? (node.path = path4);
1447
+ node.route ?? (node.route = route);
1448
+ }
1449
+ for (const route of routes) {
1450
+ for (const path4 of route.paths) {
1451
+ insert(path4, route);
1452
+ }
1453
+ }
1454
+ return root;
1455
+ }
229
1456
 
230
1457
  // src/vite/utils/ast.ts
231
1458
  import * as t from "@babel/types";
1459
+ function getExportIdentifiers(astProgramNode) {
1460
+ const result = [];
1461
+ if (t.isProgram(astProgramNode)) {
1462
+ for (const node of astProgramNode.body) {
1463
+ if (t.isExportNamedDeclaration(node)) {
1464
+ const { declaration, specifiers } = node;
1465
+ if (declaration) {
1466
+ if (t.isFunctionDeclaration(declaration) && declaration.id) {
1467
+ result.push(declaration.id.name);
1468
+ } else if (t.isVariableDeclaration(declaration)) {
1469
+ for (const declarator of declaration.declarations) {
1470
+ if (t.isIdentifier(declarator.id)) {
1471
+ result.push(declarator.id.name);
1472
+ }
1473
+ }
1474
+ }
1475
+ } else if (specifiers) {
1476
+ for (const specifier of specifiers) {
1477
+ if (t.isExportSpecifier(specifier) && t.isIdentifier(specifier.exported)) {
1478
+ result.push(specifier.exported.name);
1479
+ }
1480
+ }
1481
+ }
1482
+ } else if (t.isExportDefaultDeclaration(node)) {
1483
+ const { declaration } = node;
1484
+ if (t.isObjectExpression(declaration)) {
1485
+ for (const property of declaration.properties) {
1486
+ if (t.isObjectMember(property) && t.isIdentifier(property.key)) {
1487
+ result.push(property.key.name);
1488
+ }
1489
+ }
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+ return result;
1495
+ }
232
1496
 
233
1497
  // src/vite/utils/log.ts
234
1498
  import zlib from "node:zlib";
@@ -243,26 +1507,585 @@ var HttpVerbColors = {
243
1507
  delete: kleur.red,
244
1508
  other: kleur.white
245
1509
  };
1510
+ var HttpVerbOrder = {
1511
+ get: 0,
1512
+ post: 1,
1513
+ put: 2,
1514
+ delete: 3
1515
+ };
1516
+ function logRoutesTable(routes, bundle, options) {
1517
+ function getRouteChunkName(route) {
1518
+ return options.sanitizeFileName(`${route.entryName}.marko`);
1519
+ }
1520
+ const hasMiddleware = routes.list.some((route) => route.middleware.length);
1521
+ const hasMeta = routes.list.some((route) => route.meta);
1522
+ const headings = ["Method", "Path", "Entry"];
1523
+ const colAligns = ["left", "left", "left"];
1524
+ if (hasMiddleware) {
1525
+ headings.push("MW");
1526
+ colAligns.push("right");
1527
+ }
1528
+ if (hasMeta) {
1529
+ headings.push("Meta");
1530
+ colAligns.push("center");
1531
+ }
1532
+ headings.push("Size/GZip");
1533
+ colAligns.push("right");
1534
+ const table = new Table({
1535
+ head: headings.map((title) => kleur.bold(kleur.white(title.toUpperCase()))),
1536
+ wordWrap: true,
1537
+ colAligns,
1538
+ style: { compact: true }
1539
+ });
1540
+ for (const route of routes.list) {
1541
+ for (const path4 of route.paths) {
1542
+ const verbs = getVerbs(route).sort(
1543
+ (a, b) => HttpVerbOrder[a] - HttpVerbOrder[b]
1544
+ );
1545
+ let firstRow = true;
1546
+ for (const verb of verbs) {
1547
+ let size = "";
1548
+ const entryType = [];
1549
+ if (route.handler) {
1550
+ entryType.push(kleur.blue("handler"));
1551
+ }
1552
+ if (verb === "get" && route.page) {
1553
+ entryType.push(kleur.yellow("page"));
1554
+ size = prettySize(computeRouteSize(getRouteChunkName(route), bundle));
1555
+ }
1556
+ const row = [kleur.bold(HttpVerbColors[verb](verb.toUpperCase()))];
1557
+ if (verbs.length === 1 || firstRow) {
1558
+ row.push({ rowSpan: verbs.length, content: prettyPath(path4.path) });
1559
+ firstRow = false;
1560
+ }
1561
+ row.push(entryType.join(" -> "));
1562
+ hasMiddleware && row.push(route.middleware.length || "");
1563
+ hasMeta && row.push(route.meta ? "\u2713" : "");
1564
+ row.push(size || "");
1565
+ table.push(row);
1566
+ }
1567
+ }
1568
+ }
1569
+ for (const [key, route] of Object.entries(routes.special).sort()) {
1570
+ const row = [kleur.bold(kleur.white("*")), key, kleur.yellow("page")];
1571
+ hasMiddleware && row.push("");
1572
+ hasMeta && row.push("");
1573
+ row.push(prettySize(computeRouteSize(getRouteChunkName(route), bundle)));
1574
+ table.push(row);
1575
+ }
1576
+ console.log(table.toString());
1577
+ }
1578
+ function computeRouteSize(entryName, bundle) {
1579
+ for (const chunk of Object.values(bundle)) {
1580
+ if (chunk.type === "chunk" && chunk.isEntry && chunk.name === entryName) {
1581
+ return computeChunkSize(chunk, bundle);
1582
+ }
1583
+ }
1584
+ return [0, 0];
1585
+ }
1586
+ function gzipSize(source) {
1587
+ return zlib.gzipSync(source, { level: 9 }).length;
1588
+ }
1589
+ function byteSize(source) {
1590
+ return new Blob([source]).size;
1591
+ }
1592
+ function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
1593
+ if (chunk.type === "asset") {
1594
+ return [
1595
+ byteSize(chunk.source),
1596
+ gzipSize(chunk.source)
1597
+ ];
1598
+ }
1599
+ const size = [byteSize(chunk.code), gzipSize(chunk.code)];
1600
+ for (const id of chunk.imports) {
1601
+ if (!seen.has(id)) {
1602
+ const [bytes, compBytes] = computeChunkSize(bundle[id], bundle, seen);
1603
+ size[0] += bytes;
1604
+ size[1] += compBytes;
1605
+ seen.add(id);
1606
+ }
1607
+ }
1608
+ return size;
1609
+ }
1610
+ function prettySize([bytes, compBytes]) {
1611
+ if (bytes <= 0) {
1612
+ return kleur.gray("0.0 kB");
1613
+ }
1614
+ const [size, prefix] = format(bytes, { decimals: 1 }).split(/\s+/);
1615
+ const compSize = format(compBytes, { decimals: 1, prefix, unit: "B" });
1616
+ let str = kleur.white(size) + kleur.gray("/");
1617
+ if (compBytes < 20 * 1e3)
1618
+ str += kleur.green(compSize);
1619
+ else if (compBytes < 50 * 1e3)
1620
+ str += kleur.yellow(compSize);
1621
+ else
1622
+ str += kleur.bold(kleur.red(compSize));
1623
+ return str;
1624
+ }
1625
+ function prettyPath(path4) {
1626
+ return path4.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur.bold(kleur.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur.bold(kleur.dim(`:${p}`)));
1627
+ }
246
1628
 
247
1629
  // src/vite/plugin.ts
248
1630
  import { fileURLToPath } from "url";
249
1631
  var __dirname = path2.dirname(fileURLToPath(import.meta.url));
1632
+ var PLUGIN_NAME_PREFIX = "marko-run-vite";
250
1633
  var POSIX_SEP = "/";
251
1634
  var WINDOWS_SEP = "\\";
252
1635
  var normalizePath = path2.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1636
+ function markoRun(opts = {}) {
1637
+ let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1638
+ let compiler;
1639
+ let store;
1640
+ let root;
1641
+ let resolvedRoutesDir;
1642
+ let typesDir;
1643
+ let isBuild = false;
1644
+ let isSSRBuild = false;
1645
+ let tsConfigExists;
1646
+ let ssrEntryFiles;
1647
+ let devEntryFile;
1648
+ let devEntryFilePosix;
1649
+ let devServer;
1650
+ let routes;
1651
+ let routeData;
1652
+ let routeDataFilename = "routes.json";
1653
+ let extractVerbs;
1654
+ let resolvedConfig;
1655
+ let typesFile;
1656
+ let isStale = true;
1657
+ let isRendered = false;
1658
+ const virtualFiles = /* @__PURE__ */ new Map();
1659
+ let times = {
1660
+ routesBuild: 0,
1661
+ routesRender: 0
1662
+ };
1663
+ async function writeTypesFile() {
1664
+ if (tsConfigExists ?? (tsConfigExists = await globFileExists(
1665
+ root,
1666
+ "{.tsconfig*,tsconfig*.json}"
1667
+ ))) {
1668
+ const filepath = path2.join(typesDir, "routes.d.ts");
1669
+ const data = await renderRouteTypeInfo(
1670
+ routes,
1671
+ normalizePath(path2.relative(typesDir, resolvedRoutesDir)),
1672
+ adapter
1673
+ );
1674
+ if (data !== typesFile || !fs3.existsSync(filepath)) {
1675
+ await ensureDir(typesDir);
1676
+ await fs3.promises.writeFile(filepath, typesFile = data);
1677
+ }
1678
+ }
1679
+ }
1680
+ async function setVirtualFiles(render = false) {
1681
+ for (const route of routes.list) {
1682
+ if (render && route.handler) {
1683
+ route.handler.verbs = await extractVerbs(route.handler.filePath);
1684
+ if (!route.handler.verbs.length) {
1685
+ console.warn(
1686
+ `Did not find any valid exports in middleware entry file:'${route.handler.filePath}' - expected to find any of 'GET', 'POST', 'PUT' or 'DELETE'`
1687
+ );
1688
+ }
1689
+ }
1690
+ if (route.page) {
1691
+ virtualFiles.set(
1692
+ path2.posix.join(root, `${route.entryName}.marko`),
1693
+ render ? renderRouteTemplate(route) : ""
1694
+ );
1695
+ }
1696
+ virtualFiles.set(
1697
+ path2.posix.join(root, `${route.entryName}.js`),
1698
+ render ? renderRouteEntry(route) : ""
1699
+ );
1700
+ }
1701
+ for (const route of Object.values(routes.special)) {
1702
+ virtualFiles.set(
1703
+ path2.posix.join(root, `${route.entryName}.marko`),
1704
+ render ? renderRouteTemplate(route) : ""
1705
+ );
1706
+ }
1707
+ virtualFiles.set(
1708
+ "@marko/run/router",
1709
+ render ? renderRouter(routes, {
1710
+ trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1711
+ }) : ""
1712
+ );
1713
+ virtualFiles.set(
1714
+ path2.posix.join(root, `${markoRunFilePrefix}middleware.js`),
1715
+ render ? renderMiddleware(routes.middleware) : ""
1716
+ );
1717
+ }
1718
+ const buildVirtualFiles = single(async () => {
1719
+ const startTime = performance.now();
1720
+ routes = await buildRoutes(createFSWalker(resolvedRoutesDir), routesDir);
1721
+ times.routesBuild = performance.now() - startTime;
1722
+ await setVirtualFiles(false);
1723
+ isStale = false;
1724
+ isRendered = false;
1725
+ });
1726
+ const renderVirtualFiles = single(async () => {
1727
+ const startTime = performance.now();
1728
+ await setVirtualFiles(true);
1729
+ await writeTypesFile();
1730
+ times.routesRender = performance.now() - startTime;
1731
+ isRendered = true;
1732
+ });
1733
+ return [
1734
+ {
1735
+ name: `${PLUGIN_NAME_PREFIX}:pre`,
1736
+ enforce: "pre",
1737
+ async config(config2, env) {
1738
+ var _a, _b, _c, _d, _e, _f;
1739
+ const externalPluginOptions = getExternalPluginOptions(config2);
1740
+ if (externalPluginOptions) {
1741
+ opts = mergeConfig(opts, externalPluginOptions);
1742
+ }
1743
+ root = normalizePath(config2.root || process.cwd());
1744
+ isBuild = env.command === "build";
1745
+ isSSRBuild = isBuild && Boolean((_a = config2.build) == null ? void 0 : _a.ssr);
1746
+ adapter = await resolveAdapter(
1747
+ root,
1748
+ opts,
1749
+ config2.logLevel !== "silent" && !isBuild || isSSRBuild
1750
+ );
1751
+ if (adapter) {
1752
+ (_b = adapter.configure) == null ? void 0 : _b.call(adapter, {
1753
+ ...getExternalAdapterOptions(config2),
1754
+ root,
1755
+ isBuild
1756
+ });
1757
+ const adapterOptions = await ((_c = adapter.pluginOptions) == null ? void 0 : _c.call(adapter, opts));
1758
+ if (adapterOptions) {
1759
+ opts = mergeConfig(opts, adapterOptions);
1760
+ }
1761
+ }
1762
+ compiler ?? (compiler = await import(opts.compiler || "@marko/compiler"));
1763
+ compiler.taglib.register("@marko/run", {
1764
+ "<*>": {
1765
+ transform: path2.resolve(
1766
+ __dirname,
1767
+ "../components/src-attributes-transformer.cjs"
1768
+ )
1769
+ }
1770
+ });
1771
+ routesDir = opts.routesDir || "src/routes";
1772
+ markoVitePluginOptions.store = store = opts.store || new FileStore(
1773
+ `marko-serve-vite-${crypto.createHash("SHA1").update(root).digest("hex")}`
1774
+ );
1775
+ markoVitePluginOptions.runtimeId = opts.runtimeId;
1776
+ markoVitePluginOptions.basePathVar = opts.basePathVar;
1777
+ resolvedRoutesDir = path2.resolve(root, routesDir);
1778
+ typesDir = path2.join(root, ".marko-run");
1779
+ devEntryFile = path2.join(root, "index.html");
1780
+ devEntryFilePosix = normalizePath(devEntryFile);
1781
+ const assetsDir = ((_d = config2.build) == null ? void 0 : _d.assetsDir) || "assets";
1782
+ let rollupOutputOptions = (_f = (_e = config2.build) == null ? void 0 : _e.rollupOptions) == null ? void 0 : _f.output;
1783
+ if (isBuild) {
1784
+ const defaultRollupOutputOptions = {
1785
+ assetFileNames({ name }) {
1786
+ if (name && name.indexOf("_marko-virtual_id_") < 0) {
1787
+ return `${assetsDir}/${getEntryFileName(name) || "[name]"}-[hash].[ext]`;
1788
+ }
1789
+ return `${assetsDir}/_[hash].[ext]`;
1790
+ },
1791
+ entryFileNames(info) {
1792
+ let name = getEntryFileName(info.facadeModuleId);
1793
+ if (!name) {
1794
+ for (let id of info.moduleIds) {
1795
+ name = getEntryFileName(id);
1796
+ if (name) {
1797
+ break;
1798
+ }
1799
+ }
1800
+ }
1801
+ return `${assetsDir}/${name || "[name]"}-[hash].js`;
1802
+ },
1803
+ chunkFileNames: isSSRBuild ? `_[hash].js` : `${assetsDir}/_[hash].js`
1804
+ };
1805
+ if (!rollupOutputOptions) {
1806
+ rollupOutputOptions = defaultRollupOutputOptions;
1807
+ } else if (!Array.isArray(rollupOutputOptions)) {
1808
+ rollupOutputOptions = {
1809
+ ...defaultRollupOutputOptions,
1810
+ ...rollupOutputOptions
1811
+ };
1812
+ } else {
1813
+ rollupOutputOptions = rollupOutputOptions.map((options) => ({
1814
+ ...defaultRollupOutputOptions,
1815
+ ...options
1816
+ }));
1817
+ }
1818
+ }
1819
+ let pluginConfig = {
1820
+ logLevel: isBuild ? "warn" : void 0,
1821
+ define: isBuild ? {
1822
+ "process.env.NODE_ENV": "'production'"
1823
+ } : void 0,
1824
+ ssr: {
1825
+ noExternal: /@marko\/run/
1826
+ },
1827
+ build: {
1828
+ emptyOutDir: isSSRBuild,
1829
+ rollupOptions: {
1830
+ output: rollupOutputOptions
1831
+ }
1832
+ },
1833
+ resolve: isBuild ? {
1834
+ browserField: isSSRBuild ? false : void 0,
1835
+ conditions: [
1836
+ isSSRBuild ? "node" : "browser",
1837
+ "import",
1838
+ "require",
1839
+ "production",
1840
+ "default"
1841
+ ]
1842
+ } : void 0
1843
+ };
1844
+ if (adapter == null ? void 0 : adapter.viteConfig) {
1845
+ const adapterConfig = await adapter.viteConfig(config2);
1846
+ if (adapterConfig) {
1847
+ pluginConfig = mergeConfig(pluginConfig, adapterConfig);
1848
+ }
1849
+ }
1850
+ return setExternalPluginOptions(pluginConfig, opts);
1851
+ },
1852
+ configResolved(config2) {
1853
+ resolvedConfig = config2;
1854
+ const {
1855
+ ssr,
1856
+ rollupOptions: { input }
1857
+ } = config2.build;
1858
+ if (typeof ssr === "string") {
1859
+ ssrEntryFiles = [ssr];
1860
+ } else if (typeof input === "string") {
1861
+ ssrEntryFiles = [input];
1862
+ } else if (Array.isArray(input)) {
1863
+ ssrEntryFiles = input;
1864
+ } else if (input) {
1865
+ ssrEntryFiles = Object.values(input);
1866
+ } else {
1867
+ ssrEntryFiles = [];
1868
+ }
1869
+ },
1870
+ configureServer(_server) {
1871
+ devServer = _server;
1872
+ devServer.watcher.on("all", async (type, filename) => {
1873
+ const routableFileType = matchRoutableFile(path2.parse(filename).base);
1874
+ if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
1875
+ if (type === "add") {
1876
+ isStale = true;
1877
+ } else if (type === "unlink") {
1878
+ isStale = true;
1879
+ } else if (type === "change") {
1880
+ if (routableFileType === RoutableFileTypes.Handler) {
1881
+ isStale = true;
1882
+ }
1883
+ }
1884
+ if (isStale) {
1885
+ for (const id of virtualFiles.keys()) {
1886
+ devServer.watcher.emit("change", id);
1887
+ break;
1888
+ }
1889
+ }
1890
+ }
1891
+ });
1892
+ },
1893
+ async buildStart(_options) {
1894
+ if (isBuild && !isSSRBuild) {
1895
+ try {
1896
+ routeData = JSON.parse(
1897
+ await store.get(routeDataFilename)
1898
+ );
1899
+ } catch {
1900
+ this.error(
1901
+ `You must run the "ssr" build before the "browser" build.`
1902
+ );
1903
+ }
1904
+ routes = routeData.routes;
1905
+ times = routeData.times;
1906
+ for (const { key, code } of routeData.files) {
1907
+ virtualFiles.set(key, code);
1908
+ }
1909
+ isStale = false;
1910
+ isRendered = true;
1911
+ } else {
1912
+ extractVerbs = isBuild ? getVerbsFromFileBuild.bind(null, this) : getVerbsFromFileDev.bind(null, devServer);
1913
+ }
1914
+ },
1915
+ async resolveId(importee, importer) {
1916
+ let resolved;
1917
+ if (importee.startsWith(virtualRuntimePrefix)) {
1918
+ return this.resolve(
1919
+ path2.resolve(__dirname, "../runtime/internal"),
1920
+ importer,
1921
+ { skipSelf: true }
1922
+ );
1923
+ } else if (importee.startsWith(virtualFilePrefix)) {
1924
+ importee = path2.resolve(
1925
+ root,
1926
+ importee.slice(virtualFilePrefix.length + 1)
1927
+ );
1928
+ } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
1929
+ importee = path2.resolve(root, "." + importee);
1930
+ }
1931
+ importee = normalizePath(importee);
1932
+ if (isStale) {
1933
+ await buildVirtualFiles();
1934
+ }
1935
+ if (virtualFiles.has(importee)) {
1936
+ resolved = importee;
1937
+ }
1938
+ return resolved || null;
1939
+ },
1940
+ async load(id) {
1941
+ var _a;
1942
+ if (id.endsWith(serverEntryQuery)) {
1943
+ id = id.slice(0, -serverEntryQuery.length);
1944
+ }
1945
+ if (virtualFiles.has(id)) {
1946
+ if (!isRendered) {
1947
+ await renderVirtualFiles();
1948
+ if (!isBuild) {
1949
+ await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes.list));
1950
+ }
1951
+ }
1952
+ return virtualFiles.get(id);
1953
+ }
1954
+ }
1955
+ },
1956
+ ...markoVitePlugin(markoVitePluginOptions),
1957
+ {
1958
+ name: `${PLUGIN_NAME_PREFIX}:post`,
1959
+ enforce: "post",
1960
+ generateBundle(options, bundle) {
1961
+ if (options.sourcemap && options.sourcemap !== "inline") {
1962
+ for (const key of Object.keys(bundle)) {
1963
+ if (key.endsWith(".map") && !bundle[key.slice(0, -4)]) {
1964
+ delete bundle[key];
1965
+ }
1966
+ }
1967
+ }
1968
+ },
1969
+ async writeBundle(options, bundle) {
1970
+ var _a;
1971
+ if (isSSRBuild) {
1972
+ const builtEntries = Object.values(bundle).reduce(
1973
+ (acc, item) => {
1974
+ if (item.type === "chunk" && item.isEntry) {
1975
+ acc.push(path2.join(options.dir, item.fileName));
1976
+ }
1977
+ return acc;
1978
+ },
1979
+ []
1980
+ );
1981
+ routeData = {
1982
+ routes,
1983
+ files: [],
1984
+ times,
1985
+ builtEntries,
1986
+ sourceEntries: ssrEntryFiles
1987
+ };
1988
+ for (const [key, code] of virtualFiles) {
1989
+ routeData.files.push({ key, code });
1990
+ }
1991
+ await store.set(routeDataFilename, JSON.stringify(routeData));
1992
+ await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes.list));
1993
+ } else if (isBuild) {
1994
+ logRoutesTable(routes, bundle, options);
1995
+ }
1996
+ },
1997
+ async closeBundle() {
1998
+ if (isBuild && !isSSRBuild && (adapter == null ? void 0 : adapter.buildEnd)) {
1999
+ await adapter.buildEnd(
2000
+ resolvedConfig,
2001
+ routes.list,
2002
+ routeData.builtEntries,
2003
+ routeData.sourceEntries
2004
+ );
2005
+ }
2006
+ }
2007
+ }
2008
+ ];
2009
+ }
2010
+ async function getVerbsFromFileBuild(context, filePath) {
2011
+ const verbs = [];
2012
+ const result = await context.load({
2013
+ id: filePath,
2014
+ resolveDependencies: false
2015
+ });
2016
+ if (result) {
2017
+ const exportIds = getExportIdentifiers(result.ast);
2018
+ for (const id of exportIds) {
2019
+ const verb = id.toLowerCase();
2020
+ if (id === verb.toUpperCase() && httpVerbs.includes(verb)) {
2021
+ verbs.push(verb);
2022
+ }
2023
+ }
2024
+ }
2025
+ return verbs;
2026
+ }
2027
+ async function getVerbsFromFileDev(devServer, filePath) {
2028
+ const verbs = [];
2029
+ const result = await devServer.transformRequest(filePath, { ssr: true });
2030
+ if (result && result.code) {
2031
+ const verbMatchReg = /__vite_ssr_exports__,\s+["'](GET|POST|PUT|DELETE)["']/gi;
2032
+ let match = verbMatchReg.exec(result.code);
2033
+ while (match) {
2034
+ const id = match[1];
2035
+ const verb = id.toLowerCase();
2036
+ if (httpVerbs.includes(verb)) {
2037
+ if (id === verb.toUpperCase()) {
2038
+ verbs.push(verb);
2039
+ } else {
2040
+ console.warn(
2041
+ `Found export '${id}' in handler ${filePath} which is close to '${verb.toUpperCase()}'. Exported handlers need to be uppercase: GET, POST, PUT or DELETE.`
2042
+ );
2043
+ }
2044
+ }
2045
+ match = verbMatchReg.exec(result.code);
2046
+ }
2047
+ }
2048
+ return verbs;
2049
+ }
2050
+ function single(fn) {
2051
+ let promise;
2052
+ return async (...args) => {
2053
+ if (promise) {
2054
+ return promise;
2055
+ }
2056
+ promise = fn(...args);
2057
+ const result = await promise;
2058
+ promise = void 0;
2059
+ return result;
2060
+ };
2061
+ }
2062
+ async function globFileExists(root, pattern) {
2063
+ return new Promise((resolve, reject) => {
2064
+ glob(pattern, { root }, (err, matches) => {
2065
+ if (err) {
2066
+ reject(err);
2067
+ }
2068
+ resolve(matches.length > 0);
2069
+ });
2070
+ });
2071
+ }
2072
+ async function ensureDir(dir) {
2073
+ if (!fs3.existsSync(dir)) {
2074
+ await fs3.promises.mkdir(dir, { recursive: true });
2075
+ }
2076
+ }
253
2077
  async function getPackageData(dir) {
254
2078
  do {
255
2079
  const pkgPath = path2.join(dir, "package.json");
256
- if (fs2.existsSync(pkgPath)) {
257
- return JSON.parse(await fs2.promises.readFile(pkgPath, "utf-8"));
2080
+ if (fs3.existsSync(pkgPath)) {
2081
+ return JSON.parse(await fs3.promises.readFile(pkgPath, "utf-8"));
258
2082
  }
259
2083
  } while (dir !== (dir = path2.dirname(dir)));
260
2084
  return null;
261
2085
  }
262
2086
  async function resolveAdapter(root, options, log) {
263
- const { adapter } = options;
264
- if (adapter !== void 0) {
265
- return adapter;
2087
+ if (options && options.adapter !== void 0) {
2088
+ return options.adapter;
266
2089
  }
267
2090
  const pkg = await getPackageData(root);
268
2091
  if (pkg) {
@@ -289,10 +2112,20 @@ async function resolveAdapter(root, options, log) {
289
2112
  log && console.log("Using default adapter");
290
2113
  return module.default();
291
2114
  }
2115
+ var markoEntryFileRegex = /__marko-run__([^.]+)\.(.+)\.marko\.([^.]+)$/;
2116
+ function getEntryFileName(file) {
2117
+ const match = file && markoEntryFileRegex.exec(file);
2118
+ return match ? match[2] : void 0;
2119
+ }
2120
+ function isPluginIncluded(config2) {
2121
+ return config2.plugins.some((plugin) => {
2122
+ return plugin.name.startsWith(PLUGIN_NAME_PREFIX);
2123
+ });
2124
+ }
292
2125
 
293
2126
  // src/cli/commands.ts
294
2127
  var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
295
- var defaultPort = +process.env.PORT || 3e3;
2128
+ var defaultPort = Number(process.env.PORT || 3e3);
296
2129
  var defaultConfigFileBases = ["serve.config", "vite.config"];
297
2130
  var defaultConfigFileExts = [".js", ".cjs", ".mjs", ".ts", ".mts"];
298
2131
  async function preview(entry, cwd, configFile, port, outDir, envFile, args = []) {
@@ -300,10 +2133,10 @@ async function preview(entry, cwd, configFile, port, outDir, envFile, args = [])
300
2133
  { root: cwd, configFile, logLevel: "silent", build: { outDir } },
301
2134
  "serve"
302
2135
  );
303
- if (port === void 0) {
304
- port = resolvedConfig.preview.port ?? defaultPort;
305
- }
306
- const adapter = await resolveAdapter2(resolvedConfig);
2136
+ const [availablePort, adapter] = await Promise.all([
2137
+ getAvailablePort(port ?? resolvedConfig.preview.port ?? resolvedConfig.server.port ?? defaultPort),
2138
+ resolveAdapter2(resolvedConfig)
2139
+ ]);
307
2140
  if (!adapter) {
308
2141
  throw new Error("No adapter specified for 'serve' command");
309
2142
  } else if (!adapter.startPreview) {
@@ -318,7 +2151,7 @@ async function preview(entry, cwd, configFile, port, outDir, envFile, args = [])
318
2151
  cwd,
319
2152
  dir,
320
2153
  args,
321
- port,
2154
+ port: availablePort,
322
2155
  envFile
323
2156
  };
324
2157
  return await adapter.startPreview(entryFile, options);
@@ -328,13 +2161,13 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
328
2161
  { root: cwd, configFile, logLevel: "silent" },
329
2162
  "build"
330
2163
  );
331
- if (port === void 0) {
332
- port = resolvedConfig.preview.port ?? defaultPort;
333
- }
334
2164
  if (envFile) {
335
2165
  envFile = path3.resolve(cwd, envFile);
336
2166
  }
337
- const adapter = await resolveAdapter2(resolvedConfig);
2167
+ const [availablePort, adapter] = await Promise.all([
2168
+ getAvailablePort(port ?? resolvedConfig.server.port ?? resolvedConfig.preview.port ?? defaultPort),
2169
+ resolveAdapter2(resolvedConfig)
2170
+ ]);
338
2171
  if (!adapter) {
339
2172
  throw new Error(
340
2173
  "No adapter specified for 'dev' command without custom target"
@@ -344,18 +2177,24 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
344
2177
  `Adapter '${adapter.name}' does not support 'serve' command`
345
2178
  );
346
2179
  }
2180
+ const config2 = {
2181
+ root: cwd,
2182
+ configFile,
2183
+ plugins: isPluginIncluded(resolvedConfig) ? void 0 : [markoRun()]
2184
+ };
347
2185
  const options = {
348
2186
  cwd,
349
2187
  args,
350
- port,
2188
+ port: availablePort,
351
2189
  envFile
352
2190
  };
353
- return await adapter.startDev(entry, { root: cwd, configFile }, options);
2191
+ return await adapter.startDev(entry, config2, options);
354
2192
  }
355
2193
  async function build(entry, cwd, configFile, outDir, envFile) {
356
2194
  var _a;
2195
+ const root = cwd;
357
2196
  const resolvedConfig = await resolveConfig(
358
- { root: cwd, configFile, logLevel: "silent" },
2197
+ { root, configFile, logLevel: "silent" },
359
2198
  "build"
360
2199
  );
361
2200
  const adapter = await resolveAdapter2(resolvedConfig);
@@ -374,7 +2213,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
374
2213
  envFile = path3.resolve(cwd, envFile);
375
2214
  }
376
2215
  let buildConfig = {
377
- root: cwd,
2216
+ root,
378
2217
  configFile,
379
2218
  build: {
380
2219
  ssr: false,
@@ -385,10 +2224,14 @@ async function build(entry, cwd, configFile, outDir, envFile) {
385
2224
  store: new MemoryStore()
386
2225
  });
387
2226
  buildConfig = setExternalAdapterOptions(buildConfig, {
2227
+ root,
2228
+ isBuild: true,
388
2229
  envFile
389
2230
  });
2231
+ const hasPlugin = isPluginIncluded(resolvedConfig);
390
2232
  await viteBuild({
391
2233
  ...buildConfig,
2234
+ plugins: hasPlugin ? void 0 : [markoRun()],
392
2235
  build: {
393
2236
  target: "esnext",
394
2237
  ...buildConfig.build,
@@ -402,6 +2245,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
402
2245
  });
403
2246
  await viteBuild({
404
2247
  ...buildConfig,
2248
+ plugins: hasPlugin ? void 0 : [markoRun()],
405
2249
  build: {
406
2250
  ...buildConfig.build,
407
2251
  sourcemap: true
@@ -411,7 +2255,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
411
2255
  function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
412
2256
  for (const ext of extensions) {
413
2257
  const filePath = path3.join(dir, base + ext);
414
- if (fs3.existsSync(filePath)) {
2258
+ if (fs4.existsSync(filePath)) {
415
2259
  return filePath;
416
2260
  }
417
2261
  }
@@ -420,7 +2264,7 @@ function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
420
2264
  async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
421
2265
  if (configFile) {
422
2266
  const configFilePath = path3.join(dir, configFile);
423
- if (!fs3.existsSync(configFilePath)) {
2267
+ if (!fs4.existsSync(configFilePath)) {
424
2268
  throw new Error(`No config file found at '${configFilePath}'`);
425
2269
  }
426
2270
  return configFile;
@@ -433,12 +2277,9 @@ async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
433
2277
  }
434
2278
  return path3.join(__dirname2, "default.config.mjs");
435
2279
  }
436
- async function resolveAdapter2(config) {
437
- const options = getExternalPluginOptions(config);
438
- if (!options) {
439
- throw new Error("Unable to resolve @marko/run options");
440
- }
441
- return resolveAdapter(config.root, options);
2280
+ async function resolveAdapter2(config2) {
2281
+ const options = getExternalPluginOptions(config2);
2282
+ return resolveAdapter(config2.root, options);
442
2283
  }
443
2284
 
444
2285
  // src/cli/index.ts
@@ -458,12 +2299,12 @@ prog.command("preview [entry]").describe("Start a production-like server for alr
458
2299
  process.env.NODE_ENV = "production";
459
2300
  const cwd = process.cwd();
460
2301
  const args = process.argv.slice(entry ? 4 : 3);
461
- const config = await getViteConfig(cwd, opts.config);
462
- await build(entry, cwd, config, opts.output, opts.env);
2302
+ const config2 = await getViteConfig(cwd, opts.config);
2303
+ await build(entry, cwd, config2, opts.output, opts.env);
463
2304
  await preview(
464
2305
  opts.file,
465
2306
  cwd,
466
- config,
2307
+ config2,
467
2308
  opts.port,
468
2309
  opts.output,
469
2310
  opts.env,
@@ -476,15 +2317,15 @@ prog.command("dev [entry]", "", { default: true }).describe("Start development s
476
2317
  ).example("dev --config vite.config.js").action(async (entry, opts) => {
477
2318
  const cwd = process.cwd();
478
2319
  const args = process.argv.slice(entry ? 4 : 3);
479
- const config = await getViteConfig(cwd, opts.config);
480
- await dev(entry, cwd, config, opts.port, opts.env, args);
2320
+ const config2 = await getViteConfig(cwd, opts.config);
2321
+ await dev(entry, cwd, config2, opts.port, opts.env, args);
481
2322
  });
482
2323
  prog.command("build [entry]").describe("Build the application (without serving it)").option(
483
2324
  "-o, --output",
484
2325
  "Directory to write built files (default: 'build.outDir' in Vite config)"
485
2326
  ).example("build --config vite.config.js").action(async (entry, opts) => {
486
2327
  const cwd = process.cwd();
487
- const config = await getViteConfig(cwd, opts.config);
488
- await build(entry, cwd, config, opts.ouput, opts.env);
2328
+ const config2 = await getViteConfig(cwd, opts.config);
2329
+ await build(entry, cwd, config2, opts.ouput, opts.env);
489
2330
  });
490
2331
  prog.parse(process.argv);