@marko/run 0.6.6 → 0.7.1

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,14 +574,16 @@ 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--;
573
- writer.writeLines(`
581
+ if (routes.list.length) {
582
+ writer.writeLines(`
574
583
  return new Response(null, {
575
584
  status: 404,
576
585
  });`);
586
+ }
577
587
  if (hasErrorPage) {
578
588
  imports.writeLines(`
579
589
  const page500ResponseInit = {
@@ -583,7 +593,7 @@ const page500ResponseInit = {
583
593
  writer.writeBlockStart(`} catch (error) {`).writeBlockStart(
584
594
  `if (context.request.headers.get('Accept')?.includes('text/html')) {`
585
595
  ).writeLines(
586
- `return pageResponse(page500, buildInput({ error }), page500ResponseInit);`
596
+ `return context.render(page500, { error }, page500ResponseInit);`
587
597
  ).writeBlockEnd("}").writeLines("throw error;").writeBlockEnd("}");
588
598
  }
589
599
  writer.writeBlockEnd("}");
@@ -595,38 +605,40 @@ function renderFetch(writer, options) {
595
605
  export async function fetch(request, platform) {
596
606
  try {
597
607
  const url = new URL(request.url);
598
- let { pathname } = url;`);
608
+ const { pathname } = url;
609
+ const last = pathname.length - 1;
610
+ const hasTrailingSlash = last && pathname.charAt(last) === '/';
611
+ const normalizedPathname = hasTrailingSlash ? pathname.slice(0, last) : pathname;
612
+ const route = match_internal(request.method, normalizedPathname);`);
599
613
  switch (options.trailingSlashes) {
600
614
  case "RedirectWithout":
601
615
  writer.write(`
602
- if (pathname !== '/' && pathname.endsWith('/')) {
603
- url.pathname = pathname.slice(0, -1);
616
+ if (route && hasTrailingSlash) {
617
+ url.pathname = normalizedPathname
604
618
  return Response.redirect(url);
605
619
  }`);
606
620
  break;
607
621
  case "RedirectWith":
608
622
  writer.write(`
609
- if (pathname !== '/' && !pathname.endsWith('/')) {
610
- url.pathname = pathname + '/';
623
+ if (route && pathname !== '/' && !hasTrailingSlash) {
624
+ url.pathname += '/';
611
625
  return Response.redirect(url);
612
626
  }`);
613
627
  break;
614
628
  case "RewriteWithout":
615
629
  writer.write(`
616
- if (pathname !== '/' && pathname.endsWith('/')) {
617
- url.pathname = pathname = pathname.slice(0, -1);
630
+ if (route && hasTrailingSlash) {
631
+ url.pathname = normalizedPathname;
618
632
  }`);
619
633
  break;
620
634
  case "RewriteWith":
621
635
  writer.write(`
622
- if (pathname !== '/' && !pathname.endsWith('/')) {
623
- url.pathname = pathname = pathname + '/';
636
+ if (route && pathname !== '/' && !hasTrailingSlash) {
637
+ url.pathname += '/';
624
638
  }`);
625
639
  break;
626
640
  }
627
641
  writer.write(`
628
-
629
- const route = match(request.method, pathname);
630
642
  return await invoke(route, request, platform, url);
631
643
  } catch (error) {
632
644
  if (import.meta.env.DEV) {
@@ -642,10 +654,9 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
642
654
  const { route, dynamic, catchAll } = trie;
643
655
  let closeCount = 0;
644
656
  if (level === 0) {
645
- writer.writeLines(`const len = pathname.length;`);
646
657
  if (route) {
647
658
  writer.writeLines(
648
- `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
659
+ `if (len === 1) return ${renderMatch(verb, route, trie.path)};`
649
660
  );
650
661
  } else if (trie.static || dynamic) {
651
662
  writer.writeBlockStart(`if (len > 1) {`);
@@ -686,17 +697,15 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
686
697
  if (useSwitch) {
687
698
  writer.writeBlockStart(`switch (${value}) {`);
688
699
  }
689
- for (const { key, path: path5, route: route2 } of terminal) {
700
+ for (const { key, path: path7, route: route2 } of terminal) {
690
701
  const decodedKey = decodeURIComponent(key);
691
702
  if (useSwitch) {
692
703
  writer.write(`case '${decodedKey}': `, true);
693
704
  } else {
694
705
  writer.write(`if (${value} === '${decodedKey}') `, true);
695
706
  }
696
- writer.write(
697
- `return ${renderMatch(verb, route2, path5)}; // ${path5.path}
698
- `
699
- );
707
+ writer.write(`return ${renderMatch(verb, route2, path7)};
708
+ `);
700
709
  }
701
710
  if (useSwitch) {
702
711
  writer.writeBlockEnd("}");
@@ -708,7 +717,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
708
717
  verb,
709
718
  dynamic.route,
710
719
  dynamic.path
711
- )}; // ${dynamic.path.path}`
720
+ )};`
712
721
  );
713
722
  }
714
723
  }
@@ -768,7 +777,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
768
777
  catchAll.route,
769
778
  catchAll.path,
770
779
  String(offset)
771
- )}; // ${catchAll.path.path}`
780
+ )};`
772
781
  );
773
782
  } else if (level === 0) {
774
783
  writer.writeLines("return null;");
@@ -797,43 +806,47 @@ function renderParams(params, pathIndex) {
797
806
  }
798
807
  return result ? result + " }" : "{}";
799
808
  }
800
- function renderMatch(verb, route, path5, pathIndex) {
809
+ function renderMatch(verb, route, path7, pathIndex) {
801
810
  const handler = `${verb}${route.index}`;
802
- const params = path5.params ? renderParams(path5.params, pathIndex) : "{}";
811
+ const params = path7.params ? renderParams(path7.params, pathIndex) : "{}";
803
812
  const meta = route.meta ? `meta${route.index}` : "{}";
804
- const pathPattern = pathToURLPatternString(path5.path);
805
- return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
813
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path7.path}' }`;
806
814
  }
807
- function renderMiddleware(middleware) {
815
+ function renderMiddleware(middleware, rootDir) {
808
816
  const writer = createStringWriter();
809
817
  writer.writeLines(
810
818
  `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
811
819
  );
812
820
  const imports = writer.branch("imports");
813
821
  imports.writeLines(
814
- `import { normalize } from '${virtualFilePrefix}/runtime/internal';`
822
+ `import { normalize } from "${virtualFilePrefix}/runtime/internal";`
815
823
  );
816
824
  writer.writeLines("");
817
- for (const { id, importPath } of middleware) {
825
+ for (const { id, filePath } of middleware) {
818
826
  const importName = `middleware${id}`;
819
- imports.writeLines(`import ${importName} from './${importPath}';`);
827
+ imports.writeLines(
828
+ `import ${importName} from "${normalizedRelativePath(rootDir, filePath)}";`
829
+ );
820
830
  writer.writeLines(`export const mware${id} = normalize(${importName});`);
821
831
  }
822
832
  imports.join();
823
833
  return writer.end();
824
834
  }
825
- function stripTsExtension(path5) {
826
- const index = path5.lastIndexOf(".");
835
+ function stripTsExtension(path7) {
836
+ const index = path7.lastIndexOf(".");
827
837
  if (index !== -1) {
828
- const ext = path5.slice(index + 1);
838
+ const ext = path7.slice(index + 1);
829
839
  if (ext.toLowerCase() === "ts") {
830
- return path5.slice(0, index);
840
+ return path7.slice(0, index);
831
841
  }
832
842
  }
833
- return path5;
843
+ return path7;
834
844
  }
835
- async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
836
- var _a, _b;
845
+ function decodePath(path7) {
846
+ return path7;
847
+ }
848
+ async function renderRouteTypeInfo(routes, outDir, adapter) {
849
+ var _a, _b, _c, _d;
837
850
  const writer = createStringWriter();
838
851
  writer.writeLines(
839
852
  `/*
@@ -862,11 +875,31 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
862
875
  const routeTypes = /* @__PURE__ */ new Map();
863
876
  for (const route of routes.list) {
864
877
  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}"];`);
878
+ let routeDefinition = "";
879
+ if (route.page || route.handler) {
880
+ const verbs = [];
881
+ if (route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes("get"))) {
882
+ verbs.push(`"get"`);
883
+ }
884
+ if ((_d = (_c = route.handler) == null ? void 0 : _c.verbs) == null ? void 0 : _d.includes("post")) {
885
+ verbs.push(`"post"`);
886
+ }
887
+ routeDefinition = `{ verb: ${verbs.join(" | ")};`;
888
+ if (route.meta) {
889
+ const metaPath = stripTsExtension(
890
+ normalizedRelativePath(outDir, route.meta.filePath)
891
+ );
892
+ let metaType = `typeof import("${metaPath}")`;
893
+ if (/\.(ts|js|mjs)$/.test(route.meta.name)) {
894
+ metaType += `["default"]`;
895
+ }
896
+ routeDefinition += ` meta: ${metaType};`;
897
+ }
898
+ routeDefinition += " }";
869
899
  }
900
+ const pathType = `"${decodePath(route.path.path)}"`;
901
+ routeType += routeType ? " | " + pathType : pathType;
902
+ routesWriter.writeLines(`${pathType}: ${routeDefinition};`);
870
903
  for (const file of [route.handler, route.page]) {
871
904
  if (file) {
872
905
  const existing = routeTypes.get(file);
@@ -899,31 +932,33 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
899
932
  const pageWriter = writer.branch("page");
900
933
  const layoutWriter = writer.branch("layout");
901
934
  for (const [file, types] of routeTypes) {
902
- const path5 = `${pathPrefix}/${file.relativePath}`;
935
+ const modulePath = stripTsExtension(
936
+ normalizedRelativePath(outDir, file.filePath)
937
+ );
903
938
  const routeType = `Run.Routes[${types.join(" | ")}]`;
904
939
  switch (file.type) {
905
940
  case RoutableFileTypes.Handler:
906
- writeModuleDeclaration(handlerWriter, path5, routeType);
941
+ writeModuleDeclaration(handlerWriter, modulePath, routeType);
907
942
  break;
908
943
  case RoutableFileTypes.Middleware:
909
- writeModuleDeclaration(middlewareWriter, path5, routeType);
944
+ writeModuleDeclaration(middlewareWriter, modulePath, routeType);
910
945
  break;
911
946
  case RoutableFileTypes.Page:
912
- writeModuleDeclaration(pageWriter, path5, routeType);
947
+ writeModuleDeclaration(pageWriter, modulePath, routeType);
913
948
  break;
914
949
  case RoutableFileTypes.Layout:
915
950
  writeModuleDeclaration(
916
951
  layoutWriter,
917
- path5,
952
+ modulePath,
918
953
  routeType,
919
954
  `
920
- export interface Input extends Run.LayoutInput<typeof import('${path5}')> {}`
955
+ export interface Input extends Run.LayoutInput<typeof import("${modulePath}")> {}`
921
956
  );
922
957
  break;
923
958
  case RoutableFileTypes.Error:
924
959
  writeModuleDeclaration(
925
960
  writer,
926
- path5,
961
+ modulePath,
927
962
  "globalThis.MarkoRun.Route",
928
963
  `
929
964
  export interface Input {
@@ -932,7 +967,7 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
932
967
  );
933
968
  break;
934
969
  case RoutableFileTypes.NotFound:
935
- writeModuleDeclaration(writer, path5, "Run.Route");
970
+ writeModuleDeclaration(writer, modulePath, "Run.Route");
936
971
  break;
937
972
  }
938
973
  }
@@ -940,40 +975,15 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
940
975
  middlewareWriter.join();
941
976
  pageWriter.join();
942
977
  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
978
  return writer.end();
969
979
  }
970
- function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
971
- writer.writeLines("").write(`declare module "${stripTsExtension(path5)}" {`);
980
+ function writeModuleDeclaration(writer, name, routeType, moduleTypes) {
981
+ writer.writeLines("").write(`declare module "${name}" {`);
972
982
  if (moduleTypes) {
973
983
  writer.write(moduleTypes);
974
984
  }
975
985
  if (routeType) {
976
- const isMarko = path5.endsWith(".marko");
986
+ const isMarko = name.endsWith(".marko");
977
987
  writer.write(`
978
988
  namespace MarkoRun {
979
989
  export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
@@ -987,21 +997,15 @@ function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
987
997
  writer.writeLines(`
988
998
  }`);
989
999
  }
990
- function pathToURLPatternString(path5) {
991
- return path5.replace(/\/\$(\$?)([^/]*)/g, (_, catchAll, name) => {
992
- name = decodeURIComponent(name);
993
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
994
- });
995
- }
996
1000
  function createRouteTrie(routes) {
997
1001
  const root = {
998
1002
  key: ""
999
1003
  };
1000
- function insert(path5, route) {
1004
+ function insert(path7, route) {
1001
1005
  let node = root;
1002
- for (const segment of path5.segments) {
1006
+ for (const segment of path7.segments) {
1003
1007
  if (segment === "$$") {
1004
- node.catchAll ?? (node.catchAll = { route, path: path5 });
1008
+ node.catchAll ?? (node.catchAll = { route, path: path7 });
1005
1009
  return;
1006
1010
  } else if (segment === "$") {
1007
1011
  node = node.dynamic ?? (node.dynamic = {
@@ -1019,17 +1023,18 @@ function createRouteTrie(routes) {
1019
1023
  node = next;
1020
1024
  }
1021
1025
  }
1022
- node.path ?? (node.path = path5);
1026
+ node.path ?? (node.path = path7);
1023
1027
  node.route ?? (node.route = route);
1024
1028
  }
1025
1029
  for (const route of routes) {
1026
- for (const path5 of route.paths) {
1027
- insert(path5, route);
1028
- }
1030
+ insert(route.path, route);
1029
1031
  }
1030
1032
  return root;
1031
1033
  }
1032
1034
 
1035
+ // src/vite/routes/builder.ts
1036
+ var import_path3 = __toESM(require("path"), 1);
1037
+
1033
1038
  // src/vite/routes/parse.ts
1034
1039
  function parseFlatRoute(pattern) {
1035
1040
  if (!pattern) throw new Error("Empty pattern");
@@ -1044,53 +1049,72 @@ function parseFlatRoute(pattern) {
1044
1049
  ]);
1045
1050
  function parse2(basePaths, group) {
1046
1051
  const pathMap = /* @__PURE__ */ new Map();
1047
- const delimiters = group ? ").," : ".,";
1052
+ const delimiters = group ? "`).," : "`.,";
1048
1053
  let charCode;
1049
1054
  let segmentStart = i;
1050
1055
  let type;
1051
1056
  let current;
1057
+ let escaped = "";
1058
+ let escapeStart = 0;
1052
1059
  do {
1053
1060
  charCode = pattern.charCodeAt(i);
1054
- if (charCode === 41 && group) {
1061
+ if (charCode === 96 /* Escape */) {
1062
+ if (escapeStart) {
1063
+ escaped += pattern.slice(segmentStart, escapeStart - 1) + pattern.slice(escapeStart, i);
1064
+ escapeStart = 0;
1065
+ segmentStart = ++i;
1066
+ } else {
1067
+ escapeStart = i + 1;
1068
+ i = pattern.indexOf("`", escapeStart);
1069
+ if (i < 0) break;
1070
+ }
1071
+ } else if (charCode === 41 /* GroupEnd */ && group) {
1055
1072
  break;
1056
- } else if (charCode === 44) {
1073
+ } else if (charCode === 44 /* Alternate */) {
1057
1074
  if (!current) {
1058
1075
  segmentEnd(
1059
- basePaths.map((path5) => ({
1060
- ...path5,
1061
- segments: path5.segments.slice()
1076
+ basePaths.map((path7) => ({
1077
+ ...path7,
1078
+ segments: path7.segments.slice()
1062
1079
  })),
1063
- "",
1080
+ escaped,
1064
1081
  "_",
1065
1082
  pathMap
1066
1083
  );
1067
1084
  } else {
1068
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1085
+ segmentEnd(
1086
+ current,
1087
+ escaped + pattern.slice(segmentStart, i),
1088
+ type,
1089
+ pathMap
1090
+ );
1069
1091
  }
1070
1092
  current = void 0;
1071
1093
  type = void 0;
1094
+ escaped = "";
1072
1095
  segmentStart = ++i;
1073
- } else if (charCode === 46) {
1096
+ } else if (charCode === 46 /* Directory */) {
1074
1097
  if (current) {
1075
- segmentEnd(current, pattern.slice(segmentStart, i), type);
1098
+ segmentEnd(current, escaped + pattern.slice(segmentStart, i), type);
1076
1099
  }
1077
1100
  type = void 0;
1101
+ escaped = "";
1078
1102
  segmentStart = ++i;
1079
- } else if (charCode === 40) {
1103
+ } else if (charCode === 40 /* GroupStart */) {
1080
1104
  const groupPaths = parse2(current || basePaths, ++i);
1081
1105
  if (groupPaths.length) {
1082
1106
  current = groupPaths;
1083
1107
  }
1084
1108
  segmentStart = ++i;
1085
1109
  } else {
1086
- if (charCode === 95) {
1110
+ if (charCode === 95 /* Pathless */) {
1087
1111
  type = "_";
1088
- } else if (charCode === 36) {
1112
+ } else if (charCode === 36 /* Dynamic */) {
1089
1113
  type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
1090
1114
  }
1091
- current ?? (current = basePaths.map((path5) => ({
1092
- ...path5,
1093
- segments: path5.segments.slice()
1115
+ current ?? (current = basePaths.map((path7) => ({
1116
+ ...path7,
1117
+ segments: path7.segments.slice()
1094
1118
  })));
1095
1119
  i = len;
1096
1120
  for (const char of delimiters) {
@@ -1101,7 +1125,12 @@ function parseFlatRoute(pattern) {
1101
1125
  }
1102
1126
  }
1103
1127
  } while (i < len);
1104
- if (group && charCode !== 41) {
1128
+ if (escapeStart) {
1129
+ throw new Error(
1130
+ `Invalid route pattern: unclosed escape '${pattern.slice(escapeStart)}' in '${pattern}'`
1131
+ );
1132
+ }
1133
+ if (group && charCode !== 41 /* GroupEnd */) {
1105
1134
  throw new Error(
1106
1135
  `Invalid route pattern: group was not closed '${pattern.slice(
1107
1136
  group
@@ -1110,16 +1139,21 @@ function parseFlatRoute(pattern) {
1110
1139
  }
1111
1140
  if (!current) {
1112
1141
  segmentEnd(
1113
- basePaths.map((path5) => ({
1114
- ...path5,
1115
- segments: path5.segments.slice()
1142
+ basePaths.map((path7) => ({
1143
+ ...path7,
1144
+ segments: path7.segments.slice()
1116
1145
  })),
1117
- "",
1118
- "_",
1146
+ escaped,
1147
+ void 0,
1119
1148
  pathMap
1120
1149
  );
1121
1150
  } else {
1122
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1151
+ segmentEnd(
1152
+ current,
1153
+ escaped + pattern.slice(segmentStart, i),
1154
+ type,
1155
+ pathMap
1156
+ );
1123
1157
  }
1124
1158
  return [...pathMap.values()];
1125
1159
  }
@@ -1128,17 +1162,17 @@ function parseFlatRoute(pattern) {
1128
1162
  if (raw) {
1129
1163
  segment = {
1130
1164
  raw,
1131
- name: raw,
1165
+ name: normalizeSegment(raw),
1132
1166
  type
1133
1167
  };
1134
1168
  if (type === "$" || type === "$$") {
1135
1169
  segment.name = type;
1136
- segment.param = raw.slice(type.length);
1170
+ segment.param = normalizeParam(raw.slice(type.length));
1137
1171
  }
1138
1172
  }
1139
- for (const path5 of paths) {
1173
+ for (const path7 of paths) {
1140
1174
  if (segment) {
1141
- if (path5.isCatchall) {
1175
+ if (path7.isCatchall) {
1142
1176
  throw new Error(
1143
1177
  `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
1144
1178
  0,
@@ -1146,26 +1180,36 @@ function parseFlatRoute(pattern) {
1146
1180
  )}' in '${pattern}'.`
1147
1181
  );
1148
1182
  }
1149
- path5.segments.push(segment);
1150
- path5.id += path5.id === "/" ? segment.name : `/${segment.name}`;
1183
+ path7.segments.push(segment);
1184
+ path7.id += path7.id === "/" ? segment.name : `/${segment.name}`;
1151
1185
  if (type === "$$") {
1152
- path5.isCatchall = true;
1186
+ path7.isCatchall = true;
1153
1187
  }
1154
1188
  }
1155
1189
  if (map) {
1156
- if (map.has(path5.id)) {
1157
- const existing = map.get(path5.id);
1190
+ if (map.has(path7.id)) {
1191
+ const existing = map.get(path7.id);
1158
1192
  const existingExpansion = existing.segments.map((s) => s.raw).join(".");
1159
- const currentExpansion = path5.segments.map((s) => s.raw).join(".");
1193
+ const currentExpansion = path7.segments.map((s) => s.raw).join(".");
1160
1194
  throw new Error(
1161
- `Invalid route pattern: route '${path5.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1195
+ `Invalid route pattern: route '${path7.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1162
1196
  );
1163
1197
  }
1164
- map.set(path5.id, path5);
1198
+ map.set(path7.id, path7);
1165
1199
  }
1166
1200
  }
1167
1201
  }
1168
1202
  }
1203
+ function normalizeParam(segment) {
1204
+ const normalized = normalizeSegment(segment);
1205
+ return /^\$/.test(normalized) ? `\`${normalized}\`` : normalized;
1206
+ }
1207
+ function normalizeSegment(segment) {
1208
+ return decodeURIComponent(segment).replace(
1209
+ /[/?#]/g,
1210
+ (char) => "%" + char.charCodeAt(0).toString(16)
1211
+ );
1212
+ }
1169
1213
 
1170
1214
  // src/vite/routes/vdir.ts
1171
1215
  var _dirs, _pathlessDirs;
@@ -1176,14 +1220,12 @@ var _VDir = class _VDir {
1176
1220
  __publicField(this, "parent");
1177
1221
  __publicField(this, "source");
1178
1222
  __publicField(this, "path");
1179
- __publicField(this, "fullPath");
1180
1223
  __publicField(this, "segment");
1181
1224
  __publicField(this, "files");
1182
1225
  if (!parent || !segment) {
1183
1226
  this.parent = null;
1184
1227
  this.source = null;
1185
1228
  this.path = "/";
1186
- this.fullPath = "/";
1187
1229
  this.segment = {
1188
1230
  raw: "",
1189
1231
  name: ""
@@ -1191,12 +1233,8 @@ var _VDir = class _VDir {
1191
1233
  } else {
1192
1234
  this.parent = parent;
1193
1235
  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
1236
  this.segment = segment;
1237
+ this.path = parent.path + (parent.path === "/" ? "" : "/") + segment.name;
1200
1238
  }
1201
1239
  }
1202
1240
  get pathInfo() {
@@ -1209,17 +1247,18 @@ var _VDir = class _VDir {
1209
1247
  for (const { segment } of this) {
1210
1248
  const { type, name, param } = segment;
1211
1249
  if (name && type !== "_") {
1212
- value.id += sep + (type || name);
1250
+ value.id += sep + name;
1213
1251
  value.path += sep + name;
1214
1252
  value.isEnd = type === "$$";
1215
1253
  if (param) {
1216
- value.path += param;
1254
+ const unescapedParam = param.charAt(0) === "`" ? param.slice(1, -1) : param;
1217
1255
  const index = type === "$$" ? null : value.segments.length;
1218
1256
  if (!value.params) {
1219
- value.params = { [param]: index };
1257
+ value.params = { [unescapedParam]: index };
1220
1258
  } else if (!(param in value.params)) {
1221
- value.params[param] = index;
1259
+ value.params[unescapedParam] = index;
1222
1260
  }
1261
+ value.path += param;
1223
1262
  }
1224
1263
  value.segments.push(name);
1225
1264
  sep = "/";
@@ -1231,11 +1270,11 @@ var _VDir = class _VDir {
1231
1270
  });
1232
1271
  return value;
1233
1272
  }
1234
- addDir(path5, segment) {
1273
+ addDir(path7, segment) {
1235
1274
  const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
1236
1275
  const key = segment.type === "$" ? segment.raw : segment.name;
1237
1276
  if (!map.has(key)) {
1238
- const dir = new _VDir(this, segment, path5);
1277
+ const dir = new _VDir(this, segment, path7);
1239
1278
  map.set(key, dir);
1240
1279
  return dir;
1241
1280
  }
@@ -1251,15 +1290,15 @@ var _VDir = class _VDir {
1251
1290
  const existing = this.files.get(file.type);
1252
1291
  if (existing !== file) {
1253
1292
  throw new Error(
1254
- `Duplicate file type '${file.type}' added at path '${this.path}'. File '${file.importPath}' collides with '${existing.importPath}'.`
1293
+ `Duplicate file type ${file.type} added at path ${this.path}. File ${file.filePath} collides with ${existing.filePath}.`
1255
1294
  );
1256
1295
  } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
1257
1296
  throw new Error(
1258
- `Ambiguous path definition: route '${this.path}' is defined multiple times by ${file.importPath}`
1297
+ `Ambiguous path definition: route ${this.path} is defined multiple times by ${file.filePath}`
1259
1298
  );
1260
1299
  }
1261
1300
  throw new Error(
1262
- `Ambiguous path definition: file '${this.path}' is included multiple times by ${file.importPath}`
1301
+ `Ambiguous path definition: file ${this.path} is included multiple times by ${file.filePath}`
1263
1302
  );
1264
1303
  }
1265
1304
  }
@@ -1281,10 +1320,10 @@ var _VDir = class _VDir {
1281
1320
  const dirs = [];
1282
1321
  const unique = /* @__PURE__ */ new Map();
1283
1322
  for (const root of roots) {
1284
- for (const path5 of paths) {
1323
+ for (const path7 of paths) {
1285
1324
  let dir = root;
1286
- for (const segment of path5.segments) {
1287
- dir = dir.addDir(path5, segment);
1325
+ for (const segment of path7.segments) {
1326
+ dir = dir.addDir(path7, segment);
1288
1327
  }
1289
1328
  const existing = unique.get(dir.path);
1290
1329
  if (existing) {
@@ -1297,7 +1336,7 @@ var _VDir = class _VDir {
1297
1336
  }
1298
1337
  }
1299
1338
  throw new Error(
1300
- `Ambiguous directory structure: '${sourcePath}${path5.source}' defines '${dir.path}' multiple times.`
1339
+ `Ambiguous directory structure: ${sourcePath}${path7.source} defines ${dir.path} multiple times.`
1301
1340
  );
1302
1341
  } else {
1303
1342
  unique.set(dir.path, dir);
@@ -1323,13 +1362,11 @@ function matchRoutableFile(filename) {
1323
1362
  const match = filename.match(routeableFileRegex);
1324
1363
  return match && (match[1] || match[3]).toLowerCase();
1325
1364
  }
1326
- function isSpecialType(type) {
1327
- return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
1328
- }
1329
- async function buildRoutes(sources) {
1365
+ async function buildRoutes(sources, outDir) {
1330
1366
  const uniqueRoutes = /* @__PURE__ */ new Map();
1331
1367
  const routes = [];
1332
1368
  const special = {};
1369
+ const seenKeys = /* @__PURE__ */ new Map();
1333
1370
  const middlewares = /* @__PURE__ */ new Set();
1334
1371
  const unusedFiles = /* @__PURE__ */ new Set();
1335
1372
  const currentLayouts = /* @__PURE__ */ new Set();
@@ -1337,13 +1374,13 @@ async function buildRoutes(sources) {
1337
1374
  const root = new VDir();
1338
1375
  const dirStack = [];
1339
1376
  let basePath;
1340
- let importPrefix;
1341
1377
  let activeDirs;
1342
1378
  let isBaseDir;
1343
1379
  let nextFileId = 1;
1344
1380
  let nextRouteIndex = 1;
1345
1381
  const walkOptions = {
1346
- onEnter({ name }) {
1382
+ onEnter(dir) {
1383
+ let { name } = dir;
1347
1384
  const prevDirStackLength = dirStack.length;
1348
1385
  if (isBaseDir) {
1349
1386
  isBaseDir = false;
@@ -1362,15 +1399,16 @@ async function buildRoutes(sources) {
1362
1399
  dirStack.length = prevDirStackLength;
1363
1400
  };
1364
1401
  },
1365
- onFile({ name, path: path5 }) {
1402
+ onFile(file) {
1403
+ const { name } = file;
1366
1404
  const match = name.match(routeableFileRegex);
1367
1405
  if (!match) {
1368
1406
  return;
1369
1407
  }
1370
1408
  const type = (match[1] || match[3]).toLowerCase();
1371
- if (dirStack.length && isSpecialType(type)) {
1409
+ if (dirStack.length && (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error)) {
1372
1410
  console.warn(
1373
- `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path5}`
1411
+ `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${file.path}`
1374
1412
  );
1375
1413
  return;
1376
1414
  }
@@ -1379,19 +1417,15 @@ async function buildRoutes(sources) {
1379
1417
  const paths = parseFlatRoute(name.slice(0, match.index));
1380
1418
  dirs = VDir.addPaths(activeDirs, paths);
1381
1419
  }
1382
- const dirPath = dirStack.join("/");
1383
- const relativePath = dirPath ? `${dirPath}/${name}` : name;
1384
- const file = {
1420
+ const routableFile = {
1385
1421
  id: String(nextFileId++),
1386
1422
  name,
1387
1423
  type,
1388
- filePath: path5,
1389
- relativePath,
1390
- importPath: `${importPrefix}/${relativePath}`,
1424
+ filePath: file.path,
1391
1425
  verbs: type === RoutableFileTypes.Page ? ["get", "head"] : void 0
1392
1426
  };
1393
1427
  for (const dir of dirs) {
1394
- dir.addFile(file);
1428
+ dir.addFile(routableFile);
1395
1429
  }
1396
1430
  }
1397
1431
  };
@@ -1399,7 +1433,6 @@ async function buildRoutes(sources) {
1399
1433
  sources = [sources];
1400
1434
  }
1401
1435
  for (const source of sources) {
1402
- importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
1403
1436
  basePath = source.basePath || "";
1404
1437
  activeDirs = [root];
1405
1438
  isBaseDir = true;
@@ -1419,7 +1452,8 @@ async function buildRoutes(sources) {
1419
1452
  layout = dir.files.get(RoutableFileTypes.Layout);
1420
1453
  const handler = dir.files.get(RoutableFileTypes.Handler);
1421
1454
  const page = dir.files.get(RoutableFileTypes.Page);
1422
- let hasSpecial = false;
1455
+ const pathInfo = dir.pathInfo;
1456
+ let layoutsUsed = false;
1423
1457
  if (middleware) {
1424
1458
  if (currentMiddleware.has(middleware)) {
1425
1459
  middleware = void 0;
@@ -1436,64 +1470,64 @@ async function buildRoutes(sources) {
1436
1470
  unusedFiles.add(layout);
1437
1471
  }
1438
1472
  }
1473
+ if (dir === root) {
1474
+ for (const [type, file] of dir.files) {
1475
+ if (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error) {
1476
+ special[type] = {
1477
+ index: nextRouteIndex++,
1478
+ key: type,
1479
+ path: dir.pathInfo,
1480
+ middleware: [],
1481
+ layouts: [...currentLayouts],
1482
+ page: file,
1483
+ templateFilePath: currentLayouts.size ? import_path3.default.join(outDir, `${type}.marko`) : void 0
1484
+ };
1485
+ layoutsUsed = true;
1486
+ }
1487
+ }
1488
+ }
1439
1489
  if (page || handler) {
1440
- const path5 = dir.pathInfo;
1441
- if (uniqueRoutes.has(path5.id)) {
1442
- const existing = uniqueRoutes.get(path5.id);
1490
+ if (uniqueRoutes.has(pathInfo.id)) {
1491
+ const existing = uniqueRoutes.get(pathInfo.id);
1443
1492
  const route = routes[existing.index];
1444
1493
  const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
1445
1494
  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
- `);
1495
+ throw new Error(
1496
+ `Duplicate routes for path ${pathInfo.id} were defined. A route established by: "${existingFiles.join(" and ")}" collides with "${currentFiles.join(" and ")}"`
1497
+ );
1498
+ }
1499
+ uniqueRoutes.set(pathInfo.id, { dir, index: routes.length });
1500
+ let key = pathInfo.segments.map(replaceInvalidFilenameChars).concat("route").join("/");
1501
+ const keyCount = (seenKeys.get(key) || 0) + 1;
1502
+ seenKeys.set(key, keyCount);
1503
+ if (keyCount > 1) {
1504
+ key += keyCount;
1451
1505
  }
1452
- uniqueRoutes.set(path5.id, { dir, index: routes.length });
1453
1506
  routes.push({
1454
1507
  index: nextRouteIndex++,
1455
- key: dir.fullPath,
1456
- paths: [path5],
1508
+ key,
1509
+ path: pathInfo,
1457
1510
  middleware: [...currentMiddleware],
1458
1511
  layouts: page ? [...currentLayouts] : [],
1459
1512
  meta: dir.files.get(RoutableFileTypes.Meta),
1460
1513
  page,
1461
1514
  handler,
1462
- entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
1515
+ templateFilePath: page && currentLayouts.size ? import_path3.default.join(outDir, key + ".marko") : void 0
1463
1516
  });
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) {
1517
+ layoutsUsed = !!page;
1482
1518
  for (const middleware2 of currentMiddleware) {
1483
1519
  middlewares.add(middleware2);
1484
1520
  unusedFiles.delete(middleware2);
1485
1521
  }
1486
1522
  }
1487
- if (page || hasSpecial) {
1523
+ if (layoutsUsed) {
1488
1524
  for (const layout2 of currentLayouts) {
1489
1525
  unusedFiles.delete(layout2);
1490
1526
  }
1491
1527
  }
1492
1528
  }
1493
- if (dir.dirs) {
1494
- for (const child of dir.dirs()) {
1495
- traverse(child);
1496
- }
1529
+ for (const childDir of dir.dirs()) {
1530
+ traverse(childDir);
1497
1531
  }
1498
1532
  if (middleware) {
1499
1533
  currentMiddleware.delete(middleware);
@@ -1503,10 +1537,13 @@ async function buildRoutes(sources) {
1503
1537
  }
1504
1538
  }
1505
1539
  }
1540
+ function replaceInvalidFilenameChars(str) {
1541
+ return str.replace(/[<>:"/\\|?*]+/g, "_");
1542
+ }
1506
1543
 
1507
1544
  // src/vite/routes/walk.ts
1508
- var import_fs = __toESM(require("fs"), 1);
1509
- var import_path2 = __toESM(require("path"), 1);
1545
+ var import_fs2 = __toESM(require("fs"), 1);
1546
+ var import_path4 = __toESM(require("path"), 1);
1510
1547
  function createFSWalker(dir) {
1511
1548
  return async function walkFS({
1512
1549
  onEnter,
@@ -1518,10 +1555,10 @@ function createFSWalker(dir) {
1518
1555
  const onExit = onEnter == null ? void 0 : onEnter(dir2);
1519
1556
  if (onExit !== false) {
1520
1557
  const dirs = [];
1521
- const entries = await import_fs.default.promises.readdir(dir2.path, {
1558
+ const entries = await import_fs2.default.promises.readdir(dir2.path, {
1522
1559
  withFileTypes: true
1523
1560
  });
1524
- const prefix = dir2.path + import_path2.default.sep;
1561
+ const prefix = dir2.path + import_path4.default.sep;
1525
1562
  for (const entry of entries) {
1526
1563
  const walkEntry = {
1527
1564
  name: entry.name,
@@ -1544,7 +1581,7 @@ function createFSWalker(dir) {
1544
1581
  await walk(
1545
1582
  {
1546
1583
  path: dir,
1547
- name: import_path2.default.basename(dir)
1584
+ name: import_path4.default.basename(dir)
1548
1585
  },
1549
1586
  maxDepth
1550
1587
  );
@@ -1661,36 +1698,34 @@ function logRoutesTable(routes, bundle) {
1661
1698
  style: { compact: true }
1662
1699
  });
1663
1700
  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;
1701
+ const verbs = getVerbs(route, true);
1702
+ let firstRow = true;
1703
+ for (const verb of verbs) {
1704
+ const entryType = [];
1705
+ let size = "";
1706
+ const verbCell = verbColor(verb)(verb.toUpperCase());
1707
+ if (route.handler) {
1708
+ entryType.push(import_kleur2.default.blue("handler"));
1709
+ }
1710
+ if (route.page && (verb === "get" || verb === "head")) {
1711
+ entryType.push(import_kleur2.default.yellow("page"));
1712
+ if (verb === "get") {
1713
+ size = prettySize(computeRouteSize(route, bundle));
1687
1714
  }
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
1715
  }
1716
+ const row = [verbCell];
1717
+ if (verbs.length === 1 || firstRow) {
1718
+ row.push({
1719
+ rowSpan: verbs.length,
1720
+ content: prettyPath(route.path.path)
1721
+ });
1722
+ firstRow = false;
1723
+ }
1724
+ row.push(entryType.join(" -> "));
1725
+ hasMiddleware && row.push(route.middleware.length || "");
1726
+ hasMeta && row.push(route.meta ? "\u2713" : "");
1727
+ row.push(size || "");
1728
+ table.push(row);
1694
1729
  }
1695
1730
  }
1696
1731
  for (const [key, route] of Object.entries(routes.special).sort()) {
@@ -1700,6 +1735,15 @@ function logRoutesTable(routes, bundle) {
1700
1735
  row.push(prettySize(computeRouteSize(route, bundle)));
1701
1736
  table.push(row);
1702
1737
  }
1738
+ if (!table.length) {
1739
+ table.push([
1740
+ {
1741
+ colSpan: 4,
1742
+ hAlign: "center",
1743
+ content: import_kleur2.default.dim(import_kleur2.default.white("No routes found"))
1744
+ }
1745
+ ]);
1746
+ }
1703
1747
  console.log(table.toString());
1704
1748
  }
1705
1749
  function computeRouteSize(route, bundle) {
@@ -1746,17 +1790,20 @@ function prettySize([bytes, compBytes]) {
1746
1790
  else str += import_kleur2.default.bold(import_kleur2.default.red(compSize));
1747
1791
  return str;
1748
1792
  }
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}`)));
1793
+ function prettyPath(path7) {
1794
+ return path7.replace(
1795
+ /\/(\$\$?)(`?)([^/`]+)\2/g,
1796
+ (_, type, tick, key) => "/" + type + tick + import_kleur2.default.bold(import_kleur2.default.dim(key)) + tick
1797
+ );
1751
1798
  }
1752
1799
 
1753
1800
  // src/vite/utils/read-once-persisted-store.ts
1754
- var import_fs2 = require("fs");
1801
+ var import_fs3 = require("fs");
1755
1802
  var import_os = __toESM(require("os"), 1);
1756
- var import_path3 = __toESM(require("path"), 1);
1803
+ var import_path5 = __toESM(require("path"), 1);
1757
1804
  var noop = () => {
1758
1805
  };
1759
- var tmpFile = import_path3.default.join(import_os.default.tmpdir(), "marko-run-storage.json");
1806
+ var tmpFile = import_path5.default.join(import_os.default.tmpdir(), "marko-run-storage.json");
1760
1807
  var values = /* @__PURE__ */ new Map();
1761
1808
  var loadedFromDisk;
1762
1809
  var ReadOncePersistedStore = class {
@@ -1776,13 +1823,13 @@ var ReadOncePersistedStore = class {
1776
1823
  if (loadedFromDisk === true) {
1777
1824
  throw new Error(`Value for ${uid} could not be loaded.`);
1778
1825
  }
1779
- await (loadedFromDisk || (loadedFromDisk = import_fs2.promises.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1826
+ await (loadedFromDisk || (loadedFromDisk = import_fs3.promises.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1780
1827
  return this.read();
1781
1828
  }
1782
1829
  };
1783
1830
  function syncDataFromDisk(data) {
1784
1831
  finishLoadFromDisk();
1785
- import_fs2.promises.unlink(tmpFile).catch(noop);
1832
+ import_fs3.promises.unlink(tmpFile).catch(noop);
1786
1833
  for (const [k, v] of JSON.parse(data)) {
1787
1834
  values.set(k, v);
1788
1835
  }
@@ -1792,23 +1839,23 @@ function finishLoadFromDisk() {
1792
1839
  }
1793
1840
  process.once("beforeExit", (code) => {
1794
1841
  if (code === 0 && values.size) {
1795
- import_fs2.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1842
+ import_fs3.promises.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1796
1843
  }
1797
1844
  });
1798
1845
 
1799
1846
  // src/vite/plugin.ts
1800
1847
  var debug = (0, import_debug.default)("@marko/run");
1801
- var __dirname = import_path4.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1848
+ var __dirname = import_path6.default.dirname((0, import_url2.fileURLToPath)(__importMetaURL));
1802
1849
  var PLUGIN_NAME_PREFIX = "marko-run-vite";
1803
- var POSIX_SEP = "/";
1804
- var WINDOWS_SEP = "\\";
1805
1850
  var CLIENT_OUT_DIR = "public";
1806
1851
  var MIDDLEWARE_FILENAME = `${markoRunFilePrefix}middleware.js`;
1807
1852
  var ROUTER_FILENAME = `${markoRunFilePrefix}router.js`;
1808
1853
  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
1854
  function markoRun(opts = {}) {
1811
- let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1855
+ let routesDir;
1856
+ let adapter;
1857
+ let trailingSlashes;
1858
+ const { ...markoVitePluginOptions } = opts;
1812
1859
  let store;
1813
1860
  let root;
1814
1861
  let shouldEmptyOutDir = false;
@@ -1841,15 +1888,11 @@ function markoRun(opts = {}) {
1841
1888
  root,
1842
1889
  "{.tsconfig*,tsconfig*.json}"
1843
1890
  )))) {
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)) {
1891
+ const filepath = import_path6.default.join(typesDir, "routes.d.ts");
1892
+ const data = await renderRouteTypeInfo(routes2, typesDir, adapter);
1893
+ if (data !== typesFile || !import_fs4.default.existsSync(filepath)) {
1851
1894
  await ensureDir(typesDir);
1852
- await import_fs3.default.promises.writeFile(filepath, typesFile = data);
1895
+ await import_fs4.default.promises.writeFile(filepath, typesFile = data);
1853
1896
  }
1854
1897
  }
1855
1898
  }
@@ -1857,20 +1900,36 @@ function markoRun(opts = {}) {
1857
1900
  function buildVirtualFiles() {
1858
1901
  return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1859
1902
  virtualFiles.clear();
1860
- routes = await buildRoutes({
1861
- walker: createFSWalker(resolvedRoutesDir),
1862
- importPrefix: routesDir
1863
- });
1864
- if (!routes.list.length) {
1865
- throw new Error("No routes generated");
1903
+ if (import_fs4.default.existsSync(resolvedRoutesDir)) {
1904
+ routes = await buildRoutes(
1905
+ {
1906
+ walker: createFSWalker(resolvedRoutesDir)
1907
+ },
1908
+ entryFilesDir
1909
+ );
1910
+ if (!isBuild && !routes.list.length && !Object.keys(routes.special).length) {
1911
+ console.warn(`No routes found in ${resolvedRoutesDir}`);
1912
+ }
1913
+ } else {
1914
+ routes = {
1915
+ list: [],
1916
+ special: {},
1917
+ middleware: []
1918
+ };
1919
+ if (!isBuild) {
1920
+ console.warn(`Routes directory ${resolvedRoutesDir} does not exist`);
1921
+ }
1866
1922
  }
1867
1923
  for (const route of routes.list) {
1868
- virtualFiles.set(import_path4.default.posix.join(root, `${route.entryName}.js`), "");
1924
+ virtualFiles.set(
1925
+ import_path6.default.posix.join(root, getRouteVirtualFileName(route)),
1926
+ ""
1927
+ );
1869
1928
  }
1870
1929
  if (routes.middleware.length) {
1871
- virtualFiles.set(import_path4.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1930
+ virtualFiles.set(import_path6.default.posix.join(root, MIDDLEWARE_FILENAME), "");
1872
1931
  }
1873
- virtualFiles.set(import_path4.default.posix.join(root, ROUTER_FILENAME), "");
1932
+ virtualFiles.set(import_path6.default.posix.join(root, ROUTER_FILENAME), "");
1874
1933
  return routes;
1875
1934
  })());
1876
1935
  }
@@ -1880,115 +1939,82 @@ function markoRun(opts = {}) {
1880
1939
  var _a;
1881
1940
  try {
1882
1941
  const routes2 = await buildVirtualFiles();
1883
- if (import_fs3.default.existsSync(entryFilesDir)) {
1884
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
1942
+ if (import_fs4.default.existsSync(entryFilesDir)) {
1943
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
1885
1944
  }
1886
1945
  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 = [];
1946
+ if (route.handler) {
1947
+ const exports2 = await getExportsFromFile(
1948
+ context,
1949
+ route.handler.filePath
1950
+ );
1951
+ route.handler.verbs = [];
1891
1952
  for (const name of exports2) {
1892
1953
  const verb = name.toLowerCase();
1893
1954
  if (name === verb.toUpperCase() && httpVerbs.includes(verb)) {
1894
- handler.verbs.push(verb);
1955
+ route.handler.verbs.push(verb);
1895
1956
  }
1896
1957
  }
1897
- if (!handler.verbs.length) {
1958
+ if (!route.handler.verbs.length) {
1898
1959
  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(", ")}`
1960
+ `Did not find any http verb exports in ${import_path6.default.relative(root, route.handler.filePath)} - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1900
1961
  );
1901
1962
  }
1902
1963
  }
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
- }
1964
+ if (route.templateFilePath) {
1965
+ import_fs4.default.mkdirSync(import_path6.default.dirname(route.templateFilePath), {
1966
+ recursive: true
1967
+ });
1968
+ import_fs4.default.writeFileSync(
1969
+ route.templateFilePath,
1970
+ renderRouteTemplate(route, root)
1971
+ );
1929
1972
  }
1930
1973
  virtualFiles.set(
1931
- import_path4.default.posix.join(root, `${route.entryName}.js`),
1932
- renderRouteEntry(route, relativeEntryFilesDirPosix)
1974
+ import_path6.default.posix.join(root, getRouteVirtualFileName(route)),
1975
+ renderRouteEntry(route, root)
1933
1976
  );
1934
1977
  }
1935
1978
  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
- }
1979
+ if (route.templateFilePath) {
1980
+ import_fs4.default.mkdirSync(import_path6.default.dirname(route.templateFilePath), {
1981
+ recursive: true
1982
+ });
1983
+ import_fs4.default.writeFileSync(
1984
+ route.templateFilePath,
1985
+ renderRouteTemplate(route, root)
1986
+ );
1961
1987
  }
1962
1988
  }
1963
1989
  if (routes2.middleware.length) {
1964
1990
  for (const middleware of routes2.middleware) {
1965
1991
  if (!(await getExportsFromFile(context, middleware.filePath)).includes("default")) {
1966
1992
  context.warn(
1967
- `Did not find a default export in middleware '${import_path4.default.relative(root, middleware.filePath)}'`
1993
+ `Did not find a default export in middleware '${import_path6.default.relative(root, middleware.filePath)}'`
1968
1994
  );
1969
1995
  }
1970
1996
  }
1971
1997
  virtualFiles.set(
1972
- import_path4.default.posix.join(root, MIDDLEWARE_FILENAME),
1973
- renderMiddleware(routes2.middleware)
1998
+ import_path6.default.posix.join(root, MIDDLEWARE_FILENAME),
1999
+ renderMiddleware(routes2.middleware, root)
1974
2000
  );
1975
2001
  }
1976
2002
  virtualFiles.set(
1977
- import_path4.default.posix.join(root, ROUTER_FILENAME),
1978
- renderRouter(routes2, relativeEntryFilesDirPosix, {
1979
- trailingSlashes: opts.trailingSlashes || "RedirectWithout"
2003
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
2004
+ renderRouter(routes2, root, {
2005
+ trailingSlashes
1980
2006
  })
1981
2007
  );
1982
2008
  await writeTypesFile(routes2);
1983
2009
  if (adapter == null ? void 0 : adapter.routesGenerated) {
1984
- await adapter.routesGenerated(
1985
- routes2,
1986
- new Map(virtualFiles.entries()),
1987
- {
2010
+ await adapter.routesGenerated({
2011
+ routes: routes2,
2012
+ virtualFiles: new Map(virtualFiles.entries()),
2013
+ meta: {
1988
2014
  buildTime: times.routesBuild,
1989
2015
  renderTime: times.routesRender
1990
2016
  }
1991
- );
2017
+ });
1992
2018
  if (!isBuild) {
1993
2019
  await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes2.list));
1994
2020
  }
@@ -1998,7 +2024,7 @@ function markoRun(opts = {}) {
1998
2024
  throw err;
1999
2025
  }
2000
2026
  virtualFiles.set(
2001
- import_path4.default.posix.join(root, ROUTER_FILENAME),
2027
+ import_path6.default.posix.join(root, ROUTER_FILENAME),
2002
2028
  `throw ${JSON.stringify(prepareError(err))}`
2003
2029
  );
2004
2030
  }
@@ -2035,27 +2061,28 @@ function markoRun(opts = {}) {
2035
2061
  }
2036
2062
  }
2037
2063
  routesDir = opts.routesDir || "src/routes";
2064
+ trailingSlashes = opts.trailingSlashes || "RedirectWithout";
2038
2065
  store = new ReadOncePersistedStore(
2039
2066
  `vite-marko-run${opts.runtimeId ? `-${opts.runtimeId}` : ""}`
2040
2067
  );
2041
2068
  markoVitePluginOptions.runtimeId = opts.runtimeId;
2042
2069
  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");
2070
+ resolvedRoutesDir = import_path6.default.resolve(root, routesDir);
2071
+ outputDir = import_path6.default.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2072
+ entryFilesDir = import_path6.default.join(outputDir, ".marko-run");
2046
2073
  entryFilesDirPosix = normalizePath(entryFilesDir);
2047
2074
  relativeEntryFilesDirPosix = normalizePath(
2048
- import_path4.default.relative(root, entryFilesDir)
2075
+ import_path6.default.relative(root, entryFilesDir)
2049
2076
  );
2050
- typesDir = import_path4.default.join(root, ".marko-run");
2051
- devEntryFile = import_path4.default.join(root, "index.html");
2077
+ typesDir = import_path6.default.join(root, ".marko-run");
2078
+ devEntryFile = import_path6.default.join(root, "index.html");
2052
2079
  devEntryFilePosix = normalizePath(devEntryFile);
2053
2080
  let outDir = ((_e = config2.build) == null ? void 0 : _e.outDir) || "dist";
2054
2081
  const assetsDir = ((_f = config2.build) == null ? void 0 : _f.assetsDir) || "assets";
2055
2082
  let rollupOutputOptions = (_h = (_g = config2.build) == null ? void 0 : _g.rollupOptions) == null ? void 0 : _h.output;
2056
2083
  if (isBuild) {
2057
2084
  if (!isSSRBuild) {
2058
- outDir = import_path4.default.join(outDir, CLIENT_OUT_DIR);
2085
+ outDir = import_path6.default.join(outDir, CLIENT_OUT_DIR);
2059
2086
  }
2060
2087
  const defaultRollupOutputOptions = {
2061
2088
  assetFileNames({ name }) {
@@ -2180,7 +2207,7 @@ function markoRun(opts = {}) {
2180
2207
  devServer.watcher.on("all", async (type, filename) => {
2181
2208
  seenErrors.clear();
2182
2209
  const routableFileType = matchRoutableFile(
2183
- import_path4.default.parse(filename).base
2210
+ import_path6.default.parse(filename).base
2184
2211
  );
2185
2212
  if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
2186
2213
  if (type === "add" || type === "unlink" || type === "change" && (routableFileType === RoutableFileTypes.Handler || routableFileType === RoutableFileTypes.Middleware)) {
@@ -2205,8 +2232,8 @@ function markoRun(opts = {}) {
2205
2232
  },
2206
2233
  async buildStart(_options) {
2207
2234
  if (isSSRBuild && shouldEmptyOutDir) {
2208
- if (import_fs3.default.existsSync(outputDir)) {
2209
- import_fs3.default.rmSync(outputDir, { recursive: true });
2235
+ if (import_fs4.default.existsSync(outputDir)) {
2236
+ import_fs4.default.rmSync(outputDir, { recursive: true });
2210
2237
  }
2211
2238
  }
2212
2239
  if (isBuild && !isSSRBuild) {
@@ -2230,19 +2257,19 @@ function markoRun(opts = {}) {
2230
2257
  },
2231
2258
  async resolveId(importee, importer) {
2232
2259
  if (importee === "@marko/run/router") {
2233
- return normalizePath(import_path4.default.resolve(root, ROUTER_FILENAME));
2260
+ return normalizePath(import_path6.default.resolve(root, ROUTER_FILENAME));
2234
2261
  } else if (importee.endsWith(".marko") && importee.includes(relativeEntryFilesDirPosix)) {
2235
2262
  if (!importee.startsWith(root)) {
2236
- importee = import_path4.default.resolve(root, "." + importee);
2263
+ importee = import_path6.default.resolve(root, "." + importee);
2237
2264
  }
2238
2265
  return normalizePath(importee);
2239
2266
  }
2240
2267
  let virtualFilePath;
2241
2268
  if (importee.startsWith(virtualFilePrefix)) {
2242
2269
  virtualFilePath = importee.slice(virtualFilePrefix.length + 1);
2243
- importee = import_path4.default.resolve(root, virtualFilePath);
2270
+ importee = import_path6.default.resolve(root, virtualFilePath);
2244
2271
  } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
2245
- importee = import_path4.default.resolve(root, "." + importee);
2272
+ importee = import_path6.default.resolve(root, "." + importee);
2246
2273
  }
2247
2274
  importee = normalizePath(importee);
2248
2275
  if (!buildVirtualFilesResult) {
@@ -2251,7 +2278,7 @@ function markoRun(opts = {}) {
2251
2278
  if (virtualFiles.has(importee)) {
2252
2279
  return importee;
2253
2280
  } else if (virtualFilePath) {
2254
- const filePath = import_path4.default.resolve(__dirname, "..", virtualFilePath);
2281
+ const filePath = import_path6.default.resolve(__dirname, "..", virtualFilePath);
2255
2282
  return await this.resolve(filePath, importer, {
2256
2283
  skipSelf: true
2257
2284
  });
@@ -2265,7 +2292,8 @@ function markoRun(opts = {}) {
2265
2292
  await renderVirtualFiles(this);
2266
2293
  }
2267
2294
  if (virtualFiles.has(id)) {
2268
- return virtualFiles.get(id);
2295
+ const file = virtualFiles.get(id);
2296
+ return file;
2269
2297
  } else if (!id.startsWith(entryFilesDirPosix) && /[/\\]__marko-run__[^?/\\]+\.(js|marko)$/.exec(id)) {
2270
2298
  return "";
2271
2299
  }
@@ -2290,7 +2318,7 @@ function markoRun(opts = {}) {
2290
2318
  const builtEntries = Object.values(bundle).reduce(
2291
2319
  (acc, item) => {
2292
2320
  if (item.type === "chunk" && item.isEntry) {
2293
- acc.push(import_path4.default.join(options.dir, item.fileName));
2321
+ acc.push(import_path6.default.join(options.dir, item.fileName));
2294
2322
  }
2295
2323
  return acc;
2296
2324
  },
@@ -2314,16 +2342,16 @@ function markoRun(opts = {}) {
2314
2342
  },
2315
2343
  async closeBundle() {
2316
2344
  if (isBuild && !isSSRBuild) {
2317
- if (import_fs3.default.existsSync(entryFilesDir)) {
2318
- import_fs3.default.rmSync(entryFilesDir, { recursive: true });
2345
+ if (import_fs4.default.existsSync(entryFilesDir)) {
2346
+ import_fs4.default.rmSync(entryFilesDir, { recursive: true });
2319
2347
  }
2320
2348
  if ((adapter == null ? void 0 : adapter.buildEnd) && routes) {
2321
- await adapter.buildEnd(
2322
- resolvedConfig,
2323
- routes.list,
2324
- routeData.builtEntries,
2325
- routeData.sourceEntries
2326
- );
2349
+ await adapter.buildEnd({
2350
+ routes,
2351
+ config: resolvedConfig,
2352
+ builtEntries: routeData.builtEntries,
2353
+ sourceEntries: routeData.sourceEntries
2354
+ });
2327
2355
  }
2328
2356
  }
2329
2357
  }
@@ -2349,17 +2377,17 @@ async function globFileExists(root, pattern) {
2349
2377
  return (await (0, import_glob.glob)(pattern, { root })).length > 0;
2350
2378
  }
2351
2379
  async function ensureDir(dir) {
2352
- if (!import_fs3.default.existsSync(dir)) {
2353
- await import_fs3.default.promises.mkdir(dir, { recursive: true });
2380
+ if (!import_fs4.default.existsSync(dir)) {
2381
+ await import_fs4.default.promises.mkdir(dir, { recursive: true });
2354
2382
  }
2355
2383
  }
2356
2384
  async function getPackageData(dir) {
2357
2385
  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"));
2386
+ const pkgPath = import_path6.default.join(dir, "package.json");
2387
+ if (import_fs4.default.existsSync(pkgPath)) {
2388
+ return JSON.parse(await import_fs4.default.promises.readFile(pkgPath, "utf-8"));
2361
2389
  }
2362
- } while (dir !== (dir = import_path4.default.dirname(dir)));
2390
+ } while (dir !== (dir = import_path6.default.dirname(dir)));
2363
2391
  return null;
2364
2392
  }
2365
2393
  async function resolveAdapter(root, options, log) {
@@ -2431,11 +2459,11 @@ var defaultConfigPlugin = {
2431
2459
  var import_child_process = __toESM(require("child_process"), 1);
2432
2460
  var import_cluster = __toESM(require("cluster"), 1);
2433
2461
  var import_dotenv = require("dotenv");
2434
- var import_fs4 = __toESM(require("fs"), 1);
2462
+ var import_fs6 = __toESM(require("fs"), 1);
2435
2463
  var import_net = __toESM(require("net"), 1);
2436
2464
  async function parseEnv(envFile) {
2437
- if (import_fs4.default.existsSync(envFile)) {
2438
- const content = await import_fs4.default.promises.readFile(envFile, "utf8");
2465
+ if (import_fs6.default.existsSync(envFile)) {
2466
+ const content = await import_fs6.default.promises.readFile(envFile, "utf8");
2439
2467
  return (0, import_dotenv.parse)(content);
2440
2468
  }
2441
2469
  }