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