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