@marko/run 0.5.13 → 0.5.15

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