@marko/run 0.6.5 → 0.7.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.
@@ -59,9 +59,9 @@ var import_vite = __toESM(require("@marko/vite"), 1);
59
59
  var import_browserslist = __toESM(require("browserslist"), 1);
60
60
  var import_debug = __toESM(require("debug"), 1);
61
61
  var import_esbuild_plugin_browserslist = require("esbuild-plugin-browserslist");
62
- var import_fs3 = __toESM(require("fs"), 1);
62
+ var import_fs4 = __toESM(require("fs"), 1);
63
63
  var import_glob = require("glob");
64
- var import_path4 = __toESM(require("path"), 1);
64
+ var import_path6 = __toESM(require("path"), 1);
65
65
  var import_url2 = require("url");
66
66
  var import_vite2 = require("vite");
67
67
 
@@ -91,7 +91,7 @@ function prepareError(err) {
91
91
  }
92
92
 
93
93
  // src/vite/codegen/index.ts
94
- var import_path = __toESM(require("path"), 1);
94
+ var import_path2 = __toESM(require("path"), 1);
95
95
 
96
96
  // src/vite/constants.ts
97
97
  var markoRunFilePrefix = "__marko-run__";
@@ -116,6 +116,12 @@ var RoutableFileTypes = {
116
116
  Error: "500"
117
117
  };
118
118
 
119
+ // src/vite/utils/fs.ts
120
+ var import_path = __toESM(require("path"), 1);
121
+ var POSIX_SEP = "/";
122
+ var WINDOWS_SEP = "\\";
123
+ var normalizePath = import_path.default.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
124
+
119
125
  // src/vite/utils/route.ts
120
126
  var httpVerbOrder = httpVerbs.reduce(
121
127
  (order, verb, index) => {
@@ -139,6 +145,9 @@ function hasVerb(route, verb) {
139
145
  var _a, _b;
140
146
  return verb === "get" && !!route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes(verb)) || verb === "head" && hasVerb(route, "get");
141
147
  }
148
+ function getRouteVirtualFileName(route) {
149
+ return `${markoRunFilePrefix}${route.key.replace(/\//g, ".")}.js`;
150
+ }
142
151
 
143
152
  // src/vite/codegen/writer.ts
144
153
  function createWriter(sink, options) {
@@ -281,27 +290,34 @@ function createStringWriter(opts) {
281
290
  }
282
291
 
283
292
  // src/vite/codegen/index.ts
284
- function renderRouteTemplate(route, getRelativePath) {
293
+ function normalizedRelativePath(from, to) {
294
+ const relativePath = normalizePath(import_path2.default.relative(from, to));
295
+ return relativePath.startsWith(".") ? relativePath : "./" + relativePath;
296
+ }
297
+ function renderRouteTemplate(route, rootDir) {
285
298
  if (!route.page) {
286
299
  throw new Error(`Route ${route.key} has no page to render`);
287
300
  }
301
+ if (!route.templateFilePath) {
302
+ throw new Error(`Route ${route.key} has no template file path`);
303
+ }
288
304
  return renderEntryTemplate(
289
- route.entryName,
305
+ normalizedRelativePath(rootDir, route.templateFilePath),
290
306
  [...route.layouts, route.page].map(
291
- (file) => getRelativePath(file.importPath)
307
+ (file) => normalizedRelativePath(
308
+ import_path2.default.dirname(route.templateFilePath),
309
+ file.filePath
310
+ )
292
311
  ),
293
312
  route.key === RoutableFileTypes.Error ? ["error"] : []
294
313
  );
295
314
  }
296
315
  function renderEntryTemplate(name, files, pageInputs = []) {
297
- if (!name) {
298
- throw new Error(`Invalid argument - 'name' cannot be empty`);
299
- }
300
316
  if (!files.length) {
301
317
  throw new Error(`Invalid argument - 'files' cannot be empty`);
302
318
  }
303
319
  const writer = createStringWriter();
304
- writer.writeLines(`// ${name}.marko`);
320
+ writer.writeLines(`// ${name}`);
305
321
  writer.branch("imports");
306
322
  writer.writeLines("");
307
323
  writeEntryTemplateTag(writer, files, pageInputs);
@@ -311,7 +327,7 @@ function writeEntryTemplateTag(writer, [file, ...rest], pageInputs, index = 1) {
311
327
  if (file) {
312
328
  const isLast = !rest.length;
313
329
  const tag = isLast ? "Page" : `Layout${index}`;
314
- writer.branch("imports").writeLines(`import ${tag} from '${file}';`);
330
+ writer.branch("imports").writeLines(`import ${tag} from "${file}";`);
315
331
  if (isLast) {
316
332
  const attributes = pageInputs.length ? " " + pageInputs.map((name) => `${name}=input.${name}`).join(" ") : "";
317
333
  writer.writeLines(`<${tag}${attributes}/>`);
@@ -322,9 +338,9 @@ function writeEntryTemplateTag(writer, [file, ...rest], pageInputs, index = 1) {
322
338
  }
323
339
  }
324
340
  }
325
- function renderRouteEntry(route, entriesDir) {
341
+ function renderRouteEntry(route, rootDir) {
326
342
  var _a;
327
- const { key, index, handler, page, middleware, meta, entryName } = route;
343
+ const { key, index, handler, page, middleware, meta } = route;
328
344
  const verbs = getVerbs(route);
329
345
  if (!verbs) {
330
346
  throw new Error(
@@ -332,7 +348,7 @@ function renderRouteEntry(route, entriesDir) {
332
348
  );
333
349
  }
334
350
  const writer = createStringWriter();
335
- writer.writeLines(`// ${virtualFilePrefix}/${entryName}.js`);
351
+ writer.writeLines(`// ${virtualFilePrefix}${getRouteVirtualFileName(route)}`);
336
352
  const imports = writer.branch("imports");
337
353
  const runtimeImports = [];
338
354
  if (handler) {
@@ -344,9 +360,6 @@ function renderRouteEntry(route, entriesDir) {
344
360
  if (!page || verbs.some((verb) => verb !== "get" && verb !== "head")) {
345
361
  runtimeImports.push("noContent");
346
362
  }
347
- if (page) {
348
- runtimeImports.push("pageResponse");
349
- }
350
363
  if (verbs.includes("head")) {
351
364
  runtimeImports.push("stripResponseBody");
352
365
  }
@@ -354,7 +367,7 @@ function renderRouteEntry(route, entriesDir) {
354
367
  imports.writeLines(
355
368
  `import { ${runtimeImports.join(
356
369
  ", "
357
- )} } from '${virtualFilePrefix}/runtime/internal';`
370
+ )} } from "${virtualFilePrefix}/runtime/internal";`
358
371
  );
359
372
  }
360
373
  if (middleware.length) {
@@ -362,7 +375,7 @@ function renderRouteEntry(route, entriesDir) {
362
375
  imports.writeLines(
363
376
  `import { ${names.join(
364
377
  ", "
365
- )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
378
+ )} } from "${virtualFilePrefix}/${markoRunFilePrefix}middleware.js";`
366
379
  );
367
380
  }
368
381
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
@@ -374,18 +387,17 @@ function renderRouteEntry(route, entriesDir) {
374
387
  writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
375
388
  }
376
389
  imports.writeLines(
377
- `import { ${names.join(", ")} } from './${handler.importPath}';`
390
+ `import { ${names.join(", ")} } from "${normalizedRelativePath(rootDir, handler.filePath)}";`
378
391
  );
379
392
  }
380
393
  if (page) {
381
- const pageNameIndex = page.name.indexOf("+page");
382
- const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
383
- const importPath = route.layouts.length ? `./${import_path.default.posix.join(entriesDir, page.relativePath, "..", pageNamePrefix + "route.marko")}` : `./${page.importPath}`;
384
- imports.writeLines(`import page from '${importPath}${serverEntryQuery}';`);
394
+ imports.writeLines(
395
+ `import page from "${normalizedRelativePath(rootDir, route.templateFilePath || page.filePath)}${serverEntryQuery}";`
396
+ );
385
397
  }
386
398
  if (meta) {
387
399
  imports.writeLines(
388
- `export { default as meta${index} } from './${meta.importPath}';`
400
+ `export { default as meta${index} } from "${normalizedRelativePath(rootDir, meta.filePath)}";`
389
401
  );
390
402
  }
391
403
  for (const verb of verbs) {
@@ -402,9 +414,7 @@ function writeRouteEntryHandler(writer, route, verb) {
402
414
  let hasBody = false;
403
415
  writer.writeLines("");
404
416
  if (page && (verb === "get" || verb === "head")) {
405
- writer.writeBlockStart(
406
- `export function ${verb}${index}(context, buildInput) {`
407
- );
417
+ writer.writeBlockStart(`export function ${verb}${index}(context) {`);
408
418
  } else {
409
419
  writer.writeBlockStart(`export function ${verb}${index}(context) {`);
410
420
  }
@@ -414,7 +424,7 @@ function writeRouteEntryHandler(writer, route, verb) {
414
424
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
415
425
  const name = `${verb}Handler`;
416
426
  continuations.writeLines(
417
- `const ${currentName} = () => pageResponse(page, buildInput());`
427
+ `const ${currentName} = () => context.render(page, {});`
418
428
  );
419
429
  if (len) {
420
430
  nextName = currentName;
@@ -433,17 +443,15 @@ function writeRouteEntryHandler(writer, route, verb) {
433
443
  hasBody = true;
434
444
  }
435
445
  } else if (verb === "head") {
436
- writer.writeLines(
437
- `return stripResponseBody(get${index}(context, buildInput));`
438
- );
446
+ writer.writeLines(`return stripResponseBody(get${index}(context));`);
439
447
  hasBody = true;
440
448
  } else if (len) {
441
449
  continuations.writeLines(
442
- `const ${currentName} = () => pageResponse(page, buildInput());`
450
+ `const ${currentName} = () => context.render(page, {});`
443
451
  );
444
452
  nextName = currentName;
445
453
  } else {
446
- writer.writeLines(`return pageResponse(page, buildInput());`);
454
+ writer.writeLines(`return context.render(page, {});`);
447
455
  hasBody = true;
448
456
  }
449
457
  } else if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes(verb)) {
@@ -493,7 +501,7 @@ function writeRouteEntryHandler(writer, route, verb) {
493
501
  continuations.join();
494
502
  writer.writeBlockEnd("}");
495
503
  }
496
- function renderRouter(routes, entriesDir, options = {
504
+ function renderRouter(routes, rootDir, options = {
497
505
  trailingSlashes: "RedirectWithout"
498
506
  }) {
499
507
  const writer = createStringWriter();
@@ -502,20 +510,19 @@ function renderRouter(routes, entriesDir, options = {
502
510
  writer.writeLines(`// @marko/run/router`);
503
511
  const imports = writer.branch("imports");
504
512
  imports.writeLines(
505
- `import { NotHandled, NotMatched, createContext${hasErrorPage || hasNotFoundPage ? ", pageResponse" : ""} } from '${virtualFilePrefix}/runtime/internal';`
513
+ `import { NotHandled, NotMatched, createContext } from "${virtualFilePrefix}/runtime/internal";`
506
514
  );
507
515
  for (const route of routes.list) {
508
516
  const verbs = getVerbs(route);
509
517
  const names = verbs.map((verb) => `${verb}${route.index}`);
510
518
  route.meta && names.push(`meta${route.index}`);
511
519
  imports.writeLines(
512
- `import { ${names.join(", ")} } from '${virtualFilePrefix}/${route.entryName}.js';`
520
+ `import { ${names.join(", ")} } from "${virtualFilePrefix}/${getRouteVirtualFileName(route)}";`
513
521
  );
514
522
  }
515
523
  for (const route of Object.values(routes.special)) {
516
- const importPath = route.layouts.length ? `./${import_path.default.posix.join(entriesDir, route.page.relativePath, "..", `route.${route.key}.marko`)}` : `./${route.page.importPath}`;
517
524
  imports.writeLines(
518
- `import page${route.key} from '${importPath}${serverEntryQuery}';`
525
+ `import page${route.key} from "${normalizedRelativePath(rootDir, route.templateFilePath || route.page.filePath)}${serverEntryQuery}";`
519
526
  );
520
527
  }
521
528
  writer.writeLines(
@@ -523,11 +530,12 @@ function renderRouter(routes, entriesDir, options = {
523
530
  globalThis.__marko_run__ = { match, fetch, invoke };
524
531
  `
525
532
  ).writeBlockStart(`export function match(method, pathname) {`).writeLines(
526
- `if (!pathname) {
527
- pathname = '/';
528
- } else if (pathname.charAt(0) !== '/') {
529
- pathname = '/' + pathname;
530
- }`
533
+ `const last = pathname.length - 1;
534
+ return match_internal(method, last && pathname.charAt(last) === '/' ? pathname.slice(0, last) : pathname)
535
+ };
536
+
537
+ function match_internal(method, pathname) {
538
+ const len = pathname.length;`
531
539
  ).writeBlockStart(`switch (method) {`);
532
540
  for (const verb of httpVerbs) {
533
541
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
@@ -543,13 +551,13 @@ globalThis.__marko_run__ = { match, fetch, invoke };
543
551
  writer.writeLines("").writeBlockStart(
544
552
  "export async function invoke(route, request, platform, url) {"
545
553
  ).writeLines(
546
- "const [context, buildInput] = createContext(route, request, platform, url);"
554
+ "const context = createContext(route, request, platform, url);"
547
555
  );
548
556
  if (hasErrorPage) {
549
557
  writer.writeBlockStart("try {");
550
558
  }
551
559
  writer.writeBlockStart("if (route) {").writeBlockStart("try {").writeLines(
552
- "const response = await route.handler(context, buildInput);",
560
+ "const response = await route.handler(context);",
553
561
  "if (response) return response;"
554
562
  ).indent--;
555
563
  writer.writeBlockStart("} catch (error) {").writeLines(
@@ -566,7 +574,7 @@ const page404ResponseInit = {
566
574
  );
567
575
  writer.write(`
568
576
  if (context.request.headers.get('Accept')?.includes('text/html')) {
569
- return pageResponse(page404, buildInput(), page404ResponseInit);
577
+ return context.render(page404, {}, page404ResponseInit);
570
578
  }`);
571
579
  }
572
580
  writer.indent--;
@@ -583,7 +591,7 @@ const page500ResponseInit = {
583
591
  writer.writeBlockStart(`} catch (error) {`).writeBlockStart(
584
592
  `if (context.request.headers.get('Accept')?.includes('text/html')) {`
585
593
  ).writeLines(
586
- `return pageResponse(page500, buildInput({ error }), page500ResponseInit);`
594
+ `return context.render(page500, { error }, page500ResponseInit);`
587
595
  ).writeBlockEnd("}").writeLines("throw error;").writeBlockEnd("}");
588
596
  }
589
597
  writer.writeBlockEnd("}");
@@ -595,38 +603,40 @@ function renderFetch(writer, options) {
595
603
  export async function fetch(request, platform) {
596
604
  try {
597
605
  const url = new URL(request.url);
598
- let { pathname } = url;`);
606
+ const { pathname } = url;
607
+ const last = pathname.length - 1;
608
+ const hasTrailingSlash = last && pathname.charAt(last) === '/';
609
+ const normalizedPathname = hasTrailingSlash ? pathname.slice(0, last) : pathname;
610
+ const route = match_internal(request.method, normalizedPathname);`);
599
611
  switch (options.trailingSlashes) {
600
612
  case "RedirectWithout":
601
613
  writer.write(`
602
- if (pathname !== '/' && pathname.endsWith('/')) {
603
- url.pathname = pathname.slice(0, -1);
614
+ if (route && hasTrailingSlash) {
615
+ url.pathname = normalizedPathname
604
616
  return Response.redirect(url);
605
617
  }`);
606
618
  break;
607
619
  case "RedirectWith":
608
620
  writer.write(`
609
- if (pathname !== '/' && !pathname.endsWith('/')) {
610
- url.pathname = pathname + '/';
621
+ if (route && pathname !== '/' && !hasTrailingSlash) {
622
+ url.pathname += '/';
611
623
  return Response.redirect(url);
612
624
  }`);
613
625
  break;
614
626
  case "RewriteWithout":
615
627
  writer.write(`
616
- if (pathname !== '/' && pathname.endsWith('/')) {
617
- url.pathname = pathname = pathname.slice(0, -1);
628
+ if (route && hasTrailingSlash) {
629
+ url.pathname = normalizedPathname;
618
630
  }`);
619
631
  break;
620
632
  case "RewriteWith":
621
633
  writer.write(`
622
- if (pathname !== '/' && !pathname.endsWith('/')) {
623
- url.pathname = pathname = pathname + '/';
634
+ if (route && pathname !== '/' && !hasTrailingSlash) {
635
+ url.pathname += '/';
624
636
  }`);
625
637
  break;
626
638
  }
627
639
  writer.write(`
628
-
629
- const route = match(request.method, pathname);
630
640
  return await invoke(route, request, platform, url);
631
641
  } catch (error) {
632
642
  if (import.meta.env.DEV) {
@@ -642,10 +652,9 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
642
652
  const { route, dynamic, catchAll } = trie;
643
653
  let closeCount = 0;
644
654
  if (level === 0) {
645
- writer.writeLines(`const len = pathname.length;`);
646
655
  if (route) {
647
656
  writer.writeLines(
648
- `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
657
+ `if (len === 1) return ${renderMatch(verb, route, trie.path)};`
649
658
  );
650
659
  } else if (trie.static || dynamic) {
651
660
  writer.writeBlockStart(`if (len > 1) {`);
@@ -686,17 +695,15 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
686
695
  if (useSwitch) {
687
696
  writer.writeBlockStart(`switch (${value}) {`);
688
697
  }
689
- for (const { key, path: path5, route: route2 } of terminal) {
698
+ for (const { key, path: path7, route: route2 } of terminal) {
690
699
  const decodedKey = decodeURIComponent(key);
691
700
  if (useSwitch) {
692
701
  writer.write(`case '${decodedKey}': `, true);
693
702
  } else {
694
703
  writer.write(`if (${value} === '${decodedKey}') `, true);
695
704
  }
696
- writer.write(
697
- `return ${renderMatch(verb, route2, path5)}; // ${path5.path}
698
- `
699
- );
705
+ writer.write(`return ${renderMatch(verb, route2, path7)};
706
+ `);
700
707
  }
701
708
  if (useSwitch) {
702
709
  writer.writeBlockEnd("}");
@@ -708,7 +715,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
708
715
  verb,
709
716
  dynamic.route,
710
717
  dynamic.path
711
- )}; // ${dynamic.path.path}`
718
+ )};`
712
719
  );
713
720
  }
714
721
  }
@@ -768,7 +775,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
768
775
  catchAll.route,
769
776
  catchAll.path,
770
777
  String(offset)
771
- )}; // ${catchAll.path.path}`
778
+ )};`
772
779
  );
773
780
  } else if (level === 0) {
774
781
  writer.writeLines("return null;");
@@ -797,43 +804,47 @@ function renderParams(params, pathIndex) {
797
804
  }
798
805
  return result ? result + " }" : "{}";
799
806
  }
800
- function renderMatch(verb, route, path5, pathIndex) {
807
+ function renderMatch(verb, route, path7, pathIndex) {
801
808
  const handler = `${verb}${route.index}`;
802
- const params = path5.params ? renderParams(path5.params, pathIndex) : "{}";
809
+ const params = path7.params ? renderParams(path7.params, pathIndex) : "{}";
803
810
  const meta = route.meta ? `meta${route.index}` : "{}";
804
- const pathPattern = pathToURLPatternString(path5.path);
805
- return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
811
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path7.path}' }`;
806
812
  }
807
- function renderMiddleware(middleware) {
813
+ function renderMiddleware(middleware, rootDir) {
808
814
  const writer = createStringWriter();
809
815
  writer.writeLines(
810
816
  `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
811
817
  );
812
818
  const imports = writer.branch("imports");
813
819
  imports.writeLines(
814
- `import { normalize } from '${virtualFilePrefix}/runtime/internal';`
820
+ `import { normalize } from "${virtualFilePrefix}/runtime/internal";`
815
821
  );
816
822
  writer.writeLines("");
817
- for (const { id, importPath } of middleware) {
823
+ for (const { id, filePath } of middleware) {
818
824
  const importName = `middleware${id}`;
819
- imports.writeLines(`import ${importName} from './${importPath}';`);
825
+ imports.writeLines(
826
+ `import ${importName} from "${normalizedRelativePath(rootDir, filePath)}";`
827
+ );
820
828
  writer.writeLines(`export const mware${id} = normalize(${importName});`);
821
829
  }
822
830
  imports.join();
823
831
  return writer.end();
824
832
  }
825
- function stripTsExtension(path5) {
826
- const index = path5.lastIndexOf(".");
833
+ function stripTsExtension(path7) {
834
+ const index = path7.lastIndexOf(".");
827
835
  if (index !== -1) {
828
- const ext = path5.slice(index + 1);
836
+ const ext = path7.slice(index + 1);
829
837
  if (ext.toLowerCase() === "ts") {
830
- return path5.slice(0, index);
838
+ return path7.slice(0, index);
831
839
  }
832
840
  }
833
- return path5;
841
+ return path7;
834
842
  }
835
- async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
836
- var _a, _b;
843
+ function decodePath(path7) {
844
+ return path7;
845
+ }
846
+ async function renderRouteTypeInfo(routes, outDir, adapter) {
847
+ var _a, _b, _c, _d;
837
848
  const writer = createStringWriter();
838
849
  writer.writeLines(
839
850
  `/*
@@ -862,11 +873,31 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
862
873
  const routeTypes = /* @__PURE__ */ new Map();
863
874
  for (const route of routes.list) {
864
875
  let routeType = "";
865
- for (const path5 of route.paths) {
866
- const pathType = `"${pathToURLPatternString(path5.path)}"`;
867
- routeType += routeType ? " | " + pathType : pathType;
868
- routesWriter.writeLines(`${pathType}: Routes["${route.key}"];`);
876
+ let routeDefinition = "";
877
+ if (route.page || route.handler) {
878
+ const verbs = [];
879
+ if (route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes("get"))) {
880
+ verbs.push(`"get"`);
881
+ }
882
+ if ((_d = (_c = route.handler) == null ? void 0 : _c.verbs) == null ? void 0 : _d.includes("post")) {
883
+ verbs.push(`"post"`);
884
+ }
885
+ routeDefinition = `{ verb: ${verbs.join(" | ")};`;
886
+ if (route.meta) {
887
+ const metaPath = stripTsExtension(
888
+ normalizedRelativePath(outDir, route.meta.filePath)
889
+ );
890
+ let metaType = `typeof import("${metaPath}")`;
891
+ if (/\.(ts|js|mjs)$/.test(route.meta.name)) {
892
+ metaType += `["default"]`;
893
+ }
894
+ routeDefinition += ` meta: ${metaType};`;
895
+ }
896
+ routeDefinition += " }";
869
897
  }
898
+ const pathType = `"${decodePath(route.path.path)}"`;
899
+ routeType += routeType ? " | " + pathType : pathType;
900
+ routesWriter.writeLines(`${pathType}: ${routeDefinition};`);
870
901
  for (const file of [route.handler, route.page]) {
871
902
  if (file) {
872
903
  const existing = routeTypes.get(file);
@@ -899,33 +930,33 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
899
930
  const pageWriter = writer.branch("page");
900
931
  const layoutWriter = writer.branch("layout");
901
932
  for (const [file, types] of routeTypes) {
902
- const path5 = `${pathPrefix}/${file.relativePath}`;
933
+ const modulePath = stripTsExtension(
934
+ normalizedRelativePath(outDir, file.filePath)
935
+ );
903
936
  const routeType = `Run.Routes[${types.join(" | ")}]`;
904
937
  switch (file.type) {
905
938
  case RoutableFileTypes.Handler:
906
- writeModuleDeclaration(handlerWriter, path5, routeType);
939
+ writeModuleDeclaration(handlerWriter, modulePath, routeType);
907
940
  break;
908
941
  case RoutableFileTypes.Middleware:
909
- writeModuleDeclaration(middlewareWriter, path5, routeType);
942
+ writeModuleDeclaration(middlewareWriter, modulePath, routeType);
910
943
  break;
911
944
  case RoutableFileTypes.Page:
912
- writeModuleDeclaration(pageWriter, path5, routeType);
945
+ writeModuleDeclaration(pageWriter, modulePath, routeType);
913
946
  break;
914
947
  case RoutableFileTypes.Layout:
915
948
  writeModuleDeclaration(
916
949
  layoutWriter,
917
- path5,
950
+ modulePath,
918
951
  routeType,
919
952
  `
920
- export interface Input {
921
- [Run.ContentKeyFor<typeof import('${path5}')>]: Marko.Body;
922
- }`
953
+ export interface Input extends Run.LayoutInput<typeof import("${modulePath}")> {}`
923
954
  );
924
955
  break;
925
956
  case RoutableFileTypes.Error:
926
957
  writeModuleDeclaration(
927
958
  writer,
928
- path5,
959
+ modulePath,
929
960
  "globalThis.MarkoRun.Route",
930
961
  `
931
962
  export interface Input {
@@ -934,7 +965,7 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
934
965
  );
935
966
  break;
936
967
  case RoutableFileTypes.NotFound:
937
- writeModuleDeclaration(writer, path5, "Run.Route");
968
+ writeModuleDeclaration(writer, modulePath, "Run.Route");
938
969
  break;
939
970
  }
940
971
  }
@@ -942,40 +973,15 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
942
973
  middlewareWriter.join();
943
974
  pageWriter.join();
944
975
  layoutWriter.join();
945
- writer.writeBlockStart(`
946
- type Routes = {`);
947
- for (const route of routes.list) {
948
- const { meta, handler, page } = route;
949
- if (page || handler) {
950
- const verbs = [];
951
- if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
952
- verbs.push(`"get"`);
953
- }
954
- if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
955
- verbs.push(`"post"`);
956
- }
957
- let routeType = `{ verb: ${verbs.join(" | ")};`;
958
- if (meta) {
959
- const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
960
- let metaType = `typeof import("${metaPath}")`;
961
- if (/\.(ts|js|mjs)$/.test(meta.name)) {
962
- metaType += `["default"]`;
963
- }
964
- routeType += ` meta: ${metaType};`;
965
- }
966
- writer.writeLines(`"${route.key}": ${routeType} };`);
967
- }
968
- }
969
- writer.writeBlockEnd("}");
970
976
  return writer.end();
971
977
  }
972
- function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
973
- writer.writeLines("").write(`declare module "${stripTsExtension(path5)}" {`);
978
+ function writeModuleDeclaration(writer, name, routeType, moduleTypes) {
979
+ writer.writeLines("").write(`declare module "${name}" {`);
974
980
  if (moduleTypes) {
975
981
  writer.write(moduleTypes);
976
982
  }
977
983
  if (routeType) {
978
- const isMarko = path5.endsWith(".marko");
984
+ const isMarko = name.endsWith(".marko");
979
985
  writer.write(`
980
986
  namespace MarkoRun {
981
987
  export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
@@ -989,21 +995,15 @@ function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
989
995
  writer.writeLines(`
990
996
  }`);
991
997
  }
992
- function pathToURLPatternString(path5) {
993
- return path5.replace(/\/\$(\$?)([^/]*)/g, (_, catchAll, name) => {
994
- name = decodeURIComponent(name);
995
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
996
- });
997
- }
998
998
  function createRouteTrie(routes) {
999
999
  const root = {
1000
1000
  key: ""
1001
1001
  };
1002
- function insert(path5, route) {
1002
+ function insert(path7, route) {
1003
1003
  let node = root;
1004
- for (const segment of path5.segments) {
1004
+ for (const segment of path7.segments) {
1005
1005
  if (segment === "$$") {
1006
- node.catchAll ?? (node.catchAll = { route, path: path5 });
1006
+ node.catchAll ?? (node.catchAll = { route, path: path7 });
1007
1007
  return;
1008
1008
  } else if (segment === "$") {
1009
1009
  node = node.dynamic ?? (node.dynamic = {
@@ -1021,17 +1021,18 @@ function createRouteTrie(routes) {
1021
1021
  node = next;
1022
1022
  }
1023
1023
  }
1024
- node.path ?? (node.path = path5);
1024
+ node.path ?? (node.path = path7);
1025
1025
  node.route ?? (node.route = route);
1026
1026
  }
1027
1027
  for (const route of routes) {
1028
- for (const path5 of route.paths) {
1029
- insert(path5, route);
1030
- }
1028
+ insert(route.path, route);
1031
1029
  }
1032
1030
  return root;
1033
1031
  }
1034
1032
 
1033
+ // src/vite/routes/builder.ts
1034
+ var import_path3 = __toESM(require("path"), 1);
1035
+
1035
1036
  // src/vite/routes/parse.ts
1036
1037
  function parseFlatRoute(pattern) {
1037
1038
  if (!pattern) throw new Error("Empty pattern");
@@ -1046,53 +1047,72 @@ function parseFlatRoute(pattern) {
1046
1047
  ]);
1047
1048
  function parse2(basePaths, group) {
1048
1049
  const pathMap = /* @__PURE__ */ new Map();
1049
- const delimiters = group ? ").," : ".,";
1050
+ const delimiters = group ? "`).," : "`.,";
1050
1051
  let charCode;
1051
1052
  let segmentStart = i;
1052
1053
  let type;
1053
1054
  let current;
1055
+ let escaped = "";
1056
+ let escapeStart = 0;
1054
1057
  do {
1055
1058
  charCode = pattern.charCodeAt(i);
1056
- if (charCode === 41 && group) {
1059
+ if (charCode === 96 /* Escape */) {
1060
+ if (escapeStart) {
1061
+ escaped += pattern.slice(segmentStart, escapeStart - 1) + pattern.slice(escapeStart, i);
1062
+ escapeStart = 0;
1063
+ segmentStart = ++i;
1064
+ } else {
1065
+ escapeStart = i + 1;
1066
+ i = pattern.indexOf("`", escapeStart);
1067
+ if (i < 0) break;
1068
+ }
1069
+ } else if (charCode === 41 /* GroupEnd */ && group) {
1057
1070
  break;
1058
- } else if (charCode === 44) {
1071
+ } else if (charCode === 44 /* Alternate */) {
1059
1072
  if (!current) {
1060
1073
  segmentEnd(
1061
- basePaths.map((path5) => ({
1062
- ...path5,
1063
- segments: path5.segments.slice()
1074
+ basePaths.map((path7) => ({
1075
+ ...path7,
1076
+ segments: path7.segments.slice()
1064
1077
  })),
1065
- "",
1078
+ escaped,
1066
1079
  "_",
1067
1080
  pathMap
1068
1081
  );
1069
1082
  } else {
1070
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1083
+ segmentEnd(
1084
+ current,
1085
+ escaped + pattern.slice(segmentStart, i),
1086
+ type,
1087
+ pathMap
1088
+ );
1071
1089
  }
1072
1090
  current = void 0;
1073
1091
  type = void 0;
1092
+ escaped = "";
1074
1093
  segmentStart = ++i;
1075
- } else if (charCode === 46) {
1094
+ } else if (charCode === 46 /* Directory */) {
1076
1095
  if (current) {
1077
- segmentEnd(current, pattern.slice(segmentStart, i), type);
1096
+ segmentEnd(current, escaped + pattern.slice(segmentStart, i), type);
1078
1097
  }
1079
1098
  type = void 0;
1099
+ escaped = "";
1080
1100
  segmentStart = ++i;
1081
- } else if (charCode === 40) {
1101
+ } else if (charCode === 40 /* GroupStart */) {
1082
1102
  const groupPaths = parse2(current || basePaths, ++i);
1083
1103
  if (groupPaths.length) {
1084
1104
  current = groupPaths;
1085
1105
  }
1086
1106
  segmentStart = ++i;
1087
1107
  } else {
1088
- if (charCode === 95) {
1108
+ if (charCode === 95 /* Pathless */) {
1089
1109
  type = "_";
1090
- } else if (charCode === 36) {
1110
+ } else if (charCode === 36 /* Dynamic */) {
1091
1111
  type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
1092
1112
  }
1093
- current ?? (current = basePaths.map((path5) => ({
1094
- ...path5,
1095
- segments: path5.segments.slice()
1113
+ current ?? (current = basePaths.map((path7) => ({
1114
+ ...path7,
1115
+ segments: path7.segments.slice()
1096
1116
  })));
1097
1117
  i = len;
1098
1118
  for (const char of delimiters) {
@@ -1103,7 +1123,12 @@ function parseFlatRoute(pattern) {
1103
1123
  }
1104
1124
  }
1105
1125
  } while (i < len);
1106
- if (group && charCode !== 41) {
1126
+ if (escapeStart) {
1127
+ throw new Error(
1128
+ `Invalid route pattern: unclosed escape '${pattern.slice(escapeStart)}' in '${pattern}'`
1129
+ );
1130
+ }
1131
+ if (group && charCode !== 41 /* GroupEnd */) {
1107
1132
  throw new Error(
1108
1133
  `Invalid route pattern: group was not closed '${pattern.slice(
1109
1134
  group
@@ -1112,16 +1137,21 @@ function parseFlatRoute(pattern) {
1112
1137
  }
1113
1138
  if (!current) {
1114
1139
  segmentEnd(
1115
- basePaths.map((path5) => ({
1116
- ...path5,
1117
- segments: path5.segments.slice()
1140
+ basePaths.map((path7) => ({
1141
+ ...path7,
1142
+ segments: path7.segments.slice()
1118
1143
  })),
1119
- "",
1120
- "_",
1144
+ escaped,
1145
+ void 0,
1121
1146
  pathMap
1122
1147
  );
1123
1148
  } else {
1124
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1149
+ segmentEnd(
1150
+ current,
1151
+ escaped + pattern.slice(segmentStart, i),
1152
+ type,
1153
+ pathMap
1154
+ );
1125
1155
  }
1126
1156
  return [...pathMap.values()];
1127
1157
  }
@@ -1130,17 +1160,17 @@ function parseFlatRoute(pattern) {
1130
1160
  if (raw) {
1131
1161
  segment = {
1132
1162
  raw,
1133
- name: raw,
1163
+ name: normalizeSegment(raw),
1134
1164
  type
1135
1165
  };
1136
1166
  if (type === "$" || type === "$$") {
1137
1167
  segment.name = type;
1138
- segment.param = raw.slice(type.length);
1168
+ segment.param = normalizeParam(raw.slice(type.length));
1139
1169
  }
1140
1170
  }
1141
- for (const path5 of paths) {
1171
+ for (const path7 of paths) {
1142
1172
  if (segment) {
1143
- if (path5.isCatchall) {
1173
+ if (path7.isCatchall) {
1144
1174
  throw new Error(
1145
1175
  `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
1146
1176
  0,
@@ -1148,26 +1178,36 @@ function parseFlatRoute(pattern) {
1148
1178
  )}' in '${pattern}'.`
1149
1179
  );
1150
1180
  }
1151
- path5.segments.push(segment);
1152
- path5.id += path5.id === "/" ? segment.name : `/${segment.name}`;
1181
+ path7.segments.push(segment);
1182
+ path7.id += path7.id === "/" ? segment.name : `/${segment.name}`;
1153
1183
  if (type === "$$") {
1154
- path5.isCatchall = true;
1184
+ path7.isCatchall = true;
1155
1185
  }
1156
1186
  }
1157
1187
  if (map) {
1158
- if (map.has(path5.id)) {
1159
- const existing = map.get(path5.id);
1188
+ if (map.has(path7.id)) {
1189
+ const existing = map.get(path7.id);
1160
1190
  const existingExpansion = existing.segments.map((s) => s.raw).join(".");
1161
- const currentExpansion = path5.segments.map((s) => s.raw).join(".");
1191
+ const currentExpansion = path7.segments.map((s) => s.raw).join(".");
1162
1192
  throw new Error(
1163
- `Invalid route pattern: route '${path5.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1193
+ `Invalid route pattern: route '${path7.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1164
1194
  );
1165
1195
  }
1166
- map.set(path5.id, path5);
1196
+ map.set(path7.id, path7);
1167
1197
  }
1168
1198
  }
1169
1199
  }
1170
1200
  }
1201
+ function normalizeParam(segment) {
1202
+ const normalized = normalizeSegment(segment);
1203
+ return /^\$/.test(normalized) ? `\`${normalized}\`` : normalized;
1204
+ }
1205
+ function normalizeSegment(segment) {
1206
+ return decodeURIComponent(segment).replace(
1207
+ /[/?#]/g,
1208
+ (char) => "%" + char.charCodeAt(0).toString(16)
1209
+ );
1210
+ }
1171
1211
 
1172
1212
  // src/vite/routes/vdir.ts
1173
1213
  var _dirs, _pathlessDirs;
@@ -1178,14 +1218,12 @@ var _VDir = class _VDir {
1178
1218
  __publicField(this, "parent");
1179
1219
  __publicField(this, "source");
1180
1220
  __publicField(this, "path");
1181
- __publicField(this, "fullPath");
1182
1221
  __publicField(this, "segment");
1183
1222
  __publicField(this, "files");
1184
1223
  if (!parent || !segment) {
1185
1224
  this.parent = null;
1186
1225
  this.source = null;
1187
1226
  this.path = "/";
1188
- this.fullPath = "/";
1189
1227
  this.segment = {
1190
1228
  raw: "",
1191
1229
  name: ""
@@ -1193,12 +1231,8 @@ var _VDir = class _VDir {
1193
1231
  } else {
1194
1232
  this.parent = parent;
1195
1233
  this.source = source;
1196
- this.path = parent.path + (parent.path === "/" ? segment.name : `/${segment.name}`);
1197
- this.fullPath = parent.fullPath + (parent.fullPath === "/" ? segment.name : `/${segment.name}`);
1198
- if (segment.param) {
1199
- this.fullPath += segment.param;
1200
- }
1201
1234
  this.segment = segment;
1235
+ this.path = parent.path + (parent.path === "/" ? "" : "/") + segment.name;
1202
1236
  }
1203
1237
  }
1204
1238
  get pathInfo() {
@@ -1211,17 +1245,18 @@ var _VDir = class _VDir {
1211
1245
  for (const { segment } of this) {
1212
1246
  const { type, name, param } = segment;
1213
1247
  if (name && type !== "_") {
1214
- value.id += sep + (type || name);
1248
+ value.id += sep + name;
1215
1249
  value.path += sep + name;
1216
1250
  value.isEnd = type === "$$";
1217
1251
  if (param) {
1218
- value.path += param;
1252
+ const unescapedParam = param.charAt(0) === "`" ? param.slice(1, -1) : param;
1219
1253
  const index = type === "$$" ? null : value.segments.length;
1220
1254
  if (!value.params) {
1221
- value.params = { [param]: index };
1255
+ value.params = { [unescapedParam]: index };
1222
1256
  } else if (!(param in value.params)) {
1223
- value.params[param] = index;
1257
+ value.params[unescapedParam] = index;
1224
1258
  }
1259
+ value.path += param;
1225
1260
  }
1226
1261
  value.segments.push(name);
1227
1262
  sep = "/";
@@ -1233,11 +1268,11 @@ var _VDir = class _VDir {
1233
1268
  });
1234
1269
  return value;
1235
1270
  }
1236
- addDir(path5, segment) {
1271
+ addDir(path7, segment) {
1237
1272
  const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
1238
1273
  const key = segment.type === "$" ? segment.raw : segment.name;
1239
1274
  if (!map.has(key)) {
1240
- const dir = new _VDir(this, segment, path5);
1275
+ const dir = new _VDir(this, segment, path7);
1241
1276
  map.set(key, dir);
1242
1277
  return dir;
1243
1278
  }
@@ -1253,15 +1288,15 @@ var _VDir = class _VDir {
1253
1288
  const existing = this.files.get(file.type);
1254
1289
  if (existing !== file) {
1255
1290
  throw new Error(
1256
- `Duplicate file type '${file.type}' added at path '${this.path}'. File '${file.importPath}' collides with '${existing.importPath}'.`
1291
+ `Duplicate file type ${file.type} added at path ${this.path}. File ${file.filePath} collides with ${existing.filePath}.`
1257
1292
  );
1258
1293
  } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
1259
1294
  throw new Error(
1260
- `Ambiguous path definition: route '${this.path}' is defined multiple times by ${file.importPath}`
1295
+ `Ambiguous path definition: route ${this.path} is defined multiple times by ${file.filePath}`
1261
1296
  );
1262
1297
  }
1263
1298
  throw new Error(
1264
- `Ambiguous path definition: file '${this.path}' is included multiple times by ${file.importPath}`
1299
+ `Ambiguous path definition: file ${this.path} is included multiple times by ${file.filePath}`
1265
1300
  );
1266
1301
  }
1267
1302
  }
@@ -1283,10 +1318,10 @@ var _VDir = class _VDir {
1283
1318
  const dirs = [];
1284
1319
  const unique = /* @__PURE__ */ new Map();
1285
1320
  for (const root of roots) {
1286
- for (const path5 of paths) {
1321
+ for (const path7 of paths) {
1287
1322
  let dir = root;
1288
- for (const segment of path5.segments) {
1289
- dir = dir.addDir(path5, segment);
1323
+ for (const segment of path7.segments) {
1324
+ dir = dir.addDir(path7, segment);
1290
1325
  }
1291
1326
  const existing = unique.get(dir.path);
1292
1327
  if (existing) {
@@ -1299,7 +1334,7 @@ var _VDir = class _VDir {
1299
1334
  }
1300
1335
  }
1301
1336
  throw new Error(
1302
- `Ambiguous directory structure: '${sourcePath}${path5.source}' defines '${dir.path}' multiple times.`
1337
+ `Ambiguous directory structure: ${sourcePath}${path7.source} defines ${dir.path} multiple times.`
1303
1338
  );
1304
1339
  } else {
1305
1340
  unique.set(dir.path, dir);
@@ -1325,13 +1360,11 @@ function matchRoutableFile(filename) {
1325
1360
  const match = filename.match(routeableFileRegex);
1326
1361
  return match && (match[1] || match[3]).toLowerCase();
1327
1362
  }
1328
- function isSpecialType(type) {
1329
- return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
1330
- }
1331
- async function buildRoutes(sources) {
1363
+ async function buildRoutes(sources, outDir) {
1332
1364
  const uniqueRoutes = /* @__PURE__ */ new Map();
1333
1365
  const routes = [];
1334
1366
  const special = {};
1367
+ const seenKeys = /* @__PURE__ */ new Map();
1335
1368
  const middlewares = /* @__PURE__ */ new Set();
1336
1369
  const unusedFiles = /* @__PURE__ */ new Set();
1337
1370
  const currentLayouts = /* @__PURE__ */ new Set();
@@ -1339,13 +1372,13 @@ async function buildRoutes(sources) {
1339
1372
  const root = new VDir();
1340
1373
  const dirStack = [];
1341
1374
  let basePath;
1342
- let importPrefix;
1343
1375
  let activeDirs;
1344
1376
  let isBaseDir;
1345
1377
  let nextFileId = 1;
1346
1378
  let nextRouteIndex = 1;
1347
1379
  const walkOptions = {
1348
- onEnter({ name }) {
1380
+ onEnter(dir) {
1381
+ let { name } = dir;
1349
1382
  const prevDirStackLength = dirStack.length;
1350
1383
  if (isBaseDir) {
1351
1384
  isBaseDir = false;
@@ -1364,15 +1397,16 @@ async function buildRoutes(sources) {
1364
1397
  dirStack.length = prevDirStackLength;
1365
1398
  };
1366
1399
  },
1367
- onFile({ name, path: path5 }) {
1400
+ onFile(file) {
1401
+ const { name } = file;
1368
1402
  const match = name.match(routeableFileRegex);
1369
1403
  if (!match) {
1370
1404
  return;
1371
1405
  }
1372
1406
  const type = (match[1] || match[3]).toLowerCase();
1373
- if (dirStack.length && isSpecialType(type)) {
1407
+ if (dirStack.length && (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error)) {
1374
1408
  console.warn(
1375
- `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path5}`
1409
+ `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${file.path}`
1376
1410
  );
1377
1411
  return;
1378
1412
  }
@@ -1381,19 +1415,15 @@ async function buildRoutes(sources) {
1381
1415
  const paths = parseFlatRoute(name.slice(0, match.index));
1382
1416
  dirs = VDir.addPaths(activeDirs, paths);
1383
1417
  }
1384
- const dirPath = dirStack.join("/");
1385
- const relativePath = dirPath ? `${dirPath}/${name}` : name;
1386
- const file = {
1418
+ const routableFile = {
1387
1419
  id: String(nextFileId++),
1388
1420
  name,
1389
1421
  type,
1390
- filePath: path5,
1391
- relativePath,
1392
- importPath: `${importPrefix}/${relativePath}`,
1422
+ filePath: file.path,
1393
1423
  verbs: type === RoutableFileTypes.Page ? ["get", "head"] : void 0
1394
1424
  };
1395
1425
  for (const dir of dirs) {
1396
- dir.addFile(file);
1426
+ dir.addFile(routableFile);
1397
1427
  }
1398
1428
  }
1399
1429
  };
@@ -1401,7 +1431,6 @@ async function buildRoutes(sources) {
1401
1431
  sources = [sources];
1402
1432
  }
1403
1433
  for (const source of sources) {
1404
- importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
1405
1434
  basePath = source.basePath || "";
1406
1435
  activeDirs = [root];
1407
1436
  isBaseDir = true;
@@ -1421,7 +1450,8 @@ async function buildRoutes(sources) {
1421
1450
  layout = dir.files.get(RoutableFileTypes.Layout);
1422
1451
  const handler = dir.files.get(RoutableFileTypes.Handler);
1423
1452
  const page = dir.files.get(RoutableFileTypes.Page);
1424
- let hasSpecial = false;
1453
+ const pathInfo = dir.pathInfo;
1454
+ let layoutsUsed = false;
1425
1455
  if (middleware) {
1426
1456
  if (currentMiddleware.has(middleware)) {
1427
1457
  middleware = void 0;
@@ -1438,64 +1468,64 @@ async function buildRoutes(sources) {
1438
1468
  unusedFiles.add(layout);
1439
1469
  }
1440
1470
  }
1471
+ if (dir === root) {
1472
+ for (const [type, file] of dir.files) {
1473
+ if (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error) {
1474
+ special[type] = {
1475
+ index: nextRouteIndex++,
1476
+ key: type,
1477
+ path: dir.pathInfo,
1478
+ middleware: [],
1479
+ layouts: [...currentLayouts],
1480
+ page: file,
1481
+ templateFilePath: currentLayouts.size ? import_path3.default.join(outDir, `${type}.marko`) : void 0
1482
+ };
1483
+ layoutsUsed = true;
1484
+ }
1485
+ }
1486
+ }
1441
1487
  if (page || handler) {
1442
- const path5 = dir.pathInfo;
1443
- if (uniqueRoutes.has(path5.id)) {
1444
- const existing = uniqueRoutes.get(path5.id);
1488
+ if (uniqueRoutes.has(pathInfo.id)) {
1489
+ const existing = uniqueRoutes.get(pathInfo.id);
1445
1490
  const route = routes[existing.index];
1446
1491
  const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
1447
1492
  const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
1448
- throw new Error(`Duplicate routes for path '${path5.id}' were defined. A route established by:
1449
- ${existingFiles.join(" and ")} via '${existing.dir.fullPath}'
1450
- collides with
1451
- ${currentFiles.join(" and ")} via '${dir.fullPath}'
1452
- `);
1493
+ throw new Error(
1494
+ `Duplicate routes for path ${pathInfo.id} were defined. A route established by: "${existingFiles.join(" and ")}" collides with "${currentFiles.join(" and ")}"`
1495
+ );
1496
+ }
1497
+ uniqueRoutes.set(pathInfo.id, { dir, index: routes.length });
1498
+ let key = pathInfo.segments.map(replaceInvalidFilenameChars).concat("route").join("/");
1499
+ const keyCount = (seenKeys.get(key) || 0) + 1;
1500
+ seenKeys.set(key, keyCount);
1501
+ if (keyCount > 1) {
1502
+ key += keyCount;
1453
1503
  }
1454
- uniqueRoutes.set(path5.id, { dir, index: routes.length });
1455
1504
  routes.push({
1456
1505
  index: nextRouteIndex++,
1457
- key: dir.fullPath,
1458
- paths: [path5],
1506
+ key,
1507
+ path: pathInfo,
1459
1508
  middleware: [...currentMiddleware],
1460
1509
  layouts: page ? [...currentLayouts] : [],
1461
1510
  meta: dir.files.get(RoutableFileTypes.Meta),
1462
1511
  page,
1463
1512
  handler,
1464
- entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
1513
+ templateFilePath: page && currentLayouts.size ? import_path3.default.join(outDir, key + ".marko") : void 0
1465
1514
  });
1466
- }
1467
- if (dir === root) {
1468
- for (const [type, file] of dir.files) {
1469
- if (isSpecialType(type)) {
1470
- hasSpecial = true;
1471
- special[type] = {
1472
- index: 0,
1473
- key: type,
1474
- paths: [],
1475
- middleware: [],
1476
- layouts: [...currentLayouts],
1477
- page: file,
1478
- entryName: `${markoRunFilePrefix}special.${type}`
1479
- };
1480
- }
1481
- }
1482
- }
1483
- if (handler || page) {
1515
+ layoutsUsed = !!page;
1484
1516
  for (const middleware2 of currentMiddleware) {
1485
1517
  middlewares.add(middleware2);
1486
1518
  unusedFiles.delete(middleware2);
1487
1519
  }
1488
1520
  }
1489
- if (page || hasSpecial) {
1521
+ if (layoutsUsed) {
1490
1522
  for (const layout2 of currentLayouts) {
1491
1523
  unusedFiles.delete(layout2);
1492
1524
  }
1493
1525
  }
1494
1526
  }
1495
- if (dir.dirs) {
1496
- for (const child of dir.dirs()) {
1497
- traverse(child);
1498
- }
1527
+ for (const childDir of dir.dirs()) {
1528
+ traverse(childDir);
1499
1529
  }
1500
1530
  if (middleware) {
1501
1531
  currentMiddleware.delete(middleware);
@@ -1505,10 +1535,13 @@ async function buildRoutes(sources) {
1505
1535
  }
1506
1536
  }
1507
1537
  }
1538
+ function replaceInvalidFilenameChars(str) {
1539
+ return str.replace(/[<>:"/\\|?*]+/g, "_");
1540
+ }
1508
1541
 
1509
1542
  // src/vite/routes/walk.ts
1510
- var import_fs = __toESM(require("fs"), 1);
1511
- var import_path2 = __toESM(require("path"), 1);
1543
+ var import_fs2 = __toESM(require("fs"), 1);
1544
+ var import_path4 = __toESM(require("path"), 1);
1512
1545
  function createFSWalker(dir) {
1513
1546
  return async function walkFS({
1514
1547
  onEnter,
@@ -1520,10 +1553,10 @@ function createFSWalker(dir) {
1520
1553
  const onExit = onEnter == null ? void 0 : onEnter(dir2);
1521
1554
  if (onExit !== false) {
1522
1555
  const dirs = [];
1523
- const entries = await import_fs.default.promises.readdir(dir2.path, {
1556
+ const entries = await import_fs2.default.promises.readdir(dir2.path, {
1524
1557
  withFileTypes: true
1525
1558
  });
1526
- const prefix = dir2.path + import_path2.default.sep;
1559
+ const prefix = dir2.path + import_path4.default.sep;
1527
1560
  for (const entry of entries) {
1528
1561
  const walkEntry = {
1529
1562
  name: entry.name,
@@ -1546,7 +1579,7 @@ function createFSWalker(dir) {
1546
1579
  await walk(
1547
1580
  {
1548
1581
  path: dir,
1549
- name: import_path2.default.basename(dir)
1582
+ name: import_path4.default.basename(dir)
1550
1583
  },
1551
1584
  maxDepth
1552
1585
  );
@@ -1663,36 +1696,34 @@ function logRoutesTable(routes, bundle) {
1663
1696
  style: { compact: true }
1664
1697
  });
1665
1698
  for (const route of routes.list) {
1666
- for (const path5 of route.paths) {
1667
- const verbs = getVerbs(route, true);
1668
- let firstRow = true;
1669
- for (const verb of verbs) {
1670
- const entryType = [];
1671
- let size = "";
1672
- let verbCell = verbColor(verb)(verb.toUpperCase());
1673
- if (verb === "get" && !verbs.includes("head")) {
1674
- verbCell += import_kleur2.default.dim(`,${verbColor(verb)("HEAD")}`);
1675
- }
1676
- if (route.handler) {
1677
- entryType.push(import_kleur2.default.blue("handler"));
1678
- }
1679
- if (route.page && (verb === "get" || verb === "head")) {
1680
- entryType.push(import_kleur2.default.yellow("page"));
1681
- if (verb === "get") {
1682
- size = prettySize(computeRouteSize(route, bundle));
1683
- }
1684
- }
1685
- const row = [verbCell];
1686
- if (verbs.length === 1 || firstRow) {
1687
- row.push({ rowSpan: verbs.length, content: prettyPath(path5.path) });
1688
- firstRow = false;
1699
+ const verbs = getVerbs(route, true);
1700
+ let firstRow = true;
1701
+ for (const verb of verbs) {
1702
+ const entryType = [];
1703
+ let size = "";
1704
+ const verbCell = verbColor(verb)(verb.toUpperCase());
1705
+ if (route.handler) {
1706
+ entryType.push(import_kleur2.default.blue("handler"));
1707
+ }
1708
+ if (route.page && (verb === "get" || verb === "head")) {
1709
+ entryType.push(import_kleur2.default.yellow("page"));
1710
+ if (verb === "get") {
1711
+ size = prettySize(computeRouteSize(route, bundle));
1689
1712
  }
1690
- row.push(entryType.join(" -> "));
1691
- hasMiddleware && row.push(route.middleware.length || "");
1692
- hasMeta && row.push(route.meta ? "\u2713" : "");
1693
- row.push(size || "");
1694
- table.push(row);
1695
1713
  }
1714
+ const row = [verbCell];
1715
+ if (verbs.length === 1 || firstRow) {
1716
+ row.push({
1717
+ rowSpan: verbs.length,
1718
+ content: prettyPath(route.path.path)
1719
+ });
1720
+ firstRow = false;
1721
+ }
1722
+ row.push(entryType.join(" -> "));
1723
+ hasMiddleware && row.push(route.middleware.length || "");
1724
+ hasMeta && row.push(route.meta ? "\u2713" : "");
1725
+ row.push(size || "");
1726
+ table.push(row);
1696
1727
  }
1697
1728
  }
1698
1729
  for (const [key, route] of Object.entries(routes.special).sort()) {
@@ -1748,17 +1779,20 @@ function prettySize([bytes, compBytes]) {
1748
1779
  else str += import_kleur2.default.bold(import_kleur2.default.red(compSize));
1749
1780
  return str;
1750
1781
  }
1751
- function prettyPath(path5) {
1752
- return path5.replace(/\/\$\$(.*)$/, (_, p) => "/" + import_kleur2.default.bold(import_kleur2.default.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + import_kleur2.default.bold(import_kleur2.default.dim(`:${p}`)));
1782
+ function prettyPath(path7) {
1783
+ return path7.replace(
1784
+ /\/(\$\$?)(`?)([^/`]+)\2/g,
1785
+ (_, type, tick, key) => "/" + type + tick + import_kleur2.default.bold(import_kleur2.default.dim(key)) + tick
1786
+ );
1753
1787
  }
1754
1788
 
1755
1789
  // src/vite/utils/read-once-persisted-store.ts
1756
- var import_fs2 = require("fs");
1790
+ var import_fs3 = require("fs");
1757
1791
  var import_os = __toESM(require("os"), 1);
1758
- var import_path3 = __toESM(require("path"), 1);
1792
+ var import_path5 = __toESM(require("path"), 1);
1759
1793
  var noop = () => {
1760
1794
  };
1761
- var tmpFile = import_path3.default.join(import_os.default.tmpdir(), "marko-run-storage.json");
1795
+ var tmpFile = import_path5.default.join(import_os.default.tmpdir(), "marko-run-storage.json");
1762
1796
  var values = /* @__PURE__ */ new Map();
1763
1797
  var loadedFromDisk;
1764
1798
  var ReadOncePersistedStore = class {
@@ -1778,13 +1812,13 @@ var ReadOncePersistedStore = class {
1778
1812
  if (loadedFromDisk === true) {
1779
1813
  throw new Error(`Value for ${uid} could not be loaded.`);
1780
1814
  }
1781
- await (loadedFromDisk || (loadedFromDisk = import_fs2.promises.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1815
+ await (loadedFromDisk || (loadedFromDisk = import_fs3.promises.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1782
1816
  return this.read();
1783
1817
  }
1784
1818
  };
1785
1819
  function syncDataFromDisk(data) {
1786
1820
  finishLoadFromDisk();
1787
- import_fs2.promises.unlink(tmpFile).catch(noop);
1821
+ import_fs3.promises.unlink(tmpFile).catch(noop);
1788
1822
  for (const [k, v] of JSON.parse(data)) {
1789
1823
  values.set(k, v);
1790
1824
  }
@@ -1794,23 +1828,23 @@ function finishLoadFromDisk() {
1794
1828
  }
1795
1829
  process.once("beforeExit", (code) => {
1796
1830
  if (code === 0 && values.size) {
1797
- import_fs2.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1831
+ import_fs3.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1798
1832
  }
1799
1833
  });
1800
1834
 
1801
1835
  // src/vite/plugin.ts
1802
1836
  var debug = (0, import_debug.default)("@marko/run");
1803
- var __dirname = import_path4.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1837
+ var __dirname = import_path6.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1804
1838
  var PLUGIN_NAME_PREFIX = "marko-run-vite";
1805
- var POSIX_SEP = "/";
1806
- var WINDOWS_SEP = "\\";
1807
1839
  var CLIENT_OUT_DIR = "public";
1808
1840
  var MIDDLEWARE_FILENAME = `${markoRunFilePrefix}middleware.js`;
1809
1841
  var ROUTER_FILENAME = `${markoRunFilePrefix}router.js`;
1810
1842
  var defaultPort = Number(process.env.PORT || 3e3);
1811
- var normalizePath = import_path4.default.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1812
1843
  function markoRun(opts = {}) {
1813
- let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1844
+ let routesDir;
1845
+ let adapter;
1846
+ let trailingSlashes;
1847
+ const { ...markoVitePluginOptions } = opts;
1814
1848
  let store;
1815
1849
  let root;
1816
1850
  let shouldEmptyOutDir = false;
@@ -1843,15 +1877,11 @@ function markoRun(opts = {}) {
1843
1877
  root,
1844
1878
  "{.tsconfig*,tsconfig*.json}"
1845
1879
  )))) {
1846
- const filepath = import_path4.default.join(typesDir, "routes.d.ts");
1847
- const data = await renderRouteTypeInfo(
1848
- routes2,
1849
- normalizePath(import_path4.default.relative(typesDir, resolvedRoutesDir)),
1850
- adapter
1851
- );
1852
- if (data !== typesFile || !import_fs3.default.existsSync(filepath)) {
1880
+ const filepath = import_path6.default.join(typesDir, "routes.d.ts");
1881
+ const data = await renderRouteTypeInfo(routes2, typesDir, adapter);
1882
+ if (data !== typesFile || !import_fs4.default.existsSync(filepath)) {
1853
1883
  await ensureDir(typesDir);
1854
- await import_fs3.default.promises.writeFile(filepath, typesFile = data);
1884
+ await import_fs4.default.promises.writeFile(filepath, typesFile = data);
1855
1885
  }
1856
1886
  }
1857
1887
  }
@@ -1859,20 +1889,25 @@ function markoRun(opts = {}) {
1859
1889
  function buildVirtualFiles() {
1860
1890
  return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1861
1891
  virtualFiles.clear();
1862
- routes = await buildRoutes({
1863
- walker: createFSWalker(resolvedRoutesDir),
1864
- importPrefix: routesDir
1865
- });
1892
+ routes = await buildRoutes(
1893
+ {
1894
+ walker: createFSWalker(resolvedRoutesDir)
1895
+ },
1896
+ entryFilesDir
1897
+ );
1866
1898
  if (!routes.list.length) {
1867
1899
  throw new Error("No routes generated");
1868
1900
  }
1869
1901
  for (const route of routes.list) {
1870
- virtualFiles.set(import_path4.default.posix.join(root, `${route.entryName}.js`), "");
1902
+ virtualFiles.set(
1903
+ import_path6.default.posix.join(root, getRouteVirtualFileName(route)),
1904
+ ""
1905
+ );
1871
1906
  }
1872
1907
  if (routes.middleware.length) {
1873
- virtualFiles.set(import_path4.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1908
+ virtualFiles.set(import_path6.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1874
1909
  }
1875
- virtualFiles.set(import_path4.default.posix.join(root, ROUTER_FILENAME), "");
1910
+ virtualFiles.set(import_path6.default.posix.join(root, ROUTER_FILENAME), "");
1876
1911
  return routes;
1877
1912
  })());
1878
1913
  }
@@ -1882,115 +1917,82 @@ function markoRun(opts = {}) {
1882
1917
  var _a;
1883
1918
  try {
1884
1919
  const routes2 = await buildVirtualFiles();
1885
- if (import_fs3.default.existsSync(entryFilesDir)) {
1886
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
1920
+ if (import_fs4.default.existsSync(entryFilesDir)) {
1921
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
1887
1922
  }
1888
1923
  for (const route of routes2.list) {
1889
- const { handler, page, layouts } = route;
1890
- if (handler) {
1891
- const exports2 = await getExportsFromFile(context, handler.filePath);
1892
- handler.verbs = [];
1924
+ if (route.handler) {
1925
+ const exports2 = await getExportsFromFile(
1926
+ context,
1927
+ route.handler.filePath
1928
+ );
1929
+ route.handler.verbs = [];
1893
1930
  for (const name of exports2) {
1894
1931
  const verb = name.toLowerCase();
1895
1932
  if (name === verb.toUpperCase() && httpVerbs.includes(verb)) {
1896
- handler.verbs.push(verb);
1933
+ route.handler.verbs.push(verb);
1897
1934
  }
1898
1935
  }
1899
- if (!handler.verbs.length) {
1936
+ if (!route.handler.verbs.length) {
1900
1937
  context.warn(
1901
- `Did not find any http verb exports in handler '${import_path4.default.relative(root, handler.filePath)}' - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1938
+ `Did not find any http verb exports in ${import_path6.default.relative(root, route.handler.filePath)} - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1902
1939
  );
1903
1940
  }
1904
1941
  }
1905
- if (page) {
1906
- if (layouts.length) {
1907
- const relativePath = import_path4.default.relative(
1908
- resolvedRoutesDir,
1909
- page.filePath
1910
- );
1911
- const routeFileDir = import_path4.default.join(entryFilesDir, relativePath, "..");
1912
- const routeFileRelativePathPosix = normalizePath(
1913
- import_path4.default.relative(routeFileDir, root)
1914
- );
1915
- import_fs3.default.mkdirSync(routeFileDir, { recursive: true });
1916
- const pageNameIndex = page.name.indexOf("+page");
1917
- const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
1918
- import_fs3.default.writeFileSync(
1919
- route.templateFilePath = import_path4.default.join(
1920
- routeFileDir,
1921
- pageNamePrefix + "route.marko"
1922
- ),
1923
- renderRouteTemplate(
1924
- route,
1925
- (to) => import_path4.default.posix.join(routeFileRelativePathPosix, to)
1926
- )
1927
- );
1928
- } else {
1929
- route.templateFilePath = page.filePath;
1930
- }
1942
+ if (route.templateFilePath) {
1943
+ import_fs4.default.mkdirSync(import_path6.default.dirname(route.templateFilePath), {
1944
+ recursive: true
1945
+ });
1946
+ import_fs4.default.writeFileSync(
1947
+ route.templateFilePath,
1948
+ renderRouteTemplate(route, root)
1949
+ );
1931
1950
  }
1932
1951
  virtualFiles.set(
1933
- import_path4.default.posix.join(root, `${route.entryName}.js`),
1934
- renderRouteEntry(route, relativeEntryFilesDirPosix)
1952
+ import_path6.default.posix.join(root, getRouteVirtualFileName(route)),
1953
+ renderRouteEntry(route, root)
1935
1954
  );
1936
1955
  }
1937
1956
  for (const route of Object.values(routes2.special)) {
1938
- const { page, layouts, key } = route;
1939
- if (page) {
1940
- if (layouts.length) {
1941
- const relativePath = import_path4.default.relative(
1942
- resolvedRoutesDir,
1943
- page.filePath
1944
- );
1945
- const routeFileDir = import_path4.default.join(entryFilesDir, relativePath, "..");
1946
- const routeFileRelativePathPosix = normalizePath(
1947
- import_path4.default.relative(routeFileDir, root)
1948
- );
1949
- import_fs3.default.mkdirSync(routeFileDir, { recursive: true });
1950
- import_fs3.default.writeFileSync(
1951
- route.templateFilePath = import_path4.default.join(
1952
- routeFileDir,
1953
- `route.${key}.marko`
1954
- ),
1955
- renderRouteTemplate(
1956
- route,
1957
- (to) => import_path4.default.posix.join(routeFileRelativePathPosix, to)
1958
- )
1959
- );
1960
- } else {
1961
- route.templateFilePath = page.filePath;
1962
- }
1957
+ if (route.templateFilePath) {
1958
+ import_fs4.default.mkdirSync(import_path6.default.dirname(route.templateFilePath), {
1959
+ recursive: true
1960
+ });
1961
+ import_fs4.default.writeFileSync(
1962
+ route.templateFilePath,
1963
+ renderRouteTemplate(route, root)
1964
+ );
1963
1965
  }
1964
1966
  }
1965
1967
  if (routes2.middleware.length) {
1966
1968
  for (const middleware of routes2.middleware) {
1967
1969
  if (!(await getExportsFromFile(context, middleware.filePath)).includes("default")) {
1968
1970
  context.warn(
1969
- `Did not find a default export in middleware '${import_path4.default.relative(root, middleware.filePath)}'`
1971
+ `Did not find a default export in middleware '${import_path6.default.relative(root, middleware.filePath)}'`
1970
1972
  );
1971
1973
  }
1972
1974
  }
1973
1975
  virtualFiles.set(
1974
- import_path4.default.posix.join(root, MIDDLEWARE_FILENAME),
1975
- renderMiddleware(routes2.middleware)
1976
+ import_path6.default.posix.join(root, MIDDLEWARE_FILENAME),
1977
+ renderMiddleware(routes2.middleware, root)
1976
1978
  );
1977
1979
  }
1978
1980
  virtualFiles.set(
1979
- import_path4.default.posix.join(root, ROUTER_FILENAME),
1980
- renderRouter(routes2, relativeEntryFilesDirPosix, {
1981
- trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1981
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
1982
+ renderRouter(routes2, root, {
1983
+ trailingSlashes
1982
1984
  })
1983
1985
  );
1984
1986
  await writeTypesFile(routes2);
1985
1987
  if (adapter == null ? void 0 : adapter.routesGenerated) {
1986
- await adapter.routesGenerated(
1987
- routes2,
1988
- new Map(virtualFiles.entries()),
1989
- {
1988
+ await adapter.routesGenerated({
1989
+ routes: routes2,
1990
+ virtualFiles: new Map(virtualFiles.entries()),
1991
+ meta: {
1990
1992
  buildTime: times.routesBuild,
1991
1993
  renderTime: times.routesRender
1992
1994
  }
1993
- );
1995
+ });
1994
1996
  if (!isBuild) {
1995
1997
  await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes2.list));
1996
1998
  }
@@ -2000,7 +2002,7 @@ function markoRun(opts = {}) {
2000
2002
  throw err;
2001
2003
  }
2002
2004
  virtualFiles.set(
2003
- import_path4.default.posix.join(root, ROUTER_FILENAME),
2005
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
2004
2006
  `throw ${JSON.stringify(prepareError(err))}`
2005
2007
  );
2006
2008
  }
@@ -2037,27 +2039,28 @@ function markoRun(opts = {}) {
2037
2039
  }
2038
2040
  }
2039
2041
  routesDir = opts.routesDir || "src/routes";
2042
+ trailingSlashes = opts.trailingSlashes || "RedirectWithout";
2040
2043
  store = new ReadOncePersistedStore(
2041
2044
  `vite-marko-run${opts.runtimeId ? `-${opts.runtimeId}` : ""}`
2042
2045
  );
2043
2046
  markoVitePluginOptions.runtimeId = opts.runtimeId;
2044
2047
  markoVitePluginOptions.basePathVar = opts.basePathVar;
2045
- resolvedRoutesDir = import_path4.default.resolve(root, routesDir);
2046
- outputDir = import_path4.default.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2047
- entryFilesDir = import_path4.default.join(outputDir, ".marko-run");
2048
+ resolvedRoutesDir = import_path6.default.resolve(root, routesDir);
2049
+ outputDir = import_path6.default.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2050
+ entryFilesDir = import_path6.default.join(outputDir, ".marko-run");
2048
2051
  entryFilesDirPosix = normalizePath(entryFilesDir);
2049
2052
  relativeEntryFilesDirPosix = normalizePath(
2050
- import_path4.default.relative(root, entryFilesDir)
2053
+ import_path6.default.relative(root, entryFilesDir)
2051
2054
  );
2052
- typesDir = import_path4.default.join(root, ".marko-run");
2053
- devEntryFile = import_path4.default.join(root, "index.html");
2055
+ typesDir = import_path6.default.join(root, ".marko-run");
2056
+ devEntryFile = import_path6.default.join(root, "index.html");
2054
2057
  devEntryFilePosix = normalizePath(devEntryFile);
2055
2058
  let outDir = ((_e = config2.build) == null ? void 0 : _e.outDir) || "dist";
2056
2059
  const assetsDir = ((_f = config2.build) == null ? void 0 : _f.assetsDir) || "assets";
2057
2060
  let rollupOutputOptions = (_h = (_g = config2.build) == null ? void 0 : _g.rollupOptions) == null ? void 0 : _h.output;
2058
2061
  if (isBuild) {
2059
2062
  if (!isSSRBuild) {
2060
- outDir = import_path4.default.join(outDir, CLIENT_OUT_DIR);
2063
+ outDir = import_path6.default.join(outDir, CLIENT_OUT_DIR);
2061
2064
  }
2062
2065
  const defaultRollupOutputOptions = {
2063
2066
  assetFileNames({ name }) {
@@ -2182,7 +2185,7 @@ function markoRun(opts = {}) {
2182
2185
  devServer.watcher.on("all", async (type, filename) => {
2183
2186
  seenErrors.clear();
2184
2187
  const routableFileType = matchRoutableFile(
2185
- import_path4.default.parse(filename).base
2188
+ import_path6.default.parse(filename).base
2186
2189
  );
2187
2190
  if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
2188
2191
  if (type === "add" || type === "unlink" || type === "change" && (routableFileType === RoutableFileTypes.Handler || routableFileType === RoutableFileTypes.Middleware)) {
@@ -2207,8 +2210,8 @@ function markoRun(opts = {}) {
2207
2210
  },
2208
2211
  async buildStart(_options) {
2209
2212
  if (isSSRBuild && shouldEmptyOutDir) {
2210
- if (import_fs3.default.existsSync(outputDir)) {
2211
- import_fs3.default.rmSync(outputDir, { recursive: true });
2213
+ if (import_fs4.default.existsSync(outputDir)) {
2214
+ import_fs4.default.rmSync(outputDir, { recursive: true });
2212
2215
  }
2213
2216
  }
2214
2217
  if (isBuild && !isSSRBuild) {
@@ -2232,19 +2235,19 @@ function markoRun(opts = {}) {
2232
2235
  },
2233
2236
  async resolveId(importee, importer) {
2234
2237
  if (importee === "@marko/run/router") {
2235
- return normalizePath(import_path4.default.resolve(root, ROUTER_FILENAME));
2238
+ return normalizePath(import_path6.default.resolve(root, ROUTER_FILENAME));
2236
2239
  } else if (importee.endsWith(".marko") && importee.includes(relativeEntryFilesDirPosix)) {
2237
2240
  if (!importee.startsWith(root)) {
2238
- importee = import_path4.default.resolve(root, "." + importee);
2241
+ importee = import_path6.default.resolve(root, "." + importee);
2239
2242
  }
2240
2243
  return normalizePath(importee);
2241
2244
  }
2242
2245
  let virtualFilePath;
2243
2246
  if (importee.startsWith(virtualFilePrefix)) {
2244
2247
  virtualFilePath = importee.slice(virtualFilePrefix.length + 1);
2245
- importee = import_path4.default.resolve(root, virtualFilePath);
2248
+ importee = import_path6.default.resolve(root, virtualFilePath);
2246
2249
  } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
2247
- importee = import_path4.default.resolve(root, "." + importee);
2250
+ importee = import_path6.default.resolve(root, "." + importee);
2248
2251
  }
2249
2252
  importee = normalizePath(importee);
2250
2253
  if (!buildVirtualFilesResult) {
@@ -2253,7 +2256,7 @@ function markoRun(opts = {}) {
2253
2256
  if (virtualFiles.has(importee)) {
2254
2257
  return importee;
2255
2258
  } else if (virtualFilePath) {
2256
- const filePath = import_path4.default.resolve(__dirname, "..", virtualFilePath);
2259
+ const filePath = import_path6.default.resolve(__dirname, "..", virtualFilePath);
2257
2260
  return await this.resolve(filePath, importer, {
2258
2261
  skipSelf: true
2259
2262
  });
@@ -2292,7 +2295,7 @@ function markoRun(opts = {}) {
2292
2295
  const builtEntries = Object.values(bundle).reduce(
2293
2296
  (acc, item) => {
2294
2297
  if (item.type === "chunk" && item.isEntry) {
2295
- acc.push(import_path4.default.join(options.dir, item.fileName));
2298
+ acc.push(import_path6.default.join(options.dir, item.fileName));
2296
2299
  }
2297
2300
  return acc;
2298
2301
  },
@@ -2316,16 +2319,16 @@ function markoRun(opts = {}) {
2316
2319
  },
2317
2320
  async closeBundle() {
2318
2321
  if (isBuild && !isSSRBuild) {
2319
- if (import_fs3.default.existsSync(entryFilesDir)) {
2320
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
2322
+ if (import_fs4.default.existsSync(entryFilesDir)) {
2323
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
2321
2324
  }
2322
2325
  if ((adapter == null ? void 0 : adapter.buildEnd) && routes) {
2323
- await adapter.buildEnd(
2324
- resolvedConfig,
2325
- routes.list,
2326
- routeData.builtEntries,
2327
- routeData.sourceEntries
2328
- );
2326
+ await adapter.buildEnd({
2327
+ routes,
2328
+ config: resolvedConfig,
2329
+ builtEntries: routeData.builtEntries,
2330
+ sourceEntries: routeData.sourceEntries
2331
+ });
2329
2332
  }
2330
2333
  }
2331
2334
  }
@@ -2351,17 +2354,17 @@ async function globFileExists(root, pattern) {
2351
2354
  return (await (0, import_glob.glob)(pattern, { root })).length > 0;
2352
2355
  }
2353
2356
  async function ensureDir(dir) {
2354
- if (!import_fs3.default.existsSync(dir)) {
2355
- await import_fs3.default.promises.mkdir(dir, { recursive: true });
2357
+ if (!import_fs4.default.existsSync(dir)) {
2358
+ await import_fs4.default.promises.mkdir(dir, { recursive: true });
2356
2359
  }
2357
2360
  }
2358
2361
  async function getPackageData(dir) {
2359
2362
  do {
2360
- const pkgPath = import_path4.default.join(dir, "package.json");
2361
- if (import_fs3.default.existsSync(pkgPath)) {
2362
- return JSON.parse(await import_fs3.default.promises.readFile(pkgPath, "utf-8"));
2363
+ const pkgPath = import_path6.default.join(dir, "package.json");
2364
+ if (import_fs4.default.existsSync(pkgPath)) {
2365
+ return JSON.parse(await import_fs4.default.promises.readFile(pkgPath, "utf-8"));
2363
2366
  }
2364
- } while (dir !== (dir = import_path4.default.dirname(dir)));
2367
+ } while (dir !== (dir = import_path6.default.dirname(dir)));
2365
2368
  return null;
2366
2369
  }
2367
2370
  async function resolveAdapter(root, options, log) {
@@ -2433,11 +2436,11 @@ var defaultConfigPlugin = {
2433
2436
  var import_child_process = __toESM(require("child_process"), 1);
2434
2437
  var import_cluster = __toESM(require("cluster"), 1);
2435
2438
  var import_dotenv = require("dotenv");
2436
- var import_fs4 = __toESM(require("fs"), 1);
2439
+ var import_fs6 = __toESM(require("fs"), 1);
2437
2440
  var import_net = __toESM(require("net"), 1);
2438
2441
  async function parseEnv(envFile) {
2439
- if (import_fs4.default.existsSync(envFile)) {
2440
- const content = await import_fs4.default.promises.readFile(envFile, "utf8");
2442
+ if (import_fs6.default.existsSync(envFile)) {
2443
+ const content = await import_fs6.default.promises.readFile(envFile, "utf8");
2441
2444
  return (0, import_dotenv.parse)(content);
2442
2445
  }
2443
2446
  }