@marko/run 0.5.12 → 0.5.14

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.
@@ -14,69 +14,53 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
14
14
  import sade from "sade";
15
15
 
16
16
  // src/cli/commands.ts
17
- import path4 from "path";
18
17
  import fs5 from "fs";
18
+ import path5 from "path";
19
19
  import { fileURLToPath as fileURLToPath2 } from "url";
20
20
  import {
21
21
  build as viteBuild,
22
22
  resolveConfig
23
23
  } from "vite";
24
24
 
25
- // src/vite/utils/config.ts
26
- var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
27
- var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
28
- function getConfig(obj, key) {
29
- return obj[key];
30
- }
31
- function setConfig(obj, key, value) {
32
- obj[key] = value;
33
- return obj;
34
- }
35
- var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
36
- var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
37
- var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
38
- var setExternalAdapterOptions = (viteConfig, value) => setConfig(viteConfig, AdapterConfigKey, value);
25
+ // src/vite/plugin.ts
26
+ import markoVitePlugin from "@marko/vite";
27
+ import browserslist from "browserslist";
28
+ import { createHash } from "crypto";
29
+ import createDebug from "debug";
30
+ import { resolveToEsbuildTarget } from "esbuild-plugin-browserslist";
31
+ import fs3 from "fs";
32
+ import { glob } from "glob";
33
+ import path4 from "path";
34
+ import { fileURLToPath } from "url";
35
+ import { buildErrorMessage, mergeConfig } from "vite";
39
36
 
40
- // src/vite/utils/server.ts
41
- import net from "net";
42
- import cp from "child_process";
43
- import { parse, config } from "dotenv";
44
- import fs from "fs";
45
- import cluster from "cluster";
46
- async function getConnection(port) {
47
- return new Promise((resolve) => {
48
- const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
49
- connection.end();
50
- resolve(null);
51
- }).on("connect", () => {
52
- resolve(connection);
53
- });
54
- });
37
+ // src/adapter/utils.ts
38
+ import kleur from "kleur";
39
+ import supporsColor from "supports-color";
40
+ function stripAnsi(string) {
41
+ return string.replace(
42
+ /([\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><])/g,
43
+ ""
44
+ );
55
45
  }
56
- async function isPortInUse(port) {
57
- return Boolean(await getConnection(port));
46
+ function cleanStack(stack) {
47
+ return stack.split(/\n/).filter((l) => /^\s*at/.test(l)).join("\n");
58
48
  }
59
- async function getAvailablePort(port) {
60
- if (port && !await isPortInUse(port)) {
61
- return port;
62
- }
63
- return new Promise((resolve) => {
64
- const server = net.createServer().listen(0, () => {
65
- const { port: port2 } = server.address();
66
- server.close(() => resolve(port2));
67
- });
68
- });
49
+ function prepareError(err) {
50
+ var _a;
51
+ return {
52
+ message: stripAnsi(err.message),
53
+ stack: stripAnsi(cleanStack(err.stack || "")),
54
+ id: err.id,
55
+ frame: stripAnsi(err.frame || ""),
56
+ plugin: err.plugin,
57
+ pluginCode: (_a = err.pluginCode) == null ? void 0 : _a.toString(),
58
+ loc: err.loc
59
+ };
69
60
  }
70
61
 
71
- // src/vite/plugin.ts
72
- import path3 from "path";
73
- import fs4 from "fs";
74
- import { glob } from "glob";
75
- import { fileURLToPath } from "url";
76
- import browserslist from "browserslist";
77
- import { resolveToEsbuildTarget } from "esbuild-plugin-browserslist";
78
- import { buildErrorMessage, mergeConfig } from "vite";
79
- import markoVitePlugin from "@marko/vite";
62
+ // src/vite/codegen/index.ts
63
+ import path from "path";
80
64
 
81
65
  // src/vite/constants.ts
82
66
  var markoRunFilePrefix = "__marko-run__";
@@ -93,702 +77,186 @@ var RoutableFileTypes = {
93
77
  Error: "500"
94
78
  };
95
79
 
96
- // src/vite/routes/vdir.ts
97
- var _dirs, _pathlessDirs;
98
- var _VDir = class _VDir {
99
- constructor(parent, segment, source) {
100
- __privateAdd(this, _dirs);
101
- __privateAdd(this, _pathlessDirs);
102
- __publicField(this, "parent");
103
- __publicField(this, "source");
104
- __publicField(this, "path");
105
- __publicField(this, "fullPath");
106
- __publicField(this, "segment");
107
- __publicField(this, "files");
108
- if (!parent || !segment) {
109
- this.parent = null;
110
- this.source = null;
111
- this.path = "/";
112
- this.fullPath = "/";
113
- this.segment = {
114
- raw: "",
115
- name: ""
116
- };
117
- } else {
118
- this.parent = parent;
119
- this.source = source;
120
- this.path = parent.path + (parent.path === "/" ? segment.name : `/${segment.name}`);
121
- this.fullPath = parent.fullPath + (parent.fullPath === "/" ? segment.name : `/${segment.name}`);
122
- if (segment.param) {
123
- this.fullPath += segment.param;
124
- }
125
- this.segment = segment;
126
- }
127
- }
128
- get pathInfo() {
129
- const value = {
130
- id: "/",
131
- path: "/",
132
- segments: []
133
- };
134
- let sep = "";
135
- for (const { segment } of this) {
136
- const { type, name, param } = segment;
137
- if (name && type !== "_") {
138
- value.id += sep + (type || name);
139
- value.path += sep + name;
140
- value.isEnd = type === "$$";
141
- if (param) {
142
- value.path += param;
143
- let index = type === "$$" ? null : value.segments.length;
144
- if (!value.params) {
145
- value.params = { [param]: index };
146
- } else if (!(param in value.params)) {
147
- value.params[param] = index;
148
- }
149
- }
150
- value.segments.push(name);
151
- sep = "/";
152
- }
153
- }
154
- Object.defineProperty(this, "pathInfo", {
155
- value,
156
- enumerable: true
157
- });
158
- return value;
80
+ // src/vite/utils/route.ts
81
+ function getVerbs(route) {
82
+ var _a, _b;
83
+ const verbs = ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.slice()) || [];
84
+ if (route.page && !verbs.includes("get")) {
85
+ verbs.unshift("get");
159
86
  }
160
- addDir(path5, segment) {
161
- const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
162
- if (!map.has(segment.name)) {
163
- const dir = new _VDir(this, segment, path5);
164
- map.set(segment.name, dir);
165
- return dir;
87
+ return verbs;
88
+ }
89
+ function hasVerb(route, verb) {
90
+ var _a, _b;
91
+ return verb === "get" && route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes(verb));
92
+ }
93
+
94
+ // src/vite/codegen/writer.ts
95
+ function createWriter(sink, options) {
96
+ let buffer = "";
97
+ let indentLevel = 0;
98
+ let indentString = "";
99
+ let firstOpenIndex = 0;
100
+ const branches = [];
101
+ const openWriters = /* @__PURE__ */ new Map();
102
+ function write(data) {
103
+ if (!writer.__isActive) {
104
+ throw new Error("Cannot write to branch that has been joined");
166
105
  }
167
- return map.get(segment.name);
168
- }
169
- addFile(file) {
170
- if (!this.files) {
171
- this.files = /* @__PURE__ */ new Map();
172
- this.files.set(file.type, file);
173
- } else if (!this.files.has(file.type)) {
174
- this.files.set(file.type, file);
106
+ if (openWriters.size) {
107
+ buffer += data;
175
108
  } else {
176
- const existing = this.files.get(file.type);
177
- if (existing !== file) {
178
- throw new Error(
179
- `Duplicate file type '${file.type}' added at path '${this.path}'. File '${file.importPath}' collides with '${existing.importPath}'.`
180
- );
181
- } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
182
- throw new Error(
183
- `Ambiguous path definition: route '${this.path}' is defined multiple times by ${file.importPath}`
184
- );
185
- }
186
- throw new Error(
187
- `Ambiguous path definition: file '${this.path}' is included multiple times by ${file.importPath}`
188
- );
189
- }
190
- }
191
- *dirs() {
192
- if (__privateGet(this, _pathlessDirs)) {
193
- yield* __privateGet(this, _pathlessDirs).values();
194
- }
195
- if (__privateGet(this, _dirs)) {
196
- yield* __privateGet(this, _dirs).values();
197
- }
198
- }
199
- *[Symbol.iterator]() {
200
- if (this.parent) {
201
- yield* this.parent;
109
+ sink(data);
202
110
  }
203
- yield this;
111
+ return writer;
204
112
  }
205
- static addPaths(roots, paths) {
206
- const dirs = [];
207
- const unique = /* @__PURE__ */ new Set();
208
- for (const root of roots) {
209
- for (const path5 of paths) {
210
- let dir = root;
211
- for (const segment of path5.segments) {
212
- dir = dir.addDir(path5, segment);
113
+ const writer = {
114
+ __isActive: true,
115
+ get indent() {
116
+ return indentLevel;
117
+ },
118
+ set indent(value) {
119
+ if (options == null ? void 0 : options.indentWith) {
120
+ if (value < 0) {
121
+ value = 0;
213
122
  }
214
- if (unique.has(dir.path)) {
215
- const sources = /* @__PURE__ */ new Set();
216
- let sourcePath = "";
217
- for (const { source } of dir) {
218
- if (source && !sources.has(source.source)) {
219
- sources.add(source.source);
220
- sourcePath += source.source + "/";
221
- }
222
- }
223
- throw new Error(
224
- `Ambiguous directory structure: '${sourcePath}${path5.source}' defines '${dir.path}' multiple times.`
225
- );
226
- } else {
227
- unique.add(dir.path);
228
- dirs.push(dir);
123
+ if (value !== indentLevel) {
124
+ indentLevel = value;
125
+ indentString = options.indentWith.repeat(indentLevel);
229
126
  }
230
127
  }
231
- }
232
- return dirs;
233
- }
234
- };
235
- _dirs = new WeakMap();
236
- _pathlessDirs = new WeakMap();
237
- var VDir = _VDir;
238
-
239
- // src/vite/routes/parse.ts
240
- function parseFlatRoute(pattern) {
241
- if (!pattern) throw new Error("Empty pattern");
242
- const len = pattern.length;
243
- let i = 0;
244
- return parse2([
245
- {
246
- id: "/",
247
- segments: [],
248
- source: pattern
249
- }
250
- ]);
251
- function parse2(basePaths, group) {
252
- const pathMap = /* @__PURE__ */ new Map();
253
- const delimiters = group ? ").," : ".,";
254
- let charCode;
255
- let segmentStart = i;
256
- let type;
257
- let current;
258
- do {
259
- charCode = pattern.charCodeAt(i);
260
- if (charCode === 41 && group) {
261
- break;
262
- } else if (charCode === 44) {
263
- if (!current) {
264
- segmentEnd(
265
- basePaths.map((path5) => ({
266
- ...path5,
267
- segments: path5.segments.slice()
268
- })),
269
- "",
270
- "_",
271
- pathMap
272
- );
273
- } else {
274
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
275
- }
276
- current = void 0;
277
- type = void 0;
278
- segmentStart = ++i;
279
- } else if (charCode === 46) {
280
- if (current) {
281
- segmentEnd(current, pattern.slice(segmentStart, i), type);
282
- }
283
- type = void 0;
284
- segmentStart = ++i;
285
- } else if (charCode === 40) {
286
- const groupPaths = parse2(current || basePaths, ++i);
287
- if (groupPaths.length) {
288
- current = groupPaths;
289
- }
290
- segmentStart = ++i;
291
- } else {
292
- if (charCode === 95) {
293
- type = "_";
294
- } else if (charCode === 36) {
295
- type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
296
- }
297
- current ?? (current = basePaths.map((path5) => ({
298
- ...path5,
299
- segments: path5.segments.slice()
300
- })));
301
- i = len;
302
- for (const char of delimiters) {
303
- const index = pattern.indexOf(char, segmentStart);
304
- if (index >= 0 && index < i) {
305
- i = index;
306
- }
307
- }
308
- }
309
- } while (i < len);
310
- if (group && charCode !== 41) {
311
- throw new Error(
312
- `Invalid route pattern: group was not closed '${pattern.slice(
313
- group
314
- )}' in '${pattern}'`
315
- );
316
- }
317
- if (!current) {
318
- segmentEnd(
319
- basePaths.map((path5) => ({
320
- ...path5,
321
- segments: path5.segments.slice()
322
- })),
323
- "",
324
- "_",
325
- pathMap
326
- );
327
- } else {
328
- segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
329
- }
330
- return [...pathMap.values()];
331
- }
332
- function segmentEnd(paths, raw, type, map) {
333
- let segment;
334
- if (raw) {
335
- segment = {
336
- raw,
337
- name: raw,
338
- type
339
- };
340
- if (type === "$" || type === "$$") {
341
- segment.name = type;
342
- segment.param = raw.slice(type.length);
128
+ },
129
+ write(data, indent = false) {
130
+ if (indent && indentString) {
131
+ write(indentString);
343
132
  }
344
- }
345
- for (const path5 of paths) {
346
- if (segment) {
347
- if (path5.isCatchall) {
348
- throw new Error(
349
- `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
350
- 0,
351
- i
352
- )}' in '${pattern}'.`
353
- );
354
- }
355
- path5.segments.push(segment);
356
- path5.id += path5.id === "/" ? segment.name : `/${segment.name}`;
357
- if (type === "$$") {
358
- path5.isCatchall = true;
133
+ return write(data);
134
+ },
135
+ writeLines(...lines) {
136
+ for (const line of lines) {
137
+ if (line) {
138
+ writer.write(line, true);
359
139
  }
140
+ writer.write("\n");
360
141
  }
361
- if (map) {
362
- if (map.has(path5.id)) {
363
- const existing = map.get(path5.id);
364
- const existingExpansion = existing.segments.map((s) => s.raw).join(".");
365
- const currentExpansion = path5.segments.map((s) => s.raw).join(".");
366
- throw new Error(
367
- `Invalid route pattern: route '${path5.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
368
- );
142
+ return writer;
143
+ },
144
+ writeBlockStart(data) {
145
+ writer.writeLines(data).indent++;
146
+ return writer;
147
+ },
148
+ writeBlockEnd(data = "}") {
149
+ writer.indent--;
150
+ writer.writeLines(data);
151
+ return writer;
152
+ },
153
+ writeBlock(start, lines, end) {
154
+ return writer.writeBlockStart(start).writeLines(...lines).writeBlockEnd(end);
155
+ },
156
+ branch(name) {
157
+ const existing = openWriters.get(name);
158
+ if (existing) {
159
+ return existing;
160
+ }
161
+ const branch = {
162
+ buffer,
163
+ writer: createWriter(
164
+ (data) => {
165
+ branch.buffer += data;
166
+ },
167
+ {
168
+ ...options,
169
+ onJoin() {
170
+ openWriters.delete(name);
171
+ for (let i = firstOpenIndex; i < branches.length; i++) {
172
+ const b = branches[i];
173
+ if (!b) {
174
+ continue;
175
+ } else if (b.writer.__isActive) {
176
+ break;
177
+ }
178
+ sink(b.buffer);
179
+ branches[i] = null;
180
+ firstOpenIndex++;
181
+ }
182
+ if (!openWriters.size) {
183
+ sink(buffer);
184
+ buffer = "";
185
+ }
186
+ }
187
+ }
188
+ )
189
+ };
190
+ branch.writer.indent = indentLevel;
191
+ openWriters.set(name, branch.writer);
192
+ branches.push(branch);
193
+ buffer = "";
194
+ return branch.writer;
195
+ },
196
+ join(recursive) {
197
+ var _a;
198
+ if (writer.__isActive) {
199
+ if (openWriters.size) {
200
+ if (recursive) {
201
+ for (const branch of openWriters.values()) {
202
+ branch.join(true);
203
+ }
204
+ } else {
205
+ throw new Error(
206
+ `Cannot join a Writer with un-joined branches - use the \`recursive\` argument to join all open branches`
207
+ );
208
+ }
369
209
  }
370
- map.set(path5.id, path5);
210
+ buffer && sink(buffer);
211
+ writer.__isActive = false;
212
+ (_a = options == null ? void 0 : options.onJoin) == null ? void 0 : _a.call(options, writer);
371
213
  }
372
214
  }
373
- }
215
+ };
216
+ return writer;
217
+ }
218
+ function createStringWriter(opts) {
219
+ let code = "";
220
+ const writer = createWriter(
221
+ (data) => {
222
+ code += data;
223
+ },
224
+ { indentWith: " ", ...opts }
225
+ );
226
+ return Object.assign(writer, {
227
+ end() {
228
+ writer.join(true);
229
+ return code;
230
+ }
231
+ });
374
232
  }
375
233
 
376
- // src/vite/routes/builder.ts
377
- var markoFiles = `(${RoutableFileTypes.Layout}|${RoutableFileTypes.Page}|${RoutableFileTypes.NotFound}|${RoutableFileTypes.Error})\\.(?:.*\\.)?(marko)`;
378
- var nonMarkoFiles = `(${RoutableFileTypes.Middleware}|${RoutableFileTypes.Handler}|${RoutableFileTypes.Meta})\\.(?:.*\\.)?(.+)`;
379
- var routeableFileRegex = new RegExp(
380
- `[+](?:${markoFiles}|${nonMarkoFiles})$`,
381
- "i"
382
- );
383
- function matchRoutableFile(filename) {
384
- const match = filename.match(routeableFileRegex);
385
- return match && (match[1] || match[3]).toLowerCase();
234
+ // src/vite/codegen/index.ts
235
+ function renderRouteTemplate(route, getRelativePath) {
236
+ if (!route.page) {
237
+ throw new Error(`Route ${route.key} has no page to render`);
238
+ }
239
+ return renderEntryTemplate(
240
+ route.entryName,
241
+ [...route.layouts, route.page].map(
242
+ (file) => getRelativePath(file.importPath)
243
+ ),
244
+ route.key === RoutableFileTypes.Error ? ["error"] : []
245
+ );
386
246
  }
387
- function isSpecialType(type) {
388
- return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
389
- }
390
- async function buildRoutes(sources) {
391
- const uniqueRoutes = /* @__PURE__ */ new Map();
392
- const routes = [];
393
- const special = {};
394
- const middlewares = /* @__PURE__ */ new Set();
395
- const unusedFiles = /* @__PURE__ */ new Set();
396
- const currentLayouts = /* @__PURE__ */ new Set();
397
- const currentMiddleware = /* @__PURE__ */ new Set();
398
- const root = new VDir();
399
- const dirStack = [];
400
- let basePath;
401
- let importPrefix;
402
- let activeDirs;
403
- let isBaseDir;
404
- let nextFileId = 1;
405
- let nextRouteIndex = 1;
406
- const walkOptions = {
407
- onEnter({ name }) {
408
- const prevDirStackLength = dirStack.length;
409
- if (isBaseDir) {
410
- isBaseDir = false;
411
- if (!basePath) {
412
- return;
413
- }
414
- name = basePath;
415
- } else {
416
- dirStack.push(name);
417
- }
418
- const previousDirs = activeDirs;
419
- const paths = parseFlatRoute(name);
420
- activeDirs = VDir.addPaths(previousDirs, paths);
421
- return () => {
422
- activeDirs = previousDirs;
423
- dirStack.length = prevDirStackLength;
424
- };
425
- },
426
- onFile({ name, path: path5 }) {
427
- const match = name.match(routeableFileRegex);
428
- if (!match) {
429
- return;
430
- }
431
- const type = (match[1] || match[3]).toLowerCase();
432
- if (dirStack.length && isSpecialType(type)) {
433
- console.warn(
434
- `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path5}`
435
- );
436
- return;
437
- }
438
- let dirs = activeDirs;
439
- if (match.index) {
440
- const paths = parseFlatRoute(name.slice(0, match.index));
441
- dirs = VDir.addPaths(activeDirs, paths);
442
- }
443
- const dirPath = dirStack.join("/");
444
- const relativePath = dirPath ? `${dirPath}/${name}` : name;
445
- const file = {
446
- id: String(nextFileId++),
447
- name,
448
- type,
449
- filePath: path5,
450
- relativePath,
451
- importPath: `${importPrefix}/${relativePath}`,
452
- verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
453
- };
454
- for (const dir of dirs) {
455
- dir.addFile(file);
456
- }
457
- }
458
- };
459
- if (!Array.isArray(sources)) {
460
- sources = [sources];
461
- }
462
- for (const source of sources) {
463
- importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
464
- basePath = source.basePath || "";
465
- activeDirs = [root];
466
- isBaseDir = true;
467
- await source.walker(walkOptions);
468
- }
469
- traverse(root);
470
- return {
471
- list: routes,
472
- middleware: [...middlewares],
473
- special
474
- };
475
- function traverse(dir) {
476
- let middleware;
477
- let layout;
478
- if (dir.files) {
479
- middleware = dir.files.get(RoutableFileTypes.Middleware);
480
- layout = dir.files.get(RoutableFileTypes.Layout);
481
- const handler = dir.files.get(RoutableFileTypes.Handler);
482
- const page = dir.files.get(RoutableFileTypes.Page);
483
- let hasSpecial = false;
484
- if (middleware) {
485
- if (currentMiddleware.has(middleware)) {
486
- middleware = void 0;
487
- } else {
488
- currentMiddleware.add(middleware);
489
- unusedFiles.add(middleware);
490
- }
491
- }
492
- if (layout) {
493
- if (currentLayouts.has(layout)) {
494
- layout = void 0;
495
- } else {
496
- currentLayouts.add(layout);
497
- unusedFiles.add(layout);
498
- }
499
- }
500
- if (page || handler) {
501
- const path5 = dir.pathInfo;
502
- if (uniqueRoutes.has(path5.id)) {
503
- const existing = uniqueRoutes.get(path5.id);
504
- const route = routes[existing.index];
505
- const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
506
- const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
507
- throw new Error(`Duplicate routes for path '${path5.path}' were defined. A route established by:
508
- ${existingFiles.join(" and ")} via '${existing.dir.path}'
509
- collides with
510
- ${currentFiles.join(" and ")} via '${dir.path}'
511
- `);
512
- }
513
- uniqueRoutes.set(path5.id, { dir, index: routes.length });
514
- routes.push({
515
- index: nextRouteIndex++,
516
- key: dir.fullPath,
517
- paths: [path5],
518
- middleware: [...currentMiddleware],
519
- layouts: page ? [...currentLayouts] : [],
520
- meta: dir.files.get(RoutableFileTypes.Meta),
521
- page,
522
- handler,
523
- entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
524
- });
525
- }
526
- if (dir === root) {
527
- for (const [type, file] of dir.files) {
528
- if (isSpecialType(type)) {
529
- hasSpecial = true;
530
- special[type] = {
531
- index: 0,
532
- key: type,
533
- paths: [],
534
- middleware: [],
535
- layouts: [...currentLayouts],
536
- page: file,
537
- entryName: `${markoRunFilePrefix}special.${type}`
538
- };
539
- }
540
- }
541
- }
542
- if (handler || page) {
543
- for (const middleware2 of currentMiddleware) {
544
- middlewares.add(middleware2);
545
- unusedFiles.delete(middleware2);
546
- }
547
- }
548
- if (page || hasSpecial) {
549
- for (const layout2 of currentLayouts) {
550
- unusedFiles.delete(layout2);
551
- }
552
- }
553
- }
554
- if (dir.dirs) {
555
- for (const child of dir.dirs()) {
556
- traverse(child);
557
- }
558
- }
559
- if (middleware) {
560
- currentMiddleware.delete(middleware);
561
- }
562
- if (layout) {
563
- currentLayouts.delete(layout);
564
- }
565
- }
566
- }
567
-
568
- // src/vite/routes/walk.ts
569
- import fs2 from "fs";
570
- import path from "path";
571
- function createFSWalker(dir) {
572
- return async function walkFS({
573
- onEnter,
574
- onFile,
575
- onDir,
576
- maxDepth = 50
577
- }) {
578
- async function walk(dir2, depth) {
579
- const onExit = onEnter == null ? void 0 : onEnter(dir2);
580
- if (onExit !== false) {
581
- const dirs = [];
582
- const entries = await fs2.promises.readdir(dir2.path, {
583
- withFileTypes: true
584
- });
585
- const prefix = dir2.path + path.sep;
586
- for (const entry of entries) {
587
- const walkEntry = {
588
- name: entry.name,
589
- path: prefix + entry.name
590
- };
591
- if (entry.isDirectory()) {
592
- dirs.push(walkEntry);
593
- } else {
594
- onFile == null ? void 0 : onFile(walkEntry);
595
- }
596
- }
597
- if ((onDir == null ? void 0 : onDir()) !== false && --depth > 0) {
598
- for (const entry of dirs) {
599
- await walk(entry, depth);
600
- }
601
- }
602
- onExit == null ? void 0 : onExit();
603
- }
604
- }
605
- await walk(
606
- {
607
- path: dir,
608
- name: path.basename(dir)
609
- },
610
- maxDepth
611
- );
612
- };
613
- }
614
-
615
- // src/vite/codegen/writer.ts
616
- function createWriter(sink, options) {
617
- let buffer = "";
618
- let indentLevel = 0;
619
- let indentString = "";
620
- let firstOpenIndex = 0;
621
- const branches = [];
622
- const openWriters = /* @__PURE__ */ new Map();
623
- function write(data) {
624
- if (!writer.__isActive) {
625
- throw new Error("Cannot write to branch that has been joined");
626
- }
627
- if (openWriters.size) {
628
- buffer += data;
629
- } else {
630
- sink(data);
631
- }
632
- return writer;
633
- }
634
- const writer = {
635
- __isActive: true,
636
- get indent() {
637
- return indentLevel;
638
- },
639
- set indent(value) {
640
- if (options == null ? void 0 : options.indentWith) {
641
- if (value < 0) {
642
- value = 0;
643
- }
644
- if (value !== indentLevel) {
645
- indentLevel = value;
646
- indentString = options.indentWith.repeat(indentLevel);
647
- }
648
- }
649
- },
650
- write(data, indent = false) {
651
- if (indent && indentString) {
652
- write(indentString);
653
- }
654
- return write(data);
655
- },
656
- writeLines(...lines) {
657
- for (const line of lines) {
658
- if (line) {
659
- writer.write(line, true);
660
- }
661
- writer.write("\n");
662
- }
663
- return writer;
664
- },
665
- writeBlockStart(data) {
666
- writer.writeLines(data).indent++;
667
- return writer;
668
- },
669
- writeBlockEnd(data = "}") {
670
- writer.indent--;
671
- writer.writeLines(data);
672
- return writer;
673
- },
674
- writeBlock(start, lines, end) {
675
- return writer.writeBlockStart(start).writeLines(...lines).writeBlockEnd(end);
676
- },
677
- branch(name) {
678
- let existing = openWriters.get(name);
679
- if (existing) {
680
- return existing;
681
- }
682
- const branch = {
683
- buffer,
684
- writer: createWriter(
685
- (data) => {
686
- branch.buffer += data;
687
- },
688
- {
689
- ...options,
690
- onJoin() {
691
- openWriters.delete(name);
692
- for (let i = firstOpenIndex; i < branches.length; i++) {
693
- const b = branches[i];
694
- if (!b) {
695
- continue;
696
- } else if (b.writer.__isActive) {
697
- break;
698
- }
699
- sink(b.buffer);
700
- branches[i] = null;
701
- firstOpenIndex++;
702
- }
703
- if (!openWriters.size) {
704
- sink(buffer);
705
- buffer = "";
706
- }
707
- }
708
- }
709
- )
710
- };
711
- branch.writer.indent = indentLevel;
712
- openWriters.set(name, branch.writer);
713
- branches.push(branch);
714
- buffer = "";
715
- return branch.writer;
716
- },
717
- join(recursive) {
718
- var _a;
719
- if (writer.__isActive) {
720
- if (openWriters.size) {
721
- if (recursive) {
722
- for (const branch of openWriters.values()) {
723
- branch.join(true);
724
- }
725
- } else {
726
- throw new Error(
727
- `Cannot join a Writer with un-joined branches - use the \`recursive\` argument to join all open branches`
728
- );
729
- }
730
- }
731
- buffer && sink(buffer);
732
- writer.__isActive = false;
733
- (_a = options == null ? void 0 : options.onJoin) == null ? void 0 : _a.call(options, writer);
734
- }
735
- }
736
- };
737
- return writer;
738
- }
739
- function createStringWriter(opts) {
740
- let code = "";
741
- const writer = createWriter((data) => {
742
- code += data;
743
- }, { indentWith: " ", ...opts });
744
- return Object.assign(writer, {
745
- end() {
746
- writer.join(true);
747
- return code;
748
- }
749
- });
750
- }
751
-
752
- // src/vite/utils/route.ts
753
- function getVerbs(route) {
754
- var _a, _b;
755
- const verbs = ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.slice()) || [];
756
- if (route.page && !verbs.includes("get")) {
757
- verbs.unshift("get");
758
- }
759
- return verbs;
760
- }
761
- function hasVerb(route, verb) {
762
- var _a, _b;
763
- return verb === "get" && route.page || ((_b = (_a = route.handler) == null ? void 0 : _a.verbs) == null ? void 0 : _b.includes(verb));
764
- }
765
-
766
- // src/vite/codegen/index.ts
767
- function renderRouteTemplate(route, getRelativePath) {
768
- if (!route.page) {
769
- throw new Error(`Route ${route.key} has no page to render`);
770
- }
771
- return renderEntryTemplate(
772
- route.entryName,
773
- [...route.layouts, route.page].map(
774
- (file) => getRelativePath(file.importPath)
775
- ),
776
- route.key === RoutableFileTypes.Error ? ["error"] : []
777
- );
778
- }
779
- function renderEntryTemplate(name, files, pageInputs = []) {
780
- if (!name) {
781
- throw new Error(`Invalid argument - 'name' cannot be empty`);
782
- }
783
- if (!files.length) {
784
- throw new Error(`Invalid argument - 'files' cannot be empty`);
785
- }
786
- const writer = createStringWriter();
787
- writer.writeLines(`// ${name}.marko`);
788
- writer.branch("imports");
789
- writer.writeLines("");
790
- writeEntryTemplateTag(writer, files, pageInputs);
791
- return writer.end();
247
+ function renderEntryTemplate(name, files, pageInputs = []) {
248
+ if (!name) {
249
+ throw new Error(`Invalid argument - 'name' cannot be empty`);
250
+ }
251
+ if (!files.length) {
252
+ throw new Error(`Invalid argument - 'files' cannot be empty`);
253
+ }
254
+ const writer = createStringWriter();
255
+ writer.writeLines(`// ${name}.marko`);
256
+ writer.branch("imports");
257
+ writer.writeLines("");
258
+ writeEntryTemplateTag(writer, files, pageInputs);
259
+ return writer.end();
792
260
  }
793
261
  function writeEntryTemplateTag(writer, [file, ...rest], pageInputs, index = 1) {
794
262
  if (file) {
@@ -814,672 +282,1193 @@ function renderRouteEntry(route, entriesDir) {
814
282
  `Route ${key} doesn't have a handler or page for any HTTP verbs`
815
283
  );
816
284
  }
817
- const writer = createStringWriter();
818
- writer.writeLines(`// ${virtualFilePrefix}/${entryName}.js`);
819
- const imports = writer.branch("imports");
820
- const runtimeImports = [];
821
- if (handler) {
822
- runtimeImports.push("normalize");
823
- }
824
- if (handler || middleware.length) {
825
- runtimeImports.push("call");
285
+ const writer = createStringWriter();
286
+ writer.writeLines(`// ${virtualFilePrefix}/${entryName}.js`);
287
+ const imports = writer.branch("imports");
288
+ const runtimeImports = [];
289
+ if (handler) {
290
+ runtimeImports.push("normalize");
291
+ }
292
+ if (handler || middleware.length) {
293
+ runtimeImports.push("call");
294
+ }
295
+ if (!page || verbs.length > 1) {
296
+ runtimeImports.push("noContent");
297
+ }
298
+ if (page) {
299
+ runtimeImports.push("pageResponse");
300
+ }
301
+ if (runtimeImports.length) {
302
+ imports.writeLines(
303
+ `import { ${runtimeImports.join(
304
+ ", "
305
+ )} } from '${virtualFilePrefix}/runtime/internal';`
306
+ );
307
+ }
308
+ if (middleware.length) {
309
+ const names = middleware.map((m) => `mware${m.id}`);
310
+ imports.writeLines(
311
+ `import { ${names.join(
312
+ ", "
313
+ )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
314
+ );
315
+ }
316
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
317
+ writer.writeLines("");
318
+ const names = [];
319
+ for (const verb of handler.verbs) {
320
+ const importName = verb.toUpperCase();
321
+ names.push(importName);
322
+ writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
323
+ }
324
+ imports.writeLines(
325
+ `import { ${names.join(", ")} } from './${handler.importPath}';`
326
+ );
327
+ }
328
+ if (page) {
329
+ const pageNameIndex = page.name.indexOf("+page");
330
+ const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
331
+ const importPath = route.layouts.length ? `./${path.posix.join(entriesDir, page.relativePath, "..", pageNamePrefix + "route.marko")}` : `./${page.importPath}`;
332
+ imports.writeLines(`import page from '${importPath}${serverEntryQuery}';`);
333
+ }
334
+ if (meta) {
335
+ imports.writeLines(
336
+ `export { default as meta${index} } from './${meta.importPath}';`
337
+ );
338
+ }
339
+ for (const verb of verbs) {
340
+ writeRouteEntryHandler(writer, route, verb);
341
+ }
342
+ return writer.end();
343
+ }
344
+ function writePageResponse(writer, wrapFn) {
345
+ writer.writeLines(
346
+ `${wrapFn ? `const ${wrapFn} = () =>` : `return`} pageResponse(page, buildInput());`
347
+ );
348
+ }
349
+ function writeMiddleware(writer, middleware, next, wrapFn) {
350
+ if (wrapFn) {
351
+ writer.writeLines(
352
+ `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
353
+ );
354
+ } else {
355
+ writer.writeLines(`return call(${middleware}, ${next}, context);`);
356
+ }
357
+ }
358
+ function writeRouteEntryHandler(writer, route, verb) {
359
+ var _a;
360
+ const { key, index, page, handler, middleware } = route;
361
+ const len = middleware.length;
362
+ let nextName;
363
+ let currentName;
364
+ let hasBody = false;
365
+ writer.writeLines("");
366
+ if (page) {
367
+ writer.writeBlockStart(
368
+ `export async function ${verb}${index}(context, buildInput) {`
369
+ );
370
+ } else {
371
+ writer.writeBlockStart(`export async function ${verb}${index}(context) {`);
372
+ }
373
+ const continuations = writer.branch("cont");
374
+ if (page && verb === "get") {
375
+ currentName = "__page";
376
+ if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
377
+ const name = `${verb}Handler`;
378
+ writePageResponse(continuations, currentName);
379
+ if (len) {
380
+ nextName = currentName;
381
+ currentName = `__${name}`;
382
+ writeMiddleware(continuations, name, nextName, currentName);
383
+ } else {
384
+ writeMiddleware(writer, name, currentName);
385
+ hasBody = true;
386
+ }
387
+ } else if (len) {
388
+ writePageResponse(continuations, currentName);
389
+ nextName = currentName;
390
+ } else {
391
+ writePageResponse(continuations);
392
+ hasBody = true;
393
+ }
394
+ } else if (handler) {
395
+ const name = `${verb}Handler`;
396
+ currentName = `__${name}`;
397
+ nextName = "noContent";
398
+ if (len) {
399
+ writeMiddleware(continuations, name, nextName, currentName);
400
+ } else {
401
+ writeMiddleware(writer, name, nextName);
402
+ hasBody = true;
403
+ }
404
+ } else {
405
+ throw new Error(`Route ${key} has no handler for ${verb} requests`);
406
+ }
407
+ if (!hasBody) {
408
+ let i = len;
409
+ while (i--) {
410
+ const { id } = middleware[i];
411
+ const name = `mware${id}`;
412
+ nextName = currentName;
413
+ currentName = i ? `__${name}` : "";
414
+ writeMiddleware(continuations, name, nextName, currentName);
415
+ }
416
+ }
417
+ continuations.join();
418
+ writer.writeBlockEnd("}");
419
+ }
420
+ function renderRouter(routes, entriesDir, options = {
421
+ trailingSlashes: "RedirectWithout"
422
+ }) {
423
+ const writer = createStringWriter();
424
+ writer.writeLines(`// @marko/run/router`);
425
+ const imports = writer.branch("imports");
426
+ imports.writeLines(
427
+ `import { NotHandled, NotMatched, createContext } from '${virtualFilePrefix}/runtime/internal';`
428
+ );
429
+ for (const route of routes.list) {
430
+ const verbs = getVerbs(route);
431
+ const names = verbs.map((verb) => `${verb}${route.index}`);
432
+ route.meta && names.push(`meta${route.index}`);
433
+ imports.writeLines(
434
+ `import { ${names.join(", ")} } from '${virtualFilePrefix}/${route.entryName}.js';`
435
+ );
436
+ }
437
+ for (const route of Object.values(routes.special)) {
438
+ const importPath = route.layouts.length ? `./${path.posix.join(entriesDir, route.page.relativePath, "..", `route.${route.key}.marko`)}` : `./${route.page.importPath}`;
439
+ imports.writeLines(
440
+ `import page${route.key} from '${importPath}${serverEntryQuery}';`
441
+ );
826
442
  }
827
- if (!page || verbs.length > 1) {
828
- runtimeImports.push("noContent");
443
+ writer.writeLines(
444
+ `
445
+ globalThis.__marko_run__ = { match, fetch, invoke };
446
+ `
447
+ ).writeBlockStart(`export function match(method, pathname) {`).writeLines(
448
+ `if (!pathname) {
449
+ pathname = '/';
450
+ } else if (pathname.charAt(0) !== '/') {
451
+ pathname = '/' + pathname;
452
+ }`
453
+ ).writeBlockStart(`switch (method) {`);
454
+ for (const verb of httpVerbs) {
455
+ const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
456
+ if (filteredRoutes.length) {
457
+ const trie = createRouteTrie(filteredRoutes);
458
+ writer.writeLines(`case '${verb.toUpperCase()}':`);
459
+ writer.writeBlockStart(`case '${verb.toLowerCase()}': {`);
460
+ writeRouterVerb(writer, trie, verb);
461
+ writer.writeBlockEnd("}");
462
+ }
829
463
  }
830
- if (page) {
831
- runtimeImports.push("pageResponse");
464
+ writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
465
+ writer.writeLines("").writeBlockStart(
466
+ "export async function invoke(route, request, platform, url) {"
467
+ ).writeLines(
468
+ "const [context, buildInput] = createContext(route, request, platform, url);"
469
+ );
470
+ const hasErrorPage = Boolean(routes.special[RoutableFileTypes.Error]);
471
+ if (hasErrorPage) {
472
+ writer.writeBlockStart("try {");
832
473
  }
833
- if (runtimeImports.length) {
474
+ writer.writeBlockStart("if (route) {").writeBlockStart("try {").writeLines(
475
+ "const response = await route.handler(context, buildInput);",
476
+ "if (response) return response;"
477
+ ).indent--;
478
+ writer.writeBlockStart("} catch (error) {").writeLines(
479
+ "if (error === NotHandled) return;",
480
+ "if (error !== NotMatched) throw error;"
481
+ ).writeBlockEnd("}").writeBlockEnd("}");
482
+ if (routes.special[RoutableFileTypes.NotFound]) {
834
483
  imports.writeLines(
835
- `import { ${runtimeImports.join(
836
- ", "
837
- )} } from '${virtualFilePrefix}/runtime/internal';`
484
+ `
485
+ const page404ResponseInit = {
486
+ status: 404,
487
+ headers: { "content-type": "text/html;charset=UTF-8" },
488
+ };`
838
489
  );
490
+ writer.write(`
491
+ if (context.request.headers.get('Accept')?.includes('text/html')) {
492
+ return new Response(page404.stream(buildInput()), page404ResponseInit);
493
+ }
494
+ `);
839
495
  }
840
- if (middleware.length) {
841
- const names = middleware.map((m) => `mware${m.id}`);
842
- imports.writeLines(
843
- `import { ${names.join(
844
- ", "
845
- )} } from '${virtualFilePrefix}/${markoRunFilePrefix}middleware.js';`
846
- );
496
+ writer.indent--;
497
+ if (hasErrorPage) {
498
+ imports.writeLines(`
499
+ const page500ResponseInit = {
500
+ status: 500,
501
+ headers: { "content-type": "text/html;charset=UTF-8" },
502
+ };`);
503
+ writer.writeBlockStart(`} catch (error) {`).writeBlockStart(
504
+ `if (context.request.headers.get('Accept')?.includes('text/html')) {`
505
+ ).writeLines(
506
+ `return new Response(page500.stream(buildInput({ error })), page500ResponseInit);`
507
+ ).writeBlockEnd("}").writeLines("throw error;").writeBlockEnd("}");
847
508
  }
848
- if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.length) {
849
- writer.writeLines("");
850
- const names = [];
851
- for (const verb of handler.verbs) {
852
- const importName = verb.toUpperCase();
853
- names.push(importName);
854
- writer.writeLines(`const ${verb}Handler = normalize(${importName});`);
509
+ writer.writeBlockEnd("}");
510
+ renderFetch(writer, options);
511
+ return writer.end();
512
+ }
513
+ function renderFetch(writer, options) {
514
+ writer.write(`
515
+ export async function fetch(request, platform) {
516
+ try {
517
+ const url = new URL(request.url);
518
+ let { pathname } = url;`);
519
+ switch (options.trailingSlashes) {
520
+ case "RedirectWithout":
521
+ writer.write(`
522
+ if (pathname !== '/' && pathname.endsWith('/')) {
523
+ url.pathname = pathname.slice(0, -1);
524
+ return Response.redirect(url);
525
+ }`);
526
+ break;
527
+ case "RedirectWith":
528
+ writer.write(`
529
+ if (pathname !== '/' && !pathname.endsWith('/')) {
530
+ url.pathname = pathname + '/';
531
+ return Response.redirect(url);
532
+ }`);
533
+ break;
534
+ case "RewriteWithout":
535
+ writer.write(`
536
+ if (pathname !== '/' && pathname.endsWith('/')) {
537
+ url.pathname = pathname = pathname.slice(0, -1);
538
+ }`);
539
+ break;
540
+ case "RewriteWith":
541
+ writer.write(`
542
+ if (pathname !== '/' && !pathname.endsWith('/')) {
543
+ url.pathname = pathname = pathname + '/';
544
+ }`);
545
+ break;
546
+ }
547
+ writer.write(`
548
+
549
+ const route = match(request.method, pathname);
550
+ return await invoke(route, request, platform, url);
551
+ } catch (error) {
552
+ if (import.meta.env.DEV) {
553
+ throw error;
554
+ }
555
+ return new Response(null, {
556
+ status: 500
557
+ });
558
+ }
559
+ }`);
560
+ }
561
+ function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
562
+ const { route, dynamic, catchAll } = trie;
563
+ let closeCount = 0;
564
+ if (level === 0) {
565
+ writer.writeLines(`const len = pathname.length;`);
566
+ if (route) {
567
+ writer.writeLines(
568
+ `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
569
+ );
570
+ } else if (trie.static || dynamic) {
571
+ writer.writeBlockStart(`if (len > 1) {`);
572
+ closeCount++;
573
+ }
574
+ }
575
+ if (trie.static || dynamic) {
576
+ const next = level + 1;
577
+ const index = `i${next}`;
578
+ let terminal;
579
+ let children;
580
+ writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
581
+ if (trie.static) {
582
+ for (const child of trie.static.values()) {
583
+ if (child.route) {
584
+ (terminal ?? (terminal = [])).push(child);
585
+ }
586
+ if (child.static || child.dynamic || child.catchAll) {
587
+ (children ?? (children = [])).push(child);
588
+ }
589
+ }
590
+ }
591
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
592
+ closeCount++;
593
+ writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
594
+ let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
595
+ if (dynamic == null ? void 0 : dynamic.route) {
596
+ const segment = `s${next}`;
597
+ writer.writeLines(`const ${segment} = decodeURIComponent(${value});`);
598
+ value = segment;
599
+ } else if (terminal == null ? void 0 : terminal.some(
600
+ (terminal2) => decodeURIComponent(terminal2.key) !== terminal2.key
601
+ )) {
602
+ value = `decodeURIComponent(${value})`;
603
+ }
604
+ if (terminal) {
605
+ const useSwitch = terminal.length > 1;
606
+ if (useSwitch) {
607
+ writer.writeBlockStart(`switch (${value}) {`);
608
+ }
609
+ for (const { key, path: path6, route: route2 } of terminal) {
610
+ const decodedKey = decodeURIComponent(key);
611
+ if (useSwitch) {
612
+ writer.write(`case '${decodedKey}': `, true);
613
+ } else {
614
+ writer.write(`if (${value} === '${decodedKey}') `, true);
615
+ }
616
+ writer.write(
617
+ `return ${renderMatch(verb, route2, path6)}; // ${path6.path}
618
+ `
619
+ );
620
+ }
621
+ if (useSwitch) {
622
+ writer.writeBlockEnd("}");
623
+ }
624
+ }
625
+ if (dynamic == null ? void 0 : dynamic.route) {
626
+ writer.writeLines(
627
+ `if (${value}) return ${renderMatch(
628
+ verb,
629
+ dynamic.route,
630
+ dynamic.path
631
+ )}; // ${dynamic.path.path}`
632
+ );
633
+ }
634
+ }
635
+ if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
636
+ if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
637
+ writer.writeBlockEnd("} else {").indent++;
638
+ } else {
639
+ writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
640
+ closeCount++;
641
+ }
642
+ let value = `pathname.slice(${offset}, ${index} - 1)`;
643
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
644
+ const segment = `s${next}`;
645
+ writer.writeLines(`const ${segment} = decodeURIComponent(${value});`);
646
+ value = segment;
647
+ } else if (children == null ? void 0 : children.some((child) => decodeURIComponent(child.key) !== child.key)) {
648
+ value = `decodeURIComponent(${value})`;
649
+ }
650
+ if (children) {
651
+ const useSwitch = children.length > 1;
652
+ if (useSwitch) {
653
+ writer.writeBlockStart(`switch (${value}) {`);
654
+ }
655
+ for (const child of children) {
656
+ const decodedKey = decodeURIComponent(child.key);
657
+ if (useSwitch) {
658
+ writer.writeBlockStart(`case '${decodedKey}': {`);
659
+ } else {
660
+ writer.writeBlockStart(`if (${value} === '${decodedKey}') {`);
661
+ }
662
+ const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
663
+ writeRouterVerb(writer, child, verb, next, nextOffset);
664
+ if (useSwitch) {
665
+ writer.writeBlockEnd("} break;");
666
+ } else {
667
+ writer.writeBlockEnd("}");
668
+ }
669
+ }
670
+ if (useSwitch) {
671
+ writer.writeBlockEnd("}");
672
+ }
673
+ }
674
+ if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
675
+ writer.writeBlockStart(`if (${value}) {`);
676
+ writeRouterVerb(writer, dynamic, verb, next, index);
677
+ writer.writeBlockEnd(`}`);
678
+ }
855
679
  }
856
- imports.writeLines(
857
- `import { ${names.join(", ")} } from './${handler.importPath}';`
858
- );
859
680
  }
860
- if (page) {
861
- const importPath = route.layouts.length ? `./${entriesDir}/${entryName}.marko` : `./${page.importPath}`;
862
- imports.writeLines(`import page from '${importPath}${serverEntryQuery}';`);
681
+ while (closeCount--) {
682
+ writer.writeBlockEnd("}");
863
683
  }
864
- if (meta) {
865
- imports.writeLines(
866
- `export { default as meta${index} } from './${meta.importPath}';`
684
+ if (catchAll) {
685
+ writer.writeLines(
686
+ `return ${renderMatch(
687
+ verb,
688
+ catchAll.route,
689
+ catchAll.path,
690
+ String(offset)
691
+ )}; // ${catchAll.path.path}`
867
692
  );
693
+ } else if (level === 0) {
694
+ writer.writeLines("return null;");
868
695
  }
869
- for (const verb of verbs) {
870
- writeRouteEntryHandler(writer, route, verb);
871
- }
872
- return writer.end();
873
696
  }
874
- function writePageResponse(writer, wrapFn) {
875
- writer.writeLines(
876
- `${wrapFn ? `const ${wrapFn} = () =>` : `return`} pageResponse(page, buildInput());`
877
- );
697
+ function wrapPropertyName(name) {
698
+ name = decodeURIComponent(name);
699
+ return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
878
700
  }
879
- function writeMiddleware(writer, middleware, next, wrapFn) {
880
- if (wrapFn) {
881
- writer.writeLines(
882
- `const ${wrapFn} = () => call(${middleware}, ${next}, context);`
883
- );
884
- } else {
885
- writer.writeLines(`return call(${middleware}, ${next}, context);`);
701
+ function renderParams(params, pathIndex) {
702
+ let result = "";
703
+ let catchAll = "";
704
+ let sep = "{";
705
+ for (const [name, index] of Object.entries(params)) {
706
+ if (typeof index === "number") {
707
+ result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
708
+ sep = ",";
709
+ } else if (pathIndex) {
710
+ catchAll = name;
711
+ }
712
+ }
713
+ if (catchAll) {
714
+ result += `${sep} ${wrapPropertyName(
715
+ catchAll
716
+ )}: pathname.slice(${pathIndex})`;
886
717
  }
718
+ return result ? result + " }" : "{}";
887
719
  }
888
- function writeRouteEntryHandler(writer, route, verb) {
889
- var _a;
890
- const { key, index, page, handler, middleware } = route;
891
- const len = middleware.length;
892
- let nextName;
893
- let currentName;
894
- let hasBody = false;
720
+ function renderMatch(verb, route, path6, pathIndex) {
721
+ const handler = `${verb}${route.index}`;
722
+ const params = path6.params ? renderParams(path6.params, pathIndex) : "{}";
723
+ const meta = route.meta ? `meta${route.index}` : "{}";
724
+ const pathPattern = pathToURLPatternString(path6.path);
725
+ return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
726
+ }
727
+ function renderMiddleware(middleware) {
728
+ const writer = createStringWriter();
729
+ writer.writeLines(
730
+ `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
731
+ );
732
+ const imports = writer.branch("imports");
733
+ imports.writeLines(
734
+ `import { normalize } from '${virtualFilePrefix}/runtime/internal';`
735
+ );
895
736
  writer.writeLines("");
896
- if (page) {
897
- writer.writeBlockStart(
898
- `export async function ${verb}${index}(context, buildInput) {`
899
- );
900
- } else {
901
- writer.writeBlockStart(`export async function ${verb}${index}(context) {`);
902
- }
903
- const continuations = writer.branch("cont");
904
- if (page && verb === "get") {
905
- currentName = "__page";
906
- if ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes(verb)) {
907
- const name = `${verb}Handler`;
908
- writePageResponse(continuations, currentName);
909
- if (len) {
910
- nextName = currentName;
911
- currentName = `__${name}`;
912
- writeMiddleware(continuations, name, nextName, currentName);
913
- } else {
914
- writeMiddleware(writer, name, currentName);
915
- hasBody = true;
916
- }
917
- } else if (len) {
918
- writePageResponse(continuations, currentName);
919
- nextName = currentName;
920
- } else {
921
- writePageResponse(continuations);
922
- hasBody = true;
923
- }
924
- } else if (handler) {
925
- const name = `${verb}Handler`;
926
- currentName = `__${name}`;
927
- nextName = "noContent";
928
- if (len) {
929
- writeMiddleware(continuations, name, nextName, currentName);
930
- } else {
931
- writeMiddleware(writer, name, nextName);
932
- hasBody = true;
933
- }
934
- } else {
935
- throw new Error(`Route ${key} has no handler for ${verb} requests`);
737
+ for (const { id, importPath } of middleware) {
738
+ const importName = `middleware${id}`;
739
+ imports.writeLines(`import ${importName} from './${importPath}';`);
740
+ writer.writeLines(`export const mware${id} = normalize(${importName});`);
936
741
  }
937
- if (!hasBody) {
938
- let i = len;
939
- while (i--) {
940
- const { id } = middleware[i];
941
- const name = `mware${id}`;
942
- nextName = currentName;
943
- currentName = i ? `__${name}` : "";
944
- writeMiddleware(continuations, name, nextName, currentName);
742
+ imports.join();
743
+ return writer.end();
744
+ }
745
+ function stripTsExtension(path6) {
746
+ const index = path6.lastIndexOf(".");
747
+ if (index !== -1) {
748
+ const ext = path6.slice(index + 1);
749
+ if (ext.toLowerCase() === "ts") {
750
+ return path6.slice(0, index);
945
751
  }
946
752
  }
947
- continuations.join();
948
- writer.writeBlockEnd("}");
753
+ return path6;
949
754
  }
950
- function renderRouter(routes, entriesDir, options = {
951
- trailingSlashes: "RedirectWithout"
952
- }) {
755
+ async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
756
+ var _a, _b;
953
757
  const writer = createStringWriter();
954
- writer.writeLines(`// @marko/run/router`);
955
- const imports = writer.branch("imports");
956
- imports.writeLines(
957
- `import { NotHandled, NotMatched, createContext } from '${virtualFilePrefix}/runtime/internal';`
758
+ writer.writeLines(
759
+ `/*
760
+ WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
761
+ Do NOT manually edit this file or your changes will be lost.
762
+ */
763
+ `,
764
+ `import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";`,
765
+ `import type * as Run from "@marko/run";`
958
766
  );
959
- for (const route of routes.list) {
960
- const verbs = getVerbs(route);
961
- const names = verbs.map((verb) => `${verb}${route.index}`);
962
- route.meta && names.push(`meta${route.index}`);
963
- imports.writeLines(
964
- `import { ${names.join(", ")} } from '${virtualFilePrefix}/${route.entryName}.js';`
767
+ const headWriter = writer.branch("head");
768
+ writer.writeLines("\n").writeBlockStart(`declare module "@marko/run" {`);
769
+ if (adapter && adapter.typeInfo) {
770
+ const platformType = await adapter.typeInfo(
771
+ (data) => headWriter.write(data)
965
772
  );
773
+ if (platformType) {
774
+ writer.writeLines(`interface Platform extends ${platformType} {}
775
+ `);
776
+ }
966
777
  }
967
- for (const route of Object.values(routes.special)) {
968
- const importPath = route.layouts.length ? `./${entriesDir}/${route.entryName}.marko` : `./${route.page.importPath}`;
969
- imports.writeLines(
970
- `import page${route.key} from '${importPath}${serverEntryQuery}';`
971
- );
778
+ headWriter.join();
779
+ writer.writeBlockStart(`interface AppData extends Run.DefineApp<{`).writeBlockStart("routes: {");
780
+ const routesWriter = writer.branch("routes");
781
+ writer.writeBlockEnd("}").writeBlockEnd(`}> {}`).writeBlockEnd(`}`);
782
+ const routeTypes = /* @__PURE__ */ new Map();
783
+ for (const route of routes.list) {
784
+ let routeType = "";
785
+ for (const path6 of route.paths) {
786
+ const pathType = `"${pathToURLPatternString(path6.path)}"`;
787
+ routeType += routeType ? " | " + pathType : pathType;
788
+ routesWriter.writeLines(`${pathType}: Routes["${route.key}"];`);
789
+ }
790
+ for (const file of [route.handler, route.page]) {
791
+ if (file) {
792
+ const existing = routeTypes.get(file);
793
+ if (!existing) {
794
+ routeTypes.set(file, [routeType]);
795
+ } else {
796
+ existing.push(routeType);
797
+ }
798
+ }
799
+ }
800
+ for (const files of [route.middleware, route.layouts]) {
801
+ if (files) {
802
+ for (const file of files) {
803
+ const existing = routeTypes.get(file);
804
+ if (!existing) {
805
+ routeTypes.set(file, [routeType]);
806
+ } else {
807
+ existing.push(routeType);
808
+ }
809
+ }
810
+ }
811
+ }
972
812
  }
973
- writer.writeLines(
974
- `
975
- globalThis.__marko_run__ = { match, fetch, invoke };
976
- `
977
- ).writeBlockStart(`export function match(method, pathname) {`).writeLines(
978
- `if (!pathname) {
979
- pathname = '/';
980
- } else if (pathname.charAt(0) !== '/') {
981
- pathname = '/' + pathname;
813
+ for (const special of Object.values(routes.special)) {
814
+ routeTypes.set(special.page, []);
815
+ }
816
+ routesWriter.join();
817
+ const handlerWriter = writer.branch("handler");
818
+ const middlewareWriter = writer.branch("middleware");
819
+ const pageWriter = writer.branch("page");
820
+ const layoutWriter = writer.branch("layout");
821
+ for (const [file, types] of routeTypes) {
822
+ const path6 = `${pathPrefix}/${file.relativePath}`;
823
+ const routeType = `Run.Routes[${types.join(" | ")}]`;
824
+ switch (file.type) {
825
+ case RoutableFileTypes.Handler:
826
+ writeModuleDeclaration(handlerWriter, path6, routeType);
827
+ break;
828
+ case RoutableFileTypes.Middleware:
829
+ writeModuleDeclaration(middlewareWriter, path6, routeType);
830
+ break;
831
+ case RoutableFileTypes.Page:
832
+ writeModuleDeclaration(pageWriter, path6, routeType);
833
+ break;
834
+ case RoutableFileTypes.Layout:
835
+ writeModuleDeclaration(
836
+ layoutWriter,
837
+ path6,
838
+ routeType,
839
+ `
840
+ export interface Input {
841
+ renderBody: Marko.Body;
842
+ }`
843
+ );
844
+ break;
845
+ case RoutableFileTypes.Error:
846
+ writeModuleDeclaration(
847
+ writer,
848
+ path6,
849
+ "globalThis.MarkoRun.Route",
850
+ `
851
+ export interface Input {
852
+ error: unknown;
982
853
  }`
983
- ).writeBlockStart(`switch (method) {`);
984
- for (const verb of httpVerbs) {
985
- const filteredRoutes = routes.list.filter((route) => hasVerb(route, verb));
986
- if (filteredRoutes.length) {
987
- const trie = createRouteTrie(filteredRoutes);
988
- writer.writeLines(`case '${verb.toUpperCase()}':`);
989
- writer.writeBlockStart(`case '${verb.toLowerCase()}': {`);
990
- writeRouterVerb(writer, trie, verb);
991
- writer.writeBlockEnd("}");
854
+ );
855
+ break;
856
+ case RoutableFileTypes.NotFound:
857
+ writeModuleDeclaration(writer, path6, "Run.Route");
858
+ break;
992
859
  }
993
860
  }
994
- writer.writeBlockEnd("}").writeLines("return null;").writeBlockEnd("}");
995
- writer.writeLines("").writeBlockStart(
996
- "export async function invoke(route, request, platform, url) {"
997
- ).writeLines(
998
- "const [context, buildInput] = createContext(route, request, platform, url);"
999
- );
1000
- const hasErrorPage = Boolean(routes.special[RoutableFileTypes.Error]);
1001
- if (hasErrorPage) {
1002
- writer.writeBlockStart("try {");
1003
- }
1004
- writer.writeBlockStart("if (route) {").writeBlockStart("try {").writeLines(
1005
- "const response = await route.handler(context, buildInput);",
1006
- "if (response) return response;"
1007
- ).indent--;
1008
- writer.writeBlockStart("} catch (error) {").writeLines(
1009
- "if (error === NotHandled) return;",
1010
- "if (error !== NotMatched) throw error;"
1011
- ).writeBlockEnd("}").writeBlockEnd("}");
1012
- if (routes.special[RoutableFileTypes.NotFound]) {
1013
- imports.writeLines(
1014
- `
1015
- const page404ResponseInit = {
1016
- status: 404,
1017
- headers: { "content-type": "text/html;charset=UTF-8" },
1018
- };`
1019
- );
1020
- writer.write(`
1021
- if (context.request.headers.get('Accept')?.includes('text/html')) {
1022
- return new Response(page404.stream(buildInput()), page404ResponseInit);
861
+ handlerWriter.join();
862
+ middlewareWriter.join();
863
+ pageWriter.join();
864
+ layoutWriter.join();
865
+ writer.writeBlockStart(`
866
+ type Routes = {`);
867
+ for (const route of routes.list) {
868
+ const { meta, handler, page } = route;
869
+ if (page || handler) {
870
+ const verbs = [];
871
+ if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
872
+ verbs.push(`"get"`);
873
+ }
874
+ if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
875
+ verbs.push(`"post"`);
876
+ }
877
+ let routeType = `{ verb: ${verbs.join(" | ")};`;
878
+ if (meta) {
879
+ const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
880
+ let metaType = `typeof import("${metaPath}")`;
881
+ if (/\.(ts|js|mjs)$/.test(meta.name)) {
882
+ metaType += `["default"]`;
883
+ }
884
+ routeType += ` meta: ${metaType};`;
885
+ }
886
+ writer.writeLines(`"${route.key}": ${routeType} };`);
1023
887
  }
1024
- `);
1025
- }
1026
- writer.indent--;
1027
- if (hasErrorPage) {
1028
- imports.writeLines(`
1029
- const page500ResponseInit = {
1030
- status: 500,
1031
- headers: { "content-type": "text/html;charset=UTF-8" },
1032
- };`);
1033
- writer.writeBlockStart(`} catch (error) {`).writeBlockStart(
1034
- `if (context.request.headers.get('Accept')?.includes('text/html')) {`
1035
- ).writeLines(
1036
- `return new Response(page500.stream(buildInput({ error })), page500ResponseInit);`
1037
- ).writeBlockEnd("}").writeLines("throw error;").writeBlockEnd("}");
1038
888
  }
1039
889
  writer.writeBlockEnd("}");
1040
- renderFetch(writer, options);
1041
890
  return writer.end();
1042
891
  }
1043
- function renderFetch(writer, options) {
1044
- writer.write(`
1045
- export async function fetch(request, platform) {
1046
- try {
1047
- const url = new URL(request.url);
1048
- let { pathname } = url;`);
1049
- switch (options.trailingSlashes) {
1050
- case "RedirectWithout":
1051
- writer.write(`
1052
- if (pathname !== '/' && pathname.endsWith('/')) {
1053
- url.pathname = pathname.slice(0, -1);
1054
- return Response.redirect(url);
1055
- }`);
1056
- break;
1057
- case "RedirectWith":
1058
- writer.write(`
1059
- if (pathname !== '/' && !pathname.endsWith('/')) {
1060
- url.pathname = pathname + '/';
1061
- return Response.redirect(url);
1062
- }`);
1063
- break;
1064
- case "RewriteWithout":
1065
- writer.write(`
1066
- if (pathname !== '/' && pathname.endsWith('/')) {
1067
- url.pathname = pathname = pathname.slice(0, -1);
1068
- }`);
1069
- break;
1070
- case "RewriteWith":
1071
- writer.write(`
1072
- if (pathname !== '/' && !pathname.endsWith('/')) {
1073
- url.pathname = pathname = pathname + '/';
1074
- }`);
1075
- break;
892
+ function writeModuleDeclaration(writer, path6, routeType, moduleTypes) {
893
+ writer.writeLines("").write(`declare module "${stripTsExtension(path6)}" {`);
894
+ if (moduleTypes) {
895
+ writer.write(moduleTypes);
1076
896
  }
1077
- writer.write(`
1078
-
1079
- const route = match(request.method, pathname);
1080
- return await invoke(route, request, platform, url);
1081
- } catch (error) {
1082
- if (import.meta.env.DEV) {
1083
- throw error;
1084
- }
1085
- return new Response(null, {
1086
- status: 500
1087
- });
897
+ if (routeType) {
898
+ const isMarko = path6.endsWith(".marko");
899
+ writer.write(`
900
+ namespace MarkoRun {
901
+ export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
902
+ export type Route = ${routeType};
903
+ export type Context = Run.MultiRouteContext<Route>${isMarko ? " & Marko.Global" : ""};
904
+ export type Handler = Run.HandlerLike<Route>;
905
+ /** @deprecated use \`((context, next) => { ... }) satisfies MarkoRun.Handler\` instead */
906
+ export const route: Run.HandlerTypeFn<Route>;
907
+ }`);
1088
908
  }
909
+ writer.writeLines(`
1089
910
  }`);
1090
911
  }
1091
- function writeRouterVerb(writer, trie, verb, level = 0, offset = 1) {
1092
- const { route, dynamic, catchAll } = trie;
1093
- let closeCount = 0;
1094
- if (level === 0) {
1095
- writer.writeLines(`const len = pathname.length;`);
1096
- if (route) {
1097
- writer.writeLines(
1098
- `if (len === 1) return ${renderMatch(verb, route, trie.path)}; // ${trie.path.path}`
912
+ function pathToURLPatternString(path6) {
913
+ return path6.replace(/\/\$(\$?)([^/]*)/g, (_, catchAll, name) => {
914
+ name = decodeURIComponent(name);
915
+ return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
916
+ });
917
+ }
918
+ function createRouteTrie(routes) {
919
+ const root = {
920
+ key: ""
921
+ };
922
+ function insert(path6, route) {
923
+ let node = root;
924
+ for (const segment of path6.segments) {
925
+ if (segment === "$$") {
926
+ node.catchAll ?? (node.catchAll = { route, path: path6 });
927
+ return;
928
+ } else if (segment === "$") {
929
+ node = node.dynamic ?? (node.dynamic = {
930
+ key: ""
931
+ });
932
+ } else {
933
+ node.static ?? (node.static = /* @__PURE__ */ new Map());
934
+ let next = node.static.get(segment);
935
+ if (!next) {
936
+ next = {
937
+ key: segment
938
+ };
939
+ node.static.set(segment, next);
940
+ }
941
+ node = next;
942
+ }
943
+ }
944
+ node.path ?? (node.path = path6);
945
+ node.route ?? (node.route = route);
946
+ }
947
+ for (const route of routes) {
948
+ for (const path6 of route.paths) {
949
+ insert(path6, route);
950
+ }
951
+ }
952
+ return root;
953
+ }
954
+
955
+ // src/vite/routes/parse.ts
956
+ function parseFlatRoute(pattern) {
957
+ if (!pattern) throw new Error("Empty pattern");
958
+ const len = pattern.length;
959
+ let i = 0;
960
+ return parse2([
961
+ {
962
+ id: "/",
963
+ segments: [],
964
+ source: pattern
965
+ }
966
+ ]);
967
+ function parse2(basePaths, group) {
968
+ const pathMap = /* @__PURE__ */ new Map();
969
+ const delimiters = group ? ").," : ".,";
970
+ let charCode;
971
+ let segmentStart = i;
972
+ let type;
973
+ let current;
974
+ do {
975
+ charCode = pattern.charCodeAt(i);
976
+ if (charCode === 41 && group) {
977
+ break;
978
+ } else if (charCode === 44) {
979
+ if (!current) {
980
+ segmentEnd(
981
+ basePaths.map((path6) => ({
982
+ ...path6,
983
+ segments: path6.segments.slice()
984
+ })),
985
+ "",
986
+ "_",
987
+ pathMap
988
+ );
989
+ } else {
990
+ segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
991
+ }
992
+ current = void 0;
993
+ type = void 0;
994
+ segmentStart = ++i;
995
+ } else if (charCode === 46) {
996
+ if (current) {
997
+ segmentEnd(current, pattern.slice(segmentStart, i), type);
998
+ }
999
+ type = void 0;
1000
+ segmentStart = ++i;
1001
+ } else if (charCode === 40) {
1002
+ const groupPaths = parse2(current || basePaths, ++i);
1003
+ if (groupPaths.length) {
1004
+ current = groupPaths;
1005
+ }
1006
+ segmentStart = ++i;
1007
+ } else {
1008
+ if (charCode === 95) {
1009
+ type = "_";
1010
+ } else if (charCode === 36) {
1011
+ type = pattern.charCodeAt(i + 1) === 36 ? "$$" : "$";
1012
+ }
1013
+ current ?? (current = basePaths.map((path6) => ({
1014
+ ...path6,
1015
+ segments: path6.segments.slice()
1016
+ })));
1017
+ i = len;
1018
+ for (const char of delimiters) {
1019
+ const index = pattern.indexOf(char, segmentStart);
1020
+ if (index >= 0 && index < i) {
1021
+ i = index;
1022
+ }
1023
+ }
1024
+ }
1025
+ } while (i < len);
1026
+ if (group && charCode !== 41) {
1027
+ throw new Error(
1028
+ `Invalid route pattern: group was not closed '${pattern.slice(
1029
+ group
1030
+ )}' in '${pattern}'`
1031
+ );
1032
+ }
1033
+ if (!current) {
1034
+ segmentEnd(
1035
+ basePaths.map((path6) => ({
1036
+ ...path6,
1037
+ segments: path6.segments.slice()
1038
+ })),
1039
+ "",
1040
+ "_",
1041
+ pathMap
1099
1042
  );
1100
- } else if (trie.static || dynamic) {
1101
- writer.writeBlockStart(`if (len > 1) {`);
1102
- closeCount++;
1043
+ } else {
1044
+ segmentEnd(current, pattern.slice(segmentStart, i), type, pathMap);
1103
1045
  }
1046
+ return [...pathMap.values()];
1104
1047
  }
1105
- if (trie.static || dynamic) {
1106
- const next = level + 1;
1107
- const index = `i${next}`;
1108
- let terminal;
1109
- let children;
1110
- writer.writeLines(`const ${index} = pathname.indexOf('/', ${offset}) + 1;`);
1111
- if (trie.static) {
1112
- for (const child of trie.static.values()) {
1113
- if (child.route) {
1114
- (terminal ?? (terminal = [])).push(child);
1115
- }
1116
- if (child.static || child.dynamic || child.catchAll) {
1117
- (children ?? (children = [])).push(child);
1118
- }
1048
+ function segmentEnd(paths, raw, type, map) {
1049
+ let segment;
1050
+ if (raw) {
1051
+ segment = {
1052
+ raw,
1053
+ name: raw,
1054
+ type
1055
+ };
1056
+ if (type === "$" || type === "$$") {
1057
+ segment.name = type;
1058
+ segment.param = raw.slice(type.length);
1119
1059
  }
1120
1060
  }
1121
- if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
1122
- closeCount++;
1123
- writer.writeBlockStart(`if (!${index} || ${index} === len) {`);
1124
- let value = `pathname.slice(${offset}, ${index} ? -1 : len)`;
1125
- if (dynamic == null ? void 0 : dynamic.route) {
1126
- const segment = `s${next}`;
1127
- writer.writeLines(`const ${segment} = decodeURIComponent(${value});`);
1128
- value = segment;
1129
- } else if (terminal == null ? void 0 : terminal.some(
1130
- (terminal2) => decodeURIComponent(terminal2.key) !== terminal2.key
1131
- )) {
1132
- value = `decodeURIComponent(${value})`;
1133
- }
1134
- if (terminal) {
1135
- const useSwitch = terminal.length > 1;
1136
- if (useSwitch) {
1137
- writer.writeBlockStart(`switch (${value}) {`);
1138
- }
1139
- for (const { key, path: path5, route: route2 } of terminal) {
1140
- const decodedKey = decodeURIComponent(key);
1141
- if (useSwitch) {
1142
- writer.write(`case '${decodedKey}': `, true);
1143
- } else {
1144
- writer.write(`if (${value} === '${decodedKey}') `, true);
1145
- }
1146
- writer.write(
1147
- `return ${renderMatch(verb, route2, path5)}; // ${path5.path}
1148
- `
1061
+ for (const path6 of paths) {
1062
+ if (segment) {
1063
+ if (path6.isCatchall) {
1064
+ throw new Error(
1065
+ `Invalid route pattern: nested segments are not allowed after a catch-all parameter. Found '.' following '${pattern.slice(
1066
+ 0,
1067
+ i
1068
+ )}' in '${pattern}'.`
1149
1069
  );
1150
1070
  }
1151
- if (useSwitch) {
1152
- writer.writeBlockEnd("}");
1071
+ path6.segments.push(segment);
1072
+ path6.id += path6.id === "/" ? segment.name : `/${segment.name}`;
1073
+ if (type === "$$") {
1074
+ path6.isCatchall = true;
1153
1075
  }
1154
1076
  }
1155
- if (dynamic == null ? void 0 : dynamic.route) {
1156
- writer.writeLines(
1157
- `if (${value}) return ${renderMatch(
1158
- verb,
1159
- dynamic.route,
1160
- dynamic.path
1161
- )}; // ${dynamic.path.path}`
1162
- );
1077
+ if (map) {
1078
+ if (map.has(path6.id)) {
1079
+ const existing = map.get(path6.id);
1080
+ const existingExpansion = existing.segments.map((s) => s.raw).join(".");
1081
+ const currentExpansion = path6.segments.map((s) => s.raw).join(".");
1082
+ throw new Error(
1083
+ `Invalid route pattern: route '${path6.id}' is ambiguous. Expansion '${currentExpansion}' collides with '${existingExpansion}' in '${pattern}'.`
1084
+ );
1085
+ }
1086
+ map.set(path6.id, path6);
1163
1087
  }
1164
1088
  }
1165
- if (children || (dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
1166
- if (terminal || (dynamic == null ? void 0 : dynamic.route)) {
1167
- writer.writeBlockEnd("} else {").indent++;
1168
- } else {
1169
- writer.writeBlockStart(`if (${index} && ${index} !== len) {`);
1170
- closeCount++;
1171
- }
1172
- let value = `pathname.slice(${offset}, ${index} - 1)`;
1173
- if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
1174
- const segment = `s${next}`;
1175
- writer.writeLines(`const ${segment} = decodeURIComponent(${value});`);
1176
- value = segment;
1177
- } else if (children == null ? void 0 : children.some((child) => decodeURIComponent(child.key) !== child.key)) {
1178
- value = `decodeURIComponent(${value})`;
1089
+ }
1090
+ }
1091
+
1092
+ // src/vite/routes/vdir.ts
1093
+ var _dirs, _pathlessDirs;
1094
+ var _VDir = class _VDir {
1095
+ constructor(parent, segment, source) {
1096
+ __privateAdd(this, _dirs);
1097
+ __privateAdd(this, _pathlessDirs);
1098
+ __publicField(this, "parent");
1099
+ __publicField(this, "source");
1100
+ __publicField(this, "path");
1101
+ __publicField(this, "fullPath");
1102
+ __publicField(this, "segment");
1103
+ __publicField(this, "files");
1104
+ if (!parent || !segment) {
1105
+ this.parent = null;
1106
+ this.source = null;
1107
+ this.path = "/";
1108
+ this.fullPath = "/";
1109
+ this.segment = {
1110
+ raw: "",
1111
+ name: ""
1112
+ };
1113
+ } else {
1114
+ this.parent = parent;
1115
+ this.source = source;
1116
+ this.path = parent.path + (parent.path === "/" ? segment.name : `/${segment.name}`);
1117
+ this.fullPath = parent.fullPath + (parent.fullPath === "/" ? segment.name : `/${segment.name}`);
1118
+ if (segment.param) {
1119
+ this.fullPath += segment.param;
1179
1120
  }
1180
- if (children) {
1181
- const useSwitch = children.length > 1;
1182
- if (useSwitch) {
1183
- writer.writeBlockStart(`switch (${value}) {`);
1184
- }
1185
- for (const child of children) {
1186
- const decodedKey = decodeURIComponent(child.key);
1187
- if (useSwitch) {
1188
- writer.writeBlockStart(`case '${decodedKey}': {`);
1189
- } else {
1190
- writer.writeBlockStart(`if (${value} === '${decodedKey}') {`);
1191
- }
1192
- const nextOffset = typeof offset === "string" ? index : offset + child.key.length + 1;
1193
- writeRouterVerb(writer, child, verb, next, nextOffset);
1194
- if (useSwitch) {
1195
- writer.writeBlockEnd("} break;");
1196
- } else {
1197
- writer.writeBlockEnd("}");
1121
+ this.segment = segment;
1122
+ }
1123
+ }
1124
+ get pathInfo() {
1125
+ const value = {
1126
+ id: "/",
1127
+ path: "/",
1128
+ segments: []
1129
+ };
1130
+ let sep = "";
1131
+ for (const { segment } of this) {
1132
+ const { type, name, param } = segment;
1133
+ if (name && type !== "_") {
1134
+ value.id += sep + (type || name);
1135
+ value.path += sep + name;
1136
+ value.isEnd = type === "$$";
1137
+ if (param) {
1138
+ value.path += param;
1139
+ const index = type === "$$" ? null : value.segments.length;
1140
+ if (!value.params) {
1141
+ value.params = { [param]: index };
1142
+ } else if (!(param in value.params)) {
1143
+ value.params[param] = index;
1198
1144
  }
1199
1145
  }
1200
- if (useSwitch) {
1201
- writer.writeBlockEnd("}");
1202
- }
1203
- }
1204
- if ((dynamic == null ? void 0 : dynamic.static) || (dynamic == null ? void 0 : dynamic.dynamic) || (dynamic == null ? void 0 : dynamic.catchAll)) {
1205
- writer.writeBlockStart(`if (${value}) {`);
1206
- writeRouterVerb(writer, dynamic, verb, next, index);
1207
- writer.writeBlockEnd(`}`);
1146
+ value.segments.push(name);
1147
+ sep = "/";
1208
1148
  }
1209
1149
  }
1150
+ Object.defineProperty(this, "pathInfo", {
1151
+ value,
1152
+ enumerable: true
1153
+ });
1154
+ return value;
1210
1155
  }
1211
- while (closeCount--) {
1212
- writer.writeBlockEnd("}");
1213
- }
1214
- if (catchAll) {
1215
- writer.writeLines(
1216
- `return ${renderMatch(
1217
- verb,
1218
- catchAll.route,
1219
- catchAll.path,
1220
- String(offset)
1221
- )}; // ${catchAll.path.path}`
1222
- );
1223
- } else if (level === 0) {
1224
- writer.writeLines("return null;");
1225
- }
1226
- }
1227
- function wrapPropertyName(name) {
1228
- name = decodeURIComponent(name);
1229
- return /^[^A-Za-z_$]|[^A-Za-z0-9$_]/.test(name) ? `'${name}'` : name;
1230
- }
1231
- function renderParams(params, pathIndex) {
1232
- let result = "";
1233
- let catchAll = "";
1234
- let sep = "{";
1235
- for (const [name, index] of Object.entries(params)) {
1236
- if (typeof index === "number") {
1237
- result += `${sep} ${wrapPropertyName(name)}: s${index + 1}`;
1238
- sep = ",";
1239
- } else if (pathIndex) {
1240
- catchAll = name;
1156
+ addDir(path6, segment) {
1157
+ const map = segment.type === "_" ? __privateGet(this, _pathlessDirs) ?? __privateSet(this, _pathlessDirs, /* @__PURE__ */ new Map()) : __privateGet(this, _dirs) ?? __privateSet(this, _dirs, /* @__PURE__ */ new Map());
1158
+ if (!map.has(segment.name)) {
1159
+ const dir = new _VDir(this, segment, path6);
1160
+ map.set(segment.name, dir);
1161
+ return dir;
1241
1162
  }
1163
+ return map.get(segment.name);
1242
1164
  }
1243
- if (catchAll) {
1244
- result += `${sep} ${wrapPropertyName(
1245
- catchAll
1246
- )}: pathname.slice(${pathIndex})`;
1247
- }
1248
- return result ? result + " }" : "{}";
1249
- }
1250
- function renderMatch(verb, route, path5, pathIndex) {
1251
- const handler = `${verb}${route.index}`;
1252
- const params = path5.params ? renderParams(path5.params, pathIndex) : "{}";
1253
- const meta = route.meta ? `meta${route.index}` : "{}";
1254
- const pathPattern = pathToURLPatternString(path5.path);
1255
- return `{ handler: ${handler}, params: ${params}, meta: ${meta}, path: '${pathPattern}' }`;
1256
- }
1257
- function renderMiddleware(middleware) {
1258
- const writer = createStringWriter();
1259
- writer.writeLines(
1260
- `// ${virtualFilePrefix}/${markoRunFilePrefix}middleware.js`
1261
- );
1262
- const imports = writer.branch("imports");
1263
- imports.writeLines(
1264
- `import { normalize } from '${virtualFilePrefix}/runtime/internal';`
1265
- );
1266
- writer.writeLines("");
1267
- for (const { id, importPath } of middleware) {
1268
- const importName = `middleware${id}`;
1269
- imports.writeLines(`import ${importName} from './${importPath}';`);
1270
- writer.writeLines(`export const mware${id} = normalize(${importName});`);
1271
- }
1272
- imports.join();
1273
- return writer.end();
1274
- }
1275
- function stripTsExtension(path5) {
1276
- const index = path5.lastIndexOf(".");
1277
- if (index !== -1) {
1278
- const ext = path5.slice(index + 1);
1279
- if (ext.toLowerCase() === "ts") {
1280
- return path5.slice(0, index);
1165
+ addFile(file) {
1166
+ if (!this.files) {
1167
+ this.files = /* @__PURE__ */ new Map();
1168
+ this.files.set(file.type, file);
1169
+ } else if (!this.files.has(file.type)) {
1170
+ this.files.set(file.type, file);
1171
+ } else {
1172
+ const existing = this.files.get(file.type);
1173
+ if (existing !== file) {
1174
+ throw new Error(
1175
+ `Duplicate file type '${file.type}' added at path '${this.path}'. File '${file.importPath}' collides with '${existing.importPath}'.`
1176
+ );
1177
+ } else if (file.type === RoutableFileTypes.Page || file.type === RoutableFileTypes.Handler) {
1178
+ throw new Error(
1179
+ `Ambiguous path definition: route '${this.path}' is defined multiple times by ${file.importPath}`
1180
+ );
1181
+ }
1182
+ throw new Error(
1183
+ `Ambiguous path definition: file '${this.path}' is included multiple times by ${file.importPath}`
1184
+ );
1281
1185
  }
1282
1186
  }
1283
- return path5;
1284
- }
1285
- async function renderRouteTypeInfo(routes, pathPrefix = ".", adapter) {
1286
- var _a, _b;
1287
- const writer = createStringWriter();
1288
- writer.writeLines(
1289
- `/*
1290
- WARNING: This file is automatically generated and any changes made to it will be overwritten without warning.
1291
- Do NOT manually edit this file or your changes will be lost.
1292
- */
1293
- `,
1294
- `import { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform } from "@marko/run/namespace";`,
1295
- `import type * as Run from "@marko/run";`
1296
- );
1297
- const headWriter = writer.branch("head");
1298
- writer.writeLines("\n").writeBlockStart(`declare module "@marko/run" {`);
1299
- if (adapter && adapter.typeInfo) {
1300
- const platformType = await adapter.typeInfo(
1301
- (data) => headWriter.write(data)
1302
- );
1303
- if (platformType) {
1304
- writer.writeLines(`interface Platform extends ${platformType} {}
1305
- `);
1187
+ *dirs() {
1188
+ if (__privateGet(this, _pathlessDirs)) {
1189
+ yield* __privateGet(this, _pathlessDirs).values();
1190
+ }
1191
+ if (__privateGet(this, _dirs)) {
1192
+ yield* __privateGet(this, _dirs).values();
1306
1193
  }
1307
1194
  }
1308
- headWriter.join();
1309
- writer.writeBlockStart(`interface AppData extends Run.DefineApp<{`).writeBlockStart("routes: {");
1310
- const routesWriter = writer.branch("routes");
1311
- writer.writeBlockEnd("}").writeBlockEnd(`}> {}`).writeBlockEnd(`}`);
1312
- const routeTypes = /* @__PURE__ */ new Map();
1313
- for (const route of routes.list) {
1314
- let routeType = "";
1315
- for (const path5 of route.paths) {
1316
- const pathType = `"${pathToURLPatternString(path5.path)}"`;
1317
- routeType += routeType ? " | " + pathType : pathType;
1318
- routesWriter.writeLines(`${pathType}: Routes["${route.key}"];`);
1195
+ *[Symbol.iterator]() {
1196
+ if (this.parent) {
1197
+ yield* this.parent;
1319
1198
  }
1320
- for (const file of [route.handler, route.page]) {
1321
- if (file) {
1322
- const existing = routeTypes.get(file);
1323
- if (!existing) {
1324
- routeTypes.set(file, [routeType]);
1325
- } else {
1326
- existing.push(routeType);
1199
+ yield this;
1200
+ }
1201
+ static addPaths(roots, paths) {
1202
+ const dirs = [];
1203
+ const unique = /* @__PURE__ */ new Set();
1204
+ for (const root of roots) {
1205
+ for (const path6 of paths) {
1206
+ let dir = root;
1207
+ for (const segment of path6.segments) {
1208
+ dir = dir.addDir(path6, segment);
1327
1209
  }
1328
- }
1329
- }
1330
- for (const files of [route.middleware, route.layouts]) {
1331
- if (files) {
1332
- for (const file of files) {
1333
- const existing = routeTypes.get(file);
1334
- if (!existing) {
1335
- routeTypes.set(file, [routeType]);
1336
- } else {
1337
- existing.push(routeType);
1210
+ if (unique.has(dir.path)) {
1211
+ const sources = /* @__PURE__ */ new Set();
1212
+ let sourcePath = "";
1213
+ for (const { source } of dir) {
1214
+ if (source && !sources.has(source.source)) {
1215
+ sources.add(source.source);
1216
+ sourcePath += source.source + "/";
1217
+ }
1338
1218
  }
1219
+ throw new Error(
1220
+ `Ambiguous directory structure: '${sourcePath}${path6.source}' defines '${dir.path}' multiple times.`
1221
+ );
1222
+ } else {
1223
+ unique.add(dir.path);
1224
+ dirs.push(dir);
1339
1225
  }
1340
1226
  }
1341
1227
  }
1228
+ return dirs;
1342
1229
  }
1343
- for (const special of Object.values(routes.special)) {
1344
- routeTypes.set(special.page, []);
1345
- }
1346
- routesWriter.join();
1347
- const handlerWriter = writer.branch("handler");
1348
- const middlewareWriter = writer.branch("middleware");
1349
- const pageWriter = writer.branch("page");
1350
- const layoutWriter = writer.branch("layout");
1351
- for (const [file, types] of routeTypes) {
1352
- const path5 = `${pathPrefix}/${file.relativePath}`;
1353
- const routeType = `Run.Routes[${types.join(" | ")}]`;
1354
- switch (file.type) {
1355
- case RoutableFileTypes.Handler:
1356
- writeModuleDeclaration(handlerWriter, path5, routeType);
1357
- break;
1358
- case RoutableFileTypes.Middleware:
1359
- writeModuleDeclaration(middlewareWriter, path5, routeType);
1360
- break;
1361
- case RoutableFileTypes.Page:
1362
- writeModuleDeclaration(pageWriter, path5, routeType);
1363
- break;
1364
- case RoutableFileTypes.Layout:
1365
- writeModuleDeclaration(
1366
- layoutWriter,
1367
- path5,
1368
- routeType,
1369
- `
1370
- export interface Input {
1371
- renderBody: Marko.Body;
1372
- }`
1373
- );
1374
- break;
1375
- case RoutableFileTypes.Error:
1376
- writeModuleDeclaration(
1377
- writer,
1378
- path5,
1379
- "globalThis.MarkoRun.Route",
1380
- `
1381
- export interface Input {
1382
- error: unknown;
1383
- }`
1230
+ };
1231
+ _dirs = new WeakMap();
1232
+ _pathlessDirs = new WeakMap();
1233
+ var VDir = _VDir;
1234
+
1235
+ // src/vite/routes/builder.ts
1236
+ var markoFiles = `(${RoutableFileTypes.Layout}|${RoutableFileTypes.Page}|${RoutableFileTypes.NotFound}|${RoutableFileTypes.Error})\\.(?:.*\\.)?(marko)`;
1237
+ var nonMarkoFiles = `(${RoutableFileTypes.Middleware}|${RoutableFileTypes.Handler}|${RoutableFileTypes.Meta})\\.(?:.*\\.)?(.+)`;
1238
+ var routeableFileRegex = new RegExp(
1239
+ `[+](?:${markoFiles}|${nonMarkoFiles})$`,
1240
+ "i"
1241
+ );
1242
+ function matchRoutableFile(filename) {
1243
+ const match = filename.match(routeableFileRegex);
1244
+ return match && (match[1] || match[3]).toLowerCase();
1245
+ }
1246
+ function isSpecialType(type) {
1247
+ return type === RoutableFileTypes.NotFound || type === RoutableFileTypes.Error;
1248
+ }
1249
+ async function buildRoutes(sources) {
1250
+ const uniqueRoutes = /* @__PURE__ */ new Map();
1251
+ const routes = [];
1252
+ const special = {};
1253
+ const middlewares = /* @__PURE__ */ new Set();
1254
+ const unusedFiles = /* @__PURE__ */ new Set();
1255
+ const currentLayouts = /* @__PURE__ */ new Set();
1256
+ const currentMiddleware = /* @__PURE__ */ new Set();
1257
+ const root = new VDir();
1258
+ const dirStack = [];
1259
+ let basePath;
1260
+ let importPrefix;
1261
+ let activeDirs;
1262
+ let isBaseDir;
1263
+ let nextFileId = 1;
1264
+ let nextRouteIndex = 1;
1265
+ const walkOptions = {
1266
+ onEnter({ name }) {
1267
+ const prevDirStackLength = dirStack.length;
1268
+ if (isBaseDir) {
1269
+ isBaseDir = false;
1270
+ if (!basePath) {
1271
+ return;
1272
+ }
1273
+ name = basePath;
1274
+ } else {
1275
+ dirStack.push(name);
1276
+ }
1277
+ const previousDirs = activeDirs;
1278
+ const paths = parseFlatRoute(name);
1279
+ activeDirs = VDir.addPaths(previousDirs, paths);
1280
+ return () => {
1281
+ activeDirs = previousDirs;
1282
+ dirStack.length = prevDirStackLength;
1283
+ };
1284
+ },
1285
+ onFile({ name, path: path6 }) {
1286
+ const match = name.match(routeableFileRegex);
1287
+ if (!match) {
1288
+ return;
1289
+ }
1290
+ const type = (match[1] || match[3]).toLowerCase();
1291
+ if (dirStack.length && isSpecialType(type)) {
1292
+ console.warn(
1293
+ `Special pages '${RoutableFileTypes.NotFound}' and '${RoutableFileTypes.Error}' are only considered in the root directory - ignoring ${path6}`
1384
1294
  );
1385
- break;
1386
- case RoutableFileTypes.NotFound:
1387
- writeModuleDeclaration(writer, path5, "Run.Route");
1388
- break;
1295
+ return;
1296
+ }
1297
+ let dirs = activeDirs;
1298
+ if (match.index) {
1299
+ const paths = parseFlatRoute(name.slice(0, match.index));
1300
+ dirs = VDir.addPaths(activeDirs, paths);
1301
+ }
1302
+ const dirPath = dirStack.join("/");
1303
+ const relativePath = dirPath ? `${dirPath}/${name}` : name;
1304
+ const file = {
1305
+ id: String(nextFileId++),
1306
+ name,
1307
+ type,
1308
+ filePath: path6,
1309
+ relativePath,
1310
+ importPath: `${importPrefix}/${relativePath}`,
1311
+ verbs: type === RoutableFileTypes.Page ? ["get"] : void 0
1312
+ };
1313
+ for (const dir of dirs) {
1314
+ dir.addFile(file);
1315
+ }
1389
1316
  }
1317
+ };
1318
+ if (!Array.isArray(sources)) {
1319
+ sources = [sources];
1390
1320
  }
1391
- handlerWriter.join();
1392
- middlewareWriter.join();
1393
- pageWriter.join();
1394
- layoutWriter.join();
1395
- writer.writeBlockStart(`
1396
- type Routes = {`);
1397
- for (const route of routes.list) {
1398
- const { meta, handler, page } = route;
1399
- if (page || handler) {
1400
- const verbs = [];
1401
- if (page || ((_a = handler == null ? void 0 : handler.verbs) == null ? void 0 : _a.includes("get"))) {
1402
- verbs.push(`"get"`);
1321
+ for (const source of sources) {
1322
+ importPrefix = source.importPrefix ? source.importPrefix.replace(/^\/+|\/+$/g, "") : "";
1323
+ basePath = source.basePath || "";
1324
+ activeDirs = [root];
1325
+ isBaseDir = true;
1326
+ await source.walker(walkOptions);
1327
+ }
1328
+ traverse(root);
1329
+ return {
1330
+ list: routes,
1331
+ middleware: [...middlewares],
1332
+ special
1333
+ };
1334
+ function traverse(dir) {
1335
+ let middleware;
1336
+ let layout;
1337
+ if (dir.files) {
1338
+ middleware = dir.files.get(RoutableFileTypes.Middleware);
1339
+ layout = dir.files.get(RoutableFileTypes.Layout);
1340
+ const handler = dir.files.get(RoutableFileTypes.Handler);
1341
+ const page = dir.files.get(RoutableFileTypes.Page);
1342
+ let hasSpecial = false;
1343
+ if (middleware) {
1344
+ if (currentMiddleware.has(middleware)) {
1345
+ middleware = void 0;
1346
+ } else {
1347
+ currentMiddleware.add(middleware);
1348
+ unusedFiles.add(middleware);
1349
+ }
1403
1350
  }
1404
- if ((_b = handler == null ? void 0 : handler.verbs) == null ? void 0 : _b.includes("post")) {
1405
- verbs.push(`"post"`);
1351
+ if (layout) {
1352
+ if (currentLayouts.has(layout)) {
1353
+ layout = void 0;
1354
+ } else {
1355
+ currentLayouts.add(layout);
1356
+ unusedFiles.add(layout);
1357
+ }
1406
1358
  }
1407
- let routeType = `{ verb: ${verbs.join(" | ")};`;
1408
- if (meta) {
1409
- const metaPath = stripTsExtension(`${pathPrefix}/${meta.relativePath}`);
1410
- let metaType = `typeof import("${metaPath}")`;
1411
- if (/\.(ts|js|mjs)$/.test(meta.relativePath)) {
1412
- metaType += `["default"]`;
1359
+ if (page || handler) {
1360
+ const path6 = dir.pathInfo;
1361
+ if (uniqueRoutes.has(path6.id)) {
1362
+ const existing = uniqueRoutes.get(path6.id);
1363
+ const route = routes[existing.index];
1364
+ const existingFiles = [route.handler, route.page].filter(Boolean).map((f) => f.filePath);
1365
+ const currentFiles = [handler, page].filter(Boolean).map((f) => f.filePath);
1366
+ throw new Error(`Duplicate routes for path '${path6.path}' were defined. A route established by:
1367
+ ${existingFiles.join(" and ")} via '${existing.dir.path}'
1368
+ collides with
1369
+ ${currentFiles.join(" and ")} via '${dir.path}'
1370
+ `);
1371
+ }
1372
+ uniqueRoutes.set(path6.id, { dir, index: routes.length });
1373
+ routes.push({
1374
+ index: nextRouteIndex++,
1375
+ key: dir.fullPath,
1376
+ paths: [path6],
1377
+ middleware: [...currentMiddleware],
1378
+ layouts: page ? [...currentLayouts] : [],
1379
+ meta: dir.files.get(RoutableFileTypes.Meta),
1380
+ page,
1381
+ handler,
1382
+ entryName: `${markoRunFilePrefix}route` + (dir.path !== "/" ? dir.fullPath.replace(/\//g, ".").replace(/(%[A-Fa-f0-9]{2})+/g, "_") : "")
1383
+ });
1384
+ }
1385
+ if (dir === root) {
1386
+ for (const [type, file] of dir.files) {
1387
+ if (isSpecialType(type)) {
1388
+ hasSpecial = true;
1389
+ special[type] = {
1390
+ index: 0,
1391
+ key: type,
1392
+ paths: [],
1393
+ middleware: [],
1394
+ layouts: [...currentLayouts],
1395
+ page: file,
1396
+ entryName: `${markoRunFilePrefix}special.${type}`
1397
+ };
1398
+ }
1399
+ }
1400
+ }
1401
+ if (handler || page) {
1402
+ for (const middleware2 of currentMiddleware) {
1403
+ middlewares.add(middleware2);
1404
+ unusedFiles.delete(middleware2);
1405
+ }
1406
+ }
1407
+ if (page || hasSpecial) {
1408
+ for (const layout2 of currentLayouts) {
1409
+ unusedFiles.delete(layout2);
1413
1410
  }
1414
- routeType += ` meta: ${metaType};`;
1415
1411
  }
1416
- writer.writeLines(`"${route.key}": ${routeType} };`);
1412
+ }
1413
+ if (dir.dirs) {
1414
+ for (const child of dir.dirs()) {
1415
+ traverse(child);
1416
+ }
1417
+ }
1418
+ if (middleware) {
1419
+ currentMiddleware.delete(middleware);
1420
+ }
1421
+ if (layout) {
1422
+ currentLayouts.delete(layout);
1417
1423
  }
1418
1424
  }
1419
- writer.writeBlockEnd("}");
1420
- return writer.end();
1421
- }
1422
- function writeModuleDeclaration(writer, path5, routeType, moduleTypes) {
1423
- writer.writeLines("").write(`declare module "${stripTsExtension(path5)}" {`);
1424
- if (moduleTypes) {
1425
- writer.write(moduleTypes);
1426
- }
1427
- if (routeType) {
1428
- const isMarko = path5.endsWith(".marko");
1429
- writer.write(`
1430
- namespace MarkoRun {
1431
- export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
1432
- export type Route = ${routeType};
1433
- export type Context = Run.MultiRouteContext<Route>${isMarko ? " & Marko.Global" : ""};
1434
- export type Handler = Run.HandlerLike<Route>;
1435
- /** @deprecated use \`((context, next) => { ... }) satisfies MarkoRun.Handler\` instead */
1436
- export const route: Run.HandlerTypeFn<Route>;
1437
- }`);
1438
- }
1439
- writer.writeLines(`
1440
- }`);
1441
- }
1442
- function pathToURLPatternString(path5) {
1443
- return path5.replace(/\/\$(\$?)([^\/]*)/g, (_, catchAll, name) => {
1444
- name = decodeURIComponent(name);
1445
- return catchAll ? `/:${name || "rest"}*` : `/:${name}`;
1446
- });
1447
1425
  }
1448
- function createRouteTrie(routes) {
1449
- const root = {
1450
- key: ""
1451
- };
1452
- function insert(path5, route) {
1453
- let node = root;
1454
- for (const segment of path5.segments) {
1455
- if (segment === "$$") {
1456
- node.catchAll ?? (node.catchAll = { route, path: path5 });
1457
- return;
1458
- } else if (segment === "$") {
1459
- node = node.dynamic ?? (node.dynamic = {
1460
- key: ""
1426
+
1427
+ // src/vite/routes/walk.ts
1428
+ import fs from "fs";
1429
+ import path2 from "path";
1430
+ function createFSWalker(dir) {
1431
+ return async function walkFS({
1432
+ onEnter,
1433
+ onFile,
1434
+ onDir,
1435
+ maxDepth = 50
1436
+ }) {
1437
+ async function walk(dir2, depth) {
1438
+ const onExit = onEnter == null ? void 0 : onEnter(dir2);
1439
+ if (onExit !== false) {
1440
+ const dirs = [];
1441
+ const entries = await fs.promises.readdir(dir2.path, {
1442
+ withFileTypes: true
1461
1443
  });
1462
- } else {
1463
- node.static ?? (node.static = /* @__PURE__ */ new Map());
1464
- let next = node.static.get(segment);
1465
- if (!next) {
1466
- next = {
1467
- key: segment
1444
+ const prefix = dir2.path + path2.sep;
1445
+ for (const entry of entries) {
1446
+ const walkEntry = {
1447
+ name: entry.name,
1448
+ path: prefix + entry.name
1468
1449
  };
1469
- node.static.set(segment, next);
1450
+ if (entry.isDirectory()) {
1451
+ dirs.push(walkEntry);
1452
+ } else {
1453
+ onFile == null ? void 0 : onFile(walkEntry);
1454
+ }
1470
1455
  }
1471
- node = next;
1456
+ if ((onDir == null ? void 0 : onDir()) !== false && --depth > 0) {
1457
+ for (const entry of dirs) {
1458
+ await walk(entry, depth);
1459
+ }
1460
+ }
1461
+ onExit == null ? void 0 : onExit();
1472
1462
  }
1473
1463
  }
1474
- node.path ?? (node.path = path5);
1475
- node.route ?? (node.route = route);
1476
- }
1477
- for (const route of routes) {
1478
- for (const path5 of route.paths) {
1479
- insert(path5, route);
1480
- }
1481
- }
1482
- return root;
1464
+ await walk(
1465
+ {
1466
+ path: dir,
1467
+ name: path2.basename(dir)
1468
+ },
1469
+ maxDepth
1470
+ );
1471
+ };
1483
1472
  }
1484
1473
 
1485
1474
  // src/vite/utils/ast.ts
@@ -1538,18 +1527,33 @@ function getViteSSRExportIdentifiers(astProgramNode, exportObjectName = "__vite_
1538
1527
  return result;
1539
1528
  }
1540
1529
 
1530
+ // src/vite/utils/config.ts
1531
+ var PluginConfigKey = "__MARKO_RUN_PLUGIN_CONFIG__";
1532
+ var AdapterConfigKey = "__MARKO_RUN_ADAPTER_CONFIG__";
1533
+ function getConfig(obj, key) {
1534
+ return obj[key];
1535
+ }
1536
+ function setConfig(obj, key, value) {
1537
+ obj[key] = value;
1538
+ return obj;
1539
+ }
1540
+ var getExternalPluginOptions = (viteConfig) => getConfig(viteConfig, PluginConfigKey);
1541
+ var setExternalPluginOptions = (viteConfig, value) => setConfig(viteConfig, PluginConfigKey, value);
1542
+ var getExternalAdapterOptions = (viteConfig) => getConfig(viteConfig, AdapterConfigKey);
1543
+ var setExternalAdapterOptions = (viteConfig, value) => setConfig(viteConfig, AdapterConfigKey, value);
1544
+
1541
1545
  // src/vite/utils/log.ts
1542
1546
  import zlib from "node:zlib";
1547
+ import { Blob } from "buffer";
1543
1548
  import Table from "cli-table3";
1544
- import kleur from "kleur";
1545
1549
  import format from "human-format";
1546
- import { Blob } from "buffer";
1550
+ import kleur2 from "kleur";
1547
1551
  var HttpVerbColors = {
1548
- get: kleur.green,
1549
- post: kleur.magenta,
1550
- put: kleur.cyan,
1551
- delete: kleur.red,
1552
- other: kleur.white
1552
+ get: kleur2.green,
1553
+ post: kleur2.magenta,
1554
+ put: kleur2.cyan,
1555
+ delete: kleur2.red,
1556
+ other: kleur2.white
1553
1557
  };
1554
1558
  var HttpVerbOrder = {
1555
1559
  get: 0,
@@ -1576,13 +1580,13 @@ function logRoutesTable(routes, bundle, options) {
1576
1580
  headings.push("Size/GZip");
1577
1581
  colAligns.push("right");
1578
1582
  const table = new Table({
1579
- head: headings.map((title) => kleur.bold(kleur.white(title.toUpperCase()))),
1583
+ head: headings.map((title) => kleur2.bold(kleur2.white(title.toUpperCase()))),
1580
1584
  wordWrap: true,
1581
1585
  colAligns,
1582
1586
  style: { compact: true }
1583
1587
  });
1584
1588
  for (const route of routes.list) {
1585
- for (const path5 of route.paths) {
1589
+ for (const path6 of route.paths) {
1586
1590
  const verbs = getVerbs(route).sort(
1587
1591
  (a, b) => HttpVerbOrder[a] - HttpVerbOrder[b]
1588
1592
  );
@@ -1591,17 +1595,17 @@ function logRoutesTable(routes, bundle, options) {
1591
1595
  let size = "";
1592
1596
  const entryType = [];
1593
1597
  if (route.handler) {
1594
- entryType.push(kleur.blue("handler"));
1598
+ entryType.push(kleur2.blue("handler"));
1595
1599
  }
1596
1600
  if (verb === "get" && route.page) {
1597
- entryType.push(kleur.yellow("page"));
1601
+ entryType.push(kleur2.yellow("page"));
1598
1602
  size = prettySize(computeRouteSize(getRouteChunkName(route), bundle));
1599
1603
  }
1600
1604
  const row = [
1601
- kleur.bold(HttpVerbColors[verb](verb.toUpperCase()))
1605
+ kleur2.bold(HttpVerbColors[verb](verb.toUpperCase()))
1602
1606
  ];
1603
1607
  if (verbs.length === 1 || firstRow) {
1604
- row.push({ rowSpan: verbs.length, content: prettyPath(path5.path) });
1608
+ row.push({ rowSpan: verbs.length, content: prettyPath(path6.path) });
1605
1609
  firstRow = false;
1606
1610
  }
1607
1611
  row.push(entryType.join(" -> "));
@@ -1613,7 +1617,7 @@ function logRoutesTable(routes, bundle, options) {
1613
1617
  }
1614
1618
  }
1615
1619
  for (const [key, route] of Object.entries(routes.special).sort()) {
1616
- const row = [kleur.bold(kleur.white("*")), key, kleur.yellow("page")];
1620
+ const row = [kleur2.bold(kleur2.white("*")), key, kleur2.yellow("page")];
1617
1621
  hasMiddleware && row.push("");
1618
1622
  hasMeta && row.push("");
1619
1623
  row.push(prettySize(computeRouteSize(getRouteChunkName(route), bundle)));
@@ -1652,30 +1656,27 @@ function computeChunkSize(chunk, bundle, seen = /* @__PURE__ */ new Set()) {
1652
1656
  }
1653
1657
  function prettySize([bytes, compBytes]) {
1654
1658
  if (bytes <= 0) {
1655
- return kleur.gray("0.0 kB");
1659
+ return kleur2.gray("0.0 kB");
1656
1660
  }
1657
1661
  const [size, prefix] = format(bytes, { decimals: 1 }).split(/\s+/);
1658
1662
  const compSize = format(compBytes, { decimals: 1, prefix, unit: "B" });
1659
- let str = kleur.white(size) + kleur.gray("/");
1660
- if (compBytes < 20 * 1e3) str += kleur.green(compSize);
1661
- else if (compBytes < 50 * 1e3) str += kleur.yellow(compSize);
1662
- else str += kleur.bold(kleur.red(compSize));
1663
+ let str = kleur2.white(size) + kleur2.gray("/");
1664
+ if (compBytes < 20 * 1e3) str += kleur2.green(compSize);
1665
+ else if (compBytes < 50 * 1e3) str += kleur2.yellow(compSize);
1666
+ else str += kleur2.bold(kleur2.red(compSize));
1663
1667
  return str;
1664
1668
  }
1665
- function prettyPath(path5) {
1666
- return path5.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur.bold(kleur.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur.bold(kleur.dim(`:${p}`)));
1669
+ function prettyPath(path6) {
1670
+ return path6.replace(/\/\$\$(.*)$/, (_, p) => "/" + kleur2.bold(kleur2.dim(`*${p}`))).replace(/\/\$([^/]+)/g, (_, p) => "/" + kleur2.bold(kleur2.dim(`:${p}`)));
1667
1671
  }
1668
1672
 
1669
- // src/vite/plugin.ts
1670
- import createDebug from "debug";
1671
-
1672
1673
  // src/vite/utils/read-once-persisted-store.ts
1674
+ import { promises as fs2 } from "fs";
1673
1675
  import os from "os";
1674
- import path2 from "path";
1675
- import { promises as fs3 } from "fs";
1676
+ import path3 from "path";
1676
1677
  var noop = () => {
1677
1678
  };
1678
- var tmpFile = path2.join(os.tmpdir(), "marko-run-storage.json");
1679
+ var tmpFile = path3.join(os.tmpdir(), "marko-run-storage.json");
1679
1680
  var values = /* @__PURE__ */ new Map();
1680
1681
  var loadedFromDisk;
1681
1682
  var ReadOncePersistedStore = class {
@@ -1695,13 +1696,13 @@ var ReadOncePersistedStore = class {
1695
1696
  if (loadedFromDisk === true) {
1696
1697
  throw new Error(`Value for ${uid} could not be loaded.`);
1697
1698
  }
1698
- await (loadedFromDisk || (loadedFromDisk = fs3.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1699
+ await (loadedFromDisk || (loadedFromDisk = fs2.readFile(tmpFile, "utf-8").then(syncDataFromDisk).catch(finishLoadFromDisk)));
1699
1700
  return this.read();
1700
1701
  }
1701
1702
  };
1702
1703
  function syncDataFromDisk(data) {
1703
1704
  finishLoadFromDisk();
1704
- fs3.unlink(tmpFile).catch(noop);
1705
+ fs2.unlink(tmpFile).catch(noop);
1705
1706
  for (const [k, v] of JSON.parse(data)) {
1706
1707
  values.set(k, v);
1707
1708
  }
@@ -1711,39 +1712,13 @@ function finishLoadFromDisk() {
1711
1712
  }
1712
1713
  process.once("beforeExit", (code) => {
1713
1714
  if (code === 0 && values.size) {
1714
- fs3.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1715
+ fs2.writeFile(tmpFile, JSON.stringify([...values])).catch(noop);
1715
1716
  }
1716
1717
  });
1717
1718
 
1718
- // src/adapter/utils.ts
1719
- import supporsColor from "supports-color";
1720
- import kleur2 from "kleur";
1721
- function stripAnsi(string) {
1722
- return string.replace(
1723
- /([\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><])/g,
1724
- ""
1725
- );
1726
- }
1727
- function cleanStack(stack) {
1728
- return stack.split(/\n/).filter((l) => /^\s*at/.test(l)).join("\n");
1729
- }
1730
- function prepareError(err) {
1731
- var _a;
1732
- return {
1733
- message: stripAnsi(err.message),
1734
- stack: stripAnsi(cleanStack(err.stack || "")),
1735
- id: err.id,
1736
- frame: stripAnsi(err.frame || ""),
1737
- plugin: err.plugin,
1738
- pluginCode: (_a = err.pluginCode) == null ? void 0 : _a.toString(),
1739
- loc: err.loc
1740
- };
1741
- }
1742
-
1743
1719
  // src/vite/plugin.ts
1744
- import { createHash } from "crypto";
1745
1720
  var debug = createDebug("@marko/run");
1746
- var __dirname = path3.dirname(fileURLToPath(import.meta.url));
1721
+ var __dirname = path4.dirname(fileURLToPath(import.meta.url));
1747
1722
  var PLUGIN_NAME_PREFIX = "marko-run-vite";
1748
1723
  var POSIX_SEP = "/";
1749
1724
  var WINDOWS_SEP = "\\";
@@ -1751,7 +1726,7 @@ var CLIENT_OUT_DIR = "public";
1751
1726
  var MIDDLEWARE_FILENAME = `${markoRunFilePrefix}middleware.js`;
1752
1727
  var ROUTER_FILENAME = `${markoRunFilePrefix}router.js`;
1753
1728
  var defaultPort = Number(process.env.PORT || 3e3);
1754
- var normalizePath = path3.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1729
+ var normalizePath = path4.sep === WINDOWS_SEP ? (id) => id.replace(/\\/g, POSIX_SEP) : (id) => id;
1755
1730
  function markoRun(opts = {}) {
1756
1731
  let { routesDir, adapter, ...markoVitePluginOptions } = opts;
1757
1732
  let store;
@@ -1773,108 +1748,119 @@ function markoRun(opts = {}) {
1773
1748
  let getExportsFromFile;
1774
1749
  let resolvedConfig;
1775
1750
  let typesFile;
1776
- let seenErrors = /* @__PURE__ */ new Set();
1751
+ const seenErrors = /* @__PURE__ */ new Set();
1777
1752
  const virtualFiles = /* @__PURE__ */ new Map();
1778
1753
  let times = {
1779
1754
  routesBuild: 0,
1780
1755
  routesRender: 0
1781
1756
  };
1782
- function getEntryFileRelativePath(to) {
1783
- return normalizePath(path3.relative(entryFilesDir, to));
1784
- }
1785
1757
  async function writeTypesFile(routes2) {
1786
1758
  if (routes2 && (tsConfigExists ?? (tsConfigExists = await globFileExists(
1787
1759
  root,
1788
1760
  "{.tsconfig*,tsconfig*.json}"
1789
1761
  )))) {
1790
- const filepath = path3.join(typesDir, "routes.d.ts");
1762
+ const filepath = path4.join(typesDir, "routes.d.ts");
1791
1763
  const data = await renderRouteTypeInfo(
1792
1764
  routes2,
1793
- normalizePath(path3.relative(typesDir, resolvedRoutesDir)),
1765
+ normalizePath(path4.relative(typesDir, resolvedRoutesDir)),
1794
1766
  adapter
1795
1767
  );
1796
- if (data !== typesFile || !fs4.existsSync(filepath)) {
1768
+ if (data !== typesFile || !fs3.existsSync(filepath)) {
1797
1769
  await ensureDir(typesDir);
1798
- await fs4.promises.writeFile(filepath, typesFile = data);
1770
+ await fs3.promises.writeFile(filepath, typesFile = data);
1799
1771
  }
1800
1772
  }
1801
1773
  }
1802
1774
  let buildVirtualFilesResult;
1803
1775
  function buildVirtualFiles() {
1804
- return buildVirtualFilesResult ?? (buildVirtualFilesResult = new Promise(async (resolve, reject) => {
1805
- try {
1806
- virtualFiles.clear();
1807
- routes = await buildRoutes({
1808
- walker: createFSWalker(resolvedRoutesDir),
1809
- importPrefix: routesDir
1810
- });
1811
- if (!routes.list.length) {
1812
- throw new Error("No routes generated");
1813
- }
1814
- for (const route of routes.list) {
1815
- virtualFiles.set(path3.posix.join(root, `${route.entryName}.js`), "");
1816
- }
1817
- if (routes.middleware.length) {
1818
- virtualFiles.set(path3.posix.join(root, MIDDLEWARE_FILENAME), "");
1819
- }
1820
- virtualFiles.set(path3.posix.join(root, ROUTER_FILENAME), "");
1821
- resolve(routes);
1822
- } catch (err) {
1823
- reject(err);
1776
+ return buildVirtualFilesResult ?? (buildVirtualFilesResult = (async () => {
1777
+ virtualFiles.clear();
1778
+ routes = await buildRoutes({
1779
+ walker: createFSWalker(resolvedRoutesDir),
1780
+ importPrefix: routesDir
1781
+ });
1782
+ if (!routes.list.length) {
1783
+ throw new Error("No routes generated");
1784
+ }
1785
+ for (const route of routes.list) {
1786
+ virtualFiles.set(path4.posix.join(root, `${route.entryName}.js`), "");
1787
+ }
1788
+ if (routes.middleware.length) {
1789
+ virtualFiles.set(path4.posix.join(root, MIDDLEWARE_FILENAME), "");
1824
1790
  }
1825
- }));
1791
+ virtualFiles.set(path4.posix.join(root, ROUTER_FILENAME), "");
1792
+ return routes;
1793
+ })());
1826
1794
  }
1827
1795
  let renderVirtualFilesResult;
1828
1796
  function renderVirtualFiles(context) {
1829
- return renderVirtualFilesResult ?? (renderVirtualFilesResult = new Promise(async (resolve) => {
1797
+ return renderVirtualFilesResult ?? (renderVirtualFilesResult = (async () => {
1830
1798
  var _a;
1831
1799
  try {
1832
1800
  const routes2 = await buildVirtualFiles();
1833
- let entryFilesDirExists = false;
1834
- if (fs4.existsSync(entryFilesDir)) {
1835
- fs4.rmSync(entryFilesDir, { recursive: true });
1801
+ if (fs3.existsSync(entryFilesDir)) {
1802
+ fs3.rmSync(entryFilesDir, { recursive: true });
1836
1803
  }
1837
1804
  for (const route of routes2.list) {
1838
- if (route.handler) {
1839
- const exports = await getExportsFromFile(
1840
- context,
1841
- route.handler.filePath
1842
- );
1843
- route.handler.verbs = [];
1805
+ const { handler, page, layouts } = route;
1806
+ if (handler) {
1807
+ const exports = await getExportsFromFile(context, handler.filePath);
1808
+ handler.verbs = [];
1844
1809
  for (const name of exports) {
1845
1810
  const verb = name.toLowerCase();
1846
1811
  if (name === verb.toUpperCase() && httpVerbs.includes(verb)) {
1847
- route.handler.verbs.push(verb);
1812
+ handler.verbs.push(verb);
1848
1813
  }
1849
1814
  }
1850
- if (!route.handler.verbs.length) {
1815
+ if (!handler.verbs.length) {
1851
1816
  context.warn(
1852
- `Did not find any http verb exports in handler '${path3.relative(root, route.handler.filePath)}' - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1817
+ `Did not find any http verb exports in handler '${path4.relative(root, handler.filePath)}' - expected ${httpVerbs.map((v) => v.toUpperCase()).join(", ")}`
1853
1818
  );
1854
1819
  }
1855
1820
  }
1856
- if (route.page && route.layouts.length) {
1857
- entryFilesDirExists || (entryFilesDirExists = !!fs4.mkdirSync(entryFilesDir, {
1858
- recursive: true
1859
- }));
1860
- fs4.writeFileSync(
1861
- path3.join(entryFilesDir, `${route.entryName}.marko`),
1862
- renderRouteTemplate(route, getEntryFileRelativePath)
1821
+ if (page && layouts.length) {
1822
+ const relativePath = path4.relative(
1823
+ resolvedRoutesDir,
1824
+ page.filePath
1825
+ );
1826
+ const routeFileDir = path4.join(entryFilesDir, relativePath, "..");
1827
+ const routeFileRelativePathPosix = normalizePath(
1828
+ path4.relative(routeFileDir, root)
1829
+ );
1830
+ fs3.mkdirSync(routeFileDir, { recursive: true });
1831
+ const pageNameIndex = page.name.indexOf("+page");
1832
+ const pageNamePrefix = pageNameIndex > 0 ? `${page.name.slice(0, pageNameIndex)}.` : "";
1833
+ fs3.writeFileSync(
1834
+ path4.join(routeFileDir, pageNamePrefix + "route.marko"),
1835
+ renderRouteTemplate(
1836
+ route,
1837
+ (to) => path4.posix.join(routeFileRelativePathPosix, to)
1838
+ )
1863
1839
  );
1864
1840
  }
1865
1841
  virtualFiles.set(
1866
- path3.posix.join(root, `${route.entryName}.js`),
1842
+ path4.posix.join(root, `${route.entryName}.js`),
1867
1843
  renderRouteEntry(route, relativeEntryFilesDirPosix)
1868
1844
  );
1869
1845
  }
1870
1846
  for (const route of Object.values(routes2.special)) {
1871
- if (route.layouts.length) {
1872
- entryFilesDirExists || (entryFilesDirExists = !!fs4.mkdirSync(entryFilesDir, {
1873
- recursive: true
1874
- }));
1875
- fs4.writeFileSync(
1876
- path3.join(entryFilesDir, `${route.entryName}.marko`),
1877
- renderRouteTemplate(route, getEntryFileRelativePath)
1847
+ const { page, layouts, key } = route;
1848
+ if (page && layouts.length) {
1849
+ const relativePath = path4.relative(
1850
+ resolvedRoutesDir,
1851
+ page.filePath
1852
+ );
1853
+ const routeFileDir = path4.join(entryFilesDir, relativePath, "..");
1854
+ const routeFileRelativePathPosix = normalizePath(
1855
+ path4.relative(routeFileDir, root)
1856
+ );
1857
+ fs3.mkdirSync(routeFileDir, { recursive: true });
1858
+ fs3.writeFileSync(
1859
+ path4.join(routeFileDir, `route.${key}.marko`),
1860
+ renderRouteTemplate(
1861
+ route,
1862
+ (to) => path4.posix.join(routeFileRelativePathPosix, to)
1863
+ )
1878
1864
  );
1879
1865
  }
1880
1866
  }
@@ -1882,17 +1868,17 @@ function markoRun(opts = {}) {
1882
1868
  for (const middleware of routes2.middleware) {
1883
1869
  if (!(await getExportsFromFile(context, middleware.filePath)).includes("default")) {
1884
1870
  context.warn(
1885
- `Did not find a default export in middleware '${path3.relative(root, middleware.filePath)}'`
1871
+ `Did not find a default export in middleware '${path4.relative(root, middleware.filePath)}'`
1886
1872
  );
1887
1873
  }
1888
1874
  }
1889
1875
  virtualFiles.set(
1890
- path3.posix.join(root, MIDDLEWARE_FILENAME),
1876
+ path4.posix.join(root, MIDDLEWARE_FILENAME),
1891
1877
  renderMiddleware(routes2.middleware)
1892
1878
  );
1893
1879
  }
1894
1880
  virtualFiles.set(
1895
- path3.posix.join(root, ROUTER_FILENAME),
1881
+ path4.posix.join(root, ROUTER_FILENAME),
1896
1882
  renderRouter(routes2, relativeEntryFilesDirPosix, {
1897
1883
  trailingSlashes: opts.trailingSlashes || "RedirectWithout"
1898
1884
  })
@@ -1916,12 +1902,11 @@ function markoRun(opts = {}) {
1916
1902
  throw err;
1917
1903
  }
1918
1904
  virtualFiles.set(
1919
- path3.posix.join(root, ROUTER_FILENAME),
1905
+ path4.posix.join(root, ROUTER_FILENAME),
1920
1906
  `throw ${JSON.stringify(prepareError(err))}`
1921
1907
  );
1922
1908
  }
1923
- resolve();
1924
- }));
1909
+ })());
1925
1910
  }
1926
1911
  return [
1927
1912
  defaultConfigPlugin,
@@ -1959,23 +1944,25 @@ function markoRun(opts = {}) {
1959
1944
  );
1960
1945
  markoVitePluginOptions.runtimeId = opts.runtimeId;
1961
1946
  markoVitePluginOptions.basePathVar = opts.basePathVar;
1962
- resolvedRoutesDir = path3.resolve(root, routesDir);
1963
- entryFilesDir = path3.join(
1947
+ resolvedRoutesDir = path4.resolve(root, routesDir);
1948
+ entryFilesDir = path4.join(
1964
1949
  getModulesDir(root),
1965
1950
  ".marko",
1966
1951
  createHash("shake256", { outputLength: 4 }).update(root).digest("hex")
1967
1952
  );
1968
1953
  entryFilesDirPosix = normalizePath(entryFilesDir);
1969
- relativeEntryFilesDirPosix = normalizePath(path3.relative(root, entryFilesDir));
1970
- typesDir = path3.join(root, ".marko-run");
1971
- devEntryFile = path3.join(root, "index.html");
1954
+ relativeEntryFilesDirPosix = normalizePath(
1955
+ path4.relative(root, entryFilesDir)
1956
+ );
1957
+ typesDir = path4.join(root, ".marko-run");
1958
+ devEntryFile = path4.join(root, "index.html");
1972
1959
  devEntryFilePosix = normalizePath(devEntryFile);
1973
1960
  let outDir = ((_d = config2.build) == null ? void 0 : _d.outDir) || "dist";
1974
1961
  const assetsDir = ((_e = config2.build) == null ? void 0 : _e.assetsDir) || "assets";
1975
1962
  let rollupOutputOptions = (_g = (_f = config2.build) == null ? void 0 : _f.rollupOptions) == null ? void 0 : _g.output;
1976
1963
  if (isBuild) {
1977
1964
  if (!isSSRBuild) {
1978
- outDir = path3.join(outDir, CLIENT_OUT_DIR);
1965
+ outDir = path4.join(outDir, CLIENT_OUT_DIR);
1979
1966
  }
1980
1967
  const defaultRollupOutputOptions = {
1981
1968
  assetFileNames({ name }) {
@@ -1987,7 +1974,7 @@ function markoRun(opts = {}) {
1987
1974
  entryFileNames(info) {
1988
1975
  let name = getEntryFileName(info.facadeModuleId);
1989
1976
  if (!name) {
1990
- for (let id of info.moduleIds) {
1977
+ for (const id of info.moduleIds) {
1991
1978
  name = getEntryFileName(id);
1992
1979
  if (name) {
1993
1980
  break;
@@ -2107,7 +2094,7 @@ function markoRun(opts = {}) {
2107
2094
  devServer.watcher.on("all", async (type, filename) => {
2108
2095
  seenErrors.clear();
2109
2096
  const routableFileType = matchRoutableFile(
2110
- path3.parse(filename).base
2097
+ path4.parse(filename).base
2111
2098
  );
2112
2099
  if (filename.startsWith(resolvedRoutesDir) && routableFileType) {
2113
2100
  if (type === "add" || type === "unlink" || type === "change" && (routableFileType === RoutableFileTypes.Handler || routableFileType === RoutableFileTypes.Middleware)) {
@@ -2152,19 +2139,20 @@ function markoRun(opts = {}) {
2152
2139
  },
2153
2140
  async resolveId(importee, importer) {
2154
2141
  if (importee === "@marko/run/router") {
2155
- return path3.resolve(root, ROUTER_FILENAME);
2142
+ console.log("plugin resolveId router");
2143
+ return path4.resolve(root, ROUTER_FILENAME);
2156
2144
  } else if (importee.endsWith(".marko") && importee.includes(relativeEntryFilesDirPosix)) {
2157
2145
  if (!importee.startsWith(root)) {
2158
- importee = path3.resolve(root, "." + importee);
2146
+ importee = path4.resolve(root, "." + importee);
2159
2147
  }
2160
2148
  return normalizePath(importee);
2161
2149
  }
2162
2150
  let virtualFilePath;
2163
2151
  if (importee.startsWith(virtualFilePrefix)) {
2164
2152
  virtualFilePath = importee.slice(virtualFilePrefix.length + 1);
2165
- importee = path3.resolve(root, virtualFilePath);
2153
+ importee = path4.resolve(root, virtualFilePath);
2166
2154
  } else if (!isBuild && importer && (importer === devEntryFile || normalizePath(importer) === devEntryFilePosix) && importee.startsWith(`/${markoRunFilePrefix}`)) {
2167
- importee = path3.resolve(root, "." + importee);
2155
+ importee = path4.resolve(root, "." + importee);
2168
2156
  }
2169
2157
  importee = normalizePath(importee);
2170
2158
  if (!buildVirtualFilesResult) {
@@ -2173,7 +2161,7 @@ function markoRun(opts = {}) {
2173
2161
  if (virtualFiles.has(importee)) {
2174
2162
  return importee;
2175
2163
  } else if (virtualFilePath) {
2176
- const filePath = path3.resolve(__dirname, "..", virtualFilePath);
2164
+ const filePath = path4.resolve(__dirname, "..", virtualFilePath);
2177
2165
  return await this.resolve(filePath, importer, {
2178
2166
  skipSelf: true
2179
2167
  });
@@ -2212,7 +2200,7 @@ function markoRun(opts = {}) {
2212
2200
  const builtEntries = Object.values(bundle).reduce(
2213
2201
  (acc, item) => {
2214
2202
  if (item.type === "chunk" && item.isEntry) {
2215
- acc.push(path3.join(options.dir, item.fileName));
2203
+ acc.push(path4.join(options.dir, item.fileName));
2216
2204
  }
2217
2205
  return acc;
2218
2206
  },
@@ -2266,17 +2254,17 @@ async function globFileExists(root, pattern) {
2266
2254
  return (await glob(pattern, { root })).length > 0;
2267
2255
  }
2268
2256
  async function ensureDir(dir) {
2269
- if (!fs4.existsSync(dir)) {
2270
- await fs4.promises.mkdir(dir, { recursive: true });
2257
+ if (!fs3.existsSync(dir)) {
2258
+ await fs3.promises.mkdir(dir, { recursive: true });
2271
2259
  }
2272
2260
  }
2273
2261
  async function getPackageData(dir) {
2274
2262
  do {
2275
- const pkgPath = path3.join(dir, "package.json");
2276
- if (fs4.existsSync(pkgPath)) {
2277
- return JSON.parse(await fs4.promises.readFile(pkgPath, "utf-8"));
2263
+ const pkgPath = path4.join(dir, "package.json");
2264
+ if (fs3.existsSync(pkgPath)) {
2265
+ return JSON.parse(await fs3.promises.readFile(pkgPath, "utf-8"));
2278
2266
  }
2279
- } while (dir !== (dir = path3.dirname(dir)));
2267
+ } while (dir !== (dir = path4.dirname(dir)));
2280
2268
  return null;
2281
2269
  }
2282
2270
  async function resolveAdapter(root, options, log) {
@@ -2292,7 +2280,10 @@ async function resolveAdapter(root, options, log) {
2292
2280
  for (const name of dependecies) {
2293
2281
  if (name.startsWith("@marko/run-adapter") || name.indexOf("marko-run-adapter") !== -1) {
2294
2282
  try {
2295
- const module2 = await import(name);
2283
+ const module2 = await import(
2284
+ /* @vite-ignore */
2285
+ name
2286
+ );
2296
2287
  log && debug(
2297
2288
  `Using adapter ${name} listed in your package.json dependecies`
2298
2289
  );
@@ -2304,7 +2295,10 @@ async function resolveAdapter(root, options, log) {
2304
2295
  }
2305
2296
  }
2306
2297
  const defaultAdapter = "@marko/run/adapter";
2307
- const module = await import(defaultAdapter);
2298
+ const module = await import(
2299
+ /* @vite-ignore */
2300
+ defaultAdapter
2301
+ );
2308
2302
  log && debug("Using default adapter");
2309
2303
  return module.default();
2310
2304
  }
@@ -2334,7 +2328,7 @@ function getModulesDir(root, dir = __dirname) {
2334
2328
  return dir.slice(0, index + 12);
2335
2329
  }
2336
2330
  }
2337
- return path3.join(root, "node_modules");
2331
+ return path4.join(root, "node_modules");
2338
2332
  }
2339
2333
  var defaultConfigPlugin = {
2340
2334
  name: `${PLUGIN_NAME_PREFIX}:defaults`,
@@ -2352,8 +2346,39 @@ var defaultConfigPlugin = {
2352
2346
  }
2353
2347
  };
2354
2348
 
2349
+ // src/vite/utils/server.ts
2350
+ import cp from "child_process";
2351
+ import cluster from "cluster";
2352
+ import { config, parse } from "dotenv";
2353
+ import fs4 from "fs";
2354
+ import net from "net";
2355
+ async function getConnection(port) {
2356
+ return new Promise((resolve) => {
2357
+ const connection = net.connect(port).setNoDelay(true).setKeepAlive(false).on("error", () => {
2358
+ connection.end();
2359
+ resolve(null);
2360
+ }).on("connect", () => {
2361
+ resolve(connection);
2362
+ });
2363
+ });
2364
+ }
2365
+ async function isPortInUse(port) {
2366
+ return Boolean(await getConnection(port));
2367
+ }
2368
+ async function getAvailablePort(port) {
2369
+ if (port && !await isPortInUse(port)) {
2370
+ return port;
2371
+ }
2372
+ return new Promise((resolve) => {
2373
+ const server = net.createServer().listen(0, () => {
2374
+ const { port: port2 } = server.address();
2375
+ server.close(() => resolve(port2));
2376
+ });
2377
+ });
2378
+ }
2379
+
2355
2380
  // src/cli/commands.ts
2356
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
2381
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
2357
2382
  var defaultConfigFileBases = ["serve.config", "vite.config"];
2358
2383
  var defaultConfigFileExts = [".js", ".cjs", ".mjs", ".ts", ".mts"];
2359
2384
  async function preview(entry, distEntry, cwd, configFile, port, outDir, envFile, args = []) {
@@ -2382,10 +2407,10 @@ async function preview(entry, distEntry, cwd, configFile, port, outDir, envFile,
2382
2407
  if (!entry) {
2383
2408
  entry = await ((_a = adapter.getEntryFile) == null ? void 0 : _a.call(adapter));
2384
2409
  }
2385
- const dir = path4.resolve(cwd, resolvedConfig.build.outDir);
2386
- const entryFile = distEntry ? path4.join(dir, distEntry) : await findFileWithExt(dir, "index", [".mjs", ".js"]);
2410
+ const dir = path5.resolve(cwd, resolvedConfig.build.outDir);
2411
+ const entryFile = distEntry ? path5.join(dir, distEntry) : await findFileWithExt(dir, "index", [".mjs", ".js"]);
2387
2412
  if (envFile) {
2388
- envFile = path4.resolve(cwd, envFile);
2413
+ envFile = path5.resolve(cwd, envFile);
2389
2414
  }
2390
2415
  const options = {
2391
2416
  cwd,
@@ -2409,7 +2434,7 @@ async function dev(entry, cwd, configFile, port, envFile, args = []) {
2409
2434
  "serve"
2410
2435
  );
2411
2436
  if (envFile) {
2412
- envFile = path4.resolve(cwd, envFile);
2437
+ envFile = path5.resolve(cwd, envFile);
2413
2438
  }
2414
2439
  const [availablePort, adapter] = await Promise.all([
2415
2440
  getAvailablePort(
@@ -2463,7 +2488,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
2463
2488
  }
2464
2489
  }
2465
2490
  if (envFile) {
2466
- envFile = path4.resolve(cwd, envFile);
2491
+ envFile = path5.resolve(cwd, envFile);
2467
2492
  }
2468
2493
  const buildConfig = setExternalAdapterOptions(
2469
2494
  {
@@ -2501,7 +2526,7 @@ async function build(entry, cwd, configFile, outDir, envFile) {
2501
2526
  }
2502
2527
  function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
2503
2528
  for (const ext of extensions) {
2504
- const filePath = path4.join(dir, base + ext);
2529
+ const filePath = path5.join(dir, base + ext);
2505
2530
  if (fs5.existsSync(filePath)) {
2506
2531
  return filePath;
2507
2532
  }
@@ -2510,7 +2535,7 @@ function findFileWithExt(dir, base, extensions = defaultConfigFileExts) {
2510
2535
  }
2511
2536
  async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
2512
2537
  if (configFile) {
2513
- const configFilePath = path4.join(dir, configFile);
2538
+ const configFilePath = path5.join(dir, configFile);
2514
2539
  if (!fs5.existsSync(configFilePath)) {
2515
2540
  throw new Error(`No config file found at '${configFilePath}'`);
2516
2541
  }
@@ -2522,7 +2547,7 @@ async function getViteConfig(dir, configFile, bases = defaultConfigFileBases) {
2522
2547
  return configFile;
2523
2548
  }
2524
2549
  }
2525
- return path4.join(__dirname2, "default.config.mjs");
2550
+ return path5.join(__dirname2, "default.config.mjs");
2526
2551
  }
2527
2552
  async function resolveAdapter2(config2) {
2528
2553
  const options = getExternalPluginOptions(config2);