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