@kosmojs/lib 0.1.0 → 0.1.2

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.
package/pkg/index.js CHANGED
@@ -1,1453 +1,1121 @@
1
- // src/ast.ts
2
- import { join, resolve } from "node:path";
1
+ import { builtinModules } from "node:module";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
3
  import { styleText } from "node:util";
4
4
  import crc from "crc/crc32";
5
5
  import { flattener } from "tfusion";
6
- import {
7
- Project,
8
- SyntaxKind
9
- } from "ts-morph";
10
- import {
11
- defaults,
12
- RequestValidationTargets
13
- } from "@kosmojs/core";
6
+ import { Project, SyntaxKind } from "ts-morph";
7
+ import { RequestValidationTargets, defaults } from "@kosmojs/core";
14
8
  import { HTTPMethods } from "@kosmojs/core/api";
9
+ import picomatch from "picomatch";
10
+ import { access, constants, mkdir, readFile, writeFile } from "node:fs/promises";
11
+ import handlebars from "handlebars";
12
+ import { glob } from "tinyglobby";
13
+ import { compile, parse } from "path-to-regexp";
14
+ import mimeTypes from "mime-types";
15
+ import { Spinner } from "@topcli/spinner";
16
+ //#region src/ast.ts
15
17
  var astFactory = () => {
16
- const createProject = (opts) => new Project(opts);
17
- const resolveRouteSignature = async (route, opts) => {
18
- const {
19
- sourceFile = createProject().addSourceFileAtPath(route.fileFullpath)
20
- } = { ...opts };
21
- const [typeDeclarations, referencedFiles] = extractTypeDeclarations(
22
- sourceFile,
23
- opts
24
- );
25
- const defaultExport = extractDefaultExport(sourceFile);
26
- const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
27
- const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
28
- return {
29
- typeDeclarations,
30
- paramsRefinements,
31
- methods: methods.map((e) => e.method),
32
- validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
33
- referencedFiles
34
- };
35
- };
36
- const extractDefaultExport = (sourceFile) => {
37
- const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
38
- if (exportAssignment.isExportEquals()) {
39
- return [];
40
- }
41
- const callExpression = exportAssignment.getExpression();
42
- return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
43
- });
44
- return defaultExport;
45
- };
46
- const extractParamsRefinements = (callExpression) => {
47
- const [
48
- _routeName,
49
- // first generic - the route name
50
- paramsGeneric
51
- // second generic - params refinements
52
- ] = extractGenerics(callExpression);
53
- if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) {
54
- return;
55
- }
56
- return paramsGeneric.getElements().map((node, index) => {
57
- return {
58
- index,
59
- text: node.getText()
60
- };
61
- });
62
- };
63
- const extractRouteMethods = (route, callExpression) => {
64
- const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
65
- if (!funcDeclaration) {
66
- return [];
67
- }
68
- const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(
69
- SyntaxKind.ArrayLiteralExpression
70
- );
71
- if (!arrayLiteralExpression) {
72
- return [];
73
- }
74
- const callExpressions = [];
75
- for (const e of arrayLiteralExpression.getChildrenOfKind(
76
- SyntaxKind.CallExpression
77
- )) {
78
- const name = e.getExpression().getText();
79
- if (HTTPMethods[name]) {
80
- callExpressions.push([e, name]);
81
- }
82
- }
83
- const methods = [];
84
- for (const [callExpression2, method] of callExpressions) {
85
- const [vDefs, vOpts] = extractGenerics(callExpression2);
86
- methods.push({
87
- method,
88
- validationDefinitions: extractValidationDefinitions(
89
- route,
90
- method,
91
- vDefs,
92
- vOpts
93
- )
94
- });
95
- }
96
- return methods;
97
- };
98
- const parseRuntimeValidation = (typeNode) => {
99
- if (typeNode.isKind(SyntaxKind.LiteralType)) {
100
- const literal = typeNode.getFirstChild();
101
- if (literal?.isKind(SyntaxKind.TrueKeyword)) {
102
- return true;
103
- } else if (literal?.isKind(SyntaxKind.FalseKeyword)) {
104
- return false;
105
- }
106
- }
107
- return void 0;
108
- };
109
- const extractResponseVariant = (typeNode) => {
110
- if (!typeNode.isKind(SyntaxKind.TupleType)) {
111
- return;
112
- }
113
- let status = 200;
114
- let contentType;
115
- let body;
116
- const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
117
- if (statusNode?.isKind(SyntaxKind.LiteralType)) {
118
- const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
119
- if (literal) {
120
- status = Number(literal.getText());
121
- }
122
- }
123
- if (contentTypeNode) {
124
- contentType = extractStringLiteral(contentTypeNode);
125
- }
126
- if (bodyNode) {
127
- body = bodyNode.getText();
128
- if (["object"].includes(body)) {
129
- body = "{}";
130
- }
131
- }
132
- return { status, contentType, body };
133
- };
134
- const parseValidationOptions = (typeNode) => {
135
- const opts = {};
136
- if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) {
137
- return opts;
138
- }
139
- for (const prop of typeNode.getMembers()) {
140
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
141
- continue;
142
- }
143
- const target = prop.getName();
144
- const typeNode2 = prop.getTypeNodeOrThrow();
145
- if (!typeNode2.isKind(SyntaxKind.TypeLiteral)) {
146
- continue;
147
- }
148
- let contentType;
149
- let runtimeValidation;
150
- const customErrors = {};
151
- for (const member of typeNode2.getMembers()) {
152
- if (!member.isKind(SyntaxKind.PropertySignature)) {
153
- continue;
154
- }
155
- const nameNode = member.getNameNode();
156
- const valueNode = member.getTypeNodeOrThrow();
157
- const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
158
- if (name === "contentType") {
159
- contentType = extractStringLiteral(valueNode);
160
- } else if (name === "runtimeValidation") {
161
- runtimeValidation = parseRuntimeValidation(valueNode);
162
- } else if (name.startsWith("error")) {
163
- const literal = extractStringLiteral(valueNode);
164
- if (literal) {
165
- customErrors[name] = literal;
166
- }
167
- }
168
- }
169
- opts[target] = {
170
- contentType,
171
- runtimeValidation,
172
- customErrors
173
- };
174
- }
175
- return opts;
176
- };
177
- const extractStringLiteral = (typeNode) => {
178
- const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
179
- return literal ? literal.getLiteralText() : void 0;
180
- };
181
- const extractValidationDefinitions = (route, method, defsNode, optsNode) => {
182
- const definitions = [];
183
- if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) {
184
- return definitions;
185
- }
186
- const optsMap = parseValidationOptions(optsNode);
187
- const createId = (target, hash) => {
188
- return [
189
- target.replace(/^./, (c) => c.toUpperCase()),
190
- "T",
191
- method,
192
- crc(route.id + hash)
193
- ].join("");
194
- };
195
- for (const prop of defsNode.getMembers()) {
196
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
197
- continue;
198
- }
199
- const target = prop.getName();
200
- const typeNode = prop.getTypeNodeOrThrow();
201
- if (target === "response") {
202
- const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
203
- definitions.push({
204
- ...optsMap[target],
205
- method,
206
- target,
207
- variants: variants.flatMap((e, i) => {
208
- const { status, contentType, body } = extractResponseVariant(e) || {};
209
- if (!status) {
210
- return [];
211
- }
212
- if (contentType && typeof contentType !== "string") {
213
- console.warn(
214
- styleText(
215
- ["bold", "red"],
216
- `\u2717 The second element of a response variant should specify the Response Content Type`
217
- )
218
- );
219
- console.warn(
220
- styleText(["blue"], ` Example: [200, "json", Schema]`)
221
- );
222
- console.warn(
223
- ` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`
224
- );
225
- console.warn();
226
- }
227
- return [
228
- {
229
- id: createId(
230
- target,
231
- JSON.stringify([status, contentType, body])
232
- ),
233
- status,
234
- contentType,
235
- body
236
- }
237
- ];
238
- })
239
- });
240
- } else if (Object.keys(RequestValidationTargets).includes(target)) {
241
- definitions.push({
242
- ...optsMap[target],
243
- method,
244
- target,
245
- schema: {
246
- id: createId(target),
247
- text: typeNode.getText()
248
- }
249
- });
250
- }
251
- }
252
- return definitions;
253
- };
254
- const extractTypeDeclarations = (sourceFile, opts) => {
255
- const declarations = [];
256
- const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
257
- for (const declaration of sourceFile.getImportDeclarations()) {
258
- const modulePath = declaration.getModuleSpecifierValue();
259
- const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
260
- const typeOnlyDeclaration = declaration.isTypeOnly();
261
- const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
262
- if (defaultImport) {
263
- const name = defaultImport.getText();
264
- const text = `import type ${name} from "${path}";`;
265
- declarations.push({
266
- importDeclaration: {
267
- name,
268
- path
269
- },
270
- text
271
- });
272
- if (referencedFiles) {
273
- referencedFiles.push(...getReferencedFiles(defaultImport));
274
- }
275
- }
276
- const namespaceImport = typeOnlyDeclaration ? declaration.getNamespaceImport() : void 0;
277
- if (namespaceImport) {
278
- const name = namespaceImport.getText();
279
- const text = `import type * as ${name} from "${path}";`;
280
- declarations.push({
281
- importDeclaration: {
282
- name,
283
- path
284
- },
285
- text
286
- });
287
- if (referencedFiles) {
288
- referencedFiles.push(...getReferencedFiles(namespaceImport));
289
- }
290
- }
291
- for (const namedImport of declaration.getNamedImports()) {
292
- if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
293
- const nameNode = namedImport.getNameNode();
294
- const name = nameNode.getText();
295
- const alias = namedImport.getAliasNode()?.getText();
296
- const nameText = alias ? `${name} as ${alias}` : name;
297
- declarations.push({
298
- importDeclaration: {
299
- name,
300
- alias,
301
- path
302
- },
303
- text: `import type { ${nameText} } from "${path}";`
304
- });
305
- if (referencedFiles) {
306
- if (nameNode.isKind(SyntaxKind.Identifier)) {
307
- referencedFiles.push(...getReferencedFiles(nameNode));
308
- }
309
- }
310
- }
311
- }
312
- }
313
- for (const declaration of sourceFile.getTypeAliases()) {
314
- const name = declaration.getName();
315
- const text = declaration.getFullText().trim();
316
- declarations.push({
317
- typeAliasDeclaration: { name },
318
- text
319
- });
320
- }
321
- for (const declaration of sourceFile.getInterfaces()) {
322
- const name = declaration.getName();
323
- const text = declaration.getFullText().trim();
324
- declarations.push({
325
- interfaceDeclaration: { name },
326
- text
327
- });
328
- }
329
- for (const declaration of sourceFile.getEnums()) {
330
- const name = declaration.getName();
331
- const text = declaration.getFullText().trim();
332
- declarations.push({
333
- enumDeclaration: { name },
334
- text
335
- });
336
- }
337
- for (const declaration of sourceFile.getExportDeclarations()) {
338
- const typeOnlyDeclaration = declaration.isTypeOnly();
339
- const modulePath = declaration.getModuleSpecifierValue();
340
- const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
341
- for (const namedExport of declaration.getNamedExports()) {
342
- if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
343
- const nameNode = namedExport.getNameNode();
344
- const name = nameNode.getText();
345
- const alias = namedExport.getAliasNode()?.getText();
346
- const nameText = alias ? `${name} as ${alias}` : name;
347
- declarations.push({
348
- exportDeclaration: {
349
- name,
350
- alias: alias ?? name,
351
- path
352
- },
353
- text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
354
- });
355
- if (referencedFiles) {
356
- if (nameNode.isKind(SyntaxKind.Identifier)) {
357
- referencedFiles.push(...getReferencedFiles(nameNode));
358
- }
359
- }
360
- }
361
- }
362
- }
363
- return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
364
- };
365
- const getReferencedFiles = (importIdentifier) => {
366
- const declarations = importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || [];
367
- return declarations.flatMap((e) => {
368
- const sourceFile = e.getSourceFile();
369
- return sourceFile ? [sourceFile.getFilePath()] : [];
370
- });
371
- };
372
- const extractGenerics = (callExpression) => {
373
- return callExpression.getTypeArguments();
374
- };
375
- const typeResolverFactory = ({ root, name }) => {
376
- const project = createProject({
377
- tsConfigFilePath: resolve(
378
- root,
379
- join(defaults.srcDir, name, "tsconfig.json")
380
- ),
381
- skipAddingFilesFromTsConfig: true
382
- });
383
- const literalTypesResolver = (literalTypes, options) => {
384
- const sourceFile = project.createSourceFile(
385
- `${crc(literalTypes)}-${Date.now()}.ts`,
386
- literalTypes,
387
- { overwrite: true }
388
- );
389
- const resolvedTypes = flattener(project, sourceFile, {
390
- ...options,
391
- stripComments: true
392
- });
393
- project.removeSourceFile(sourceFile);
394
- return resolvedTypes;
395
- };
396
- return {
397
- getSourceFile: (fileFullpath) => {
398
- return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
399
- },
400
- refreshSourceFile: async (fileFullpath) => {
401
- const sourceFile = project.getSourceFile(fileFullpath);
402
- if (sourceFile) {
403
- await sourceFile.refreshFromFileSystem();
404
- }
405
- },
406
- literalTypesResolver
407
- };
408
- };
409
- return {
410
- createProject,
411
- extractDefaultExport,
412
- extractParamsRefinements,
413
- extractRouteMethods,
414
- extractTypeDeclarations,
415
- resolveRouteSignature,
416
- typeResolverFactory
417
- };
18
+ const createProject = (opts) => new Project(opts);
19
+ const resolveRouteSignature = async (route, opts) => {
20
+ const { sourceFile = createProject().addSourceFileAtPath(route.fileFullpath) } = { ...opts };
21
+ const [typeDeclarations, referencedFiles] = extractTypeDeclarations(sourceFile, opts);
22
+ const defaultExport = extractDefaultExport(sourceFile);
23
+ const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
24
+ const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
25
+ return {
26
+ typeDeclarations,
27
+ paramsRefinements,
28
+ methods: methods.map((e) => e.method),
29
+ validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
30
+ referencedFiles
31
+ };
32
+ };
33
+ const extractDefaultExport = (sourceFile) => {
34
+ const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
35
+ if (exportAssignment.isExportEquals()) return [];
36
+ const callExpression = exportAssignment.getExpression();
37
+ return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
38
+ });
39
+ return defaultExport;
40
+ };
41
+ const extractParamsRefinements = (callExpression) => {
42
+ const [_routeName, paramsGeneric] = extractGenerics(callExpression);
43
+ if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) return;
44
+ return paramsGeneric.getElements().map((node, index) => {
45
+ return {
46
+ index,
47
+ text: node.getText()
48
+ };
49
+ });
50
+ };
51
+ const extractRouteMethods = (route, callExpression) => {
52
+ const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
53
+ if (!funcDeclaration) return [];
54
+ const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
55
+ if (!arrayLiteralExpression) return [];
56
+ const callExpressions = [];
57
+ for (const e of arrayLiteralExpression.getChildrenOfKind(SyntaxKind.CallExpression)) {
58
+ const name = e.getExpression().getText();
59
+ if (HTTPMethods[name]) callExpressions.push([e, name]);
60
+ }
61
+ const methods = [];
62
+ for (const [callExpression, method] of callExpressions) {
63
+ const [vDefs, vOpts] = extractGenerics(callExpression);
64
+ methods.push({
65
+ method,
66
+ validationDefinitions: extractValidationDefinitions(route, method, vDefs, vOpts)
67
+ });
68
+ }
69
+ return methods;
70
+ };
71
+ /** Parse a boolean literal type node */
72
+ const parseRuntimeValidation = (typeNode) => {
73
+ if (typeNode.isKind(SyntaxKind.LiteralType)) {
74
+ const literal = typeNode.getFirstChild();
75
+ if (literal?.isKind(SyntaxKind.TrueKeyword)) return true;
76
+ else if (literal?.isKind(SyntaxKind.FalseKeyword)) return false;
77
+ }
78
+ };
79
+ const extractResponseVariant = (typeNode) => {
80
+ if (!typeNode.isKind(SyntaxKind.TupleType)) return;
81
+ let status = 200;
82
+ let contentType;
83
+ let body;
84
+ const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
85
+ if (statusNode?.isKind(SyntaxKind.LiteralType)) {
86
+ const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
87
+ if (literal) status = Number(literal.getText());
88
+ }
89
+ if (contentTypeNode) contentType = extractStringLiteral(contentTypeNode);
90
+ if (bodyNode) {
91
+ body = bodyNode.getText();
92
+ if (["object"].includes(body)) body = "{}";
93
+ }
94
+ return {
95
+ status,
96
+ contentType,
97
+ body
98
+ };
99
+ };
100
+ /** Parse opts TypeLiteral into a map keyed by target */
101
+ const parseValidationOptions = (typeNode) => {
102
+ const opts = {};
103
+ if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) return opts;
104
+ for (const prop of typeNode.getMembers()) {
105
+ if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
106
+ const target = prop.getName();
107
+ const typeNode = prop.getTypeNodeOrThrow();
108
+ if (!typeNode.isKind(SyntaxKind.TypeLiteral)) continue;
109
+ let contentType;
110
+ let runtimeValidation;
111
+ const customErrors = {};
112
+ for (const member of typeNode.getMembers()) {
113
+ if (!member.isKind(SyntaxKind.PropertySignature)) continue;
114
+ const nameNode = member.getNameNode();
115
+ const valueNode = member.getTypeNodeOrThrow();
116
+ const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
117
+ if (name === "contentType") contentType = extractStringLiteral(valueNode);
118
+ else if (name === "runtimeValidation") runtimeValidation = parseRuntimeValidation(valueNode);
119
+ else if (name.startsWith("error")) {
120
+ const literal = extractStringLiteral(valueNode);
121
+ if (literal) customErrors[name] = literal;
122
+ }
123
+ }
124
+ opts[target] = {
125
+ contentType,
126
+ runtimeValidation,
127
+ customErrors
128
+ };
129
+ }
130
+ return opts;
131
+ };
132
+ const extractStringLiteral = (typeNode) => {
133
+ const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
134
+ return literal ? literal.getLiteralText() : void 0;
135
+ };
136
+ /**
137
+ * Extract validation definitions from route handler generics.
138
+ * Merges defs (schemas) and opts (validation options) into a flat array.
139
+ * */
140
+ const extractValidationDefinitions = (route, method, defsNode, optsNode) => {
141
+ const definitions = [];
142
+ if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) return definitions;
143
+ const optsMap = parseValidationOptions(optsNode);
144
+ const createId = (target, hash) => {
145
+ return [
146
+ target.replace(/^./, (c) => c.toUpperCase()),
147
+ "T",
148
+ method,
149
+ crc(route.id + hash)
150
+ ].join("");
151
+ };
152
+ for (const prop of defsNode.getMembers()) {
153
+ if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
154
+ const target = prop.getName();
155
+ const typeNode = prop.getTypeNodeOrThrow();
156
+ if (target === "response") {
157
+ const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
158
+ definitions.push({
159
+ ...optsMap[target],
160
+ method,
161
+ target,
162
+ variants: variants.flatMap((e, i) => {
163
+ const { status, contentType, body } = extractResponseVariant(e) || {};
164
+ if (!status) return [];
165
+ if (contentType && typeof contentType !== "string") {
166
+ console.warn(styleText(["bold", "red"], `✗ The second element of a response variant should specify the Response Content Type`));
167
+ console.warn(styleText(["blue"], ` Example: [200, "json", Schema]`));
168
+ console.warn(` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`);
169
+ console.warn();
170
+ }
171
+ return [{
172
+ id: createId(target, JSON.stringify([
173
+ status,
174
+ contentType,
175
+ body
176
+ ])),
177
+ status,
178
+ contentType,
179
+ body
180
+ }];
181
+ })
182
+ });
183
+ } else if (Object.keys(RequestValidationTargets).includes(target)) definitions.push({
184
+ ...optsMap[target],
185
+ method,
186
+ target,
187
+ schema: {
188
+ id: createId(target),
189
+ text: typeNode.getText()
190
+ }
191
+ });
192
+ }
193
+ return definitions;
194
+ };
195
+ const extractTypeDeclarations = (sourceFile, opts) => {
196
+ const declarations = [];
197
+ const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
198
+ for (const declaration of sourceFile.getImportDeclarations()) {
199
+ const modulePath = declaration.getModuleSpecifierValue();
200
+ const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
201
+ const typeOnlyDeclaration = declaration.isTypeOnly();
202
+ const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
203
+ if (defaultImport) {
204
+ const name = defaultImport.getText();
205
+ const text = `import type ${name} from "${path}";`;
206
+ declarations.push({
207
+ importDeclaration: {
208
+ name,
209
+ path
210
+ },
211
+ text
212
+ });
213
+ if (referencedFiles) referencedFiles.push(...getReferencedFiles(defaultImport));
214
+ }
215
+ const namespaceImport = typeOnlyDeclaration ? declaration.getNamespaceImport() : void 0;
216
+ if (namespaceImport) {
217
+ const name = namespaceImport.getText();
218
+ const text = `import type * as ${name} from "${path}";`;
219
+ declarations.push({
220
+ importDeclaration: {
221
+ name,
222
+ path
223
+ },
224
+ text
225
+ });
226
+ if (referencedFiles) referencedFiles.push(...getReferencedFiles(namespaceImport));
227
+ }
228
+ for (const namedImport of declaration.getNamedImports()) if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
229
+ const nameNode = namedImport.getNameNode();
230
+ const name = nameNode.getText();
231
+ const alias = namedImport.getAliasNode()?.getText();
232
+ const nameText = alias ? `${name} as ${alias}` : name;
233
+ declarations.push({
234
+ importDeclaration: {
235
+ name,
236
+ alias,
237
+ path
238
+ },
239
+ text: `import type { ${nameText} } from "${path}";`
240
+ });
241
+ if (referencedFiles) {
242
+ if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
243
+ }
244
+ }
245
+ }
246
+ for (const declaration of sourceFile.getTypeAliases()) {
247
+ const name = declaration.getName();
248
+ const text = declaration.getFullText().trim();
249
+ declarations.push({
250
+ typeAliasDeclaration: { name },
251
+ text
252
+ });
253
+ }
254
+ for (const declaration of sourceFile.getInterfaces()) {
255
+ const name = declaration.getName();
256
+ const text = declaration.getFullText().trim();
257
+ declarations.push({
258
+ interfaceDeclaration: { name },
259
+ text
260
+ });
261
+ }
262
+ for (const declaration of sourceFile.getEnums()) {
263
+ const name = declaration.getName();
264
+ const text = declaration.getFullText().trim();
265
+ declarations.push({
266
+ enumDeclaration: { name },
267
+ text
268
+ });
269
+ }
270
+ for (const declaration of sourceFile.getExportDeclarations()) {
271
+ const typeOnlyDeclaration = declaration.isTypeOnly();
272
+ const modulePath = declaration.getModuleSpecifierValue();
273
+ const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
274
+ for (const namedExport of declaration.getNamedExports()) if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
275
+ const nameNode = namedExport.getNameNode();
276
+ const name = nameNode.getText();
277
+ const alias = namedExport.getAliasNode()?.getText();
278
+ const nameText = alias ? `${name} as ${alias}` : name;
279
+ declarations.push({
280
+ exportDeclaration: {
281
+ name,
282
+ alias: alias ?? name,
283
+ path
284
+ },
285
+ text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
286
+ });
287
+ if (referencedFiles) {
288
+ if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
289
+ }
290
+ }
291
+ }
292
+ return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
293
+ };
294
+ const getReferencedFiles = (importIdentifier) => {
295
+ return (importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || []).flatMap((e) => {
296
+ const sourceFile = e.getSourceFile();
297
+ return sourceFile ? [sourceFile.getFilePath()] : [];
298
+ });
299
+ };
300
+ const extractGenerics = (callExpression) => {
301
+ return callExpression.getTypeArguments();
302
+ };
303
+ const typeResolverFactory = ({ root, name }) => {
304
+ const project = createProject({
305
+ tsConfigFilePath: resolve(root, join(defaults.srcDir, name, "tsconfig.json")),
306
+ skipAddingFilesFromTsConfig: true
307
+ });
308
+ const literalTypesResolver = (literalTypes, options) => {
309
+ const sourceFile = project.createSourceFile(`${crc(literalTypes)}-${Date.now()}.ts`, literalTypes, { overwrite: true });
310
+ const resolvedTypes = flattener(project, sourceFile, {
311
+ ...options,
312
+ stripComments: true
313
+ });
314
+ project.removeSourceFile(sourceFile);
315
+ return resolvedTypes;
316
+ };
317
+ return {
318
+ getSourceFile: (fileFullpath) => {
319
+ return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
320
+ },
321
+ refreshSourceFile: async (fileFullpath) => {
322
+ const sourceFile = project.getSourceFile(fileFullpath);
323
+ if (sourceFile) await sourceFile.refreshFromFileSystem();
324
+ },
325
+ literalTypesResolver
326
+ };
327
+ };
328
+ return {
329
+ createProject,
330
+ extractDefaultExport,
331
+ extractParamsRefinements,
332
+ extractRouteMethods,
333
+ extractTypeDeclarations,
334
+ resolveRouteSignature,
335
+ typeResolverFactory
336
+ };
418
337
  };
419
-
420
- // src/generators.ts
421
- import picomatch from "picomatch";
338
+ //#endregion
339
+ //#region src/generators.ts
422
340
  var defineGenerator = (f) => f;
423
341
  var defineGeneratorFactory = (f) => f;
424
342
  var createTemplateResolver = (customTemplates, defaultTemplate) => {
425
- const templates = Object.entries({
426
- ...customTemplates
427
- }).map(([pattern, template]) => [picomatch(pattern), template]);
428
- return (pattern, entry) => {
429
- const match = templates.find(([isMatch]) => isMatch(pattern));
430
- if (!match) {
431
- return defaultTemplate;
432
- }
433
- return typeof match[1] === "function" ? match[1](entry) : match[1];
434
- };
343
+ const templates = Object.entries({ ...customTemplates }).map(([pattern, template]) => [picomatch(pattern), template]);
344
+ return (pattern, entry) => {
345
+ const match = templates.find(([isMatch]) => isMatch(pattern));
346
+ if (!match) return defaultTemplate;
347
+ return typeof match[1] === "function" ? match[1](entry) : match[1];
348
+ };
435
349
  };
436
-
437
- // src/paths.ts
438
- import { access, constants } from "node:fs/promises";
439
- import { join as join2, resolve as resolve2 } from "node:path";
440
- import { defaults as defaults2 } from "@kosmojs/core";
350
+ //#endregion
351
+ //#region src/paths.ts
441
352
  var pathResolver = (sourceFolder) => {
442
- const createPath = (...a) => {
443
- return sourceFolder.root ? resolve2(sourceFolder.root, join2(...a)) : join2(...a);
444
- };
445
- const createImport = {
446
- src(a, { origin }) {
447
- return origin === "src" ? join2(defaults2.srcPrefix, ...a) : join2(defaults2.appPrefix, defaults2.srcDir, sourceFolder.name, ...a);
448
- },
449
- config(a, o) {
450
- return this.src([defaults2.configDir, ...a], o);
451
- },
452
- api(a, o) {
453
- return this.src([defaults2.apiDir, ...a], o);
454
- },
455
- pages(a, o) {
456
- return this.src([defaults2.pagesDir, ...a], o);
457
- },
458
- lib(a, { origin }) {
459
- return origin === "src" ? join2(defaults2.libPrefix, ...a) : join2(defaults2.appPrefix, defaults2.libDir, sourceFolder.name, ...a);
460
- },
461
- libCore(a, o) {
462
- return this.lib(["core", ...a], o);
463
- },
464
- libApi(a, o) {
465
- return this.lib([defaults2.apiDir, ...a], o);
466
- },
467
- libEntry(a, o) {
468
- return this.lib([defaults2.entryDir, ...a], o);
469
- }
470
- };
471
- return {
472
- createPath: {
473
- src(...a) {
474
- return createPath(defaults2.srcDir, sourceFolder.name, ...a);
475
- },
476
- api(...a) {
477
- return this.src(defaults2.apiDir, ...a);
478
- },
479
- pages(...a) {
480
- return this.src(defaults2.pagesDir, ...a);
481
- },
482
- config(...a) {
483
- return this.src(defaults2.configDir, ...a);
484
- },
485
- entry(...a) {
486
- return this.src(defaults2.entryDir, ...a);
487
- },
488
- lib(...a) {
489
- return createPath(defaults2.libDir, sourceFolder.name, ...a);
490
- },
491
- libCore(...a) {
492
- return this.lib("core", ...a);
493
- },
494
- libApi(...a) {
495
- return this.lib(defaults2.apiDir, ...a);
496
- },
497
- libEntry(...a) {
498
- return this.lib(defaults2.entryDir, ...a);
499
- },
500
- libPages(...a) {
501
- return this.lib(defaults2.pagesDir, ...a);
502
- },
503
- distDir(...a) {
504
- return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
505
- }
506
- },
507
- createImport,
508
- createImportHelpers(o) {
509
- if (!["src", "lib"].includes(o?.origin)) {
510
- throw new Error(
511
- `createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`
512
- );
513
- }
514
- return {
515
- createImport(key, ...a) {
516
- return createImport[key](a.slice(0, -1), o);
517
- }
518
- };
519
- }
520
- };
353
+ const createPath = (...a) => {
354
+ return sourceFolder.root ? resolve(sourceFolder.root, join(...a)) : join(...a);
355
+ };
356
+ const createImport = {
357
+ src(a, { origin }) {
358
+ return origin === "src" ? join(defaults.srcPrefix, ...a) : join(defaults.appPrefix, defaults.srcDir, sourceFolder.name, ...a);
359
+ },
360
+ api(a, o) {
361
+ return this.src([defaults.apiDir, ...a], o);
362
+ },
363
+ pages(a, o) {
364
+ return this.src([defaults.pagesDir, ...a], o);
365
+ },
366
+ lib(a, { origin }) {
367
+ return origin === "src" ? join(defaults.libPrefix, ...a) : join(defaults.appPrefix, defaults.libDir, sourceFolder.name, ...a);
368
+ },
369
+ libCore(a, o) {
370
+ return this.lib(["core", ...a], o);
371
+ },
372
+ libApi(a, o) {
373
+ return this.lib([defaults.apiDir, ...a], o);
374
+ },
375
+ libEntry(a, o) {
376
+ return this.lib([defaults.entryDir, ...a], o);
377
+ }
378
+ };
379
+ return {
380
+ createPath: {
381
+ src(...a) {
382
+ return createPath(defaults.srcDir, sourceFolder.name, ...a);
383
+ },
384
+ api(...a) {
385
+ return this.src(defaults.apiDir, ...a);
386
+ },
387
+ pages(...a) {
388
+ return this.src(defaults.pagesDir, ...a);
389
+ },
390
+ entry(...a) {
391
+ return this.src(defaults.entryDir, ...a);
392
+ },
393
+ lib(...a) {
394
+ return createPath(defaults.libDir, sourceFolder.name, ...a);
395
+ },
396
+ libCore(...a) {
397
+ return this.lib("core", ...a);
398
+ },
399
+ libApi(...a) {
400
+ return this.lib(defaults.apiDir, ...a);
401
+ },
402
+ libEntry(...a) {
403
+ return this.lib(defaults.entryDir, ...a);
404
+ },
405
+ libPages(...a) {
406
+ return this.lib(defaults.pagesDir, ...a);
407
+ },
408
+ distDir(...a) {
409
+ return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
410
+ }
411
+ },
412
+ createImport,
413
+ createImportHelpers(o) {
414
+ if (!["src", "lib"].includes(o?.origin)) throw new Error(`createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`);
415
+ return { createImport(key, ...a) {
416
+ return createImport[key](a.slice(0, -1), o);
417
+ } };
418
+ }
419
+ };
521
420
  };
522
421
  var pathExists = async (path) => {
523
- try {
524
- await access(path, constants.F_OK);
525
- return true;
526
- } catch {
527
- return false;
528
- }
422
+ try {
423
+ await access(path, constants.F_OK);
424
+ return true;
425
+ } catch {
426
+ return false;
427
+ }
529
428
  };
530
-
531
- // src/render.ts
532
- import { mkdir, readFile, writeFile } from "node:fs/promises";
533
- import { dirname, join as join3 } from "node:path";
534
- import crc2 from "crc/crc32";
535
- import handlebars from "handlebars";
429
+ //#endregion
430
+ //#region src/render.ts
536
431
  var render = (template, context, options) => {
537
- const { noEscape = true, renderer = handlebars } = { ...options };
538
- return renderer.compile(template, { noEscape })(context);
432
+ const { noEscape = true, renderer = handlebars } = { ...options };
433
+ return renderer.compile(template, { noEscape })(context);
539
434
  };
540
435
  var renderToFile = async (file, template, context, options) => {
541
- const content = render(template, context, options);
542
- if (await pathExists(file)) {
543
- const { overwrite = true } = { ...options };
544
- if (overwrite === false) {
545
- return;
546
- }
547
- const fileContent = await readFile(file, "utf8");
548
- if (typeof overwrite === "function" && !overwrite(fileContent)) {
549
- return;
550
- }
551
- if (crc2(content) === crc2(fileContent)) {
552
- return;
553
- }
554
- }
555
- await mkdir(dirname(file), { recursive: true });
556
- await writeFile(file, content, "utf8");
436
+ const content = render(template, context, options);
437
+ /**
438
+ * Two fs calls (exists + read) are worth it to avoid touching the file
439
+ * and triggering watchers unnecessarily.
440
+ * */
441
+ if (await pathExists(file)) {
442
+ const { overwrite = true } = { ...options };
443
+ if (overwrite === false) return;
444
+ const fileContent = await readFile(file, "utf8");
445
+ if (typeof overwrite === "function" && !overwrite(fileContent)) return;
446
+ if (crc(content) === crc(fileContent)) return;
447
+ }
448
+ await mkdir(dirname(file), { recursive: true });
449
+ await writeFile(file, content, "utf8");
557
450
  };
558
451
  var renderFactory = (options) => {
559
- const createRenderer = (selfOoptions) => {
560
- const renderer = handlebars.create();
561
- renderer.registerPartial({
562
- ...options?.partials,
563
- ...selfOoptions?.partials
564
- });
565
- renderer.registerHelper({
566
- ...options?.helpers,
567
- ...selfOoptions?.helpers
568
- });
569
- return renderer;
570
- };
571
- return {
572
- render(template, context, selfOoptions) {
573
- return render(template, context, {
574
- ...options,
575
- ...selfOoptions,
576
- renderer: createRenderer(selfOoptions)
577
- });
578
- },
579
- async renderToFile(file, template, context, selfOoptions) {
580
- return renderToFile(
581
- options?.outdir ? join3(options.outdir, file) : file,
582
- template,
583
- context,
584
- {
585
- ...options,
586
- ...selfOoptions,
587
- renderer: createRenderer(selfOoptions)
588
- }
589
- );
590
- }
591
- };
452
+ const createRenderer = (selfOoptions) => {
453
+ const renderer = handlebars.create();
454
+ renderer.registerPartial({
455
+ ...options?.partials,
456
+ ...selfOoptions?.partials
457
+ });
458
+ renderer.registerHelper({
459
+ ...options?.helpers,
460
+ ...selfOoptions?.helpers
461
+ });
462
+ return renderer;
463
+ };
464
+ return {
465
+ render(template, context, selfOoptions) {
466
+ return render(template, context, {
467
+ ...options,
468
+ ...selfOoptions,
469
+ renderer: createRenderer(selfOoptions)
470
+ });
471
+ },
472
+ async renderToFile(file, template, context, selfOoptions) {
473
+ return renderToFile(options?.outdir ? join(options.outdir, file) : file, template, context, {
474
+ ...options,
475
+ ...selfOoptions,
476
+ renderer: createRenderer(selfOoptions)
477
+ });
478
+ }
479
+ };
592
480
  };
593
-
594
- // src/routes/index.ts
595
- import {
596
- defaults as defaults5
597
- } from "@kosmojs/core";
598
-
599
- // src/routes/base.ts
600
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "node:path";
601
- import { styleText as styleText2 } from "node:util";
602
- import crc4 from "crc/crc32";
603
- import picomatch2 from "picomatch";
604
- import { glob } from "tinyglobby";
605
- import {
606
- defaults as defaults3
607
- } from "@kosmojs/core";
608
-
609
- // src/routes/paths.ts
610
- import crc3 from "crc/crc32";
611
- import { parse } from "path-to-regexp";
612
- var pathTokensFactory = (path, {
613
- transformStaticValue = normalizeStaticValue
614
- } = {}) => {
615
- const extractParts = (tokens2, createConst, insideGroup = false) => {
616
- const parts = [];
617
- for (const token of tokens2) {
618
- switch (token.type) {
619
- case "text":
620
- if (token.value !== "/") {
621
- parts.push({
622
- type: "static",
623
- value: transformStaticValue(token.value)
624
- });
625
- }
626
- break;
627
- case "param":
628
- parts.push({
629
- type: "param",
630
- kind: insideGroup ? "optional" : "required",
631
- name: token.name,
632
- const: createConst(token.name)
633
- });
634
- break;
635
- case "wildcard":
636
- parts.push({
637
- type: "param",
638
- kind: "splat",
639
- name: token.name,
640
- const: createConst(token.name)
641
- });
642
- break;
643
- case "group":
644
- parts.push(...extractParts(token.tokens, createConst, true));
645
- break;
646
- }
647
- }
648
- return parts;
649
- };
650
- const patternTransforms = [
651
- // Transform required params: [id] => :id
652
- // Only pure \w param names,
653
- // [some-id] used as is, not treated as param,
654
- // use [some_id] instead.
655
- (s) => s.replace(/\[(\w+)\]/g, ":$1"),
656
- // Transform optional params: {id} => {:id}
657
- // Only pure \w param names,
658
- // anything else treated as a path-to-regexp pattern and used as is.
659
- // {some-id} treated as an optional static segment.
660
- // use {some_id} for simple param syntax
661
- // or {:some-id} pattern where :some is the param name and -id is a static segment.
662
- (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
663
- // Transform splat params: {...param} => {*param}
664
- (s) => s.replace(/\{\.\.\./g, "{*"),
665
- // Insert leading slash inside optional/splat groups.
666
- // {:name} => {/:name}
667
- // {*name} => {/*name}
668
- (s) => {
669
- return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
670
- }
671
- ];
672
- const detectBareParams = (s) => {
673
- let depth = 0;
674
- for (const [i, ch] of [...s].entries()) {
675
- if (ch === "{") {
676
- depth += 1;
677
- } else if (ch === "}") {
678
- depth -= 1;
679
- } else if (ch === ":" && depth === 0) {
680
- const match = s.slice(i + 1).match(/^\w+/);
681
- return match?.[0] || ":";
682
- }
683
- }
684
- return;
685
- };
686
- const tokens = path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
687
- if (!orig.length) {
688
- return [];
689
- }
690
- const bareParam = detectBareParams(orig);
691
- if (bareParam === ":") {
692
- throw new Error(
693
- `${path} contains colons outside braces, use : only within {}`
694
- );
695
- } else if (bareParam) {
696
- throw new Error(
697
- `${path} contains bare params, use [${bareParam}] instead of :${bareParam}`
698
- );
699
- }
700
- const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
701
- const { tokens: tokens2 } = parse(
702
- pattern.replace(/\{[^}]*\}|(\+)/g, (m, p1) => p1 ? "\\+" : m)
703
- );
704
- const parts = extractParts(tokens2, (val) => {
705
- return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc3(orig)].join("_") : val;
706
- });
707
- const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
708
- const isParam = parts.length === 1 ? parts[0].type === "param" : false;
709
- const kind = isStatic ? "static" : isParam ? "param" : "mixed";
710
- return [
711
- {
712
- kind,
713
- orig,
714
- pattern,
715
- parts
716
- }
717
- ];
718
- });
719
- return tokens;
481
+ //#endregion
482
+ //#region src/routes/paths.ts
483
+ /**
484
+ * Parse a filesystem route path into structured PathToken array.
485
+ *
486
+ * Uses path-to-regexp v8 AST for parsing, with directory-friendly syntax:
487
+ * - [param] => required param - :param
488
+ * - {param} => optional param - {:param}
489
+ * - {...param} => splat - {*param}
490
+ *
491
+ * Direct :param syntax is prohibited outside {} to avoid ambiguity.
492
+ * Inside {} it is treated as path-to-regexp power syntax and used as-is.
493
+ *
494
+ * Throw error if path can not be compiled into path-to-regexp pattern.
495
+ *
496
+ * Examples:
497
+ * Required: [id], [name]
498
+ * Optional: {name}, {format}
499
+ * Splat: {...path}
500
+ * Mixed segments: shop/[id]-{name}
501
+ * Power syntax: {-v:version{-:pre}}, :name{@:version{.:min}}.js
502
+ * */
503
+ var pathTokensFactory = (path) => {
504
+ /**
505
+ * Recursively extract parts from path-to-regexp AST tokens.
506
+ * A param inside a group is optional; top-level params are required.
507
+ * Wildcard tokens are always splat.
508
+ * Slash-only text nodes (restored for parsing) are skipped.
509
+ * */
510
+ const extractParts = (tokens, createConst, insideGroup = false) => {
511
+ const parts = [];
512
+ for (const token of tokens) switch (token.type) {
513
+ case "text":
514
+ if (token.value !== "/") parts.push({
515
+ type: "static",
516
+ value: token.value
517
+ });
518
+ break;
519
+ case "param":
520
+ parts.push({
521
+ type: "param",
522
+ kind: insideGroup ? "optional" : "required",
523
+ name: token.name,
524
+ const: createConst(token.name)
525
+ });
526
+ break;
527
+ case "wildcard":
528
+ parts.push({
529
+ type: "param",
530
+ kind: "splat",
531
+ name: token.name,
532
+ const: createConst(token.name)
533
+ });
534
+ break;
535
+ case "group":
536
+ parts.push(...extractParts(token.tokens, createConst, true));
537
+ break;
538
+ }
539
+ return parts;
540
+ };
541
+ const patternTransforms = [
542
+ (s) => s.replace(/\[(\w+)\]/g, ":$1"),
543
+ (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
544
+ (s) => s.replace(/\{\.\.\./g, "{*"),
545
+ (s) => {
546
+ return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
547
+ }
548
+ ];
549
+ const detectBareParams = (s) => {
550
+ let depth = 0;
551
+ for (const [i, ch] of [...s].entries()) if (ch === "{") depth += 1;
552
+ else if (ch === "}") depth -= 1;
553
+ else if (ch === ":" && depth === 0) return s.slice(i + 1).match(/^\w+/)?.[0] || ":";
554
+ };
555
+ const tokens = path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
556
+ if (!orig.length) return [];
557
+ const bareParam = detectBareParams(orig);
558
+ if (bareParam === ":") throw new Error(`${path} contains colons outside braces, use : only within {}`);
559
+ else if (bareParam) throw new Error(`${path} contains bare params, use [${bareParam}] instead of :${bareParam}`);
560
+ const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
561
+ const { tokens } = parse(pattern.replace(/\{[^}]*\}|(\+)/g, (m, p1) => p1 ? "\\+" : m));
562
+ const parts = extractParts(tokens, (val) => {
563
+ return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc(orig)].join("_") : val;
564
+ });
565
+ const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
566
+ const isParam = parts.length === 1 ? parts[0].type === "param" : false;
567
+ return [{
568
+ kind: isStatic ? "static" : isParam ? "param" : "mixed",
569
+ orig,
570
+ pattern,
571
+ parts
572
+ }];
573
+ });
574
+ compile(tokens.map((e) => e.pattern).join("/"));
575
+ return tokens;
720
576
  };
721
577
  var createPathPattern = (tokens) => {
722
- return tokens.map(({ pattern }, i) => {
723
- const next = tokens[i + 1];
724
- if (!next || next.pattern.includes("/")) {
725
- return pattern;
726
- }
727
- const slashRequired = tokens.slice(i + 1).some((e) => {
728
- return e.parts.some((e2) => {
729
- return e2.type === "static" || e2.kind === "required";
730
- });
731
- });
732
- return slashRequired ? `${pattern}/` : pattern;
733
- }).join("");
578
+ return tokens.map(({ pattern }, i) => {
579
+ const next = tokens[i + 1];
580
+ if (!next || next.pattern.includes("/")) return pattern;
581
+ return tokens.slice(i + 1).some((e) => {
582
+ return e.parts.some((e) => {
583
+ return e.type === "static" || e.kind === "required";
584
+ });
585
+ }) ? `${pattern}/` : pattern;
586
+ }).join("");
734
587
  };
735
588
  var createHonoPattern = (tokens) => {
736
- const staticValue = ({ value }) => {
737
- return normalizeStaticValue(value);
738
- };
739
- const paramValue = (p) => {
740
- if (p.kind === "splat") {
741
- return `*`;
742
- }
743
- if (p.kind === "optional") {
744
- return `:${p.name}?`;
745
- }
746
- return `:${p.name}`;
747
- };
748
- const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
749
- const tokensToRegex = (tokens2) => {
750
- return tokens2.flatMap((t) => {
751
- if (t.type === "text") {
752
- return t.value === "/" ? [] : [escapeRegex(t.value)];
753
- }
754
- if (t.type === "wildcard") {
755
- return [".+"];
756
- }
757
- if (t.type === "param") {
758
- return ["[^/]+"];
759
- }
760
- if (t.type === "group") {
761
- return [`(?:${tokensToRegex(t.tokens)})?`];
762
- }
763
- return [];
764
- }).join("");
765
- };
766
- return tokens.flatMap((token, i) => {
767
- if (token.kind === "static") {
768
- return [staticValue(token.parts[0])];
769
- }
770
- if (token.kind === "param") {
771
- return [paramValue(token.parts[0])];
772
- }
773
- const { tokens: tokens2 } = parse(token.pattern.replace(/\//g, ""));
774
- const regex = tokensToRegex(tokens2);
775
- return [`:_${i}{${regex}}`];
776
- }).join("/");
777
- };
778
- var normalizeStaticValue = (value) => {
779
- return value.replace(/\+/g, "\\\\+");
589
+ const paramValue = (p) => {
590
+ if (p.kind === "splat") return `*`;
591
+ if (p.kind === "optional") return `:${p.name}?`;
592
+ return `:${p.name}`;
593
+ };
594
+ const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
595
+ const tokensToRegex = (tokens) => {
596
+ return tokens.flatMap((t) => {
597
+ if (t.type === "text") return t.value === "/" ? [] : [escapeRegex(t.value)];
598
+ if (t.type === "wildcard") return [".+"];
599
+ if (t.type === "param") return ["[^/]+"];
600
+ if (t.type === "group") return [`(?:${tokensToRegex(t.tokens)})?`];
601
+ return [];
602
+ }).join("");
603
+ };
604
+ return tokens.flatMap((token, i) => {
605
+ if (token.kind === "static") return [token.parts[0].value];
606
+ if (token.kind === "param") return [paramValue(token.parts[0])];
607
+ const { tokens } = parse(token.pattern.replace(/\//g, ""));
608
+ return [`:_${i}{${tokensToRegex(tokens)}}`];
609
+ }).join("/");
780
610
  };
781
-
782
- // src/routes/base.ts
611
+ //#endregion
612
+ //#region src/routes/base.ts
783
613
  var API_INDEX_BASENAME = "index";
784
614
  var API_INDEX_PATTERN = `${API_INDEX_BASENAME}.ts`;
785
615
  var API_USE_BASENAME = "use";
786
- var API_USE_PATTERN = `${API_USE_BASENAME}.ts`;
616
+ var API_USE_PATTERN = `use.ts`;
787
617
  var PAGE_INDEX_BASENAME = "index";
788
618
  var PAGE_INDEX_PATTERN = `${PAGE_INDEX_BASENAME}.{tsx,vue,mdx,md}`;
789
619
  var PAGE_LAYOUT_BASENAME = "layout";
790
620
  var PAGE_LAYOUT_PATTERN = `${PAGE_LAYOUT_BASENAME}.{tsx,vue,mdx}`;
791
621
  var ROUTE_FILE_PATTERNS = [
792
- // match index files in api dir
793
- `${defaults3.apiDir}/**/${API_INDEX_PATTERN}`,
794
- // match use files in api dir
795
- `${defaults3.apiDir}/**/${API_USE_PATTERN}`,
796
- // match index files in pages dir
797
- `${defaults3.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
798
- // match layout files in pages dir
799
- `${defaults3.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
622
+ `${defaults.apiDir}/**/${API_INDEX_PATTERN}`,
623
+ `${defaults.apiDir}/**/${API_USE_PATTERN}`,
624
+ `${defaults.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
625
+ `${defaults.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
800
626
  ];
801
627
  var scanRoutes = async (sourceFolder) => {
802
- const { createPath } = pathResolver(sourceFolder);
803
- return glob(ROUTE_FILE_PATTERNS, {
804
- cwd: createPath.src(),
805
- absolute: true,
806
- onlyFiles: true,
807
- followSymbolicLinks: false,
808
- ignore: [
809
- // ignore top-level matches, routes resides in folders, even index route
810
- `${defaults3.apiDir}/${API_INDEX_PATTERN}`,
811
- `${defaults3.apiDir}/${API_USE_PATTERN}`,
812
- `${defaults3.pagesDir}/${PAGE_INDEX_PATTERN}`,
813
- `${defaults3.pagesDir}/${PAGE_LAYOUT_PATTERN}`
814
- ]
815
- });
628
+ const { createPath } = pathResolver(sourceFolder);
629
+ return glob(ROUTE_FILE_PATTERNS, {
630
+ cwd: createPath.src(),
631
+ absolute: true,
632
+ onlyFiles: true,
633
+ followSymbolicLinks: false,
634
+ ignore: [
635
+ `${defaults.apiDir}/${API_INDEX_PATTERN}`,
636
+ `${defaults.apiDir}/${API_USE_PATTERN}`,
637
+ `${defaults.pagesDir}/${PAGE_INDEX_PATTERN}`,
638
+ `${defaults.pagesDir}/${PAGE_LAYOUT_PATTERN}`
639
+ ]
640
+ });
816
641
  };
817
642
  var isRouteFile = (file, sourceFolder) => {
818
- const [
819
- // source folder name
820
- _sourceFolder,
821
- // route folder, api or pages
822
- _folder,
823
- ...rest
824
- ] = resolve3(sourceFolder.root, file).replace(`${sourceFolder.root}/${defaults3.srcDir}/`, "").split("/");
825
- if (!_folder || _sourceFolder !== sourceFolder.name || rest.length < 2) {
826
- return false;
827
- }
828
- return picomatch2.isMatch(join4(_folder, ...rest), ROUTE_FILE_PATTERNS) ? [_folder, rest.join("/")] : false;
643
+ const [_sourceFolder, _folder, ...rest] = resolve(sourceFolder.root, file).replace(`${sourceFolder.root}/${defaults.srcDir}/`, "").split("/");
644
+ /**
645
+ * Ensure the file:
646
+ * - is under the correct source root
647
+ * - belongs to a known route folder (`api/` or `pages/`)
648
+ * - is nested at least one level deep (not a direct child of the folder)
649
+ * */
650
+ if (!_folder || _sourceFolder !== sourceFolder.name || rest.length < 2) return false;
651
+ return picomatch.isMatch(join(_folder, ...rest), ROUTE_FILE_PATTERNS) ? [_folder, rest.join("/")] : false;
829
652
  };
830
653
  var isApiRoute = (file) => {
831
- return picomatch2.matchBase(file, `**/${API_INDEX_PATTERN}`);
654
+ return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
832
655
  };
833
656
  var isApiUse = (file) => {
834
- return picomatch2.matchBase(file, `**/${API_USE_PATTERN}`);
657
+ return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
835
658
  };
836
659
  var isPageRoute = (file) => {
837
- return picomatch2.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
660
+ return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
838
661
  };
839
662
  var isPageLayout = (file) => {
840
- return picomatch2.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
663
+ return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
841
664
  };
842
665
  var createRouteEntry = (fileFullpath, sourceFolder) => {
843
- const resolvedPaths = isRouteFile(fileFullpath, sourceFolder);
844
- if (!resolvedPaths) {
845
- return;
846
- }
847
- const [folder, file] = resolvedPaths;
848
- const id = `${file.replace(/\W+/g, "_")}_${crc4(file)}`;
849
- const name = dirname2(file);
850
- try {
851
- const pathTokens = pathTokensFactory(dirname2(file));
852
- return {
853
- id,
854
- name,
855
- folder,
856
- file,
857
- fileFullpath,
858
- pathTokens,
859
- pathPattern: createPathPattern(pathTokens),
860
- honoPattern: createHonoPattern(pathTokens)
861
- };
862
- } catch (error) {
863
- console.error(
864
- `\u2757${styleText2("red", "ERROR")}: Failed parsing path for "${styleText2("cyan", file)}"`
865
- );
866
- console.error(error);
867
- return;
868
- }
666
+ const resolvedPaths = isRouteFile(fileFullpath, sourceFolder);
667
+ if (!resolvedPaths) return;
668
+ const [folder, file] = resolvedPaths;
669
+ const id = `${file.replace(/\W+/g, "_")}_${crc(file)}`;
670
+ const name = dirname(file);
671
+ try {
672
+ const pathTokens = pathTokensFactory(dirname(file));
673
+ return {
674
+ id,
675
+ name,
676
+ folder,
677
+ file,
678
+ fileFullpath,
679
+ pathTokens,
680
+ pathPattern: createPathPattern(pathTokens),
681
+ honoPattern: createHonoPattern(pathTokens)
682
+ };
683
+ } catch (error) {
684
+ console.error(`❗${styleText("red", "ERROR")}: Failed parsing path for "${styleText("cyan", file)}"`);
685
+ console.error(error.message);
686
+ return;
687
+ }
869
688
  };
689
+ /**
690
+ * Weight of a single path segment.
691
+ *
692
+ * Fixed values per kind prevent complex mixed segments
693
+ * from outscoring multiple statics.
694
+ *
695
+ * static: 4 (exact match, most specific)
696
+ * mixed: 3+ (static + param parts; more statics = slightly higher)
697
+ * required: 2 (matches any single segment)
698
+ * optional: 1 (may or may not match)
699
+ * splat: 0 (matches anything, least specific)
700
+ * */
870
701
  var segmentWeight = (token) => {
871
- if (token.kind === "static") {
872
- return 4;
873
- }
874
- if (token.kind === "mixed") {
875
- let statics = 0;
876
- let optionals = 0;
877
- for (const part2 of token.parts) {
878
- if (part2.type === "static") {
879
- statics += 1;
880
- } else if (part2.kind === "optional" || part2.kind === "splat") {
881
- optionals += 1;
882
- }
883
- }
884
- return 3 + statics * 0.01 - optionals * 5e-3;
885
- }
886
- const part = token.parts[0];
887
- switch (part.kind) {
888
- case "required":
889
- return 2;
890
- case "optional":
891
- return 1;
892
- case "splat":
893
- return 0;
894
- }
702
+ if (token.kind === "static") return 4;
703
+ if (token.kind === "mixed") {
704
+ let statics = 0;
705
+ let optionals = 0;
706
+ for (const part of token.parts) if (part.type === "static") statics += 1;
707
+ else if (part.kind === "optional" || part.kind === "splat") optionals += 1;
708
+ return 3 + statics * .01 - optionals * .005;
709
+ }
710
+ switch (token.parts[0].kind) {
711
+ case "required": return 2;
712
+ case "optional": return 1;
713
+ case "splat": return 0;
714
+ }
895
715
  };
716
+ /**
717
+ * Compare two route entries for registration order.
718
+ *
719
+ * More specific routes sort first (higher priority).
720
+ * Segments are compared left-to-right; first difference wins.
721
+ *
722
+ * When one route is a prefix of the other, the trailing segment
723
+ * determines order: a trailing splat loses (shorter sibling first),
724
+ * anything else wins (longer route is more specific).
725
+ *
726
+ * At equal weight and length, lexicographic tiebreak ensures
727
+ * deterministic ordering across JS engines.
728
+ * */
896
729
  var sortRoutes = (a, b) => {
897
- const pairs = Array.from(
898
- { length: Math.max(a.pathTokens.length, b.pathTokens.length) },
899
- (_, i) => [a.pathTokens[i], b.pathTokens[i]]
900
- );
901
- for (const [at, bt] of pairs) {
902
- if (!at || !bt) {
903
- const extra = at ?? bt;
904
- const isSplat = extra?.kind === "param" ? extra.parts[0].kind === "splat" : false;
905
- if (isSplat) {
906
- return at ? 1 : -1;
907
- }
908
- return at ? -1 : 1;
909
- }
910
- const aw = segmentWeight(at);
911
- const bw = segmentWeight(bt);
912
- if (aw !== bw) {
913
- return bw - aw;
914
- }
915
- }
916
- return a.pathPattern.localeCompare(b.pathPattern);
730
+ const pairs = Array.from({ length: Math.max(a.pathTokens.length, b.pathTokens.length) }, (_, i) => [a.pathTokens[i], b.pathTokens[i]]);
731
+ for (const [at, bt] of pairs) {
732
+ if (!at || !bt) {
733
+ const extra = at ?? bt;
734
+ if (extra?.kind === "param" ? extra.parts[0].kind === "splat" : false) return at ? 1 : -1;
735
+ return at ? -1 : 1;
736
+ }
737
+ const aw = segmentWeight(at);
738
+ const bw = segmentWeight(bt);
739
+ if (aw !== bw) return bw - aw;
740
+ }
741
+ return a.pathPattern.localeCompare(b.pathPattern);
917
742
  };
918
-
919
- // src/routes/resolve.ts
920
- import { dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
921
- import { styleText as styleText3 } from "node:util";
922
- import crc5 from "crc/crc32";
923
- import mimeTypes from "mime-types";
924
- import {
925
- defaults as defaults4
926
- } from "@kosmojs/core";
927
-
928
- // src/templates/resolved-types.hbs
743
+ //#endregion
744
+ //#region src/templates/resolved-types.hbs?raw
929
745
  var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
930
-
931
- // src/templates/types.hbs
932
- var types_default = '{{#each typeDeclarations}}{{text}}\n{{/each}}\n\nexport type {{params.id}} = {\n {{#each paramsSchema}}\n "{{name}}"{{#unless isRequired}}?{{/unless}}: {{#if refinement}}\n {{refinement.text}},\n {{else}}\n {{#if isSplat}}Array<string>{{else}}string{{/if}},\n {{/if}}\n {{/each}}\n};\n\n{{#each validationTypes}}export type {{id}} = {{text}};\n{{/each}}\n';
933
-
934
- // src/routes/resolve.ts
746
+ //#endregion
747
+ //#region src/templates/types.hbs?raw
748
+ var types_default = "{{#each typeDeclarations}}{{text}}\n{{/each}}\n\nexport type {{params.id}} = {\n {{#each paramsSchema}}\n \"{{name}}\"{{#unless isRequired}}?{{/unless}}: {{#if refinement}}\n {{refinement.text}},\n {{else}}\n {{#if isSplat}}Array<string>{{else}}string{{/if}},\n {{/if}}\n {{/each}}\n};\n\n{{#each validationTypes}}export type {{id}} = {{text}};\n{{/each}}\n";
749
+ //#endregion
750
+ //#region src/routes/resolve.ts
935
751
  var resolverFactory = (sourceFolder, cacheFactory) => {
936
- const {
937
- //
938
- generators = [],
939
- refineTypeName = defaults4.refineTypeName
940
- } = sourceFolder.config;
941
- const { typeResolverFactory, resolveRouteSignature } = astFactory();
942
- const resolveTypes = generators.some(({ meta }) => meta.resolveTypes);
943
- const {
944
- //
945
- literalTypesResolver,
946
- getSourceFile,
947
- refreshSourceFile
948
- } = typeResolverFactory(sourceFolder);
949
- return {
950
- pageLayoutResolver(entry) {
951
- const { name } = entry;
952
- const handler = async () => {
953
- return {
954
- kind: "pageLayout",
955
- entry
956
- };
957
- };
958
- return { name, handler };
959
- },
960
- pageRouteResolver(entry) {
961
- const {
962
- id,
963
- name,
964
- folder,
965
- file,
966
- fileFullpath,
967
- pathTokens,
968
- pathPattern,
969
- honoPattern
970
- } = entry;
971
- const handler = async () => {
972
- const entry2 = {
973
- id,
974
- name,
975
- pathTokens,
976
- pathPattern,
977
- honoPattern,
978
- params: {
979
- schema: pathTokens.flatMap((e) => {
980
- return e.parts.filter((p) => p.type === "param");
981
- })
982
- },
983
- folder,
984
- file,
985
- fileFullpath
986
- };
987
- return {
988
- kind: "pageRoute",
989
- entry: entry2
990
- };
991
- };
992
- return { name, handler };
993
- },
994
- apiUseResolver(entry) {
995
- const { name } = entry;
996
- const handler = async () => {
997
- return {
998
- kind: "apiUse",
999
- entry
1000
- };
1001
- };
1002
- return { name, handler };
1003
- },
1004
- apiRouteResolver(entry) {
1005
- const {
1006
- id,
1007
- name,
1008
- file,
1009
- folder,
1010
- fileFullpath,
1011
- pathTokens,
1012
- pathPattern,
1013
- honoPattern
1014
- } = entry;
1015
- const handler = async (updatedFile) => {
1016
- const paramsSchema = pathTokens.flatMap(
1017
- (e) => {
1018
- return e.parts.flatMap((p) => {
1019
- return p.type === "param" ? [p] : [];
1020
- });
1021
- }
1022
- );
1023
- const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
1024
- const cacheStore = cacheFactory ? cacheFactory({ id, file, fileFullpath }, sourceFolder, {
1025
- resolveTypes
1026
- }) : void 0;
1027
- let cache = cacheStore ? await cacheStore.get({ validate: true }) : void 0;
1028
- if (!cache) {
1029
- if (updatedFile === fileFullpath) {
1030
- await refreshSourceFile(fileFullpath);
1031
- }
1032
- const {
1033
- typeDeclarations,
1034
- paramsRefinements,
1035
- methods,
1036
- validationDefinitions: validationDefinitions2,
1037
- referencedFiles = []
1038
- } = await resolveRouteSignature(
1039
- { id, name, fileFullpath, optionalParams },
1040
- {
1041
- withReferencedFiles: true,
1042
- sourceFile: getSourceFile(fileFullpath),
1043
- relpathResolver(path) {
1044
- return join5(
1045
- sourceFolder.name,
1046
- defaults4.apiDir,
1047
- dirname3(file),
1048
- path
1049
- );
1050
- }
1051
- }
1052
- );
1053
- const validationTypes = validationDefinitions2.flatMap((def) => {
1054
- return def.target === "response" ? def.variants.flatMap(({ id: id2, body }) => {
1055
- return body ? [{ id: id2, text: body }] : [];
1056
- }) : [def.schema];
1057
- });
1058
- const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
1059
- if (text === "number") {
1060
- const param = paramsSchema.at(index);
1061
- return param ? [param.name] : [];
1062
- }
1063
- return [];
1064
- }) : [];
1065
- const typesFile = pathResolver(sourceFolder).createPath.libApi(
1066
- dirname3(file),
1067
- "types.ts"
1068
- );
1069
- const params = {
1070
- id: ["ParamsT", crc5(name)].join(""),
1071
- schema: paramsSchema,
1072
- resolvedType: void 0
1073
- };
1074
- const typesFileContent = render(types_default, {
1075
- params,
1076
- paramsSchema: paramsSchema.map((param, index) => {
1077
- return {
1078
- ...param,
1079
- isRequired: param.kind === "required",
1080
- isSplat: param.kind === "splat",
1081
- refinement: paramsRefinements?.at(index)
1082
- };
1083
- }),
1084
- typeDeclarations,
1085
- validationTypes
1086
- });
1087
- const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
1088
- stripComments: true,
1089
- overrides: { [refineTypeName]: refineTypeName },
1090
- withProperties: [
1091
- params.id,
1092
- ...validationTypes.flatMap(({ id: id2 }) => id2)
1093
- ]
1094
- }) : void 0;
1095
- await renderToFile(
1096
- typesFile,
1097
- resolvedTypes ? resolved_types_default : typesFileContent,
1098
- { resolvedTypes }
1099
- );
1100
- params.resolvedType = resolvedTypes?.find(
1101
- (e) => e.name === params.id
1102
- );
1103
- cache = {
1104
- // placeholder values, if cacheFactory given,
1105
- // they will be replaced by persistCache
1106
- hash: 0,
1107
- referencedFiles: {},
1108
- // real values
1109
- params,
1110
- methods,
1111
- typeDeclarations,
1112
- numericParams,
1113
- validationDefinitions: validationDefinitions2.map((def) => {
1114
- return {
1115
- ...def,
1116
- ...def.target === "response" ? {
1117
- variants: def.variants.map((variant) => {
1118
- return {
1119
- ...variant,
1120
- resolvedType: resolvedTypes?.find(
1121
- (e) => e.name === variant.id
1122
- )
1123
- };
1124
- })
1125
- } : {
1126
- schema: {
1127
- ...def.schema,
1128
- resolvedType: resolvedTypes?.find(
1129
- (e) => e.name === def.schema.id
1130
- )
1131
- }
1132
- }
1133
- };
1134
- })
1135
- };
1136
- if (cacheStore) {
1137
- const { referencedFiles: _void, ...rest } = cache;
1138
- cache = await cacheStore.set({ ...rest, referencedFiles });
1139
- }
1140
- }
1141
- const validationDefinitions = cache.validationDefinitions.flatMap(
1142
- (def) => {
1143
- let augmentedDef = def;
1144
- if (def.target === "response") {
1145
- augmentedDef = {
1146
- ...def,
1147
- variants: def.variants.flatMap((variant, i) => {
1148
- if (typeof variant.contentType !== "string") {
1149
- return [variant];
1150
- }
1151
- if (variant.contentType.includes("/")) {
1152
- return [variant];
1153
- }
1154
- const contentType = mimeTypes.lookup(variant.contentType);
1155
- if (contentType === false) {
1156
- console.warn(
1157
- styleText3(
1158
- ["bold", "red"],
1159
- "\u2717 Failed resolving Response Content Type"
1160
- )
1161
- );
1162
- console.warn(
1163
- ` Invalid value provided for mime-types lookup - ${variant.contentType}`
1164
- );
1165
- console.warn(
1166
- styleText3(
1167
- ["cyan"],
1168
- ` Response variant #${i} excluded from route schemas`
1169
- )
1170
- );
1171
- console.warn(` Route: ${name}; Method: ${def.method}`);
1172
- console.warn();
1173
- return [];
1174
- }
1175
- return [{ ...variant, contentType }];
1176
- })
1177
- };
1178
- } else if (def.contentType && !def.contentType.includes("/")) {
1179
- const contentType = mimeTypes.lookup(def.contentType);
1180
- if (contentType === false) {
1181
- console.warn(
1182
- styleText3(
1183
- ["bold", "red"],
1184
- "\u2717 Failed resolving Response Content Type"
1185
- )
1186
- );
1187
- console.warn(
1188
- ` Invalid value provided for mime-types lookup - ${def.contentType}`
1189
- );
1190
- console.warn(` Route: ${name}; Method: ${def.method}`);
1191
- console.warn();
1192
- } else {
1193
- augmentedDef = { ...def, contentType };
1194
- }
1195
- }
1196
- return augmentedDef ? [augmentedDef] : [];
1197
- }
1198
- );
1199
- const entry2 = {
1200
- id,
1201
- name,
1202
- pathTokens,
1203
- pathPattern,
1204
- honoPattern,
1205
- params: cache.params,
1206
- numericParams: cache.numericParams,
1207
- optionalParams,
1208
- folder,
1209
- file,
1210
- fileFullpath,
1211
- methods: cache.methods,
1212
- typeDeclarations: cache.typeDeclarations,
1213
- validationDefinitions,
1214
- referencedFiles: Object.keys(cache.referencedFiles).map(
1215
- // expand referenced files path,
1216
- // they are stored as relative in cache
1217
- (e) => resolve4(sourceFolder.root, e)
1218
- )
1219
- };
1220
- return {
1221
- kind: "apiRoute",
1222
- entry: entry2
1223
- };
1224
- };
1225
- return { name, handler };
1226
- }
1227
- };
752
+ const { generators = [], refineTypeName = defaults.refineTypeName } = sourceFolder.config;
753
+ const { typeResolverFactory, resolveRouteSignature } = astFactory();
754
+ const resolveTypes = generators.some(({ meta }) => meta.resolveTypes);
755
+ const { literalTypesResolver, getSourceFile, refreshSourceFile } = typeResolverFactory(sourceFolder);
756
+ return {
757
+ pageLayoutResolver(entry) {
758
+ const { name } = entry;
759
+ const handler = async () => {
760
+ return {
761
+ kind: "pageLayout",
762
+ entry
763
+ };
764
+ };
765
+ return {
766
+ name,
767
+ handler
768
+ };
769
+ },
770
+ pageRouteResolver(entry) {
771
+ const { id, name, folder, file, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
772
+ const handler = async () => {
773
+ return {
774
+ kind: "pageRoute",
775
+ entry: {
776
+ id,
777
+ name,
778
+ pathTokens,
779
+ pathPattern,
780
+ honoPattern,
781
+ params: { schema: pathTokens.flatMap((e) => {
782
+ return e.parts.filter((p) => p.type === "param");
783
+ }) },
784
+ folder,
785
+ file,
786
+ fileFullpath
787
+ }
788
+ };
789
+ };
790
+ return {
791
+ name,
792
+ handler
793
+ };
794
+ },
795
+ apiUseResolver(entry) {
796
+ const { name } = entry;
797
+ const handler = async () => {
798
+ return {
799
+ kind: "apiUse",
800
+ entry
801
+ };
802
+ };
803
+ return {
804
+ name,
805
+ handler
806
+ };
807
+ },
808
+ apiRouteResolver(entry) {
809
+ const { id, name, file, folder, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
810
+ const handler = async (updatedFile) => {
811
+ const paramsSchema = pathTokens.flatMap((e) => {
812
+ return e.parts.flatMap((p) => {
813
+ return p.type === "param" ? [p] : [];
814
+ });
815
+ });
816
+ const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
817
+ const cacheStore = cacheFactory ? cacheFactory({
818
+ id,
819
+ file,
820
+ fileFullpath
821
+ }, sourceFolder, { resolveTypes }) : void 0;
822
+ let cache = cacheStore ? await cacheStore.get({ validate: true }) : void 0;
823
+ if (!cache) {
824
+ if (updatedFile === fileFullpath) await refreshSourceFile(fileFullpath);
825
+ const { typeDeclarations, paramsRefinements, methods, validationDefinitions, referencedFiles = [] } = await resolveRouteSignature({
826
+ id,
827
+ name,
828
+ fileFullpath,
829
+ optionalParams
830
+ }, {
831
+ withReferencedFiles: true,
832
+ sourceFile: getSourceFile(fileFullpath),
833
+ relpathResolver(path) {
834
+ return join(sourceFolder.name, defaults.apiDir, dirname(file), path);
835
+ }
836
+ });
837
+ const validationTypes = validationDefinitions.flatMap((def) => {
838
+ return def.target === "response" ? def.variants.flatMap(({ id, body }) => {
839
+ return body ? [{
840
+ id,
841
+ text: body
842
+ }] : [];
843
+ }) : [def.schema];
844
+ });
845
+ const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
846
+ if (text === "number") {
847
+ const param = paramsSchema.at(index);
848
+ return param ? [param.name] : [];
849
+ }
850
+ return [];
851
+ }) : [];
852
+ const typesFile = pathResolver(sourceFolder).createPath.libApi(dirname(file), "types.ts");
853
+ const params = {
854
+ id: ["ParamsT", crc(name)].join(""),
855
+ schema: paramsSchema,
856
+ resolvedType: void 0
857
+ };
858
+ const typesFileContent = render(types_default, {
859
+ params,
860
+ paramsSchema: paramsSchema.map((param, index) => {
861
+ return {
862
+ ...param,
863
+ isRequired: param.kind === "required",
864
+ isSplat: param.kind === "splat",
865
+ refinement: paramsRefinements?.at(index)
866
+ };
867
+ }),
868
+ typeDeclarations,
869
+ validationTypes
870
+ });
871
+ const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
872
+ stripComments: true,
873
+ overrides: { [refineTypeName]: refineTypeName },
874
+ withProperties: [params.id, ...validationTypes.flatMap(({ id }) => id)]
875
+ }) : void 0;
876
+ /**
877
+ * Deploy types.ts file; required by core generators (like fetch).
878
+ * If types resolved, write resolved types;
879
+ * otherwise write original types extracted from API route.
880
+ * */
881
+ await renderToFile(typesFile, resolvedTypes ? resolved_types_default : typesFileContent, { resolvedTypes });
882
+ params.resolvedType = resolvedTypes?.find((e) => e.name === params.id);
883
+ cache = {
884
+ hash: 0,
885
+ referencedFiles: {},
886
+ params,
887
+ methods,
888
+ typeDeclarations,
889
+ numericParams,
890
+ validationDefinitions: validationDefinitions.map((def) => {
891
+ return {
892
+ ...def,
893
+ ...def.target === "response" ? { variants: def.variants.map((variant) => {
894
+ return {
895
+ ...variant,
896
+ resolvedType: resolvedTypes?.find((e) => e.name === variant.id)
897
+ };
898
+ }) } : { schema: {
899
+ ...def.schema,
900
+ resolvedType: resolvedTypes?.find((e) => e.name === def.schema.id)
901
+ } }
902
+ };
903
+ })
904
+ };
905
+ if (cacheStore) {
906
+ const { referencedFiles: _void, ...rest } = cache;
907
+ cache = await cacheStore.set({
908
+ ...rest,
909
+ referencedFiles
910
+ });
911
+ }
912
+ }
913
+ const validationDefinitions = cache.validationDefinitions.flatMap((def) => {
914
+ let augmentedDef = def;
915
+ if (def.target === "response") augmentedDef = {
916
+ ...def,
917
+ variants: def.variants.flatMap((variant, i) => {
918
+ if (typeof variant.contentType !== "string") return [variant];
919
+ if (variant.contentType.includes("/")) return [variant];
920
+ const contentType = mimeTypes.lookup(variant.contentType);
921
+ if (contentType === false) {
922
+ console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
923
+ console.warn(` Invalid value provided for mime-types lookup - ${variant.contentType}`);
924
+ console.warn(styleText(["cyan"], ` Response variant #${i} excluded from route schemas`));
925
+ console.warn(` Route: ${name}; Method: ${def.method}`);
926
+ console.warn();
927
+ return [];
928
+ }
929
+ return [{
930
+ ...variant,
931
+ contentType
932
+ }];
933
+ })
934
+ };
935
+ else if (def.contentType && !def.contentType.includes("/")) {
936
+ const contentType = mimeTypes.lookup(def.contentType);
937
+ if (contentType === false) {
938
+ console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
939
+ console.warn(` Invalid value provided for mime-types lookup - ${def.contentType}`);
940
+ console.warn(` Route: ${name}; Method: ${def.method}`);
941
+ console.warn();
942
+ } else augmentedDef = {
943
+ ...def,
944
+ contentType
945
+ };
946
+ }
947
+ return augmentedDef ? [augmentedDef] : [];
948
+ });
949
+ return {
950
+ kind: "apiRoute",
951
+ entry: {
952
+ id,
953
+ name,
954
+ pathTokens,
955
+ pathPattern,
956
+ honoPattern,
957
+ params: cache.params,
958
+ numericParams: cache.numericParams,
959
+ optionalParams,
960
+ folder,
961
+ file,
962
+ fileFullpath,
963
+ methods: cache.methods,
964
+ typeDeclarations: cache.typeDeclarations,
965
+ validationDefinitions,
966
+ referencedFiles: Object.keys(cache.referencedFiles).map((e) => resolve(sourceFolder.root, e))
967
+ }
968
+ };
969
+ };
970
+ return {
971
+ name,
972
+ handler
973
+ };
974
+ }
975
+ };
1228
976
  };
1229
-
1230
- // src/routes/nesting.ts
1231
- import { basename } from "node:path";
977
+ //#endregion
978
+ //#region src/routes/nesting.ts
1232
979
  var nestedRoutesFactory = (routeEntries) => {
1233
- const entryStack = structuredClone(routeEntries).sort(sortRoutes);
1234
- const transformEntries = (entries, parent) => {
1235
- return [...new Set(entries.map((e) => e.name))].flatMap((name) => {
1236
- const nameEntries = entryStack.flatMap(({ fileFullpath, ...entry }) => {
1237
- return entry.name === name ? [entry] : [];
1238
- });
1239
- const index = nameEntries.find(
1240
- (e) => basename(e.file).startsWith(PAGE_INDEX_BASENAME)
1241
- );
1242
- const layout = nameEntries.find(
1243
- (e) => basename(e.file).startsWith(PAGE_LAYOUT_BASENAME)
1244
- );
1245
- if (index || layout) {
1246
- return [
1247
- {
1248
- index: index ? {
1249
- ...index,
1250
- pathTokens: index.pathTokens.slice(
1251
- parent?.pathTokens.length || 0
1252
- )
1253
- } : void 0,
1254
- layout: layout ? {
1255
- ...layout,
1256
- pathTokens: layout.pathTokens.slice(
1257
- parent?.pathTokens.length || 0
1258
- )
1259
- } : void 0,
1260
- parent: parent?.name,
1261
- children: transformEntries(
1262
- findDescendantEntries(index || layout),
1263
- index || layout
1264
- )
1265
- }
1266
- ];
1267
- }
1268
- return [];
1269
- });
1270
- };
1271
- const findDescendantEntries = ({
1272
- name,
1273
- pathTokens
1274
- }) => {
1275
- const potentialChildren = entryStack.filter((entry) => {
1276
- if (entry.pathTokens.length <= pathTokens.length) {
1277
- return false;
1278
- }
1279
- if (!entry.name.startsWith(`${name}/`)) {
1280
- return false;
1281
- }
1282
- return true;
1283
- });
1284
- return potentialChildren.filter((child) => {
1285
- const hasIntermediateRoute = potentialChildren.some((intermediate) => {
1286
- if (intermediate === child) {
1287
- return false;
1288
- }
1289
- if (intermediate.pathTokens.length <= pathTokens.length) {
1290
- return false;
1291
- }
1292
- if (intermediate.pathTokens.length >= child.pathTokens.length) {
1293
- return false;
1294
- }
1295
- return child.name.startsWith(`${intermediate.name}/`);
1296
- });
1297
- return !hasIntermediateRoute;
1298
- });
1299
- };
1300
- const rootEntries = entryStack.filter((entry) => {
1301
- const hasParent = entryStack.some((potential) => {
1302
- if (potential === entry) {
1303
- return false;
1304
- }
1305
- if (potential.pathTokens.length >= entry.pathTokens.length) {
1306
- return false;
1307
- }
1308
- return entry.name.startsWith(`${potential.name}/`);
1309
- });
1310
- return !hasParent;
1311
- });
1312
- return transformEntries(rootEntries);
980
+ const entryStack = structuredClone(routeEntries).sort(sortRoutes);
981
+ /**
982
+ * Group entries by name (at the same level, both layout and index share same name).
983
+ *
984
+ * Handle 3 cases:
985
+ * - both index and layout exists
986
+ * - only index exists
987
+ * - only layout exists
988
+ *
989
+ * */
990
+ const transformEntries = (entries, parent) => {
991
+ return [...new Set(entries.map((e) => e.name))].flatMap((name) => {
992
+ const nameEntries = entryStack.flatMap(({ fileFullpath, ...entry }) => {
993
+ return entry.name === name ? [entry] : [];
994
+ });
995
+ const index = nameEntries.find((e) => basename(e.file).startsWith(PAGE_INDEX_BASENAME));
996
+ const layout = nameEntries.find((e) => basename(e.file).startsWith(PAGE_LAYOUT_BASENAME));
997
+ if (index || layout) return [{
998
+ index: index ? {
999
+ ...index,
1000
+ pathTokens: index.pathTokens.slice(parent?.pathTokens.length || 0)
1001
+ } : void 0,
1002
+ layout: layout ? {
1003
+ ...layout,
1004
+ pathTokens: layout.pathTokens.slice(parent?.pathTokens.length || 0)
1005
+ } : void 0,
1006
+ parent: parent?.name,
1007
+ children: transformEntries(findDescendantEntries(index || layout), index || layout)
1008
+ }];
1009
+ return [];
1010
+ });
1011
+ };
1012
+ /**
1013
+ * Recursively finds direct descendant routes.
1014
+ *
1015
+ * "Direct" means the next existing route in the path hierarchy,
1016
+ * skipping intermediate path segments that don't have index/layout files.
1017
+ *
1018
+ * Example: blog/post/[slug]/index.tsx is a direct child of blog/index.tsx
1019
+ * even though "post" segment exists (it has no index.tsx).
1020
+ * */
1021
+ const findDescendantEntries = ({ name, pathTokens }) => {
1022
+ const potentialChildren = entryStack.filter((entry) => {
1023
+ if (entry.pathTokens.length <= pathTokens.length) return false;
1024
+ if (!entry.name.startsWith(`${name}/`)) return false;
1025
+ return true;
1026
+ });
1027
+ return potentialChildren.filter((child) => {
1028
+ return !potentialChildren.some((intermediate) => {
1029
+ if (intermediate === child) return false;
1030
+ if (intermediate.pathTokens.length <= pathTokens.length) return false;
1031
+ if (intermediate.pathTokens.length >= child.pathTokens.length) return false;
1032
+ return child.name.startsWith(`${intermediate.name}/`);
1033
+ });
1034
+ });
1035
+ };
1036
+ return transformEntries(entryStack.filter((entry) => {
1037
+ return !entryStack.some((potential) => {
1038
+ if (potential === entry) return false;
1039
+ if (potential.pathTokens.length >= entry.pathTokens.length) return false;
1040
+ return entry.name.startsWith(`${potential.name}/`);
1041
+ });
1042
+ }));
1313
1043
  };
1314
-
1315
- // src/routes/index.ts
1044
+ //#endregion
1045
+ //#region src/routes/index.ts
1316
1046
  var routesFactory = async (sourceFolder, cacheFactory) => {
1317
- const {
1318
- apiRouteResolver,
1319
- apiUseResolver,
1320
- pageRouteResolver,
1321
- pageLayoutResolver
1322
- } = resolverFactory(sourceFolder, cacheFactory);
1323
- const resolversFactory = (routeFiles2) => {
1324
- const resolvers = /* @__PURE__ */ new Map();
1325
- const entries = routeFiles2.flatMap((file) => {
1326
- const entry = createRouteEntry(file, sourceFolder);
1327
- return entry ? [entry] : [];
1328
- });
1329
- for (const entry of entries) {
1330
- if (entry.folder === defaults5.apiDir) {
1331
- if (isApiRoute(entry.file)) {
1332
- resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
1333
- } else if (isApiUse(entry.file)) {
1334
- resolvers.set(entry.fileFullpath, apiUseResolver(entry));
1335
- }
1336
- } else if (entry.folder === defaults5.pagesDir) {
1337
- if (isPageRoute(entry.file)) {
1338
- resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
1339
- } else if (isPageLayout(entry.file)) {
1340
- resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
1341
- }
1342
- }
1343
- }
1344
- return resolvers;
1345
- };
1346
- const routeFiles = await scanRoutes(sourceFolder);
1347
- return {
1348
- resolvers: resolversFactory(routeFiles),
1349
- resolversFactory
1350
- };
1047
+ const { apiRouteResolver, apiUseResolver, pageRouteResolver, pageLayoutResolver } = resolverFactory(sourceFolder, cacheFactory);
1048
+ const resolversFactory = (routeFiles) => {
1049
+ const resolvers = /* @__PURE__ */ new Map();
1050
+ const entries = routeFiles.flatMap((file) => {
1051
+ const entry = createRouteEntry(file, sourceFolder);
1052
+ return entry ? [entry] : [];
1053
+ });
1054
+ for (const entry of entries) if (entry.folder === defaults.apiDir) {
1055
+ if (isApiRoute(entry.file)) resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
1056
+ else if (isApiUse(entry.file)) resolvers.set(entry.fileFullpath, apiUseResolver(entry));
1057
+ } else if (entry.folder === defaults.pagesDir) {
1058
+ if (isPageRoute(entry.file)) resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
1059
+ else if (isPageLayout(entry.file)) resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
1060
+ }
1061
+ return resolvers;
1062
+ };
1063
+ return {
1064
+ resolvers: resolversFactory(await scanRoutes(sourceFolder)),
1065
+ resolversFactory
1066
+ };
1351
1067
  };
1352
-
1353
- // src/spinner.ts
1354
- import { Spinner } from "@topcli/spinner";
1068
+ //#endregion
1069
+ //#region src/spinner.ts
1355
1070
  var spinnerFactory = (startText) => {
1356
- const spinner = new Spinner().start(startText);
1357
- let _text = startText;
1358
- return {
1359
- text(text) {
1360
- _text = text;
1361
- spinner.text = text;
1362
- },
1363
- append(text) {
1364
- spinner.text = `${_text} \u203A ${text}`;
1365
- },
1366
- succeed(text) {
1367
- if (text) {
1368
- this.append(text);
1369
- } else {
1370
- this.text(_text);
1371
- }
1372
- spinner.succeed();
1373
- },
1374
- failed(text) {
1375
- if (text) {
1376
- this.text([_text, text].join("\n"));
1377
- }
1378
- spinner.failed();
1379
- }
1380
- };
1071
+ const spinner = new Spinner().start(startText);
1072
+ let _text = startText;
1073
+ return {
1074
+ text(text) {
1075
+ _text = text;
1076
+ spinner.text = text;
1077
+ },
1078
+ append(text) {
1079
+ spinner.text = `${_text} › ${text}`;
1080
+ },
1081
+ succeed(text) {
1082
+ if (text) this.append(text);
1083
+ else this.text(_text);
1084
+ spinner.succeed();
1085
+ },
1086
+ failed(text) {
1087
+ if (text) this.text([_text, text].join("\n"));
1088
+ spinner.failed();
1089
+ }
1090
+ };
1381
1091
  };
1382
1092
  var withSpinner = (text, pipe, spinner) => pipe(spinner || spinnerFactory(text));
1383
-
1384
- // src/typebox.ts
1385
- import { defaults as defaults6 } from "@kosmojs/core";
1093
+ //#endregion
1094
+ //#region src/typebox.ts
1386
1095
  var typeboxLiteralText = (text, sourceFolder) => {
1387
- const { refineTypeName = defaults6.refineTypeName } = sourceFolder.config;
1388
- return [
1389
- // Escape backticks for safe use in template literals
1390
- [/(?<!\\)`/g, "\\`"],
1391
- // Escape $ for safe use in template literals
1392
- [/(?<!\\)\$\{/g, "\\${"],
1393
- /**
1394
- * TypeBox's built-in `Options` type is not configurable.
1395
- * To allow a custom type name, exposing `refineTypeName` option,
1396
- * defaulted to VRefine, then renaming it to `Options`.
1397
- * */
1398
- [new RegExp(`\\b${refineTypeName}\\s*<`, "g"), "Options<"]
1399
- ].reduce((text2, [a, b]) => text2.replace(a, b), text);
1096
+ const { refineTypeName = defaults.refineTypeName } = sourceFolder.config;
1097
+ return [
1098
+ [/(?<!\\)`/g, "\\`"],
1099
+ [/(?<!\\)\$\{/g, "\\${"],
1100
+ [new RegExp(`\\b${refineTypeName}\\s*<`, "g"), "Options<"]
1101
+ ].reduce((text, [a, b]) => text.replace(a, b), text);
1400
1102
  };
1401
-
1402
- // src/vite.ts
1403
- import { builtinModules } from "node:module";
1103
+ //#endregion
1104
+ //#region src/vite.ts
1404
1105
  var nodePrefix = () => {
1405
- return {
1406
- name: "kosmojs:node-prefix",
1407
- enforce: "pre",
1408
- resolveId(source) {
1409
- return builtinModules.includes(source) ? { id: `node:${source}`, external: true } : void 0;
1410
- }
1411
- };
1412
- };
1413
- var vitePlugins = {
1414
- nodePrefix
1106
+ return {
1107
+ name: "kosmojs:node-prefix",
1108
+ enforce: "pre",
1109
+ resolveId(source) {
1110
+ return builtinModules.includes(source) ? {
1111
+ id: `node:${source}`,
1112
+ external: true
1113
+ } : void 0;
1114
+ }
1115
+ };
1415
1116
  };
1416
- export {
1417
- API_INDEX_BASENAME,
1418
- API_INDEX_PATTERN,
1419
- API_USE_BASENAME,
1420
- API_USE_PATTERN,
1421
- PAGE_INDEX_BASENAME,
1422
- PAGE_INDEX_PATTERN,
1423
- PAGE_LAYOUT_BASENAME,
1424
- PAGE_LAYOUT_PATTERN,
1425
- astFactory,
1426
- createHonoPattern,
1427
- createPathPattern,
1428
- createRouteEntry,
1429
- createTemplateResolver,
1430
- defineGenerator,
1431
- defineGeneratorFactory,
1432
- isApiRoute,
1433
- isApiUse,
1434
- isPageLayout,
1435
- isPageRoute,
1436
- isRouteFile,
1437
- nestedRoutesFactory,
1438
- normalizeStaticValue,
1439
- pathExists,
1440
- pathResolver,
1441
- pathTokensFactory,
1442
- render,
1443
- renderFactory,
1444
- renderToFile,
1445
- routesFactory,
1446
- scanRoutes,
1447
- sortRoutes,
1448
- spinnerFactory,
1449
- typeboxLiteralText,
1450
- vitePlugins,
1451
- withSpinner
1452
- };
1453
- //# sourceMappingURL=index.js.map
1117
+ var vitePlugins = { nodePrefix };
1118
+ //#endregion
1119
+ export { API_INDEX_BASENAME, API_INDEX_PATTERN, API_USE_BASENAME, API_USE_PATTERN, PAGE_INDEX_BASENAME, PAGE_INDEX_PATTERN, PAGE_LAYOUT_BASENAME, PAGE_LAYOUT_PATTERN, astFactory, createHonoPattern, createPathPattern, createRouteEntry, createTemplateResolver, defineGenerator, defineGeneratorFactory, isApiRoute, isApiUse, isPageLayout, isPageRoute, isRouteFile, nestedRoutesFactory, pathExists, pathResolver, pathTokensFactory, render, renderFactory, renderToFile, routesFactory, scanRoutes, sortRoutes, spinnerFactory, typeboxLiteralText, vitePlugins, withSpinner };
1120
+
1121
+ //# sourceMappingURL=index.js.map