@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.
@@ -15,7 +15,7 @@ import sade from "sade";
15
15
 
16
16
  // src/cli/commands.ts
17
17
  import fs5 from "fs";
18
- import path5 from "path";
18
+ import path7 from "path";
19
19
  import { fileURLToPath as fileURLToPath2 } from "url";
20
20
  import {
21
21
  build as viteBuild,
@@ -29,9 +29,12 @@ import createDebug from "debug";
29
29
  import { resolveToEsbuildTarget } from "esbuild-plugin-browserslist";
30
30
  import fs3 from "fs";
31
31
  import { glob } from "glob";
32
- import path4 from "path";
32
+ import path6 from "path";
33
33
  import { fileURLToPath } from "url";
34
- import { buildErrorMessage, mergeConfig } from "vite";
34
+ import {
35
+ buildErrorMessage,
36
+ mergeConfig
37
+ } from "vite";
35
38
 
36
39
  // src/adapter/utils.ts
37
40
  import kleur from "kleur";
@@ -59,7 +62,7 @@ function prepareError(err) {
59
62
  }
60
63
 
61
64
  // src/vite/codegen/index.ts
62
- import path from "path";
65
+ import path2 from "path";
63
66
 
64
67
  // src/vite/constants.ts
65
68
  var markoRunFilePrefix = "__marko-run__";
@@ -84,6 +87,12 @@ var RoutableFileTypes = {
84
87
  Error: "500"
85
88
  };
86
89
 
90
+ // src/vite/utils/fs.ts
91
+ import path from "path";
92
+ var POSIX_SEP = "/";
93
+ var WINDOWS_SEP = "\\";
94
+ var normalizePath = path.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
95
+
87
96
  // src/vite/utils/route.ts
88
97
  var httpVerbOrder = httpVerbs.reduce(
89
98
  (order, verb, index) => {
@@ -107,6 +116,9 @@ function hasVerb(route, verb) {
107
116
  var _a, _b;
108
117
  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");
109
118
  }
119
+ function getRouteVirtualFileName(route) {
120
+ return `${markoRunFilePrefix}${route.key.replace(/\//g, ".")}.js`;
121
+ }
110
122
 
111
123
  // src/vite/codegen/writer.ts
112
124
  function createWriter(sink, options) {
@@ -249,27 +261,34 @@ function createStringWriter(opts) {
249
261
  }
250
262
 
251
263
  // src/vite/codegen/index.ts
252
- function renderRouteTemplate(route, getRelativePath) {
264
+ function normalizedRelativePath(from, to) {
265
+ const relativePath = normalizePath(path2.relative(from, to));
266
+ return relativePath.startsWith(".") ? relativePath : "./" + relativePath;
267
+ }
268
+ function renderRouteTemplate(route, rootDir) {
253
269
  if (!route.page) {
254
270
  throw new Error(`Route ${route.key} has no page to render`);
255
271
  }
272
+ if (!route.templateFilePath) {
273
+ throw new Error(`Route ${route.key} has no template file path`);
274
+ }
256
275
  return renderEntryTemplate(
257
- route.entryName,
276
+ normalizedRelativePath(rootDir, route.templateFilePath),
258
277
  [...route.layouts, route.page].map(
259
- (file) => getRelativePath(file.importPath)
278
+ (file) => normalizedRelativePath(
279
+ path2.dirname(route.templateFilePath),
280
+ file.filePath
281
+ )
260
282
  ),
261
283
  route.key === RoutableFileTypes.Error ? ["error"] : []
262
284
  );
263
285
  }
264
286
  function renderEntryTemplate(name, files, pageInputs = []) {
265
- if (!name) {
266
- throw new Error(`Invalid argument - 'name' cannot be empty`);
267
- }
268
287
  if (!files.length) {
269
288
  throw new Error(`Invalid argument - 'files' cannot be empty`);
270
289
  }
271
290
  const writer = createStringWriter();
272
- writer.writeLines(`// ${name}.marko`);
291
+ writer.writeLines(`// ${name}`);
273
292
  writer.branch("imports");
274
293
  writer.writeLines("");
275
294
  writeEntryTemplateTag(writer, files, pageInputs);
@@ -279,7 +298,7 @@ function writeEntryTemplateTag(writer, [file, ...rest], pageInputs, index = 1) {
279
298
  if (file) {
280
299
  const isLast = !rest.length;
281
300
  const tag = isLast ? "Page" : `Layout${index}`;
282
- writer.branch("imports").writeLines(`import ${tag} from '${file}';`);
301
+ writer.branch("imports").writeLines(`import ${tag} from "${file}";`);
283
302
  if (isLast) {
284
303
  const attributes = pageInputs.length ? " " + pageInputs.map((name) => `${name}=input.${name}`).join(" ") : "";
285
304
  writer.writeLines(`<${tag}${attributes}/>`);
@@ -290,9 +309,9 @@ function writeEntryTemplateTag(writer, [file, ...rest], pageInputs, index = 1) {
290
309
  }
291
310
  }
292
311
  }
293
- function renderRouteEntry(route, entriesDir) {
312
+ function renderRouteEntry(route, rootDir) {
294
313
  var _a;
295
- const { key, index, handler, page, middleware, meta, entryName } = route;
314
+ const { key, index, handler, page, middleware, meta } = route;
296
315
  const verbs = getVerbs(route);
297
316
  if (!verbs) {
298
317
  throw new Error(
@@ -300,7 +319,7 @@ function renderRouteEntry(route, entriesDir) {
300
319
  );
301
320
  }
302
321
  const writer = createStringWriter();
303
- writer.writeLines(`// ${virtualFilePrefix}/${entryName}.js`);
322
+ writer.writeLines(`// ${virtualFilePrefix}${getRouteVirtualFileName(route)}`);
304
323
  const imports = writer.branch("imports");
305
324
  const runtimeImports = [];
306
325
  if (handler) {
@@ -312,9 +331,6 @@ function renderRouteEntry(route, entriesDir) {
312
331
  if (!page || verbs.some((verb) => verb !== "get" && verb !== "head")) {
313
332
  runtimeImports.push("noContent");
314
333
  }
315
- if (page) {
316
- runtimeImports.push("pageResponse");
317
- }
318
334
  if (verbs.includes("head")) {
319
335
  runtimeImports.push("stripResponseBody");
320
336
  }
@@ -322,7 +338,7 @@ function renderRouteEntry(route, entriesDir) {
322
338
  imports.writeLines(
323
339
  `import { ${runtimeImports.join(
324
340
  ", "
325
- )} } from '${virtualFilePrefix}/runtime/internal';`
341
+ )} } from "${virtualFilePrefix}/runtime/internal";`
326
342
  );
327
343
  }
328
344
  if (middleware.length) {
@@ -330,7 +346,7 @@ function renderRouteEntry(route, entriesDir) {
330
346
  imports.writeLines(
331
347
  `import { ${names.join(
332
348
  ", "
333
- )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
349
+ )} } from "${virtualFilePrefix}/${markoRunFilePrefix}middleware.js";`
334
350
  );
335
351
  }
336
352
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
@@ -342,18 +358,17 @@ function renderRouteEntry(route, entriesDir) {
342
358
  writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
343
359
  }
344
360
  imports.writeLines(
345
- `import { ${names.join(", ")} } from './${handler.importPath}';`
361
+ `import { ${names.join(", ")} } from "${normalizedRelativePath(rootDir, handler.filePath)}";`
346
362
  );
347
363
  }
348
364
  if (page) {
349
- const pageNameIndex = page.name.indexOf("+page");
350
- const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
351
- const importPath = route.layouts.length ? `./${path.posix.join(entriesDir, page.relativePath, "..", pageNamePrefix + "route.marko")}` : `./${page.importPath}`;
352
- imports.writeLines(`import page from '${importPath}${serverEntryQuery}';`);
365
+ imports.writeLines(
366
+ `import page from "${normalizedRelativePath(rootDir, route.templateFilePath || page.filePath)}${serverEntryQuery}";`
367
+ );
353
368
  }
354
369
  if (meta) {
355
370
  imports.writeLines(
356
- `export { default as meta${index} } from './${meta.importPath}';`
371
+ `export { default as meta${index} } from "${normalizedRelativePath(rootDir, meta.filePath)}";`
357
372
  );
358
373
  }
359
374
  for (const verb of verbs) {
@@ -370,9 +385,7 @@ function writeRouteEntryHandler(writer, route, verb) {
370
385
  let hasBody = false;
371
386
  writer.writeLines("");
372
387
  if (page && (verb === "get" || verb === "head")) {
373
- writer.writeBlockStart(
374
- `export function ${verb}${index}(context, buildInput) {`
375
- );
388
+ writer.writeBlockStart(`export function ${verb}${index}(context) {`);
376
389
  } else {
377
390
  writer.writeBlockStart(`export function ${verb}${index}(context) {`);
378
391
  }
@@ -382,7 +395,7 @@ function writeRouteEntryHandler(writer, route, verb) {
382
395
  if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
383
396
  const name = `${verb}Handler`;
384
397
  continuations.writeLines(
385
- `const ${currentName} = () => pageResponse(page, buildInput());`
398
+ `const ${currentName} = () => context.render(page, {});`
386
399
  );
387
400
  if (len) {
388
401
  nextName = currentName;
@@ -401,17 +414,15 @@ function writeRouteEntryHandler(writer, route, verb) {
401
414
  hasBody = true;
402
415
  }
403
416
  } else if (verb === "head") {
404
- writer.writeLines(
405
- `return stripResponseBody(get${index}(context, buildInput));`
406
- );
417
+ writer.writeLines(`return stripResponseBody(get${index}(context));`);
407
418
  hasBody = true;
408
419
  } else if (len) {
409
420
  continuations.writeLines(
410
- `const ${currentName} = () => pageResponse(page, buildInput());`
421
+ `const ${currentName} = () => context.render(page, {});`
411
422
  );
412
423
  nextName = currentName;
413
424
  } else {
414
- writer.writeLines(`return pageResponse(page, buildInput());`);
425
+ writer.writeLines(`return context.render(page, {});`);
415
426
  hasBody = true;
416
427
  }
417
428
  } else if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes(verb)) {
@@ -461,7 +472,7 @@ function writeRouteEntryHandler(writer, route, verb) {
461
472
  continuations.join();
462
473
  writer.writeBlockEnd("}");
463
474
  }
464
- function renderRouter(routes, entriesDir, options = {
475
+ function renderRouter(routes, rootDir, options = {
465
476
  trailingSlashes: "RedirectWithout"
466
477
  }) {
467
478
  const writer = createStringWriter();
@@ -470,20 +481,19 @@ function renderRouter(routes, entriesDir, options = {
470
481
  writer.writeLines(`// @marko/run/router`);
471
482
  const imports = writer.branch("imports");
472
483
  imports.writeLines(
473
- `import { NotHandled, NotMatched, createContext${hasErrorPage || hasNotFoundPage ? ", pageResponse" : ""} } from '${virtualFilePrefix}/runtime/internal';`
484
+ `import { NotHandled, NotMatched, createContext } from "${virtualFilePrefix}/runtime/internal";`
474
485
  );
475
486
  for (const route of routes.list) {
476
487
  const verbs = getVerbs(route);
477
488
  const names = verbs.map((verb) => `${verb}${route.index}`);
478
489
  route.meta && names.push(`meta${route.index}`);
479
490
  imports.writeLines(
480
- `import { ${names.join(", ")} } from '${virtualFilePrefix}/${route.entryName}.js';`
491
+ `import { ${names.join(", ")} } from "${virtualFilePrefix}/${getRouteVirtualFileName(route)}";`
481
492
  );
482
493
  }
483
494
  for (const route of Object.values(routes.special)) {
484
- const importPath = route.layouts.length ? `./${path.posix.join(entriesDir, route.page.relativePath, "..", `route.${route.key}.marko`)}` : `./${route.page.importPath}`;
485
495
  imports.writeLines(
486
- `import page${route.key} from '${importPath}${serverEntryQuery}';`
496
+ `import page${route.key} from "${normalizedRelativePath(rootDir, route.templateFilePath || route.page.filePath)}${serverEntryQuery}";`
487
497
  );
488
498
  }
489
499
  writer.writeLines(
@@ -491,11 +501,12 @@ function renderRouter(routes, entriesDir, options = {
491
501
  globalThis.__marko_run__ = { match, fetch, invoke };
492
502
  `
493
503
  ).writeBlockStart(`export function match(method, pathname) {`).writeLines(
494
- `if (!pathname) {
495
- pathname = '/';
496
- } else if (pathname.charAt(0) !== '/') {
497
- pathname = '/' + pathname;
498
- }`
504
+ `const last = pathname.length - 1;
505
+ return match_internal(method, last && pathname.charAt(last) === '/' ? pathname.slice(0, last) : pathname)
506
+ };
507
+
508
+ function match_internal(method, pathname) {
509
+ const len = pathname.length;`
499
510
  ).writeBlockStart(`switch (method) {`);
500
511
  for (const verb of httpVerbs) {
501
512
  const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
@@ -511,13 +522,13 @@ globalThis.__marko_run__ = { match, fetch, invoke };
511
522
  writer.writeLines("").writeBlockStart(
512
523
  "export async function invoke(route, request, platform, url) {"
513
524
  ).writeLines(
514
- "const [context, buildInput] = createContext(route, request, platform, url);"
525
+ "const context = createContext(route, request, platform, url);"
515
526
  );
516
527
  if (hasErrorPage) {
517
528
  writer.writeBlockStart("try {");
518
529
  }
519
530
  writer.writeBlockStart("if (route) {").writeBlockStart("try {").writeLines(
520
- "const response = await route.handler(context, buildInput);",
531
+ "const response = await route.handler(context);",
521
532
  "if (response) return response;"
522
533
  ).indent--;
523
534
  writer.writeBlockStart("} catch (error) {").writeLines(
@@ -534,14 +545,16 @@ const page404ResponseInit = {
534
545
  );
535
546
  writer.write(`
536
547
  if (context.request.headers.get('Accept')?.includes('text/html')) {
537
- return pageResponse(page404, buildInput(), page404ResponseInit);
548
+ return context.render(page404, {}, page404ResponseInit);
538
549
  }`);
539
550
  }
540
551
  writer.indent--;
541
- writer.writeLines(`
552
+ if (routes.list.length) {
553
+ writer.writeLines(`
542
554
  return new Response(null, {
543
555
  status: 404,
544
556
  });`);
557
+ }
545
558
  if (hasErrorPage) {
546
559
  imports.writeLines(`
547
560
  const page500ResponseInit = {
@@ -551,7 +564,7 @@ const page500ResponseInit = {
551
564
  writer.writeBlockStart(`} catch (error) {`).writeBlockStart(
552
565
  `if (context.request.headers.get('Accept')?.includes('text/html')) {`
553
566
  ).writeLines(
554
- `return pageResponse(page500, buildInput({ error }), page500ResponseInit);`
567
+ `return context.render(page500, { error }, page500ResponseInit);`
555
568
  ).writeBlockEnd("}").writeLines("throw error;").writeBlockEnd("}");
556
569
  }
557
570
  writer.writeBlockEnd("}");
@@ -563,38 +576,40 @@ function renderFetch(writer, options) {
563
576
  export async function fetch(request, platform) {
564
577
  try {
565
578
  const url = new URL(request.url);
566
- let { pathname } = url;`);
579
+ const { pathname } = url;
580
+ const last = pathname.length - 1;
581
+ const hasTrailingSlash = last && pathname.charAt(last) === '/';
582
+ const normalizedPathname = hasTrailingSlash ? pathname.slice(0, last) : pathname;
583
+ const route = match_internal(request.method, normalizedPathname);`);
567
584
  switch (options.trailingSlashes) {
568
585
  case "RedirectWithout":
569
586
  writer.write(`
570
- if (pathname !== '/' && pathname.endsWith('/')) {
571
- url.pathname = pathname.slice(0, -1);
587
+ if (route && hasTrailingSlash) {
588
+ url.pathname = normalizedPathname
572
589
  return Response.redirect(url);
573
590
  }`);
574
591
  break;
575
592
  case "RedirectWith":
576
593
  writer.write(`
577
- if (pathname !== '/' && !pathname.endsWith('/')) {
578
- url.pathname = pathname + '/';
594
+ if (route && pathname !== '/' && !hasTrailingSlash) {
595
+ url.pathname += '/';
579
596
  return Response.redirect(url);
580
597
  }`);
581
598
  break;
582
599
  case "RewriteWithout":
583
600
  writer.write(`
584
- if (pathname !== '/' && pathname.endsWith('/')) {
585
- url.pathname = pathname = pathname.slice(0, -1);
601
+ if (route && hasTrailingSlash) {
602
+ url.pathname = normalizedPathname;
586
603
  }`);
587
604
  break;
588
605
  case "RewriteWith":
589
606
  writer.write(`
590
- if (pathname !== '/' && !pathname.endsWith('/')) {
591
- url.pathname = pathname = pathname + '/';
607
+ if (route && pathname !== '/' && !hasTrailingSlash) {
608
+ url.pathname += '/';
592
609
  }`);
593
610
  break;
594
611
  }
595
612
  writer.write(`
596
-
597
- const route = match(request.method, pathname);
598
613
  return await invoke(route, request, platform, url);
599
614
  } catch (error) {
600
615
  if (import.meta.env.DEV) {
@@ -610,10 +625,9 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
610
625
  const { route, dynamic, catchAll } = trie;
611
626
  let closeCount = 0;
612
627
  if (level === 0) {
613
- writer.writeLines(`const len = pathname.length;`);
614
628
  if (route) {
615
629
  writer.writeLines(
616
- `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
630
+ `if (len === 1) return ${renderMatch(verb, route, trie.path)};`
617
631
  );
618
632
  } else if (trie.static || dynamic) {
619
633
  writer.writeBlockStart(`if (len > 1) {`);
@@ -654,17 +668,15 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
654
668
  if (useSwitch) {
655
669
  writer.writeBlockStart(`switch (${value}) {`);
656
670
  }
657
- for (const { key, path: path6, route: route2 } of terminal) {
671
+ for (const { key, path: path8, route: route2 } of terminal) {
658
672
  const decodedKey = decodeURIComponent(key);
659
673
  if (useSwitch) {
660
674
  writer.write(`case '${decodedKey}': `, true);
661
675
  } else {
662
676
  writer.write(`if (${value} === '${decodedKey}') `, true);
663
677
  }
664
- writer.write(
665
- `return ${renderMatch(verb, route2, path6)}; // ${path6.path}
666
- `
667
- );
678
+ writer.write(`return ${renderMatch(verb, route2, path8)};
679
+ `);
668
680
  }
669
681
  if (useSwitch) {
670
682
  writer.writeBlockEnd("}");
@@ -676,7 +688,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
676
688
  verb,
677
689
  dynamic.route,
678
690
  dynamic.path
679
- )}; // ${dynamic.path.path}`
691
+ )};`
680
692
  );
681
693
  }
682
694
  }
@@ -736,7 +748,7 @@ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
736
748
  catchAll.route,
737
749
  catchAll.path,
738
750
  String(offset)
739
- )}; // ${catchAll.path.path}`
751
+ )};`
740
752
  );
741
753
  } else if (level === 0) {
742
754
  writer.writeLines("return null;");
@@ -765,43 +777,47 @@ function renderParams(params, pathIndex) {
765
777
  }
766
778
  return result ? result + " }" : "{}";
767
779
  }
768
- function renderMatch(verb, route, path6, pathIndex) {
780
+ function renderMatch(verb, route, path8, pathIndex) {
769
781
  const handler = `${verb}${route.index}`;
770
- const params = path6.params ? renderParams(path6.params, pathIndex) : "{}";
782
+ const params = path8.params ? renderParams(path8.params, pathIndex) : "{}";
771
783
  const meta = route.meta ? `meta${route.index}` : "{}";
772
- const pathPattern = pathToURLPatternString(path6.path);
773
- return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
784
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${path8.path}' }`;
774
785
  }
775
- function renderMiddleware(middleware) {
786
+ function renderMiddleware(middleware, rootDir) {
776
787
  const writer = createStringWriter();
777
788
  writer.writeLines(
778
789
  `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
779
790
  );
780
791
  const imports = writer.branch("imports");
781
792
  imports.writeLines(
782
- `import { normalize } from '${virtualFilePrefix}/runtime/internal';`
793
+ `import { normalize } from "${virtualFilePrefix}/runtime/internal";`
783
794
  );
784
795
  writer.writeLines("");
785
- for (const { id, importPath } of middleware) {
796
+ for (const { id, filePath } of middleware) {
786
797
  const importName = `middleware${id}`;
787
- imports.writeLines(`import ${importName} from './${importPath}';`);
798
+ imports.writeLines(
799
+ `import ${importName} from "${normalizedRelativePath(rootDir, filePath)}";`
800
+ );
788
801
  writer.writeLines(`export const mware${id} = normalize(${importName});`);
789
802
  }
790
803
  imports.join();
791
804
  return writer.end();
792
805
  }
793
- function stripTsExtension(path6) {
794
- const index = path6.lastIndexOf(".");
806
+ function stripTsExtension(path8) {
807
+ const index = path8.lastIndexOf(".");
795
808
  if (index !== -1) {
796
- const ext = path6.slice(index + 1);
809
+ const ext = path8.slice(index + 1);
797
810
  if (ext.toLowerCase() === "ts") {
798
- return path6.slice(0, index);
811
+ return path8.slice(0, index);
799
812
  }
800
813
  }
801
- return path6;
814
+ return path8;
802
815
  }
803
- async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
804
- var _a, _b;
816
+ function decodePath(path8) {
817
+ return path8;
818
+ }
819
+ async function renderRouteTypeInfo(routes, outDir, adapter) {
820
+ var _a, _b, _c, _d;
805
821
  const writer = createStringWriter();
806
822
  writer.writeLines(
807
823
  `/*
@@ -830,11 +846,31 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
830
846
  const routeTypes = /* @__PURE__ */ new Map();
831
847
  for (const route of routes.list) {
832
848
  let routeType = "";
833
- for (const path6 of route.paths) {
834
- const pathType = `"${pathToURLPatternString(path6.path)}"`;
835
- routeType += routeType ? " | " + pathType : pathType;
836
- routesWriter.writeLines(`${pathType}: Routes["${route.key}"];`);
849
+ let routeDefinition = "";
850
+ if (route.page || route.handler) {
851
+ const verbs = [];
852
+ if (route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes("get"))) {
853
+ verbs.push(`"get"`);
854
+ }
855
+ if ((_d = (_c = route.handler) == null ? void 0 : _c.verbs) == null ? void 0 : _d.includes("post")) {
856
+ verbs.push(`"post"`);
857
+ }
858
+ routeDefinition = `{ verb: ${verbs.join(" | ")};`;
859
+ if (route.meta) {
860
+ const metaPath = stripTsExtension(
861
+ normalizedRelativePath(outDir, route.meta.filePath)
862
+ );
863
+ let metaType = `typeof import("${metaPath}")`;
864
+ if (/\.(ts|js|mjs)$/.test(route.meta.name)) {
865
+ metaType += `["default"]`;
866
+ }
867
+ routeDefinition += ` meta: ${metaType};`;
868
+ }
869
+ routeDefinition += " }";
837
870
  }
871
+ const pathType = `"${decodePath(route.path.path)}"`;
872
+ routeType += routeType ? " | " + pathType : pathType;
873
+ routesWriter.writeLines(`${pathType}: ${routeDefinition};`);
838
874
  for (const file of [route.handler, route.page]) {
839
875
  if (file) {
840
876
  const existing = routeTypes.get(file);
@@ -867,31 +903,33 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
867
903
  const pageWriter = writer.branch("page");
868
904
  const layoutWriter = writer.branch("layout");
869
905
  for (const [file, types] of routeTypes) {
870
- const path6 = `${pathPrefix}/${file.relativePath}`;
906
+ const modulePath = stripTsExtension(
907
+ normalizedRelativePath(outDir, file.filePath)
908
+ );
871
909
  const routeType = `Run.Routes[${types.join(" | ")}]`;
872
910
  switch (file.type) {
873
911
  case RoutableFileTypes.Handler:
874
- writeModuleDeclaration(handlerWriter, path6, routeType);
912
+ writeModuleDeclaration(handlerWriter, modulePath, routeType);
875
913
  break;
876
914
  case RoutableFileTypes.Middleware:
877
- writeModuleDeclaration(middlewareWriter, path6, routeType);
915
+ writeModuleDeclaration(middlewareWriter, modulePath, routeType);
878
916
  break;
879
917
  case RoutableFileTypes.Page:
880
- writeModuleDeclaration(pageWriter, path6, routeType);
918
+ writeModuleDeclaration(pageWriter, modulePath, routeType);
881
919
  break;
882
920
  case RoutableFileTypes.Layout:
883
921
  writeModuleDeclaration(
884
922
  layoutWriter,
885
- path6,
923
+ modulePath,
886
924
  routeType,
887
925
  `
888
- export interface Input extends Run.LayoutInput<typeof import('${path6}')> {}`
926
+ export interface Input extends Run.LayoutInput<typeof import("${modulePath}")> {}`
889
927
  );
890
928
  break;
891
929
  case RoutableFileTypes.Error:
892
930
  writeModuleDeclaration(
893
931
  writer,
894
- path6,
932
+ modulePath,
895
933
  "globalThis.MarkoRun.Route",
896
934
  `
897
935
  export interface Input {
@@ -900,7 +938,7 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
900
938
  );
901
939
  break;
902
940
  case RoutableFileTypes.NotFound:
903
- writeModuleDeclaration(writer, path6, "Run.Route");
941
+ writeModuleDeclaration(writer, modulePath, "Run.Route");
904
942
  break;
905
943
  }
906
944
  }
@@ -908,40 +946,15 @@ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
908
946
  middlewareWriter.join();
909
947
  pageWriter.join();
910
948
  layoutWriter.join();
911
- writer.writeBlockStart(`
912
- type Routes = {`);
913
- for (const route of routes.list) {
914
- const { meta, handler, page } = route;
915
- if (page || handler) {
916
- const verbs = [];
917
- if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
918
- verbs.push(`"get"`);
919
- }
920
- if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
921
- verbs.push(`"post"`);
922
- }
923
- let routeType = `{ verb: ${verbs.join(" | ")};`;
924
- if (meta) {
925
- const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
926
- let metaType = `typeof import("${metaPath}")`;
927
- if (/\.(ts|js|mjs)$/.test(meta.name)) {
928
- metaType += `["default"]`;
929
- }
930
- routeType += ` meta: ${metaType};`;
931
- }
932
- writer.writeLines(`"${route.key}": ${routeType} };`);
933
- }
934
- }
935
- writer.writeBlockEnd("}");
936
949
  return writer.end();
937
950
  }
938
- function writeModuleDeclaration(writer, path6, routeType, moduleTypes) {
939
- writer.writeLines("").write(`declare module "${stripTsExtension(path6)}" {`);
951
+ function writeModuleDeclaration(writer, name, routeType, moduleTypes) {
952
+ writer.writeLines("").write(`declare module "${name}" {`);
940
953
  if (moduleTypes) {
941
954
  writer.write(moduleTypes);
942
955
  }
943
956
  if (routeType) {
944
- const isMarko = path6.endsWith(".marko");
957
+ const isMarko = name.endsWith(".marko");
945
958
  writer.write(`
946
959
  namespace MarkoRun {
947
960
  export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
@@ -955,21 +968,15 @@ function writeModuleDeclaration(writer, path6, routeType, moduleTypes) {
955
968
  writer.writeLines(`
956
969
  }`);
957
970
  }
958
- function pathToURLPatternString(path6) {
959
- return path6.replace(/\/\$(\$?)([^/]*)/g, (_, catchAll, name) => {
960
- name = decodeURIComponent(name);
961
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
962
- });
963
- }
964
971
  function createRouteTrie(routes) {
965
972
  const root = {
966
973
  key: ""
967
974
  };
968
- function insert(path6, route) {
975
+ function insert(path8, route) {
969
976
  let node = root;
970
- for (const segment of path6.segments) {
977
+ for (const segment of path8.segments) {
971
978
  if (segment === "$$") {
972
- node.catchAll ?? (node.catchAll = { route, path: path6 });
979
+ node.catchAll ?? (node.catchAll = { route, path: path8 });
973
980
  return;
974
981
  } else if (segment === "$") {
975
982
  node = node.dynamic ?? (node.dynamic = {
@@ -987,17 +994,18 @@ function createRouteTrie(routes) {
987
994
  node = next;
988
995
  }
989
996
  }
990
- node.path ?? (node.path = path6);
997
+ node.path ?? (node.path = path8);
991
998
  node.route ?? (node.route = route);
992
999
  }
993
1000
  for (const route of routes) {
994
- for (const path6 of route.paths) {
995
- insert(path6, route);
996
- }
1001
+ insert(route.path, route);
997
1002
  }
998
1003
  return root;
999
1004
  }
1000
1005
 
1006
+ // src/vite/routes/builder.ts
1007
+ import path3 from "path";
1008
+
1001
1009
  // src/vite/routes/parse.ts
1002
1010
  function parseFlatRoute(pattern) {
1003
1011
  if (!pattern) throw new Error("Empty pattern");
@@ -1012,53 +1020,72 @@ function parseFlatRoute(pattern) {
1012
1020
  ]);
1013
1021
  function parse2(basePaths, group) {
1014
1022
  const pathMap = /* @__PURE__ */ new Map();
1015
- const delimiters = group ? ").," : ".,";
1023
+ const delimiters = group ? "`).," : "`.,";
1016
1024
  let charCode;
1017
1025
  let segmentStart = i;
1018
1026
  let type;
1019
1027
  let current;
1028
+ let escaped = "";
1029
+ let escapeStart = 0;
1020
1030
  do {
1021
1031
  charCode = pattern.charCodeAt(i);
1022
- if (charCode === 41 && group) {
1032
+ if (charCode === 96 /* Escape */) {
1033
+ if (escapeStart) {
1034
+ escaped += pattern.slice(segmentStart, escapeStart - 1) + pattern.slice(escapeStart, i);
1035
+ escapeStart = 0;
1036
+ segmentStart = ++i;
1037
+ } else {
1038
+ escapeStart = i + 1;
1039
+ i = pattern.indexOf("`", escapeStart);
1040
+ if (i < 0) break;
1041
+ }
1042
+ } else if (charCode === 41 /* GroupEnd */ && group) {
1023
1043
  break;
1024
- } else if (charCode === 44) {
1044
+ } else if (charCode === 44 /* Alternate */) {
1025
1045
  if (!current) {
1026
1046
  segmentEnd(
1027
- basePaths.map((path6) => ({
1028
- ...path6,
1029
- segments: path6.segments.slice()
1047
+ basePaths.map((path8) => ({
1048
+ ...path8,
1049
+ segments: path8.segments.slice()
1030
1050
  })),
1031
- "",
1051
+ escaped,
1032
1052
  "_",
1033
1053
  pathMap
1034
1054
  );
1035
1055
  } else {
1036
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1056
+ segmentEnd(
1057
+ current,
1058
+ escaped + pattern.slice(segmentStart, i),
1059
+ type,
1060
+ pathMap
1061
+ );
1037
1062
  }
1038
1063
  current = void 0;
1039
1064
  type = void 0;
1065
+ escaped = "";
1040
1066
  segmentStart = ++i;
1041
- } else if (charCode === 46) {
1067
+ } else if (charCode === 46 /* Directory */) {
1042
1068
  if (current) {
1043
- segmentEnd(current, pattern.slice(segmentStart, i), type);
1069
+ segmentEnd(current, escaped + pattern.slice(segmentStart, i), type);
1044
1070
  }
1045
1071
  type = void 0;
1072
+ escaped = "";
1046
1073
  segmentStart = ++i;
1047
- } else if (charCode === 40) {
1074
+ } else if (charCode === 40 /* GroupStart */) {
1048
1075
  const groupPaths = parse2(current || basePaths, ++i);
1049
1076
  if (groupPaths.length) {
1050
1077
  current = groupPaths;
1051
1078
  }
1052
1079
  segmentStart = ++i;
1053
1080
  } else {
1054
- if (charCode === 95) {
1081
+ if (charCode === 95 /* Pathless */) {
1055
1082
  type = "_";
1056
- } else if (charCode === 36) {
1083
+ } else if (charCode === 36 /* Dynamic */) {
1057
1084
  type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
1058
1085
  }
1059
- current ?? (current = basePaths.map((path6) => ({
1060
- ...path6,
1061
- segments: path6.segments.slice()
1086
+ current ?? (current = basePaths.map((path8) => ({
1087
+ ...path8,
1088
+ segments: path8.segments.slice()
1062
1089
  })));
1063
1090
  i = len;
1064
1091
  for (const char of delimiters) {
@@ -1069,7 +1096,12 @@ function parseFlatRoute(pattern) {
1069
1096
  }
1070
1097
  }
1071
1098
  } while (i < len);
1072
- if (group && charCode !== 41) {
1099
+ if (escapeStart) {
1100
+ throw new Error(
1101
+ `Invalid route pattern: unclosed escape '${pattern.slice(escapeStart)}' in '${pattern}'`
1102
+ );
1103
+ }
1104
+ if (group && charCode !== 41 /* GroupEnd */) {
1073
1105
  throw new Error(
1074
1106
  `Invalid route pattern: group was not closed '${pattern.slice(
1075
1107
  group
@@ -1078,16 +1110,21 @@ function parseFlatRoute(pattern) {
1078
1110
  }
1079
1111
  if (!current) {
1080
1112
  segmentEnd(
1081
- basePaths.map((path6) => ({
1082
- ...path6,
1083
- segments: path6.segments.slice()
1113
+ basePaths.map((path8) => ({
1114
+ ...path8,
1115
+ segments: path8.segments.slice()
1084
1116
  })),
1085
- "",
1086
- "_",
1117
+ escaped,
1118
+ void 0,
1087
1119
  pathMap
1088
1120
  );
1089
1121
  } else {
1090
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1122
+ segmentEnd(
1123
+ current,
1124
+ escaped + pattern.slice(segmentStart, i),
1125
+ type,
1126
+ pathMap
1127
+ );
1091
1128
  }
1092
1129
  return [...pathMap.values()];
1093
1130
  }
@@ -1096,17 +1133,17 @@ function parseFlatRoute(pattern) {
1096
1133
  if (raw) {
1097
1134
  segment = {
1098
1135
  raw,
1099
- name: raw,
1136
+ name: normalizeSegment(raw),
1100
1137
  type
1101
1138
  };
1102
1139
  if (type === "$" || type === "$$") {
1103
1140
  segment.name = type;
1104
- segment.param = raw.slice(type.length);
1141
+ segment.param = normalizeParam(raw.slice(type.length));
1105
1142
  }
1106
1143
  }
1107
- for (const path6 of paths) {
1144
+ for (const path8 of paths) {
1108
1145
  if (segment) {
1109
- if (path6.isCatchall) {
1146
+ if (path8.isCatchall) {
1110
1147
  throw new Error(
1111
1148
  `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
1112
1149
  0,
@@ -1114,26 +1151,36 @@ function parseFlatRoute(pattern) {
1114
1151
  )}' in '${pattern}'.`
1115
1152
  );
1116
1153
  }
1117
- path6.segments.push(segment);
1118
- path6.id += path6.id === "/" ? segment.name : `/${segment.name}`;
1154
+ path8.segments.push(segment);
1155
+ path8.id += path8.id === "/" ? segment.name : `/${segment.name}`;
1119
1156
  if (type === "$$") {
1120
- path6.isCatchall = true;
1157
+ path8.isCatchall = true;
1121
1158
  }
1122
1159
  }
1123
1160
  if (map) {
1124
- if (map.has(path6.id)) {
1125
- const existing = map.get(path6.id);
1161
+ if (map.has(path8.id)) {
1162
+ const existing = map.get(path8.id);
1126
1163
  const existingExpansion = existing.segments.map((s) => s.raw).join(".");
1127
- const currentExpansion = path6.segments.map((s) => s.raw).join(".");
1164
+ const currentExpansion = path8.segments.map((s) => s.raw).join(".");
1128
1165
  throw new Error(
1129
- `Invalid route pattern: route '${path6.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1166
+ `Invalid route pattern: route '${path8.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1130
1167
  );
1131
1168
  }
1132
- map.set(path6.id, path6);
1169
+ map.set(path8.id, path8);
1133
1170
  }
1134
1171
  }
1135
1172
  }
1136
1173
  }
1174
+ function normalizeParam(segment) {
1175
+ const normalized = normalizeSegment(segment);
1176
+ return /^\$/.test(normalized) ? `\`${normalized}\`` : normalized;
1177
+ }
1178
+ function normalizeSegment(segment) {
1179
+ return decodeURIComponent(segment).replace(
1180
+ /[/?#]/g,
1181
+ (char) => "%" + char.charCodeAt(0).toString(16)
1182
+ );
1183
+ }
1137
1184
 
1138
1185
  // src/vite/routes/vdir.ts
1139
1186
  var _dirs, _pathlessDirs;
@@ -1144,14 +1191,12 @@ var _VDir = class _VDir {
1144
1191
  __publicField(this, "parent");
1145
1192
  __publicField(this, "source");
1146
1193
  __publicField(this, "path");
1147
- __publicField(this, "fullPath");
1148
1194
  __publicField(this, "segment");
1149
1195
  __publicField(this, "files");
1150
1196
  if (!parent || !segment) {
1151
1197
  this.parent = null;
1152
1198
  this.source = null;
1153
1199
  this.path = "/";
1154
- this.fullPath = "/";
1155
1200
  this.segment = {
1156
1201
  raw: "",
1157
1202
  name: ""
@@ -1159,12 +1204,8 @@ var _VDir = class _VDir {
1159
1204
  } else {
1160
1205
  this.parent = parent;
1161
1206
  this.source = source;
1162
- this.path = parent.path + (parent.path === "/" ? segment.name : `/${segment.name}`);
1163
- this.fullPath = parent.fullPath + (parent.fullPath === "/" ? segment.name : `/${segment.name}`);
1164
- if (segment.param) {
1165
- this.fullPath += segment.param;
1166
- }
1167
1207
  this.segment = segment;
1208
+ this.path = parent.path + (parent.path === "/" ? "" : "/") + segment.name;
1168
1209
  }
1169
1210
  }
1170
1211
  get pathInfo() {
@@ -1177,17 +1218,18 @@ var _VDir = class _VDir {
1177
1218
  for (const { segment } of this) {
1178
1219
  const { type, name, param } = segment;
1179
1220
  if (name && type !== "_") {
1180
- value.id += sep + (type || name);
1221
+ value.id += sep + name;
1181
1222
  value.path += sep + name;
1182
1223
  value.isEnd = type === "$$";
1183
1224
  if (param) {
1184
- value.path += param;
1225
+ const unescapedParam = param.charAt(0) === "`" ? param.slice(1, -1) : param;
1185
1226
  const index = type === "$$" ? null : value.segments.length;
1186
1227
  if (!value.params) {
1187
- value.params = { [param]: index };
1228
+ value.params = { [unescapedParam]: index };
1188
1229
  } else if (!(param in value.params)) {
1189
- value.params[param] = index;
1230
+ value.params[unescapedParam] = index;
1190
1231
  }
1232
+ value.path += param;
1191
1233
  }
1192
1234
  value.segments.push(name);
1193
1235
  sep = "/";
@@ -1199,11 +1241,11 @@ var _VDir = class _VDir {
1199
1241
  });
1200
1242
  return value;
1201
1243
  }
1202
- addDir(path6, segment) {
1244
+ addDir(path8, segment) {
1203
1245
  const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
1204
1246
  const key = segment.type === "$" ? segment.raw : segment.name;
1205
1247
  if (!map.has(key)) {
1206
- const dir = new _VDir(this, segment, path6);
1248
+ const dir = new _VDir(this, segment, path8);
1207
1249
  map.set(key, dir);
1208
1250
  return dir;
1209
1251
  }
@@ -1219,15 +1261,15 @@ var _VDir = class _VDir {
1219
1261
  const existing = this.files.get(file.type);
1220
1262
  if (existing !== file) {
1221
1263
  throw new Error(
1222
- `Duplicate file type '${file.type}' added at path '${this.path}'. File '${file.importPath}' collides with '${existing.importPath}'.`
1264
+ `Duplicate file type ${file.type} added at path ${this.path}. File ${file.filePath} collides with ${existing.filePath}.`
1223
1265
  );
1224
1266
  } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
1225
1267
  throw new Error(
1226
- `Ambiguous path definition: route '${this.path}' is defined multiple times by ${file.importPath}`
1268
+ `Ambiguous path definition: route ${this.path} is defined multiple times by ${file.filePath}`
1227
1269
  );
1228
1270
  }
1229
1271
  throw new Error(
1230
- `Ambiguous path definition: file '${this.path}' is included multiple times by ${file.importPath}`
1272
+ `Ambiguous path definition: file ${this.path} is included multiple times by ${file.filePath}`
1231
1273
  );
1232
1274
  }
1233
1275
  }
@@ -1249,10 +1291,10 @@ var _VDir = class _VDir {
1249
1291
  const dirs = [];
1250
1292
  const unique = /* @__PURE__ */ new Map();
1251
1293
  for (const root of roots) {
1252
- for (const path6 of paths) {
1294
+ for (const path8 of paths) {
1253
1295
  let dir = root;
1254
- for (const segment of path6.segments) {
1255
- dir = dir.addDir(path6, segment);
1296
+ for (const segment of path8.segments) {
1297
+ dir = dir.addDir(path8, segment);
1256
1298
  }
1257
1299
  const existing = unique.get(dir.path);
1258
1300
  if (existing) {
@@ -1265,7 +1307,7 @@ var _VDir = class _VDir {
1265
1307
  }
1266
1308
  }
1267
1309
  throw new Error(
1268
- `Ambiguous directory structure: '${sourcePath}${path6.source}' defines '${dir.path}' multiple times.`
1310
+ `Ambiguous directory structure: ${sourcePath}${path8.source} defines ${dir.path} multiple times.`
1269
1311
  );
1270
1312
  } else {
1271
1313
  unique.set(dir.path, dir);
@@ -1291,13 +1333,11 @@ function matchRoutableFile(filename) {
1291
1333
  const match = filename.match(routeableFileRegex);
1292
1334
  return match && (match[1] || match[3]).toLowerCase();
1293
1335
  }
1294
- function isSpecialType(type) {
1295
- return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
1296
- }
1297
- async function buildRoutes(sources) {
1336
+ async function buildRoutes(sources, outDir) {
1298
1337
  const uniqueRoutes = /* @__PURE__ */ new Map();
1299
1338
  const routes = [];
1300
1339
  const special = {};
1340
+ const seenKeys = /* @__PURE__ */ new Map();
1301
1341
  const middlewares = /* @__PURE__ */ new Set();
1302
1342
  const unusedFiles = /* @__PURE__ */ new Set();
1303
1343
  const currentLayouts = /* @__PURE__ */ new Set();
@@ -1305,13 +1345,13 @@ async function buildRoutes(sources) {
1305
1345
  const root = new VDir();
1306
1346
  const dirStack = [];
1307
1347
  let basePath;
1308
- let importPrefix;
1309
1348
  let activeDirs;
1310
1349
  let isBaseDir;
1311
1350
  let nextFileId = 1;
1312
1351
  let nextRouteIndex = 1;
1313
1352
  const walkOptions = {
1314
- onEnter({ name }) {
1353
+ onEnter(dir) {
1354
+ let { name } = dir;
1315
1355
  const prevDirStackLength = dirStack.length;
1316
1356
  if (isBaseDir) {
1317
1357
  isBaseDir = false;
@@ -1330,15 +1370,16 @@ async function buildRoutes(sources) {
1330
1370
  dirStack.length = prevDirStackLength;
1331
1371
  };
1332
1372
  },
1333
- onFile({ name, path: path6 }) {
1373
+ onFile(file) {
1374
+ const { name } = file;
1334
1375
  const match = name.match(routeableFileRegex);
1335
1376
  if (!match) {
1336
1377
  return;
1337
1378
  }
1338
1379
  const type = (match[1] || match[3]).toLowerCase();
1339
- if (dirStack.length && isSpecialType(type)) {
1380
+ if (dirStack.length && (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error)) {
1340
1381
  console.warn(
1341
- `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path6}`
1382
+ `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${file.path}`
1342
1383
  );
1343
1384
  return;
1344
1385
  }
@@ -1347,19 +1388,15 @@ async function buildRoutes(sources) {
1347
1388
  const paths = parseFlatRoute(name.slice(0, match.index));
1348
1389
  dirs = VDir.addPaths(activeDirs, paths);
1349
1390
  }
1350
- const dirPath = dirStack.join("/");
1351
- const relativePath = dirPath ? `${dirPath}/${name}` : name;
1352
- const file = {
1391
+ const routableFile = {
1353
1392
  id: String(nextFileId++),
1354
1393
  name,
1355
1394
  type,
1356
- filePath: path6,
1357
- relativePath,
1358
- importPath: `${importPrefix}/${relativePath}`,
1395
+ filePath: file.path,
1359
1396
  verbs: type === RoutableFileTypes.Page ? ["get", "head"] : void 0
1360
1397
  };
1361
1398
  for (const dir of dirs) {
1362
- dir.addFile(file);
1399
+ dir.addFile(routableFile);
1363
1400
  }
1364
1401
  }
1365
1402
  };
@@ -1367,7 +1404,6 @@ async function buildRoutes(sources) {
1367
1404
  sources = [sources];
1368
1405
  }
1369
1406
  for (const source of sources) {
1370
- importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
1371
1407
  basePath = source.basePath || "";
1372
1408
  activeDirs = [root];
1373
1409
  isBaseDir = true;
@@ -1387,7 +1423,8 @@ async function buildRoutes(sources) {
1387
1423
  layout = dir.files.get(RoutableFileTypes.Layout);
1388
1424
  const handler = dir.files.get(RoutableFileTypes.Handler);
1389
1425
  const page = dir.files.get(RoutableFileTypes.Page);
1390
- let hasSpecial = false;
1426
+ const pathInfo = dir.pathInfo;
1427
+ let layoutsUsed = false;
1391
1428
  if (middleware) {
1392
1429
  if (currentMiddleware.has(middleware)) {
1393
1430
  middleware = void 0;
@@ -1404,64 +1441,64 @@ async function buildRoutes(sources) {
1404
1441
  unusedFiles.add(layout);
1405
1442
  }
1406
1443
  }
1444
+ if (dir === root) {
1445
+ for (const [type, file] of dir.files) {
1446
+ if (type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error) {
1447
+ special[type] = {
1448
+ index: nextRouteIndex++,
1449
+ key: type,
1450
+ path: dir.pathInfo,
1451
+ middleware: [],
1452
+ layouts: [...currentLayouts],
1453
+ page: file,
1454
+ templateFilePath: currentLayouts.size ? path3.join(outDir, `${type}.marko`) : void 0
1455
+ };
1456
+ layoutsUsed = true;
1457
+ }
1458
+ }
1459
+ }
1407
1460
  if (page || handler) {
1408
- const path6 = dir.pathInfo;
1409
- if (uniqueRoutes.has(path6.id)) {
1410
- const existing = uniqueRoutes.get(path6.id);
1461
+ if (uniqueRoutes.has(pathInfo.id)) {
1462
+ const existing = uniqueRoutes.get(pathInfo.id);
1411
1463
  const route = routes[existing.index];
1412
1464
  const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
1413
1465
  const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
1414
- throw new Error(`Duplicate routes for path '${path6.id}' were defined. A route established by:
1415
- ${existingFiles.join(" and ")} via '${existing.dir.fullPath}'
1416
- collides with
1417
- ${currentFiles.join(" and ")} via '${dir.fullPath}'
1418
- `);
1466
+ throw new Error(
1467
+ `Duplicate routes for path ${pathInfo.id} were defined. A route established by: "${existingFiles.join(" and ")}" collides with "${currentFiles.join(" and ")}"`
1468
+ );
1469
+ }
1470
+ uniqueRoutes.set(pathInfo.id, { dir, index: routes.length });
1471
+ let key = pathInfo.segments.map(replaceInvalidFilenameChars).concat("route").join("/");
1472
+ const keyCount = (seenKeys.get(key) || 0) + 1;
1473
+ seenKeys.set(key, keyCount);
1474
+ if (keyCount > 1) {
1475
+ key += keyCount;
1419
1476
  }
1420
- uniqueRoutes.set(path6.id, { dir, index: routes.length });
1421
1477
  routes.push({
1422
1478
  index: nextRouteIndex++,
1423
- key: dir.fullPath,
1424
- paths: [path6],
1479
+ key,
1480
+ path: pathInfo,
1425
1481
  middleware: [...currentMiddleware],
1426
1482
  layouts: page ? [...currentLayouts] : [],
1427
1483
  meta: dir.files.get(RoutableFileTypes.Meta),
1428
1484
  page,
1429
1485
  handler,
1430
- entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
1486
+ templateFilePath: page && currentLayouts.size ? path3.join(outDir, key + ".marko") : void 0
1431
1487
  });
1432
- }
1433
- if (dir === root) {
1434
- for (const [type, file] of dir.files) {
1435
- if (isSpecialType(type)) {
1436
- hasSpecial = true;
1437
- special[type] = {
1438
- index: 0,
1439
- key: type,
1440
- paths: [],
1441
- middleware: [],
1442
- layouts: [...currentLayouts],
1443
- page: file,
1444
- entryName: `${markoRunFilePrefix}special.${type}`
1445
- };
1446
- }
1447
- }
1448
- }
1449
- if (handler || page) {
1488
+ layoutsUsed = !!page;
1450
1489
  for (const middleware2 of currentMiddleware) {
1451
1490
  middlewares.add(middleware2);
1452
1491
  unusedFiles.delete(middleware2);
1453
1492
  }
1454
1493
  }
1455
- if (page || hasSpecial) {
1494
+ if (layoutsUsed) {
1456
1495
  for (const layout2 of currentLayouts) {
1457
1496
  unusedFiles.delete(layout2);
1458
1497
  }
1459
1498
  }
1460
1499
  }
1461
- if (dir.dirs) {
1462
- for (const child of dir.dirs()) {
1463
- traverse(child);
1464
- }
1500
+ for (const childDir of dir.dirs()) {
1501
+ traverse(childDir);
1465
1502
  }
1466
1503
  if (middleware) {
1467
1504
  currentMiddleware.delete(middleware);
@@ -1471,10 +1508,13 @@ async function buildRoutes(sources) {
1471
1508
  }
1472
1509
  }
1473
1510
  }
1511
+ function replaceInvalidFilenameChars(str) {
1512
+ return str.replace(/[<>:"/\\|?*]+/g, "_");
1513
+ }
1474
1514
 
1475
1515
  // src/vite/routes/walk.ts
1476
1516
  import fs from "fs";
1477
- import path2 from "path";
1517
+ import path4 from "path";
1478
1518
  function createFSWalker(dir) {
1479
1519
  return async function walkFS({
1480
1520
  onEnter,
@@ -1489,7 +1529,7 @@ function createFSWalker(dir) {
1489
1529
  const entries = await fs.promises.readdir(dir2.path, {
1490
1530
  withFileTypes: true
1491
1531
  });
1492
- const prefix = dir2.path + path2.sep;
1532
+ const prefix = dir2.path + path4.sep;
1493
1533
  for (const entry of entries) {
1494
1534
  const walkEntry = {
1495
1535
  name: entry.name,
@@ -1512,7 +1552,7 @@ function createFSWalker(dir) {
1512
1552
  await walk(
1513
1553
  {
1514
1554
  path: dir,
1515
- name: path2.basename(dir)
1555
+ name: path4.basename(dir)
1516
1556
  },
1517
1557
  maxDepth
1518
1558
  );
@@ -1630,36 +1670,34 @@ function logRoutesTable(routes, bundle) {
1630
1670
  style: { compact: true }
1631
1671
  });
1632
1672
  for (const route of routes.list) {
1633
- for (const path6 of route.paths) {
1634
- const verbs = getVerbs(route, true);
1635
- let firstRow = true;
1636
- for (const verb of verbs) {
1637
- const entryType = [];
1638
- let size = "";
1639
- let verbCell = verbColor(verb)(verb.toUpperCase());
1640
- if (verb === "get" && !verbs.includes("head")) {
1641
- verbCell += kleur2.dim(`,${verbColor(verb)("HEAD")}`);
1642
- }
1643
- if (route.handler) {
1644
- entryType.push(kleur2.blue("handler"));
1645
- }
1646
- if (route.page && (verb === "get" || verb === "head")) {
1647
- entryType.push(kleur2.yellow("page"));
1648
- if (verb === "get") {
1649
- size = prettySize(computeRouteSize(route, bundle));
1650
- }
1651
- }
1652
- const row = [verbCell];
1653
- if (verbs.length === 1 || firstRow) {
1654
- row.push({ rowSpan: verbs.length, content: prettyPath(path6.path) });
1655
- firstRow = false;
1673
+ const verbs = getVerbs(route, true);
1674
+ let firstRow = true;
1675
+ for (const verb of verbs) {
1676
+ const entryType = [];
1677
+ let size = "";
1678
+ const verbCell = verbColor(verb)(verb.toUpperCase());
1679
+ if (route.handler) {
1680
+ entryType.push(kleur2.blue("handler"));
1681
+ }
1682
+ if (route.page && (verb === "get" || verb === "head")) {
1683
+ entryType.push(kleur2.yellow("page"));
1684
+ if (verb === "get") {
1685
+ size = prettySize(computeRouteSize(route, bundle));
1656
1686
  }
1657
- row.push(entryType.join(" -> "));
1658
- hasMiddleware && row.push(route.middleware.length || "");
1659
- hasMeta && row.push(route.meta ? "\u2713" : "");
1660
- row.push(size || "");
1661
- table.push(row);
1662
1687
  }
1688
+ const row = [verbCell];
1689
+ if (verbs.length === 1 || firstRow) {
1690
+ row.push({
1691
+ rowSpan: verbs.length,
1692
+ content: prettyPath(route.path.path)
1693
+ });
1694
+ firstRow = false;
1695
+ }
1696
+ row.push(entryType.join(" -> "));
1697
+ hasMiddleware && row.push(route.middleware.length || "");
1698
+ hasMeta && row.push(route.meta ? "\u2713" : "");
1699
+ row.push(size || "");
1700
+ table.push(row);
1663
1701
  }
1664
1702
  }
1665
1703
  for (const [key, route] of Object.entries(routes.special).sort()) {
@@ -1669,6 +1707,15 @@ function logRoutesTable(routes, bundle) {
1669
1707
  row.push(prettySize(computeRouteSize(route, bundle)));
1670
1708
  table.push(row);
1671
1709
  }
1710
+ if (!table.length) {
1711
+ table.push([
1712
+ {
1713
+ colSpan: 4,
1714
+ hAlign: "center",
1715
+ content: kleur2.dim(kleur2.white("No routes found"))
1716
+ }
1717
+ ]);
1718
+ }
1672
1719
  console.log(table.toString());
1673
1720
  }
1674
1721
  function computeRouteSize(route, bundle) {
@@ -1715,17 +1762,20 @@ function prettySize([bytes, compBytes]) {
1715
1762
  else str += kleur2.bold(kleur2.red(compSize));
1716
1763
  return str;
1717
1764
  }
1718
- function prettyPath(path6) {
1719
- return path6.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur2.bold(kleur2.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur2.bold(kleur2.dim(`:${p}`)));
1765
+ function prettyPath(path8) {
1766
+ return path8.replace(
1767
+ /\/(\$\$?)(`?)([^/`]+)\2/g,
1768
+ (_, type, tick, key) => "/" + type + tick + kleur2.bold(kleur2.dim(key)) + tick
1769
+ );
1720
1770
  }
1721
1771
 
1722
1772
  // src/vite/utils/read-once-persisted-store.ts
1723
1773
  import { promises as fs2 } from "fs";
1724
1774
  import os from "os";
1725
- import path3 from "path";
1775
+ import path5 from "path";
1726
1776
  var noop = () => {
1727
1777
  };
1728
- var tmpFile = path3.join(os.tmpdir(), "marko-run-storage.json");
1778
+ var tmpFile = path5.join(os.tmpdir(), "marko-run-storage.json");
1729
1779
  var values = /* @__PURE__ */ new Map();
1730
1780
  var loadedFromDisk;
1731
1781
  var ReadOncePersistedStore = class {
@@ -1767,17 +1817,17 @@ process.once("beforeExit", (code) => {
1767
1817
 
1768
1818
  // src/vite/plugin.ts
1769
1819
  var debug = createDebug("@marko/run");
1770
- var __dirname = path4.dirname(fileURLToPath(import.meta.url));
1820
+ var __dirname = path6.dirname(fileURLToPath(import.meta.url));
1771
1821
  var PLUGIN_NAME_PREFIX = "marko-run-vite";
1772
- var POSIX_SEP = "/";
1773
- var WINDOWS_SEP = "\\";
1774
1822
  var CLIENT_OUT_DIR = "public";
1775
1823
  var MIDDLEWARE_FILENAME = `${markoRunFilePrefix}middleware.js`;
1776
1824
  var ROUTER_FILENAME = `${markoRunFilePrefix}router.js`;
1777
1825
  var defaultPort = Number(process.env.PORT || 3e3);
1778
- var normalizePath = path4.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1779
1826
  function markoRun(opts = {}) {
1780
- let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1827
+ let routesDir;
1828
+ let adapter;
1829
+ let trailingSlashes;
1830
+ const { ...markoVitePluginOptions } = opts;
1781
1831
  let store;
1782
1832
  let root;
1783
1833
  let shouldEmptyOutDir = false;
@@ -1810,12 +1860,8 @@ function markoRun(opts = {}) {
1810
1860
  root,
1811
1861
  "{.tsconfig*,tsconfig*.json}"
1812
1862
  )))) {
1813
- const filepath = path4.join(typesDir, "routes.d.ts");
1814
- const data = await renderRouteTypeInfo(
1815
- routes2,
1816
- normalizePath(path4.relative(typesDir, resolvedRoutesDir)),
1817
- adapter
1818
- );
1863
+ const filepath = path6.join(typesDir, "routes.d.ts");
1864
+ const data = await renderRouteTypeInfo(routes2, typesDir, adapter);
1819
1865
  if (data !== typesFile || !fs3.existsSync(filepath)) {
1820
1866
  await ensureDir(typesDir);
1821
1867
  await fs3.promises.writeFile(filepath, typesFile = data);
@@ -1826,20 +1872,36 @@ function markoRun(opts = {}) {
1826
1872
  function buildVirtualFiles() {
1827
1873
  return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1828
1874
  virtualFiles.clear();
1829
- routes = await buildRoutes({
1830
- walker: createFSWalker(resolvedRoutesDir),
1831
- importPrefix: routesDir
1832
- });
1833
- if (!routes.list.length) {
1834
- throw new Error("No routes generated");
1875
+ if (fs3.existsSync(resolvedRoutesDir)) {
1876
+ routes = await buildRoutes(
1877
+ {
1878
+ walker: createFSWalker(resolvedRoutesDir)
1879
+ },
1880
+ entryFilesDir
1881
+ );
1882
+ if (!isBuild && !routes.list.length && !Object.keys(routes.special).length) {
1883
+ console.warn(`No routes found in ${resolvedRoutesDir}`);
1884
+ }
1885
+ } else {
1886
+ routes = {
1887
+ list: [],
1888
+ special: {},
1889
+ middleware: []
1890
+ };
1891
+ if (!isBuild) {
1892
+ console.warn(`Routes directory ${resolvedRoutesDir} does not exist`);
1893
+ }
1835
1894
  }
1836
1895
  for (const route of routes.list) {
1837
- virtualFiles.set(path4.posix.join(root, `${route.entryName}.js`), "");
1896
+ virtualFiles.set(
1897
+ path6.posix.join(root, getRouteVirtualFileName(route)),
1898
+ ""
1899
+ );
1838
1900
  }
1839
1901
  if (routes.middleware.length) {
1840
- virtualFiles.set(path4.posix.join(root, MIDDLEWARE_FILENAME), "");
1902
+ virtualFiles.set(path6.posix.join(root, MIDDLEWARE_FILENAME), "");
1841
1903
  }
1842
- virtualFiles.set(path4.posix.join(root, ROUTER_FILENAME), "");
1904
+ virtualFiles.set(path6.posix.join(root, ROUTER_FILENAME), "");
1843
1905
  return routes;
1844
1906
  })());
1845
1907
  }
@@ -1853,111 +1915,78 @@ function markoRun(opts = {}) {
1853
1915
  fs3.rmSync(entryFilesDir, { recursive: true });
1854
1916
  }
1855
1917
  for (const route of routes2.list) {
1856
- const { handler, page, layouts } = route;
1857
- if (handler) {
1858
- const exports = await getExportsFromFile(context, handler.filePath);
1859
- handler.verbs = [];
1918
+ if (route.handler) {
1919
+ const exports = await getExportsFromFile(
1920
+ context,
1921
+ route.handler.filePath
1922
+ );
1923
+ route.handler.verbs = [];
1860
1924
  for (const name of exports) {
1861
1925
  const verb = name.toLowerCase();
1862
1926
  if (name === verb.toUpperCase() && httpVerbs.includes(verb)) {
1863
- handler.verbs.push(verb);
1927
+ route.handler.verbs.push(verb);
1864
1928
  }
1865
1929
  }
1866
- if (!handler.verbs.length) {
1930
+ if (!route.handler.verbs.length) {
1867
1931
  context.warn(
1868
- `Did not find any http verb exports in handler '${path4.relative(root, handler.filePath)}' - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1932
+ `Did not find any http verb exports in ${path6.relative(root, route.handler.filePath)} - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1869
1933
  );
1870
1934
  }
1871
1935
  }
1872
- if (page) {
1873
- if (layouts.length) {
1874
- const relativePath = path4.relative(
1875
- resolvedRoutesDir,
1876
- page.filePath
1877
- );
1878
- const routeFileDir = path4.join(entryFilesDir, relativePath, "..");
1879
- const routeFileRelativePathPosix = normalizePath(
1880
- path4.relative(routeFileDir, root)
1881
- );
1882
- fs3.mkdirSync(routeFileDir, { recursive: true });
1883
- const pageNameIndex = page.name.indexOf("+page");
1884
- const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
1885
- fs3.writeFileSync(
1886
- route.templateFilePath = path4.join(
1887
- routeFileDir,
1888
- pageNamePrefix + "route.marko"
1889
- ),
1890
- renderRouteTemplate(
1891
- route,
1892
- (to) => path4.posix.join(routeFileRelativePathPosix, to)
1893
- )
1894
- );
1895
- } else {
1896
- route.templateFilePath = page.filePath;
1897
- }
1936
+ if (route.templateFilePath) {
1937
+ fs3.mkdirSync(path6.dirname(route.templateFilePath), {
1938
+ recursive: true
1939
+ });
1940
+ fs3.writeFileSync(
1941
+ route.templateFilePath,
1942
+ renderRouteTemplate(route, root)
1943
+ );
1898
1944
  }
1899
1945
  virtualFiles.set(
1900
- path4.posix.join(root, `${route.entryName}.js`),
1901
- renderRouteEntry(route, relativeEntryFilesDirPosix)
1946
+ path6.posix.join(root, getRouteVirtualFileName(route)),
1947
+ renderRouteEntry(route, root)
1902
1948
  );
1903
1949
  }
1904
1950
  for (const route of Object.values(routes2.special)) {
1905
- const { page, layouts, key } = route;
1906
- if (page) {
1907
- if (layouts.length) {
1908
- const relativePath = path4.relative(
1909
- resolvedRoutesDir,
1910
- page.filePath
1911
- );
1912
- const routeFileDir = path4.join(entryFilesDir, relativePath, "..");
1913
- const routeFileRelativePathPosix = normalizePath(
1914
- path4.relative(routeFileDir, root)
1915
- );
1916
- fs3.mkdirSync(routeFileDir, { recursive: true });
1917
- fs3.writeFileSync(
1918
- route.templateFilePath = path4.join(
1919
- routeFileDir,
1920
- `route.${key}.marko`
1921
- ),
1922
- renderRouteTemplate(
1923
- route,
1924
- (to) => path4.posix.join(routeFileRelativePathPosix, to)
1925
- )
1926
- );
1927
- } else {
1928
- route.templateFilePath = page.filePath;
1929
- }
1951
+ if (route.templateFilePath) {
1952
+ fs3.mkdirSync(path6.dirname(route.templateFilePath), {
1953
+ recursive: true
1954
+ });
1955
+ fs3.writeFileSync(
1956
+ route.templateFilePath,
1957
+ renderRouteTemplate(route, root)
1958
+ );
1930
1959
  }
1931
1960
  }
1932
1961
  if (routes2.middleware.length) {
1933
1962
  for (const middleware of routes2.middleware) {
1934
1963
  if (!(await getExportsFromFile(context, middleware.filePath)).includes("default")) {
1935
1964
  context.warn(
1936
- `Did not find a default export in middleware '${path4.relative(root, middleware.filePath)}'`
1965
+ `Did not find a default export in middleware '${path6.relative(root, middleware.filePath)}'`
1937
1966
  );
1938
1967
  }
1939
1968
  }
1940
1969
  virtualFiles.set(
1941
- path4.posix.join(root, MIDDLEWARE_FILENAME),
1942
- renderMiddleware(routes2.middleware)
1970
+ path6.posix.join(root, MIDDLEWARE_FILENAME),
1971
+ renderMiddleware(routes2.middleware, root)
1943
1972
  );
1944
1973
  }
1945
1974
  virtualFiles.set(
1946
- path4.posix.join(root, ROUTER_FILENAME),
1947
- renderRouter(routes2, relativeEntryFilesDirPosix, {
1948
- trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1975
+ path6.posix.join(root, ROUTER_FILENAME),
1976
+ renderRouter(routes2, root, {
1977
+ trailingSlashes
1949
1978
  })
1950
1979
  );
1951
1980
  await writeTypesFile(routes2);
1952
1981
  if (adapter == null ? void 0 : adapter.routesGenerated) {
1953
- await adapter.routesGenerated(
1954
- routes2,
1955
- new Map(virtualFiles.entries()),
1956
- {
1982
+ await adapter.routesGenerated({
1983
+ routes: routes2,
1984
+ virtualFiles: new Map(virtualFiles.entries()),
1985
+ meta: {
1957
1986
  buildTime: times.routesBuild,
1958
1987
  renderTime: times.routesRender
1959
1988
  }
1960
- );
1989
+ });
1961
1990
  if (!isBuild) {
1962
1991
  await ((_a = opts == null ? void 0 : opts.emitRoutes) == null ? void 0 : _a.call(opts, routes2.list));
1963
1992
  }
@@ -1967,7 +1996,7 @@ function markoRun(opts = {}) {
1967
1996
  throw err;
1968
1997
  }
1969
1998
  virtualFiles.set(
1970
- path4.posix.join(root, ROUTER_FILENAME),
1999
+ path6.posix.join(root, ROUTER_FILENAME),
1971
2000
  `throw ${JSON.stringify(prepareError(err))}`
1972
2001
  );
1973
2002
  }
@@ -2004,27 +2033,28 @@ function markoRun(opts = {}) {
2004
2033
  }
2005
2034
  }
2006
2035
  routesDir = opts.routesDir || "src/routes";
2036
+ trailingSlashes = opts.trailingSlashes || "RedirectWithout";
2007
2037
  store = new ReadOncePersistedStore(
2008
2038
  `vite-marko-run${opts.runtimeId ? `-${opts.runtimeId}` : ""}`
2009
2039
  );
2010
2040
  markoVitePluginOptions.runtimeId = opts.runtimeId;
2011
2041
  markoVitePluginOptions.basePathVar = opts.basePathVar;
2012
- resolvedRoutesDir = path4.resolve(root, routesDir);
2013
- outputDir = path4.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2014
- entryFilesDir = path4.join(outputDir, ".marko-run");
2042
+ resolvedRoutesDir = path6.resolve(root, routesDir);
2043
+ outputDir = path6.join(root, ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist");
2044
+ entryFilesDir = path6.join(outputDir, ".marko-run");
2015
2045
  entryFilesDirPosix = normalizePath(entryFilesDir);
2016
2046
  relativeEntryFilesDirPosix = normalizePath(
2017
- path4.relative(root, entryFilesDir)
2047
+ path6.relative(root, entryFilesDir)
2018
2048
  );
2019
- typesDir = path4.join(root, ".marko-run");
2020
- devEntryFile = path4.join(root, "index.html");
2049
+ typesDir = path6.join(root, ".marko-run");
2050
+ devEntryFile = path6.join(root, "index.html");
2021
2051
  devEntryFilePosix = normalizePath(devEntryFile);
2022
2052
  let outDir = ((_e = config2.build) == null ? void 0 : _e.outDir) || "dist";
2023
2053
  const assetsDir = ((_f = config2.build) == null ? void 0 : _f.assetsDir) || "assets";
2024
2054
  let rollupOutputOptions = (_h = (_g = config2.build) == null ? void 0 : _g.rollupOptions) == null ? void 0 : _h.output;
2025
2055
  if (isBuild) {
2026
2056
  if (!isSSRBuild) {
2027
- outDir = path4.join(outDir, CLIENT_OUT_DIR);
2057
+ outDir = path6.join(outDir, CLIENT_OUT_DIR);
2028
2058
  }
2029
2059
  const defaultRollupOutputOptions = {
2030
2060
  assetFileNames({ name }) {
@@ -2149,7 +2179,7 @@ function markoRun(opts = {}) {
2149
2179
  devServer.watcher.on("all", async (type, filename) => {
2150
2180
  seenErrors.clear();
2151
2181
  const routableFileType = matchRoutableFile(
2152
- path4.parse(filename).base
2182
+ path6.parse(filename).base
2153
2183
  );
2154
2184
  if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
2155
2185
  if (type === "add" || type === "unlink" || type === "change" && (routableFileType === RoutableFileTypes.Handler || routableFileType === RoutableFileTypes.Middleware)) {
@@ -2199,19 +2229,19 @@ function markoRun(opts = {}) {
2199
2229
  },
2200
2230
  async resolveId(importee, importer) {
2201
2231
  if (importee === "@marko/run/router") {
2202
- return normalizePath(path4.resolve(root, ROUTER_FILENAME));
2232
+ return normalizePath(path6.resolve(root, ROUTER_FILENAME));
2203
2233
  } else if (importee.endsWith(".marko") && importee.includes(relativeEntryFilesDirPosix)) {
2204
2234
  if (!importee.startsWith(root)) {
2205
- importee = path4.resolve(root, "." + importee);
2235
+ importee = path6.resolve(root, "." + importee);
2206
2236
  }
2207
2237
  return normalizePath(importee);
2208
2238
  }
2209
2239
  let virtualFilePath;
2210
2240
  if (importee.startsWith(virtualFilePrefix)) {
2211
2241
  virtualFilePath = importee.slice(virtualFilePrefix.length + 1);
2212
- importee = path4.resolve(root, virtualFilePath);
2242
+ importee = path6.resolve(root, virtualFilePath);
2213
2243
  } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
2214
- importee = path4.resolve(root, "." + importee);
2244
+ importee = path6.resolve(root, "." + importee);
2215
2245
  }
2216
2246
  importee = normalizePath(importee);
2217
2247
  if (!buildVirtualFilesResult) {
@@ -2220,7 +2250,7 @@ function markoRun(opts = {}) {
2220
2250
  if (virtualFiles.has(importee)) {
2221
2251
  return importee;
2222
2252
  } else if (virtualFilePath) {
2223
- const filePath = path4.resolve(__dirname, "..", virtualFilePath);
2253
+ const filePath = path6.resolve(__dirname, "..", virtualFilePath);
2224
2254
  return await this.resolve(filePath, importer, {
2225
2255
  skipSelf: true
2226
2256
  });
@@ -2234,7 +2264,8 @@ function markoRun(opts = {}) {
2234
2264
  await renderVirtualFiles(this);
2235
2265
  }
2236
2266
  if (virtualFiles.has(id)) {
2237
- return virtualFiles.get(id);
2267
+ const file = virtualFiles.get(id);
2268
+ return file;
2238
2269
  } else if (!id.startsWith(entryFilesDirPosix) && /[/\\]__marko-run__[^?/\\]+\.(js|marko)$/.exec(id)) {
2239
2270
  return "";
2240
2271
  }
@@ -2259,7 +2290,7 @@ function markoRun(opts = {}) {
2259
2290
  const builtEntries = Object.values(bundle).reduce(
2260
2291
  (acc, item) => {
2261
2292
  if (item.type === "chunk" && item.isEntry) {
2262
- acc.push(path4.join(options.dir, item.fileName));
2293
+ acc.push(path6.join(options.dir, item.fileName));
2263
2294
  }
2264
2295
  return acc;
2265
2296
  },
@@ -2287,12 +2318,12 @@ function markoRun(opts = {}) {
2287
2318
  fs3.rmSync(entryFilesDir, { recursive: true });
2288
2319
  }
2289
2320
  if ((adapter == null ? void 0 : adapter.buildEnd) && routes) {
2290
- await adapter.buildEnd(
2291
- resolvedConfig,
2292
- routes.list,
2293
- routeData.builtEntries,
2294
- routeData.sourceEntries
2295
- );
2321
+ await adapter.buildEnd({
2322
+ routes,
2323
+ config: resolvedConfig,
2324
+ builtEntries: routeData.builtEntries,
2325
+ sourceEntries: routeData.sourceEntries
2326
+ });
2296
2327
  }
2297
2328
  }
2298
2329
  }
@@ -2324,11 +2355,11 @@ async function ensureDir(dir) {
2324
2355
  }
2325
2356
  async function getPackageData(dir) {
2326
2357
  do {
2327
- const pkgPath = path4.join(dir, "package.json");
2358
+ const pkgPath = path6.join(dir, "package.json");
2328
2359
  if (fs3.existsSync(pkgPath)) {
2329
2360
  return JSON.parse(await fs3.promises.readFile(pkgPath, "utf-8"));
2330
2361
  }
2331
- } while (dir !== (dir = path4.dirname(dir)));
2362
+ } while (dir !== (dir = path6.dirname(dir)));
2332
2363
  return null;
2333
2364
  }
2334
2365
  async function resolveAdapter(root, options, log) {
@@ -2433,7 +2464,7 @@ async function getAvailablePort(port) {
2433
2464
  }
2434
2465
 
2435
2466
  // src/cli/commands.ts
2436
- var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
2467
+ var __dirname2 = path7.dirname(fileURLToPath2(import.meta.url));
2437
2468
  var defaultConfigFileBases = ["vite.config"];
2438
2469
  var defaultConfigFileExts = [".js", ".cjs", ".mjs", ".ts", ".mts"];
2439
2470
  async function preview(entry, distEntry, cwd, configFile, port, outDir, envFile, args = []) {
@@ -2462,10 +2493,10 @@ async function preview(entry, distEntry, cwd, configFile, port, outDir, envFile,
2462
2493
  if (!entry) {
2463
2494
  entry = await ((_a = adapter.getEntryFile) == null ? void 0 : _a.call(adapter));
2464
2495
  }
2465
- const dir = path5.resolve(cwd, resolvedConfig.build.outDir);
2466
- const entryFile = distEntry ? path5.join(dir, distEntry) : await findFileWithExt(dir, "index", [".mjs", ".js"]);
2496
+ const dir = path7.resolve(cwd, resolvedConfig.build.outDir);
2497
+ const entryFile = distEntry ? path7.join(dir, distEntry) : findFileWithExt(dir, "index", [".mjs", ".js"]);
2467
2498
  if (envFile) {
2468
- envFile = path5.resolve(cwd, envFile);
2499
+ envFile = path7.resolve(cwd, envFile);
2469
2500
  }
2470
2501
  const options = {
2471
2502
  cwd,
@@ -2475,13 +2506,17 @@ async function preview(entry, distEntry, cwd, configFile, port, outDir, envFile,
2475
2506
  envFile,
2476
2507
  entry
2477
2508
  };
2478
- return await adapter.startPreview(entryFile, options);
2509
+ return await adapter.startPreview({
2510
+ entry: entryFile,
2511
+ options
2512
+ });
2479
2513
  }
2480
2514
  async function dev(entry, cwd, configFile, port, envFile, args = []) {
2481
2515
  var _a;
2516
+ const root = cwd;
2482
2517
  const resolvedConfig = await resolveConfig(
2483
2518
  {
2484
- root: cwd,
2519
+ root,
2485
2520
  configFile,
2486
2521
  logLevel: "silent",
2487
2522
  plugins: [defaultConfigPlugin]
@@ -2489,7 +2524,7 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
2489
2524
  "serve"
2490
2525
  );
2491
2526
  if (envFile) {
2492
- envFile = path5.resolve(cwd, envFile);
2527
+ envFile = path7.resolve(cwd, envFile);
2493
2528
  }
2494
2529
  const [availablePort, adapter] = await Promise.all([
2495
2530
  getAvailablePort(
@@ -2508,10 +2543,14 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
2508
2543
  if (!entry) {
2509
2544
  entry = await ((_a = adapter.getEntryFile) == null ? void 0 : _a.call(adapter));
2510
2545
  }
2546
+ let plugins = adapter.plugins && await adapter.plugins({ root, command: "dev" });
2547
+ if (!isPluginIncluded(resolvedConfig)) {
2548
+ plugins = (plugins || []).concat(markoRun());
2549
+ }
2511
2550
  const config2 = {
2512
2551
  root: cwd,
2513
2552
  configFile,
2514
- plugins: isPluginIncluded(resolvedConfig) ? void 0 : markoRun()
2553
+ plugins
2515
2554
  };
2516
2555
  const options = {
2517
2556
  cwd,
@@ -2519,7 +2558,7 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
2519
2558
  port: availablePort,
2520
2559
  envFile
2521
2560
  };
2522
- return await adapter.startDev(entry, config2, options);
2561
+ return await adapter.startDev({ entry, config: config2, options });
2523
2562
  }
2524
2563
  async function build(entry, cwd, configFile, outDir, envFile) {
2525
2564
  var _a;
@@ -2543,13 +2582,17 @@ async function build(entry, cwd, configFile, outDir, envFile) {
2543
2582
  }
2544
2583
  }
2545
2584
  if (envFile) {
2546
- envFile = path5.resolve(cwd, envFile);
2585
+ envFile = path7.resolve(cwd, envFile);
2586
+ }
2587
+ let plugins = adapter.plugins && await adapter.plugins({ root, command: "build" });
2588
+ if (!isPluginIncluded(resolvedConfig)) {
2589
+ plugins = (plugins || []).concat(markoRun());
2547
2590
  }
2548
2591
  const buildConfig = setExternalAdapterOptions(
2549
2592
  {
2550
2593
  root,
2551
2594
  configFile,
2552
- plugins: isPluginIncluded(resolvedConfig) ? void 0 : markoRun(),
2595
+ plugins,
2553
2596
  build: {
2554
2597
  ssr: false,
2555
2598
  outDir
@@ -2581,7 +2624,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
2581
2624
  }
2582
2625
  function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
2583
2626
  for (const ext of extensions) {
2584
- const filePath = path5.join(dir, base + ext);
2627
+ const filePath = path7.join(dir, base + ext);
2585
2628
  if (fs5.existsSync(filePath)) {
2586
2629
  return filePath;
2587
2630
  }
@@ -2590,7 +2633,7 @@ function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
2590
2633
  }
2591
2634
  async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
2592
2635
  if (configFile) {
2593
- const configFilePath = path5.join(dir, configFile);
2636
+ const configFilePath = path7.join(dir, configFile);
2594
2637
  if (!fs5.existsSync(configFilePath)) {
2595
2638
  throw new Error(`No config file found at '${configFilePath}'`);
2596
2639
  }
@@ -2602,7 +2645,7 @@ async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
2602
2645
  return configFile;
2603
2646
  }
2604
2647
  }
2605
- return path5.join(__dirname2, "default.config.mjs");
2648
+ return path7.join(__dirname2, "default.config.mjs");
2606
2649
  }
2607
2650
  async function resolveAdapter2(config2) {
2608
2651
  const options = getExternalPluginOptions(config2);