@marko/run 0.6.6 → 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,31 +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 extends Run.LayoutInput<typeof import('${path5}')> {}`
953
+ export interface Input extends Run.LayoutInput<typeof import("${modulePath}")> {}`
921
954
  );
922
955
  break;
923
956
  case RoutableFileTypes.Error:
924
957
  writeModuleDeclaration(
925
958
  writer,
926
- path5,
959
+ modulePath,
927
960
  "globalThis.MarkoRun.Route",
928
961
  `
929
962
  export interface Input {
@@ -932,7 +965,7 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
932
965
  );
933
966
  break;
934
967
  case RoutableFileTypes.NotFound:
935
- writeModuleDeclaration(writer, path5, "Run.Route");
968
+ writeModuleDeclaration(writer, modulePath, "Run.Route");
936
969
  break;
937
970
  }
938
971
  }
@@ -940,40 +973,15 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
940
973
  middlewareWriter.join();
941
974
  pageWriter.join();
942
975
  layoutWriter.join();
943
- writer.writeBlockStart(`
944
- type Routes = {`);
945
- for (const route of routes.list) {
946
- const { meta, handler, page } = route;
947
- if (page || handler) {
948
- const verbs = [];
949
- if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
950
- verbs.push(`"get"`);
951
- }
952
- if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
953
- verbs.push(`"post"`);
954
- }
955
- let routeType = `{ verb: ${verbs.join(" | ")};`;
956
- if (meta) {
957
- const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
958
- let metaType = `typeof import("${metaPath}")`;
959
- if (/\.(ts|js|mjs)$/.test(meta.name)) {
960
- metaType += `["default"]`;
961
- }
962
- routeType += ` meta: ${metaType};`;
963
- }
964
- writer.writeLines(`"${route.key}": ${routeType} };`);
965
- }
966
- }
967
- writer.writeBlockEnd("}");
968
976
  return writer.end();
969
977
  }
970
- function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
971
- writer.writeLines("").write(`declare module "${stripTsExtension(path5)}" {`);
978
+ function writeModuleDeclaration(writer, name, routeType, moduleTypes) {
979
+ writer.writeLines("").write(`declare module "${name}" {`);
972
980
  if (moduleTypes) {
973
981
  writer.write(moduleTypes);
974
982
  }
975
983
  if (routeType) {
976
- const isMarko = path5.endsWith(".marko");
984
+ const isMarko = name.endsWith(".marko");
977
985
  writer.write(`
978
986
  namespace MarkoRun {
979
987
  export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
@@ -987,21 +995,15 @@ function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
987
995
  writer.writeLines(`
988
996
  }`);
989
997
  }
990
- function pathToURLPatternString(path5) {
991
- return path5.replace(/\/\$(\$?)([^/]*)/g, (_, catchAll, name) => {
992
- name = decodeURIComponent(name);
993
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
994
- });
995
- }
996
998
  function createRouteTrie(routes) {
997
999
  const root = {
998
1000
  key: ""
999
1001
  };
1000
- function insert(path5, route) {
1002
+ function insert(path7, route) {
1001
1003
  let node = root;
1002
- for (const segment of path5.segments) {
1004
+ for (const segment of path7.segments) {
1003
1005
  if (segment === "$$") {
1004
- node.catchAll ?? (node.catchAll = { route, path: path5 });
1006
+ node.catchAll ?? (node.catchAll = { route, path: path7 });
1005
1007
  return;
1006
1008
  } else if (segment === "$") {
1007
1009
  node = node.dynamic ?? (node.dynamic = {
@@ -1019,17 +1021,18 @@ function createRouteTrie(routes) {
1019
1021
  node = next;
1020
1022
  }
1021
1023
  }
1022
- node.path ?? (node.path = path5);
1024
+ node.path ?? (node.path = path7);
1023
1025
  node.route ?? (node.route = route);
1024
1026
  }
1025
1027
  for (const route of routes) {
1026
- for (const path5 of route.paths) {
1027
- insert(path5, route);
1028
- }
1028
+ insert(route.path, route);
1029
1029
  }
1030
1030
  return root;
1031
1031
  }
1032
1032
 
1033
+ // src/vite/routes/builder.ts
1034
+ var import_path3 = __toESM(require("path"), 1);
1035
+
1033
1036
  // src/vite/routes/parse.ts
1034
1037
  function parseFlatRoute(pattern) {
1035
1038
  if (!pattern) throw new Error("Empty pattern");
@@ -1044,53 +1047,72 @@ function parseFlatRoute(pattern) {
1044
1047
  ]);
1045
1048
  function parse2(basePaths, group) {
1046
1049
  const pathMap = /* @__PURE__ */ new Map();
1047
- const delimiters = group ? ").," : ".,";
1050
+ const delimiters = group ? "`).," : "`.,";
1048
1051
  let charCode;
1049
1052
  let segmentStart = i;
1050
1053
  let type;
1051
1054
  let current;
1055
+ let escaped = "";
1056
+ let escapeStart = 0;
1052
1057
  do {
1053
1058
  charCode = pattern.charCodeAt(i);
1054
- 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) {
1055
1070
  break;
1056
- } else if (charCode === 44) {
1071
+ } else if (charCode === 44 /* Alternate */) {
1057
1072
  if (!current) {
1058
1073
  segmentEnd(
1059
- basePaths.map((path5) => ({
1060
- ...path5,
1061
- segments: path5.segments.slice()
1074
+ basePaths.map((path7) => ({
1075
+ ...path7,
1076
+ segments: path7.segments.slice()
1062
1077
  })),
1063
- "",
1078
+ escaped,
1064
1079
  "_",
1065
1080
  pathMap
1066
1081
  );
1067
1082
  } else {
1068
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1083
+ segmentEnd(
1084
+ current,
1085
+ escaped + pattern.slice(segmentStart, i),
1086
+ type,
1087
+ pathMap
1088
+ );
1069
1089
  }
1070
1090
  current = void 0;
1071
1091
  type = void 0;
1092
+ escaped = "";
1072
1093
  segmentStart = ++i;
1073
- } else if (charCode === 46) {
1094
+ } else if (charCode === 46 /* Directory */) {
1074
1095
  if (current) {
1075
- segmentEnd(current, pattern.slice(segmentStart, i), type);
1096
+ segmentEnd(current, escaped + pattern.slice(segmentStart, i), type);
1076
1097
  }
1077
1098
  type = void 0;
1099
+ escaped = "";
1078
1100
  segmentStart = ++i;
1079
- } else if (charCode === 40) {
1101
+ } else if (charCode === 40 /* GroupStart */) {
1080
1102
  const groupPaths = parse2(current || basePaths, ++i);
1081
1103
  if (groupPaths.length) {
1082
1104
  current = groupPaths;
1083
1105
  }
1084
1106
  segmentStart = ++i;
1085
1107
  } else {
1086
- if (charCode === 95) {
1108
+ if (charCode === 95 /* Pathless */) {
1087
1109
  type = "_";
1088
- } else if (charCode === 36) {
1110
+ } else if (charCode === 36 /* Dynamic */) {
1089
1111
  type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
1090
1112
  }
1091
- current ?? (current = basePaths.map((path5) => ({
1092
- ...path5,
1093
- segments: path5.segments.slice()
1113
+ current ?? (current = basePaths.map((path7) => ({
1114
+ ...path7,
1115
+ segments: path7.segments.slice()
1094
1116
  })));
1095
1117
  i = len;
1096
1118
  for (const char of delimiters) {
@@ -1101,7 +1123,12 @@ function parseFlatRoute(pattern) {
1101
1123
  }
1102
1124
  }
1103
1125
  } while (i < len);
1104
- 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 */) {
1105
1132
  throw new Error(
1106
1133
  `Invalid route pattern: group was not closed '${pattern.slice(
1107
1134
  group
@@ -1110,16 +1137,21 @@ function parseFlatRoute(pattern) {
1110
1137
  }
1111
1138
  if (!current) {
1112
1139
  segmentEnd(
1113
- basePaths.map((path5) => ({
1114
- ...path5,
1115
- segments: path5.segments.slice()
1140
+ basePaths.map((path7) => ({
1141
+ ...path7,
1142
+ segments: path7.segments.slice()
1116
1143
  })),
1117
- "",
1118
- "_",
1144
+ escaped,
1145
+ void 0,
1119
1146
  pathMap
1120
1147
  );
1121
1148
  } else {
1122
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1149
+ segmentEnd(
1150
+ current,
1151
+ escaped + pattern.slice(segmentStart, i),
1152
+ type,
1153
+ pathMap
1154
+ );
1123
1155
  }
1124
1156
  return [...pathMap.values()];
1125
1157
  }
@@ -1128,17 +1160,17 @@ function parseFlatRoute(pattern) {
1128
1160
  if (raw) {
1129
1161
  segment = {
1130
1162
  raw,
1131
- name: raw,
1163
+ name: normalizeSegment(raw),
1132
1164
  type
1133
1165
  };
1134
1166
  if (type === "$" || type === "$$") {
1135
1167
  segment.name = type;
1136
- segment.param = raw.slice(type.length);
1168
+ segment.param = normalizeParam(raw.slice(type.length));
1137
1169
  }
1138
1170
  }
1139
- for (const path5 of paths) {
1171
+ for (const path7 of paths) {
1140
1172
  if (segment) {
1141
- if (path5.isCatchall) {
1173
+ if (path7.isCatchall) {
1142
1174
  throw new Error(
1143
1175
  `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
1144
1176
  0,
@@ -1146,26 +1178,36 @@ function parseFlatRoute(pattern) {
1146
1178
  )}' in '${pattern}'.`
1147
1179
  );
1148
1180
  }
1149
- path5.segments.push(segment);
1150
- path5.id += path5.id === "/" ? segment.name : `/${segment.name}`;
1181
+ path7.segments.push(segment);
1182
+ path7.id += path7.id === "/" ? segment.name : `/${segment.name}`;
1151
1183
  if (type === "$$") {
1152
- path5.isCatchall = true;
1184
+ path7.isCatchall = true;
1153
1185
  }
1154
1186
  }
1155
1187
  if (map) {
1156
- if (map.has(path5.id)) {
1157
- const existing = map.get(path5.id);
1188
+ if (map.has(path7.id)) {
1189
+ const existing = map.get(path7.id);
1158
1190
  const existingExpansion = existing.segments.map((s) => s.raw).join(".");
1159
- const currentExpansion = path5.segments.map((s) => s.raw).join(".");
1191
+ const currentExpansion = path7.segments.map((s) => s.raw).join(".");
1160
1192
  throw new Error(
1161
- `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}'.`
1162
1194
  );
1163
1195
  }
1164
- map.set(path5.id, path5);
1196
+ map.set(path7.id, path7);
1165
1197
  }
1166
1198
  }
1167
1199
  }
1168
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
+ }
1169
1211
 
1170
1212
  // src/vite/routes/vdir.ts
1171
1213
  var _dirs, _pathlessDirs;
@@ -1176,14 +1218,12 @@ var _VDir = class _VDir {
1176
1218
  __publicField(this, "parent");
1177
1219
  __publicField(this, "source");
1178
1220
  __publicField(this, "path");
1179
- __publicField(this, "fullPath");
1180
1221
  __publicField(this, "segment");
1181
1222
  __publicField(this, "files");
1182
1223
  if (!parent || !segment) {
1183
1224
  this.parent = null;
1184
1225
  this.source = null;
1185
1226
  this.path = "/";
1186
- this.fullPath = "/";
1187
1227
  this.segment = {
1188
1228
  raw: "",
1189
1229
  name: ""
@@ -1191,12 +1231,8 @@ var _VDir = class _VDir {
1191
1231
  } else {
1192
1232
  this.parent = parent;
1193
1233
  this.source = source;
1194
- this.path = parent.path + (parent.path === "/" ? segment.name : `/${segment.name}`);
1195
- this.fullPath = parent.fullPath + (parent.fullPath === "/" ? segment.name : `/${segment.name}`);
1196
- if (segment.param) {
1197
- this.fullPath += segment.param;
1198
- }
1199
1234
  this.segment = segment;
1235
+ this.path = parent.path + (parent.path === "/" ? "" : "/") + segment.name;
1200
1236
  }
1201
1237
  }
1202
1238
  get pathInfo() {
@@ -1209,17 +1245,18 @@ var _VDir = class _VDir {
1209
1245
  for (const { segment } of this) {
1210
1246
  const { type, name, param } = segment;
1211
1247
  if (name && type !== "_") {
1212
- value.id += sep + (type || name);
1248
+ value.id += sep + name;
1213
1249
  value.path += sep + name;
1214
1250
  value.isEnd = type === "$$";
1215
1251
  if (param) {
1216
- value.path += param;
1252
+ const unescapedParam = param.charAt(0) === "`" ? param.slice(1, -1) : param;
1217
1253
  const index = type === "$$" ? null : value.segments.length;
1218
1254
  if (!value.params) {
1219
- value.params = { [param]: index };
1255
+ value.params = { [unescapedParam]: index };
1220
1256
  } else if (!(param in value.params)) {
1221
- value.params[param] = index;
1257
+ value.params[unescapedParam] = index;
1222
1258
  }
1259
+ value.path += param;
1223
1260
  }
1224
1261
  value.segments.push(name);
1225
1262
  sep = "/";
@@ -1231,11 +1268,11 @@ var _VDir = class _VDir {
1231
1268
  });
1232
1269
  return value;
1233
1270
  }
1234
- addDir(path5, segment) {
1271
+ addDir(path7, segment) {
1235
1272
  const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
1236
1273
  const key = segment.type === "$" ? segment.raw : segment.name;
1237
1274
  if (!map.has(key)) {
1238
- const dir = new _VDir(this, segment, path5);
1275
+ const dir = new _VDir(this, segment, path7);
1239
1276
  map.set(key, dir);
1240
1277
  return dir;
1241
1278
  }
@@ -1251,15 +1288,15 @@ var _VDir = class _VDir {
1251
1288
  const existing = this.files.get(file.type);
1252
1289
  if (existing !== file) {
1253
1290
  throw new Error(
1254
- `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}.`
1255
1292
  );
1256
1293
  } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
1257
1294
  throw new Error(
1258
- `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}`
1259
1296
  );
1260
1297
  }
1261
1298
  throw new Error(
1262
- `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}`
1263
1300
  );
1264
1301
  }
1265
1302
  }
@@ -1281,10 +1318,10 @@ var _VDir = class _VDir {
1281
1318
  const dirs = [];
1282
1319
  const unique = /* @__PURE__ */ new Map();
1283
1320
  for (const root of roots) {
1284
- for (const path5 of paths) {
1321
+ for (const path7 of paths) {
1285
1322
  let dir = root;
1286
- for (const segment of path5.segments) {
1287
- dir = dir.addDir(path5, segment);
1323
+ for (const segment of path7.segments) {
1324
+ dir = dir.addDir(path7, segment);
1288
1325
  }
1289
1326
  const existing = unique.get(dir.path);
1290
1327
  if (existing) {
@@ -1297,7 +1334,7 @@ var _VDir = class _VDir {
1297
1334
  }
1298
1335
  }
1299
1336
  throw new Error(
1300
- `Ambiguous directory structure: '${sourcePath}${path5.source}' defines '${dir.path}' multiple times.`
1337
+ `Ambiguous directory structure: ${sourcePath}${path7.source} defines ${dir.path} multiple times.`
1301
1338
  );
1302
1339
  } else {
1303
1340
  unique.set(dir.path, dir);
@@ -1323,13 +1360,11 @@ function matchRoutableFile(filename) {
1323
1360
  const match = filename.match(routeableFileRegex);
1324
1361
  return match && (match[1] || match[3]).toLowerCase();
1325
1362
  }
1326
- function isSpecialType(type) {
1327
- return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
1328
- }
1329
- async function buildRoutes(sources) {
1363
+ async function buildRoutes(sources, outDir) {
1330
1364
  const uniqueRoutes = /* @__PURE__ */ new Map();
1331
1365
  const routes = [];
1332
1366
  const special = {};
1367
+ const seenKeys = /* @__PURE__ */ new Map();
1333
1368
  const middlewares = /* @__PURE__ */ new Set();
1334
1369
  const unusedFiles = /* @__PURE__ */ new Set();
1335
1370
  const currentLayouts = /* @__PURE__ */ new Set();
@@ -1337,13 +1372,13 @@ async function buildRoutes(sources) {
1337
1372
  const root = new VDir();
1338
1373
  const dirStack = [];
1339
1374
  let basePath;
1340
- let importPrefix;
1341
1375
  let activeDirs;
1342
1376
  let isBaseDir;
1343
1377
  let nextFileId = 1;
1344
1378
  let nextRouteIndex = 1;
1345
1379
  const walkOptions = {
1346
- onEnter({ name }) {
1380
+ onEnter(dir) {
1381
+ let { name } = dir;
1347
1382
  const prevDirStackLength = dirStack.length;
1348
1383
  if (isBaseDir) {
1349
1384
  isBaseDir = false;
@@ -1362,15 +1397,16 @@ async function buildRoutes(sources) {
1362
1397
  dirStack.length = prevDirStackLength;
1363
1398
  };
1364
1399
  },
1365
- onFile({ name, path: path5 }) {
1400
+ onFile(file) {
1401
+ const { name } = file;
1366
1402
  const match = name.match(routeableFileRegex);
1367
1403
  if (!match) {
1368
1404
  return;
1369
1405
  }
1370
1406
  const type = (match[1] || match[3]).toLowerCase();
1371
- if (dirStack.length && isSpecialType(type)) {
1407
+ if (dirStack.length && (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error)) {
1372
1408
  console.warn(
1373
- `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}`
1374
1410
  );
1375
1411
  return;
1376
1412
  }
@@ -1379,19 +1415,15 @@ async function buildRoutes(sources) {
1379
1415
  const paths = parseFlatRoute(name.slice(0, match.index));
1380
1416
  dirs = VDir.addPaths(activeDirs, paths);
1381
1417
  }
1382
- const dirPath = dirStack.join("/");
1383
- const relativePath = dirPath ? `${dirPath}/${name}` : name;
1384
- const file = {
1418
+ const routableFile = {
1385
1419
  id: String(nextFileId++),
1386
1420
  name,
1387
1421
  type,
1388
- filePath: path5,
1389
- relativePath,
1390
- importPath: `${importPrefix}/${relativePath}`,
1422
+ filePath: file.path,
1391
1423
  verbs: type === RoutableFileTypes.Page ? ["get", "head"] : void 0
1392
1424
  };
1393
1425
  for (const dir of dirs) {
1394
- dir.addFile(file);
1426
+ dir.addFile(routableFile);
1395
1427
  }
1396
1428
  }
1397
1429
  };
@@ -1399,7 +1431,6 @@ async function buildRoutes(sources) {
1399
1431
  sources = [sources];
1400
1432
  }
1401
1433
  for (const source of sources) {
1402
- importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
1403
1434
  basePath = source.basePath || "";
1404
1435
  activeDirs = [root];
1405
1436
  isBaseDir = true;
@@ -1419,7 +1450,8 @@ async function buildRoutes(sources) {
1419
1450
  layout = dir.files.get(RoutableFileTypes.Layout);
1420
1451
  const handler = dir.files.get(RoutableFileTypes.Handler);
1421
1452
  const page = dir.files.get(RoutableFileTypes.Page);
1422
- let hasSpecial = false;
1453
+ const pathInfo = dir.pathInfo;
1454
+ let layoutsUsed = false;
1423
1455
  if (middleware) {
1424
1456
  if (currentMiddleware.has(middleware)) {
1425
1457
  middleware = void 0;
@@ -1436,64 +1468,64 @@ async function buildRoutes(sources) {
1436
1468
  unusedFiles.add(layout);
1437
1469
  }
1438
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
+ }
1439
1487
  if (page || handler) {
1440
- const path5 = dir.pathInfo;
1441
- if (uniqueRoutes.has(path5.id)) {
1442
- const existing = uniqueRoutes.get(path5.id);
1488
+ if (uniqueRoutes.has(pathInfo.id)) {
1489
+ const existing = uniqueRoutes.get(pathInfo.id);
1443
1490
  const route = routes[existing.index];
1444
1491
  const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
1445
1492
  const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
1446
- throw new Error(`Duplicate routes for path '${path5.id}' were defined. A route established by:
1447
- ${existingFiles.join(" and ")} via '${existing.dir.fullPath}'
1448
- collides with
1449
- ${currentFiles.join(" and ")} via '${dir.fullPath}'
1450
- `);
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;
1451
1503
  }
1452
- uniqueRoutes.set(path5.id, { dir, index: routes.length });
1453
1504
  routes.push({
1454
1505
  index: nextRouteIndex++,
1455
- key: dir.fullPath,
1456
- paths: [path5],
1506
+ key,
1507
+ path: pathInfo,
1457
1508
  middleware: [...currentMiddleware],
1458
1509
  layouts: page ? [...currentLayouts] : [],
1459
1510
  meta: dir.files.get(RoutableFileTypes.Meta),
1460
1511
  page,
1461
1512
  handler,
1462
- 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
1463
1514
  });
1464
- }
1465
- if (dir === root) {
1466
- for (const [type, file] of dir.files) {
1467
- if (isSpecialType(type)) {
1468
- hasSpecial = true;
1469
- special[type] = {
1470
- index: 0,
1471
- key: type,
1472
- paths: [],
1473
- middleware: [],
1474
- layouts: [...currentLayouts],
1475
- page: file,
1476
- entryName: `${markoRunFilePrefix}special.${type}`
1477
- };
1478
- }
1479
- }
1480
- }
1481
- if (handler || page) {
1515
+ layoutsUsed = !!page;
1482
1516
  for (const middleware2 of currentMiddleware) {
1483
1517
  middlewares.add(middleware2);
1484
1518
  unusedFiles.delete(middleware2);
1485
1519
  }
1486
1520
  }
1487
- if (page || hasSpecial) {
1521
+ if (layoutsUsed) {
1488
1522
  for (const layout2 of currentLayouts) {
1489
1523
  unusedFiles.delete(layout2);
1490
1524
  }
1491
1525
  }
1492
1526
  }
1493
- if (dir.dirs) {
1494
- for (const child of dir.dirs()) {
1495
- traverse(child);
1496
- }
1527
+ for (const childDir of dir.dirs()) {
1528
+ traverse(childDir);
1497
1529
  }
1498
1530
  if (middleware) {
1499
1531
  currentMiddleware.delete(middleware);
@@ -1503,10 +1535,13 @@ async function buildRoutes(sources) {
1503
1535
  }
1504
1536
  }
1505
1537
  }
1538
+ function replaceInvalidFilenameChars(str) {
1539
+ return str.replace(/[<>:"/\\|?*]+/g, "_");
1540
+ }
1506
1541
 
1507
1542
  // src/vite/routes/walk.ts
1508
- var import_fs = __toESM(require("fs"), 1);
1509
- var import_path2 = __toESM(require("path"), 1);
1543
+ var import_fs2 = __toESM(require("fs"), 1);
1544
+ var import_path4 = __toESM(require("path"), 1);
1510
1545
  function createFSWalker(dir) {
1511
1546
  return async function walkFS({
1512
1547
  onEnter,
@@ -1518,10 +1553,10 @@ function createFSWalker(dir) {
1518
1553
  const onExit = onEnter == null ? void 0 : onEnter(dir2);
1519
1554
  if (onExit !== false) {
1520
1555
  const dirs = [];
1521
- const entries = await import_fs.default.promises.readdir(dir2.path, {
1556
+ const entries = await import_fs2.default.promises.readdir(dir2.path, {
1522
1557
  withFileTypes: true
1523
1558
  });
1524
- const prefix = dir2.path + import_path2.default.sep;
1559
+ const prefix = dir2.path + import_path4.default.sep;
1525
1560
  for (const entry of entries) {
1526
1561
  const walkEntry = {
1527
1562
  name: entry.name,
@@ -1544,7 +1579,7 @@ function createFSWalker(dir) {
1544
1579
  await walk(
1545
1580
  {
1546
1581
  path: dir,
1547
- name: import_path2.default.basename(dir)
1582
+ name: import_path4.default.basename(dir)
1548
1583
  },
1549
1584
  maxDepth
1550
1585
  );
@@ -1661,36 +1696,34 @@ function logRoutesTable(routes, bundle) {
1661
1696
  style: { compact: true }
1662
1697
  });
1663
1698
  for (const route of routes.list) {
1664
- for (const path5 of route.paths) {
1665
- const verbs = getVerbs(route, true);
1666
- let firstRow = true;
1667
- for (const verb of verbs) {
1668
- const entryType = [];
1669
- let size = "";
1670
- let verbCell = verbColor(verb)(verb.toUpperCase());
1671
- if (verb === "get" && !verbs.includes("head")) {
1672
- verbCell += import_kleur2.default.dim(`,${verbColor(verb)("HEAD")}`);
1673
- }
1674
- if (route.handler) {
1675
- entryType.push(import_kleur2.default.blue("handler"));
1676
- }
1677
- if (route.page && (verb === "get" || verb === "head")) {
1678
- entryType.push(import_kleur2.default.yellow("page"));
1679
- if (verb === "get") {
1680
- size = prettySize(computeRouteSize(route, bundle));
1681
- }
1682
- }
1683
- const row = [verbCell];
1684
- if (verbs.length === 1 || firstRow) {
1685
- row.push({ rowSpan: verbs.length, content: prettyPath(path5.path) });
1686
- 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));
1687
1712
  }
1688
- row.push(entryType.join(" -> "));
1689
- hasMiddleware && row.push(route.middleware.length || "");
1690
- hasMeta && row.push(route.meta ? "\u2713" : "");
1691
- row.push(size || "");
1692
- table.push(row);
1693
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);
1694
1727
  }
1695
1728
  }
1696
1729
  for (const [key, route] of Object.entries(routes.special).sort()) {
@@ -1746,17 +1779,20 @@ function prettySize([bytes, compBytes]) {
1746
1779
  else str += import_kleur2.default.bold(import_kleur2.default.red(compSize));
1747
1780
  return str;
1748
1781
  }
1749
- function prettyPath(path5) {
1750
- 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
+ );
1751
1787
  }
1752
1788
 
1753
1789
  // src/vite/utils/read-once-persisted-store.ts
1754
- var import_fs2 = require("fs");
1790
+ var import_fs3 = require("fs");
1755
1791
  var import_os = __toESM(require("os"), 1);
1756
- var import_path3 = __toESM(require("path"), 1);
1792
+ var import_path5 = __toESM(require("path"), 1);
1757
1793
  var noop = () => {
1758
1794
  };
1759
- 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");
1760
1796
  var values = /* @__PURE__ */ new Map();
1761
1797
  var loadedFromDisk;
1762
1798
  var ReadOncePersistedStore = class {
@@ -1776,13 +1812,13 @@ var ReadOncePersistedStore = class {
1776
1812
  if (loadedFromDisk === true) {
1777
1813
  throw new Error(`Value for ${uid} could not be loaded.`);
1778
1814
  }
1779
- 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)));
1780
1816
  return this.read();
1781
1817
  }
1782
1818
  };
1783
1819
  function syncDataFromDisk(data) {
1784
1820
  finishLoadFromDisk();
1785
- import_fs2.promises.unlink(tmpFile).catch(noop);
1821
+ import_fs3.promises.unlink(tmpFile).catch(noop);
1786
1822
  for (const [k, v] of JSON.parse(data)) {
1787
1823
  values.set(k, v);
1788
1824
  }
@@ -1792,23 +1828,23 @@ function finishLoadFromDisk() {
1792
1828
  }
1793
1829
  process.once("beforeExit", (code) => {
1794
1830
  if (code === 0 && values.size) {
1795
- import_fs2.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1831
+ import_fs3.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1796
1832
  }
1797
1833
  });
1798
1834
 
1799
1835
  // src/vite/plugin.ts
1800
1836
  var debug = (0, import_debug.default)("@marko/run");
1801
- var __dirname = import_path4.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1837
+ var __dirname = import_path6.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1802
1838
  var PLUGIN_NAME_PREFIX = "marko-run-vite";
1803
- var POSIX_SEP = "/";
1804
- var WINDOWS_SEP = "\\";
1805
1839
  var CLIENT_OUT_DIR = "public";
1806
1840
  var MIDDLEWARE_FILENAME = `${markoRunFilePrefix}middleware.js`;
1807
1841
  var ROUTER_FILENAME = `${markoRunFilePrefix}router.js`;
1808
1842
  var defaultPort = Number(process.env.PORT || 3e3);
1809
- var normalizePath = import_path4.default.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1810
1843
  function markoRun(opts = {}) {
1811
- let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1844
+ let routesDir;
1845
+ let adapter;
1846
+ let trailingSlashes;
1847
+ const { ...markoVitePluginOptions } = opts;
1812
1848
  let store;
1813
1849
  let root;
1814
1850
  let shouldEmptyOutDir = false;
@@ -1841,15 +1877,11 @@ function markoRun(opts = {}) {
1841
1877
  root,
1842
1878
  "{.tsconfig*,tsconfig*.json}"
1843
1879
  )))) {
1844
- const filepath = import_path4.default.join(typesDir, "routes.d.ts");
1845
- const data = await renderRouteTypeInfo(
1846
- routes2,
1847
- normalizePath(import_path4.default.relative(typesDir, resolvedRoutesDir)),
1848
- adapter
1849
- );
1850
- 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)) {
1851
1883
  await ensureDir(typesDir);
1852
- await import_fs3.default.promises.writeFile(filepath, typesFile = data);
1884
+ await import_fs4.default.promises.writeFile(filepath, typesFile = data);
1853
1885
  }
1854
1886
  }
1855
1887
  }
@@ -1857,20 +1889,25 @@ function markoRun(opts = {}) {
1857
1889
  function buildVirtualFiles() {
1858
1890
  return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1859
1891
  virtualFiles.clear();
1860
- routes = await buildRoutes({
1861
- walker: createFSWalker(resolvedRoutesDir),
1862
- importPrefix: routesDir
1863
- });
1892
+ routes = await buildRoutes(
1893
+ {
1894
+ walker: createFSWalker(resolvedRoutesDir)
1895
+ },
1896
+ entryFilesDir
1897
+ );
1864
1898
  if (!routes.list.length) {
1865
1899
  throw new Error("No routes generated");
1866
1900
  }
1867
1901
  for (const route of routes.list) {
1868
- 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
+ );
1869
1906
  }
1870
1907
  if (routes.middleware.length) {
1871
- virtualFiles.set(import_path4.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1908
+ virtualFiles.set(import_path6.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1872
1909
  }
1873
- virtualFiles.set(import_path4.default.posix.join(root, ROUTER_FILENAME), "");
1910
+ virtualFiles.set(import_path6.default.posix.join(root, ROUTER_FILENAME), "");
1874
1911
  return routes;
1875
1912
  })());
1876
1913
  }
@@ -1880,115 +1917,82 @@ function markoRun(opts = {}) {
1880
1917
  var _a;
1881
1918
  try {
1882
1919
  const routes2 = await buildVirtualFiles();
1883
- if (import_fs3.default.existsSync(entryFilesDir)) {
1884
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
1920
+ if (import_fs4.default.existsSync(entryFilesDir)) {
1921
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
1885
1922
  }
1886
1923
  for (const route of routes2.list) {
1887
- const { handler, page, layouts } = route;
1888
- if (handler) {
1889
- const exports2 = await getExportsFromFile(context, handler.filePath);
1890
- handler.verbs = [];
1924
+ if (route.handler) {
1925
+ const exports2 = await getExportsFromFile(
1926
+ context,
1927
+ route.handler.filePath
1928
+ );
1929
+ route.handler.verbs = [];
1891
1930
  for (const name of exports2) {
1892
1931
  const verb = name.toLowerCase();
1893
1932
  if (name === verb.toUpperCase() && httpVerbs.includes(verb)) {
1894
- handler.verbs.push(verb);
1933
+ route.handler.verbs.push(verb);
1895
1934
  }
1896
1935
  }
1897
- if (!handler.verbs.length) {
1936
+ if (!route.handler.verbs.length) {
1898
1937
  context.warn(
1899
- `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(", ")}`
1900
1939
  );
1901
1940
  }
1902
1941
  }
1903
- if (page) {
1904
- if (layouts.length) {
1905
- const relativePath = import_path4.default.relative(
1906
- resolvedRoutesDir,
1907
- page.filePath
1908
- );
1909
- const routeFileDir = import_path4.default.join(entryFilesDir, relativePath, "..");
1910
- const routeFileRelativePathPosix = normalizePath(
1911
- import_path4.default.relative(routeFileDir, root)
1912
- );
1913
- import_fs3.default.mkdirSync(routeFileDir, { recursive: true });
1914
- const pageNameIndex = page.name.indexOf("+page");
1915
- const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
1916
- import_fs3.default.writeFileSync(
1917
- route.templateFilePath = import_path4.default.join(
1918
- routeFileDir,
1919
- pageNamePrefix + "route.marko"
1920
- ),
1921
- renderRouteTemplate(
1922
- route,
1923
- (to) => import_path4.default.posix.join(routeFileRelativePathPosix, to)
1924
- )
1925
- );
1926
- } else {
1927
- route.templateFilePath = page.filePath;
1928
- }
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
+ );
1929
1950
  }
1930
1951
  virtualFiles.set(
1931
- import_path4.default.posix.join(root, `${route.entryName}.js`),
1932
- renderRouteEntry(route, relativeEntryFilesDirPosix)
1952
+ import_path6.default.posix.join(root, getRouteVirtualFileName(route)),
1953
+ renderRouteEntry(route, root)
1933
1954
  );
1934
1955
  }
1935
1956
  for (const route of Object.values(routes2.special)) {
1936
- const { page, layouts, key } = route;
1937
- if (page) {
1938
- if (layouts.length) {
1939
- const relativePath = import_path4.default.relative(
1940
- resolvedRoutesDir,
1941
- page.filePath
1942
- );
1943
- const routeFileDir = import_path4.default.join(entryFilesDir, relativePath, "..");
1944
- const routeFileRelativePathPosix = normalizePath(
1945
- import_path4.default.relative(routeFileDir, root)
1946
- );
1947
- import_fs3.default.mkdirSync(routeFileDir, { recursive: true });
1948
- import_fs3.default.writeFileSync(
1949
- route.templateFilePath = import_path4.default.join(
1950
- routeFileDir,
1951
- `route.${key}.marko`
1952
- ),
1953
- renderRouteTemplate(
1954
- route,
1955
- (to) => import_path4.default.posix.join(routeFileRelativePathPosix, to)
1956
- )
1957
- );
1958
- } else {
1959
- route.templateFilePath = page.filePath;
1960
- }
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
+ );
1961
1965
  }
1962
1966
  }
1963
1967
  if (routes2.middleware.length) {
1964
1968
  for (const middleware of routes2.middleware) {
1965
1969
  if (!(await getExportsFromFile(context, middleware.filePath)).includes("default")) {
1966
1970
  context.warn(
1967
- `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)}'`
1968
1972
  );
1969
1973
  }
1970
1974
  }
1971
1975
  virtualFiles.set(
1972
- import_path4.default.posix.join(root, MIDDLEWARE_FILENAME),
1973
- renderMiddleware(routes2.middleware)
1976
+ import_path6.default.posix.join(root, MIDDLEWARE_FILENAME),
1977
+ renderMiddleware(routes2.middleware, root)
1974
1978
  );
1975
1979
  }
1976
1980
  virtualFiles.set(
1977
- import_path4.default.posix.join(root, ROUTER_FILENAME),
1978
- renderRouter(routes2, relativeEntryFilesDirPosix, {
1979
- trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1981
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
1982
+ renderRouter(routes2, root, {
1983
+ trailingSlashes
1980
1984
  })
1981
1985
  );
1982
1986
  await writeTypesFile(routes2);
1983
1987
  if (adapter == null ? void 0 : adapter.routesGenerated) {
1984
- await adapter.routesGenerated(
1985
- routes2,
1986
- new Map(virtualFiles.entries()),
1987
- {
1988
+ await adapter.routesGenerated({
1989
+ routes: routes2,
1990
+ virtualFiles: new Map(virtualFiles.entries()),
1991
+ meta: {
1988
1992
  buildTime: times.routesBuild,
1989
1993
  renderTime: times.routesRender
1990
1994
  }
1991
- );
1995
+ });
1992
1996
  if (!isBuild) {
1993
1997
  await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes2.list));
1994
1998
  }
@@ -1998,7 +2002,7 @@ function markoRun(opts = {}) {
1998
2002
  throw err;
1999
2003
  }
2000
2004
  virtualFiles.set(
2001
- import_path4.default.posix.join(root, ROUTER_FILENAME),
2005
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
2002
2006
  `throw ${JSON.stringify(prepareError(err))}`
2003
2007
  );
2004
2008
  }
@@ -2035,27 +2039,28 @@ function markoRun(opts = {}) {
2035
2039
  }
2036
2040
  }
2037
2041
  routesDir = opts.routesDir || "src/routes";
2042
+ trailingSlashes = opts.trailingSlashes || "RedirectWithout";
2038
2043
  store = new ReadOncePersistedStore(
2039
2044
  `vite-marko-run${opts.runtimeId ? `-${opts.runtimeId}` : ""}`
2040
2045
  );
2041
2046
  markoVitePluginOptions.runtimeId = opts.runtimeId;
2042
2047
  markoVitePluginOptions.basePathVar = opts.basePathVar;
2043
- resolvedRoutesDir = import_path4.default.resolve(root, routesDir);
2044
- outputDir = import_path4.default.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2045
- 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");
2046
2051
  entryFilesDirPosix = normalizePath(entryFilesDir);
2047
2052
  relativeEntryFilesDirPosix = normalizePath(
2048
- import_path4.default.relative(root, entryFilesDir)
2053
+ import_path6.default.relative(root, entryFilesDir)
2049
2054
  );
2050
- typesDir = import_path4.default.join(root, ".marko-run");
2051
- 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");
2052
2057
  devEntryFilePosix = normalizePath(devEntryFile);
2053
2058
  let outDir = ((_e = config2.build) == null ? void 0 : _e.outDir) || "dist";
2054
2059
  const assetsDir = ((_f = config2.build) == null ? void 0 : _f.assetsDir) || "assets";
2055
2060
  let rollupOutputOptions = (_h = (_g = config2.build) == null ? void 0 : _g.rollupOptions) == null ? void 0 : _h.output;
2056
2061
  if (isBuild) {
2057
2062
  if (!isSSRBuild) {
2058
- outDir = import_path4.default.join(outDir, CLIENT_OUT_DIR);
2063
+ outDir = import_path6.default.join(outDir, CLIENT_OUT_DIR);
2059
2064
  }
2060
2065
  const defaultRollupOutputOptions = {
2061
2066
  assetFileNames({ name }) {
@@ -2180,7 +2185,7 @@ function markoRun(opts = {}) {
2180
2185
  devServer.watcher.on("all", async (type, filename) => {
2181
2186
  seenErrors.clear();
2182
2187
  const routableFileType = matchRoutableFile(
2183
- import_path4.default.parse(filename).base
2188
+ import_path6.default.parse(filename).base
2184
2189
  );
2185
2190
  if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
2186
2191
  if (type === "add" || type === "unlink" || type === "change" && (routableFileType === RoutableFileTypes.Handler || routableFileType === RoutableFileTypes.Middleware)) {
@@ -2205,8 +2210,8 @@ function markoRun(opts = {}) {
2205
2210
  },
2206
2211
  async buildStart(_options) {
2207
2212
  if (isSSRBuild && shouldEmptyOutDir) {
2208
- if (import_fs3.default.existsSync(outputDir)) {
2209
- import_fs3.default.rmSync(outputDir, { recursive: true });
2213
+ if (import_fs4.default.existsSync(outputDir)) {
2214
+ import_fs4.default.rmSync(outputDir, { recursive: true });
2210
2215
  }
2211
2216
  }
2212
2217
  if (isBuild && !isSSRBuild) {
@@ -2230,19 +2235,19 @@ function markoRun(opts = {}) {
2230
2235
  },
2231
2236
  async resolveId(importee, importer) {
2232
2237
  if (importee === "@marko/run/router") {
2233
- return normalizePath(import_path4.default.resolve(root, ROUTER_FILENAME));
2238
+ return normalizePath(import_path6.default.resolve(root, ROUTER_FILENAME));
2234
2239
  } else if (importee.endsWith(".marko") && importee.includes(relativeEntryFilesDirPosix)) {
2235
2240
  if (!importee.startsWith(root)) {
2236
- importee = import_path4.default.resolve(root, "." + importee);
2241
+ importee = import_path6.default.resolve(root, "." + importee);
2237
2242
  }
2238
2243
  return normalizePath(importee);
2239
2244
  }
2240
2245
  let virtualFilePath;
2241
2246
  if (importee.startsWith(virtualFilePrefix)) {
2242
2247
  virtualFilePath = importee.slice(virtualFilePrefix.length + 1);
2243
- importee = import_path4.default.resolve(root, virtualFilePath);
2248
+ importee = import_path6.default.resolve(root, virtualFilePath);
2244
2249
  } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
2245
- importee = import_path4.default.resolve(root, "." + importee);
2250
+ importee = import_path6.default.resolve(root, "." + importee);
2246
2251
  }
2247
2252
  importee = normalizePath(importee);
2248
2253
  if (!buildVirtualFilesResult) {
@@ -2251,7 +2256,7 @@ function markoRun(opts = {}) {
2251
2256
  if (virtualFiles.has(importee)) {
2252
2257
  return importee;
2253
2258
  } else if (virtualFilePath) {
2254
- const filePath = import_path4.default.resolve(__dirname, "..", virtualFilePath);
2259
+ const filePath = import_path6.default.resolve(__dirname, "..", virtualFilePath);
2255
2260
  return await this.resolve(filePath, importer, {
2256
2261
  skipSelf: true
2257
2262
  });
@@ -2290,7 +2295,7 @@ function markoRun(opts = {}) {
2290
2295
  const builtEntries = Object.values(bundle).reduce(
2291
2296
  (acc, item) => {
2292
2297
  if (item.type === "chunk" && item.isEntry) {
2293
- acc.push(import_path4.default.join(options.dir, item.fileName));
2298
+ acc.push(import_path6.default.join(options.dir, item.fileName));
2294
2299
  }
2295
2300
  return acc;
2296
2301
  },
@@ -2314,16 +2319,16 @@ function markoRun(opts = {}) {
2314
2319
  },
2315
2320
  async closeBundle() {
2316
2321
  if (isBuild && !isSSRBuild) {
2317
- if (import_fs3.default.existsSync(entryFilesDir)) {
2318
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
2322
+ if (import_fs4.default.existsSync(entryFilesDir)) {
2323
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
2319
2324
  }
2320
2325
  if ((adapter == null ? void 0 : adapter.buildEnd) && routes) {
2321
- await adapter.buildEnd(
2322
- resolvedConfig,
2323
- routes.list,
2324
- routeData.builtEntries,
2325
- routeData.sourceEntries
2326
- );
2326
+ await adapter.buildEnd({
2327
+ routes,
2328
+ config: resolvedConfig,
2329
+ builtEntries: routeData.builtEntries,
2330
+ sourceEntries: routeData.sourceEntries
2331
+ });
2327
2332
  }
2328
2333
  }
2329
2334
  }
@@ -2349,17 +2354,17 @@ async function globFileExists(root, pattern) {
2349
2354
  return (await (0, import_glob.glob)(pattern, { root })).length > 0;
2350
2355
  }
2351
2356
  async function ensureDir(dir) {
2352
- if (!import_fs3.default.existsSync(dir)) {
2353
- 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 });
2354
2359
  }
2355
2360
  }
2356
2361
  async function getPackageData(dir) {
2357
2362
  do {
2358
- const pkgPath = import_path4.default.join(dir, "package.json");
2359
- if (import_fs3.default.existsSync(pkgPath)) {
2360
- 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"));
2361
2366
  }
2362
- } while (dir !== (dir = import_path4.default.dirname(dir)));
2367
+ } while (dir !== (dir = import_path6.default.dirname(dir)));
2363
2368
  return null;
2364
2369
  }
2365
2370
  async function resolveAdapter(root, options, log) {
@@ -2431,11 +2436,11 @@ var defaultConfigPlugin = {
2431
2436
  var import_child_process = __toESM(require("child_process"), 1);
2432
2437
  var import_cluster = __toESM(require("cluster"), 1);
2433
2438
  var import_dotenv = require("dotenv");
2434
- var import_fs4 = __toESM(require("fs"), 1);
2439
+ var import_fs6 = __toESM(require("fs"), 1);
2435
2440
  var import_net = __toESM(require("net"), 1);
2436
2441
  async function parseEnv(envFile) {
2437
- if (import_fs4.default.existsSync(envFile)) {
2438
- 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");
2439
2444
  return (0, import_dotenv.parse)(content);
2440
2445
  }
2441
2446
  }