@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/package.json +7 -6
- package/pkg/index.js +1055 -1387
- package/pkg/index.js.map +1 -7
- package/pkg/paths.d.ts +2 -2
- package/pkg/routes/paths.d.ts +3 -4
package/pkg/index.js
CHANGED
|
@@ -1,1453 +1,1121 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
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
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
422
|
+
try {
|
|
423
|
+
await access(path, constants.F_OK);
|
|
424
|
+
return true;
|
|
425
|
+
} catch {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
529
428
|
};
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
538
|
-
|
|
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
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
654
|
+
return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
|
|
832
655
|
};
|
|
833
656
|
var isApiUse = (file) => {
|
|
834
|
-
|
|
657
|
+
return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
|
|
835
658
|
};
|
|
836
659
|
var isPageRoute = (file) => {
|
|
837
|
-
|
|
660
|
+
return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
|
|
838
661
|
};
|
|
839
662
|
var isPageLayout = (file) => {
|
|
840
|
-
|
|
663
|
+
return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
|
|
841
664
|
};
|
|
842
665
|
var createRouteEntry = (fileFullpath, sourceFolder) => {
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
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
|
-
|
|
932
|
-
var types_default =
|
|
933
|
-
|
|
934
|
-
|
|
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
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
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
|
-
|
|
1231
|
-
import { basename } from "node:path";
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/routes/nesting.ts
|
|
1232
979
|
var nestedRoutesFactory = (routeEntries) => {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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
|
-
|
|
1044
|
+
//#endregion
|
|
1045
|
+
//#region src/routes/index.ts
|
|
1316
1046
|
var routesFactory = async (sourceFolder, cacheFactory) => {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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
|
-
|
|
1354
|
-
import { Spinner } from "@topcli/spinner";
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/spinner.ts
|
|
1355
1070
|
var spinnerFactory = (startText) => {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
-
|
|
1385
|
-
import { defaults as defaults6 } from "@kosmojs/core";
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/typebox.ts
|
|
1386
1095
|
var typeboxLiteralText = (text, sourceFolder) => {
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
-
|
|
1403
|
-
import { builtinModules } from "node:module";
|
|
1103
|
+
//#endregion
|
|
1104
|
+
//#region src/vite.ts
|
|
1404
1105
|
var nodePrefix = () => {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
};
|
|
1413
|
-
|
|
1414
|
-
|
|
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
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
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
|