@kosmojs/dev 0.0.24 → 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/pkg/index.js CHANGED
@@ -1,1781 +1,26 @@
1
- // src/alias-plugin/index.ts
2
- import { glob } from "tinyglobby";
3
-
4
- // src/paths.ts
5
- import { join } from "node:path";
6
-
7
- // src/defaults.ts
8
- var defaults = {
9
- appPrefix: "~",
10
- srcPrefix: "@",
11
- libPrefix: "_",
12
- coreDir: "core",
13
- srcDir: "src",
14
- libDir: "lib",
15
- configDir: "config",
16
- apiDir: "api",
17
- pagesDir: "pages",
18
- entryDir: "entry",
19
- fetchDir: "fetch"
20
- };
21
-
22
- // src/paths.ts
23
- var createTsconfigPaths = (prefix) => {
24
- return {
25
- [`${defaults.appPrefix}/*`]: [`${prefix}/*`],
26
- [`${defaults.srcPrefix}/*`]: [`${prefix}/${defaults.srcDir}/*`],
27
- [`${defaults.libPrefix}/*`]: [
28
- `${prefix}/${defaults.libDir}/${defaults.srcDir}/*`
29
- ]
30
- };
31
- };
32
- var pathResolver = ({
33
- appRoot,
34
- sourceFolder
35
- }) => {
36
- const createPath = (...a) => {
37
- return appRoot ? join(appRoot, ...a) : join(...a);
38
- };
39
- const createImport = {
40
- coreApi(...a) {
41
- return join(defaults.appPrefix, defaults.coreDir, defaults.apiDir, ...a);
42
- },
43
- src(...a) {
44
- return join(defaults.srcPrefix, sourceFolder, ...a);
45
- },
46
- config(...a) {
47
- return this.src(defaults.configDir, ...a);
48
- },
49
- api(...a) {
50
- return this.src(defaults.apiDir, ...a);
51
- },
52
- pages(...a) {
53
- return this.src(defaults.pagesDir, ...a);
54
- },
55
- lib(...a) {
56
- return join(defaults.libPrefix, sourceFolder, ...a);
57
- },
58
- libApi(...a) {
59
- return this.lib(defaults.apiDir, ...a);
60
- },
61
- libEntry(...a) {
62
- return this.lib(defaults.entryDir, ...a);
63
- }
64
- };
65
- return {
66
- createPath: {
67
- coreApi(...a) {
68
- return createPath(defaults.coreDir, defaults.apiDir, ...a);
69
- },
70
- src(...a) {
71
- return createPath(defaults.srcDir, sourceFolder, ...a);
72
- },
73
- api(...a) {
74
- return this.src(defaults.apiDir, ...a);
75
- },
76
- pages(...a) {
77
- return this.src(defaults.pagesDir, ...a);
78
- },
79
- config(...a) {
80
- return this.src(defaults.configDir, ...a);
81
- },
82
- entry(...a) {
83
- return this.src(defaults.entryDir, ...a);
84
- },
85
- lib(...a) {
86
- return createPath(defaults.libDir, defaults.srcDir, sourceFolder, ...a);
87
- },
88
- libApi(...a) {
89
- return this.lib(defaults.apiDir, ...a);
90
- },
91
- libEntry(...a) {
92
- return this.lib(defaults.entryDir, ...a);
93
- },
94
- libPages(...a) {
95
- return this.lib(defaults.pagesDir, ...a);
96
- }
97
- },
98
- createImport,
99
- createImportHelper: (key, ...a) => {
100
- return createImport[key](...a.slice(0, -1));
101
- }
102
- };
103
- };
104
-
105
- // src/alias-plugin/index.ts
106
- var alias_plugin_default = (appRoot, opt) => {
107
- return {
108
- name: "@kosmojs:aliasPlugin",
109
- async config() {
110
- const paths = await import(`${appRoot}/tsconfig.json`, { with: { type: "json" } }).then((e) => {
111
- return {
112
- ...e.default.compilerOptions?.paths,
113
- ...createTsconfigPaths(".")
114
- };
115
- });
116
- const aliasmap = [];
117
- const pathEntries = Object.entries(paths);
118
- for (const [aliasPattern, pathPatterns] of pathEntries) {
119
- const alias = aliasPattern.replace("/*", "");
120
- const paths2 = pathPatterns.map((e) => e.replace("/*", "")).sort((a, b) => a.split(/\/+/).length - b.split(/\/+/).length);
121
- if (paths2.length === 1) {
122
- aliasmap.push({
123
- find: new RegExp(`^${alias}/`),
124
- replacement: `${appRoot}/${paths2[0]}/`
125
- });
126
- } else if (paths2.length > 1) {
127
- aliasmap.push({
128
- find: new RegExp(`^${alias}/`),
129
- replacement: "",
130
- async customResolver(_src) {
131
- const src = _src.replace(/(\$|\^|\+|\(|\)|\[|\])/g, "\\$1");
132
- const patterns = paths2.flatMap((path) => [
133
- // Case 1: Extension is explicitly provided
134
- // e.g. import styles from "@admin/{solid}/styles.module.css"
135
- `${path}/${src}*`,
136
- // Case 2: No extension provided
137
- // Match any extension and return the first match
138
- `${path}/${src}.*`,
139
- // Case 3: Folder containing an index file of any extension
140
- `${path}/${src}/index.*`
141
- ]);
142
- const [file] = await glob(patterns, {
143
- cwd: appRoot,
144
- onlyFiles: true,
145
- absolute: true,
146
- dot: true,
147
- followSymbolicLinks: false,
148
- braceExpansion: false,
149
- globstar: false,
150
- ignore: opt?.ignore || [
151
- "**/.git/**",
152
- "**/node_modules/**",
153
- "**/public/**",
154
- "**/var/**"
155
- ]
156
- });
157
- return file;
158
- }
159
- });
160
- }
161
- }
162
- return {
163
- resolve: {
164
- alias: aliasmap
165
- }
166
- };
167
- }
168
- };
169
- };
170
-
171
- // src/base-plugin/index.ts
172
- import { writeFile as writeFile3 } from "node:fs/promises";
173
- import { basename as basename2, join as join5, resolve as resolve5 } from "node:path";
174
- import { styleText as styleText4 } from "node:util";
175
- import { Worker } from "node:worker_threads";
176
- import stubGenerator from "@kosmojs/dev/stub-generator";
177
-
178
- // src/routes-factory/resolve.ts
179
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
180
- import { styleText as styleText2 } from "node:util";
181
- import crc5 from "crc/crc32";
182
- import mimeTypes from "mime-types";
183
- import picomatch from "picomatch";
184
- import { glob as glob2 } from "tinyglobby";
185
-
186
- // src/ast.ts
187
- import { resolve } from "node:path";
188
- import { styleText } from "node:util";
189
- import crc from "crc/crc32";
190
- import { flattener } from "tfusion";
191
- import {
192
- Project,
193
- SyntaxKind
194
- } from "ts-morph";
195
- import {
196
- HTTPMethods,
197
- RequestValidationTargets
198
- } from "@kosmojs/api";
199
- var createProject = (opts) => new Project(opts);
200
- var resolveRouteSignature = async (route, opts) => {
201
- const {
202
- sourceFile = createProject().addSourceFileAtPath(route.fileFullpath)
203
- } = { ...opts };
204
- const [typeDeclarations, referencedFiles] = extractTypeDeclarations(
205
- sourceFile,
206
- opts
207
- );
208
- const defaultExport = extractDefaultExport(sourceFile);
209
- const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
210
- const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
211
- return {
212
- typeDeclarations,
213
- paramsRefinements,
214
- methods: methods.map((e) => e.method),
215
- validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
216
- referencedFiles
217
- };
218
- };
219
- var extractDefaultExport = (sourceFile) => {
220
- const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
221
- if (exportAssignment.isExportEquals()) {
222
- return [];
223
- }
224
- const callExpression = exportAssignment.getExpression();
225
- return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
226
- });
227
- return defaultExport;
228
- };
229
- var extractParamsRefinements = (callExpression) => {
230
- const [
231
- _routeName,
232
- // first generic - the route name
233
- paramsGeneric
234
- // second generic - params refinements
235
- ] = extractGenerics(callExpression);
236
- if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) {
237
- return;
238
- }
239
- return paramsGeneric.getElements().map((node, index) => {
240
- return {
241
- index,
242
- text: node.getText()
243
- };
244
- });
245
- };
246
- var extractRouteMethods = (route, callExpression) => {
247
- const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
248
- if (!funcDeclaration) {
249
- return [];
250
- }
251
- const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(
252
- SyntaxKind.ArrayLiteralExpression
253
- );
254
- if (!arrayLiteralExpression) {
255
- return [];
256
- }
257
- const callExpressions = [];
258
- for (const e of arrayLiteralExpression.getChildrenOfKind(
259
- SyntaxKind.CallExpression
260
- )) {
261
- const name = e.getExpression().getText();
262
- if (HTTPMethods[name]) {
263
- callExpressions.push([e, name]);
264
- }
265
- }
266
- const methods = [];
267
- for (const [callExpression2, method] of callExpressions) {
268
- const [vDefs, vOpts] = extractGenerics(callExpression2);
269
- methods.push({
270
- method,
271
- validationDefinitions: extractValidationDefinitions(
272
- route,
273
- method,
274
- vDefs,
275
- vOpts
276
- )
277
- });
278
- }
279
- return methods;
280
- };
281
- var parseRuntimeValidation = (typeNode) => {
282
- if (typeNode.isKind(SyntaxKind.LiteralType)) {
283
- const literal = typeNode.getFirstChild();
284
- if (literal?.isKind(SyntaxKind.TrueKeyword)) {
285
- return true;
286
- } else if (literal?.isKind(SyntaxKind.FalseKeyword)) {
287
- return false;
288
- }
289
- }
290
- return void 0;
291
- };
292
- var extractResponseVariant = (typeNode) => {
293
- if (!typeNode.isKind(SyntaxKind.TupleType)) {
294
- return;
295
- }
296
- let status = 200;
297
- let contentType;
298
- let body;
299
- const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
300
- if (statusNode?.isKind(SyntaxKind.LiteralType)) {
301
- const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
302
- if (literal) {
303
- status = Number(literal.getText());
304
- }
305
- }
306
- if (contentTypeNode) {
307
- contentType = extractStringLiteral(contentTypeNode);
308
- }
309
- if (bodyNode) {
310
- body = bodyNode.getText();
311
- if (["object"].includes(body)) {
312
- body = "{}";
313
- }
314
- }
315
- return { status, contentType, body };
316
- };
317
- var parseValidationOptions = (typeNode) => {
318
- const opts = {};
319
- if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) {
320
- return opts;
321
- }
322
- for (const prop of typeNode.getMembers()) {
323
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
324
- continue;
325
- }
326
- const target = prop.getName();
327
- const typeNode2 = prop.getTypeNodeOrThrow();
328
- if (!typeNode2.isKind(SyntaxKind.TypeLiteral)) {
329
- continue;
330
- }
331
- let contentType;
332
- let runtimeValidation;
333
- const customErrors = {};
334
- for (const member of typeNode2.getMembers()) {
335
- if (!member.isKind(SyntaxKind.PropertySignature)) {
336
- continue;
337
- }
338
- const nameNode = member.getNameNode();
339
- const valueNode = member.getTypeNodeOrThrow();
340
- const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
341
- if (name === "contentType") {
342
- contentType = extractStringLiteral(valueNode);
343
- } else if (name === "runtimeValidation") {
344
- runtimeValidation = parseRuntimeValidation(valueNode);
345
- } else if (name.startsWith("error")) {
346
- const literal = extractStringLiteral(valueNode);
347
- if (literal) {
348
- customErrors[name] = literal;
349
- }
350
- }
351
- }
352
- opts[target] = {
353
- contentType,
354
- runtimeValidation,
355
- customErrors
356
- };
357
- }
358
- return opts;
359
- };
360
- var extractStringLiteral = (typeNode) => {
361
- const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
362
- return literal ? literal.getLiteralText() : void 0;
363
- };
364
- var extractValidationDefinitions = (route, method, defsNode, optsNode) => {
365
- const definitions = [];
366
- if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) {
367
- return definitions;
368
- }
369
- const optsMap = parseValidationOptions(optsNode);
370
- const createId = (target, hash) => {
371
- return [
372
- target.replace(/^./, (c) => c.toUpperCase()),
373
- "T",
374
- method,
375
- crc(route.id + hash)
376
- ].join("");
377
- };
378
- for (const prop of defsNode.getMembers()) {
379
- if (!prop.isKind(SyntaxKind.PropertySignature)) {
380
- continue;
381
- }
382
- const target = prop.getName();
383
- const typeNode = prop.getTypeNodeOrThrow();
384
- if (target === "response") {
385
- const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
386
- definitions.push({
387
- ...optsMap[target],
388
- method,
389
- target,
390
- variants: variants.flatMap((e, i) => {
391
- const { status, contentType, body } = extractResponseVariant(e) || {};
392
- if (!status) {
393
- return [];
394
- }
395
- if (contentType && typeof contentType !== "string") {
396
- console.warn(
397
- styleText(
398
- ["bold", "red"],
399
- `\u2717 The second element of a response variant should specify the Response Content Type`
400
- )
401
- );
402
- console.warn(
403
- styleText(["blue"], ` Example: [200, "json", Schema]`)
404
- );
405
- console.warn(
406
- ` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`
407
- );
408
- console.warn();
409
- }
410
- return [
411
- {
412
- id: createId(target, JSON.stringify([status, contentType, body])),
413
- status,
414
- contentType,
415
- body
416
- }
417
- ];
418
- })
419
- });
420
- } else if (Object.keys(RequestValidationTargets).includes(target)) {
421
- definitions.push({
422
- ...optsMap[target],
423
- method,
424
- target,
425
- schema: {
426
- id: createId(target),
427
- text: typeNode.getText()
428
- }
429
- });
430
- }
431
- }
432
- return definitions;
433
- };
434
- var extractTypeDeclarations = (sourceFile, opts) => {
435
- const declarations = [];
436
- const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
437
- for (const declaration of sourceFile.getImportDeclarations()) {
438
- const modulePath = declaration.getModuleSpecifierValue();
439
- const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
440
- const typeOnlyDeclaration = declaration.isTypeOnly();
441
- const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
442
- if (defaultImport) {
443
- const name = defaultImport.getText();
444
- const text = `import type ${name} from "${path}";`;
445
- declarations.push({
446
- importDeclaration: {
447
- name,
448
- path
449
- },
450
- text
451
- });
452
- if (referencedFiles) {
453
- referencedFiles.push(...getReferencedFiles(defaultImport));
454
- }
455
- }
456
- for (const namedImport of declaration.getNamedImports()) {
457
- if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
458
- const nameNode = namedImport.getNameNode();
459
- const name = nameNode.getText();
460
- const alias = namedImport.getAliasNode()?.getText();
461
- const nameText = alias ? `${name} as ${alias}` : name;
462
- declarations.push({
463
- importDeclaration: {
464
- name,
465
- alias,
466
- path
467
- },
468
- text: `import type { ${nameText} } from "${path}";`
469
- });
470
- if (referencedFiles) {
471
- if (nameNode.isKind(SyntaxKind.Identifier)) {
472
- referencedFiles.push(...getReferencedFiles(nameNode));
473
- }
474
- }
475
- }
476
- }
477
- }
478
- for (const declaration of sourceFile.getTypeAliases()) {
479
- const name = declaration.getName();
480
- const text = declaration.getFullText().trim();
481
- declarations.push({
482
- typeAliasDeclaration: { name },
483
- text
484
- });
485
- }
486
- for (const declaration of sourceFile.getInterfaces()) {
487
- const name = declaration.getName();
488
- const text = declaration.getFullText().trim();
489
- declarations.push({
490
- interfaceDeclaration: { name },
491
- text
492
- });
493
- }
494
- for (const declaration of sourceFile.getEnums()) {
495
- const name = declaration.getName();
496
- const text = declaration.getFullText().trim();
497
- declarations.push({
498
- enumDeclaration: { name },
499
- text
500
- });
501
- }
502
- for (const declaration of sourceFile.getExportDeclarations()) {
503
- const typeOnlyDeclaration = declaration.isTypeOnly();
504
- const modulePath = declaration.getModuleSpecifierValue();
505
- const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
506
- for (const namedExport of declaration.getNamedExports()) {
507
- if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
508
- const nameNode = namedExport.getNameNode();
509
- const name = nameNode.getText();
510
- const alias = namedExport.getAliasNode()?.getText();
511
- const nameText = alias ? `${name} as ${alias}` : name;
512
- declarations.push({
513
- exportDeclaration: {
514
- name,
515
- alias: alias ?? name,
516
- path
517
- },
518
- text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
519
- });
520
- if (referencedFiles) {
521
- if (nameNode.isKind(SyntaxKind.Identifier)) {
522
- referencedFiles.push(...getReferencedFiles(nameNode));
523
- }
524
- }
525
- }
526
- }
527
- }
528
- return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
529
- };
530
- var getReferencedFiles = (importIdentifier) => {
531
- const declarations = importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || [];
532
- return declarations.flatMap((e) => {
533
- const sourceFile = e.getSourceFile();
534
- return sourceFile ? [sourceFile.getFilePath()] : [];
535
- });
536
- };
537
- var extractGenerics = (callExpression) => {
538
- return callExpression.getTypeArguments();
539
- };
540
- var typeResolverFactory = ({ appRoot }) => {
541
- const project = createProject({
542
- tsConfigFilePath: resolve(appRoot, "tsconfig.json"),
543
- skipAddingFilesFromTsConfig: true
544
- });
545
- const literalTypesResolver = (literalTypes, options) => {
546
- const sourceFile = project.createSourceFile(
547
- `${crc(literalTypes)}-${Date.now()}.ts`,
548
- literalTypes,
549
- { overwrite: true }
550
- );
551
- const resolvedTypes = flattener(project, sourceFile, {
552
- ...options,
553
- stripComments: true
554
- });
555
- project.removeSourceFile(sourceFile);
556
- return resolvedTypes;
557
- };
558
- return {
559
- getSourceFile: (fileFullpath) => {
560
- return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
561
- },
562
- refreshSourceFile: async (fileFullpath) => {
563
- const sourceFile = project.getSourceFile(fileFullpath);
564
- if (sourceFile) {
565
- await sourceFile.refreshFromFileSystem();
566
- }
567
- },
568
- literalTypesResolver
569
- };
570
- };
571
-
572
- // src/cache.ts
573
- import { mkdir, readFile, writeFile } from "node:fs/promises";
574
- import { dirname, resolve as resolve2 } from "node:path";
575
- import crc2 from "crc/crc32";
576
- import self from "@kosmojs/dev/package.json" with { type: "json" };
577
-
578
- // src/fs.ts
579
- import { access, constants } from "node:fs/promises";
580
- var pathExists = async (path) => {
581
- try {
582
- await access(path, constants.F_OK);
583
- return true;
584
- } catch {
585
- return false;
586
- }
587
- };
588
-
589
- // src/cache.ts
590
- var cacheFactory = (route, {
591
- appRoot,
592
- sourceFolder,
593
- extraContext
594
- }) => {
595
- const cacheFile = pathResolver({
596
- appRoot,
597
- sourceFolder
598
- }).createPath.libApi(dirname(route.file), "cache.json");
599
- const getCache = async (opt) => {
600
- if (await pathExists(cacheFile)) {
601
- try {
602
- const cache = JSON.parse(await readFile(cacheFile, "utf8"));
603
- return opt?.validate ? validateCache(cache) : cache;
604
- } catch (_e) {
605
- }
606
- }
607
- return void 0;
608
- };
609
- const persistCache = async ({
610
- referencedFiles: _referencedFiles,
611
- ...rest
612
- }) => {
613
- const hash = await generateFileHash(route.fileFullpath, {
614
- ...extraContext
615
- });
616
- const referencedFiles = {};
617
- for (const file of _referencedFiles) {
618
- referencedFiles[
619
- // Strip project root to ensure cached paths are relative
620
- // and portable across environments (CI, local, etc.)
621
- file.replace(`${appRoot}/`, "")
622
- ] = await generateFileHash(file);
623
- }
624
- const cache = { ...rest, hash, referencedFiles };
625
- await mkdir(dirname(cacheFile), { recursive: true });
626
- await writeFile(cacheFile, JSON.stringify(cache, null, 2), "utf8");
627
- return cache;
628
- };
629
- const validateCache = async (cache) => {
630
- if (!cache?.hash) {
631
- return;
632
- }
633
- if (!cache.typeDeclarations || !cache.referencedFiles) {
634
- return;
635
- }
636
- const hash = await generateFileHash(route.fileFullpath, {
637
- ...extraContext
638
- });
639
- if (!identicalHashSum(cache.hash, hash)) {
640
- return;
641
- }
642
- for (const [file, hash2] of Object.entries(cache.referencedFiles)) {
643
- if (!identicalHashSum(hash2, await generateFileHash(resolve2(appRoot, file)))) {
644
- return;
645
- }
646
- }
647
- return cache;
648
- };
649
- return {
650
- getCache,
651
- validateCache,
652
- persistCache
653
- };
654
- };
655
- var generateFileHash = async (file, extraContext) => {
656
- let fileContent;
657
- try {
658
- fileContent = await readFile(file, "utf8");
659
- } catch (_e) {
660
- return 0;
661
- }
662
- return fileContent ? crc2(
663
- JSON.stringify({
664
- ...extraContext,
665
- [self.cacheVersion]: fileContent
666
- })
667
- ) : 0;
668
- };
669
- var identicalHashSum = (a, b) => {
670
- return a === b;
671
- };
672
-
673
- // src/render.ts
674
- import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
675
- import { dirname as dirname2, join as join2 } from "node:path";
676
- import crc3 from "crc/crc32";
677
- import handlebars from "handlebars";
678
- var render = (template, context2, options) => {
679
- const { noEscape = true, renderer = handlebars } = { ...options };
680
- return renderer.compile(template, { noEscape })(context2);
681
- };
682
- var renderToFile = async (file, template, context2, options) => {
683
- const content = render(template, context2, options);
684
- if (await pathExists(file)) {
685
- const { overwrite = true } = { ...options };
686
- if (overwrite === false) {
687
- return;
688
- }
689
- const fileContent = await readFile2(file, "utf8");
690
- if (typeof overwrite === "function" && !overwrite(fileContent)) {
691
- return;
692
- }
693
- if (crc3(content) === crc3(fileContent)) {
694
- return;
695
- }
696
- }
697
- await mkdir2(dirname2(file), { recursive: true });
698
- await writeFile2(file, content, "utf8");
699
- };
700
- var renderFactory = (options) => {
701
- const renderer = handlebars.create();
702
- renderer.registerPartial({ ...options?.partials });
703
- renderer.registerHelper({ ...options?.helpers });
704
- return {
705
- render(template, context2, selfOoptions) {
706
- return render(template, context2, {
707
- renderer,
708
- ...options,
709
- ...selfOoptions
710
- });
711
- },
712
- async renderToFile(file, template, context2, selfOoptions) {
713
- return renderToFile(
714
- options?.outdir ? join2(options.outdir, file) : file,
715
- template,
716
- context2,
717
- { renderer, ...options, ...selfOoptions }
718
- );
719
- }
720
- };
721
- };
722
- var renderHelpers = {
723
- createParamsLiteral: (params) => {
724
- return params.schema.map((p) => {
725
- return p.kind === "splat" ? `${p.const}?: Array<string | number>` : p.kind === "optional" ? `${p.const}?: string | number` : `${p.const}: string | number`;
726
- }).join(", ");
727
- }
728
- };
729
-
730
- // src/routes-factory/base.ts
731
- import crc4 from "crc/crc32";
732
- import { parse } from "path-to-regexp";
733
- var pathTokensFactory = (path, {
734
- transformStaticValue = normalizeStaticValue
735
- } = {}) => {
736
- const extractParts = (tokens2, createConst, insideGroup = false) => {
737
- const parts = [];
738
- for (const token of tokens2) {
739
- switch (token.type) {
740
- case "text":
741
- if (token.value !== "/") {
742
- parts.push({
743
- type: "static",
744
- value: transformStaticValue(token.value)
745
- });
746
- }
747
- break;
748
- case "param":
749
- parts.push({
750
- type: "param",
751
- kind: insideGroup ? "optional" : "required",
752
- name: token.name,
753
- const: createConst(token.name)
754
- });
755
- break;
756
- case "wildcard":
757
- parts.push({
758
- type: "param",
759
- kind: "splat",
760
- name: token.name,
761
- const: createConst(token.name)
762
- });
763
- break;
764
- case "group":
765
- parts.push(...extractParts(token.tokens, createConst, true));
766
- break;
767
- }
768
- }
769
- return parts;
770
- };
771
- const patternTransforms = [
772
- // Transform required params: [id] => :id
773
- // Only pure \w param names,
774
- // [some-id] used as is, not treated as param,
775
- // use [some_id] instead.
776
- (s) => s.replace(/\[(\w+)\]/g, ":$1"),
777
- // Transform optional params: {id} => {:id}
778
- // Only pure \w param names,
779
- // anything else treated as a path-to-regexp pattern and used as is.
780
- // {some-id} treated as an optional static segment.
781
- // use {some_id} for simple param syntax
782
- // or {:some-id} pattern where :some is the param name and -id is a static segment.
783
- (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
784
- // Transform splat params: {...param} => {*param}
785
- (s) => s.replace(/\{\.\.\./g, "{*"),
786
- // Insert leading slash inside optional/splat groups.
787
- // {:name} => {/:name}
788
- // {*name} => {/*name}
789
- (s) => {
790
- return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
791
- }
792
- ];
793
- const detectBareParams = (s) => {
794
- let depth = 0;
795
- for (const [i, ch] of [...s].entries()) {
796
- if (ch === "{") {
797
- depth += 1;
798
- } else if (ch === "}") {
799
- depth -= 1;
800
- } else if (ch === ":" && depth === 0) {
801
- const match = s.slice(i + 1).match(/^\w+/);
802
- return match?.[0] || ":";
803
- }
804
- }
805
- return;
806
- };
807
- const tokens = path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
808
- if (!orig.length) {
809
- return [];
810
- }
811
- const bareParam = detectBareParams(orig);
812
- if (bareParam === ":") {
813
- throw new Error(
814
- `${path} contains colons outside braces, use : only within {}`
815
- );
816
- } else if (bareParam) {
817
- throw new Error(
818
- `${path} contains bare params, use [${bareParam}] instead of :${bareParam}`
819
- );
820
- }
821
- const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
822
- const { tokens: tokens2 } = parse(pattern);
823
- const parts = extractParts(tokens2, (val) => {
824
- return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc4(orig)].join("_") : val;
825
- });
826
- const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
827
- const isParam = parts.length === 1 ? parts[0].type === "param" : false;
828
- const kind = isStatic ? "static" : isParam ? "param" : "mixed";
829
- return [
830
- {
831
- kind,
832
- orig,
833
- pattern,
834
- parts
835
- }
836
- ];
837
- });
838
- return [
839
- tokens,
840
- tokens.map(({ pattern }, i) => {
841
- const next = tokens[i + 1];
842
- if (!next || next.pattern.includes("/")) {
843
- return pattern;
844
- }
845
- const slashRequired = tokens.slice(i + 1).some((e) => {
846
- return e.parts.some((e2) => {
847
- return e2.type === "static" || e2.kind === "required";
848
- });
849
- });
850
- return slashRequired ? `${pattern}/` : pattern;
851
- }).join("")
852
- ];
853
- };
854
- var normalizeStaticValue = (value) => {
855
- return value.replace(/\+/g, "\\\\+");
856
- };
857
- var sortRoutes = (a, b) => {
858
- const aSpecificity = routeSpecificity(a.pathTokens);
859
- const bSpecificity = routeSpecificity(b.pathTokens);
860
- if (aSpecificity !== bSpecificity) {
861
- return bSpecificity - aSpecificity;
862
- }
863
- if (a.pathTokens.length !== b.pathTokens.length) {
864
- return a.pathTokens.length - b.pathTokens.length;
865
- }
866
- return a.name.localeCompare(b.name);
867
- };
868
- var paramWeight = (part) => {
869
- return {
870
- required: 2,
871
- optional: 1,
872
- splat: 0
873
- }[part.kind];
874
- };
875
- var mixedSegmentWeight = (parts) => {
876
- const hasSplat = parts.some((p) => {
877
- return p.type === "param" ? p.kind === "splat" : false;
878
- });
879
- return hasSplat ? 0.5 : 3;
880
- };
881
- var segmentWeight = (token) => {
882
- return {
883
- static: 4,
884
- mixed: mixedSegmentWeight(token.parts),
885
- param: paramWeight(token.parts[0])
886
- }[token.kind];
887
- };
888
- var routeSpecificity = (pathTokens) => {
889
- return pathTokens.reduce((sum, token) => sum + segmentWeight(token), 0);
890
- };
891
-
892
- // src/routes-factory/templates/resolved-types.hbs
893
- var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
894
-
895
- // src/routes-factory/templates/types.hbs
896
- 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';
897
-
898
- // src/routes-factory/resolve.ts
899
- var API_INDEX_BASENAME = "index";
900
- var API_INDEX_PATTERN = `${API_INDEX_BASENAME}.ts`;
901
- var API_USE_BASENAME = "use";
902
- var API_USE_PATTERN = `${API_USE_BASENAME}.ts`;
903
- var PAGE_INDEX_BASENAME = "index";
904
- var PAGE_INDEX_PATTERN = `${PAGE_INDEX_BASENAME}.{tsx,vue}`;
905
- var PAGE_LAYOUT_BASENAME = "layout";
906
- var PAGE_LAYOUT_PATTERN = `${PAGE_LAYOUT_BASENAME}.{tsx,vue}`;
907
- var ROUTE_FILE_PATTERNS = [
908
- // match index files in api dir
909
- `${defaults.apiDir}/**/${API_INDEX_PATTERN}`,
910
- // match use files in api dir
911
- `${defaults.apiDir}/**/${API_USE_PATTERN}`,
912
- // match index files in pages dir
913
- `${defaults.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
914
- // match layout files in pages dir
915
- `${defaults.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
916
- ];
917
- var scanRoutes = async ({
918
- appRoot,
919
- sourceFolder
920
- }) => {
921
- const { createPath } = pathResolver({ appRoot, sourceFolder });
922
- return glob2(ROUTE_FILE_PATTERNS, {
923
- cwd: createPath.src(),
924
- absolute: true,
925
- onlyFiles: true,
926
- followSymbolicLinks: false,
927
- ignore: [
928
- // ignore top-level matches, routes resides in folders, even index route
929
- `${defaults.apiDir}/${API_INDEX_PATTERN}`,
930
- `${defaults.apiDir}/${API_USE_PATTERN}`,
931
- `${defaults.pagesDir}/${PAGE_INDEX_PATTERN}`,
932
- `${defaults.pagesDir}/${PAGE_LAYOUT_PATTERN}`
933
- ]
934
- });
935
- };
936
- var isRouteFile = (file, {
937
- appRoot,
938
- sourceFolder
939
- }) => {
940
- const [_sourceFolder, folder, ...rest] = resolve3(appRoot, file).replace(`${appRoot}/${defaults.srcDir}/`, "").split("/");
941
- if (!folder || _sourceFolder !== sourceFolder || rest.length < 2) {
942
- return false;
943
- }
944
- return picomatch.isMatch(join3(folder, ...rest), ROUTE_FILE_PATTERNS) ? [folder, rest.join("/")] : false;
945
- };
946
- var isApiRoute = (file) => {
947
- return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
948
- };
949
- var isApiUse = (file) => {
950
- return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
951
- };
952
- var isPageRoute = (file) => {
953
- return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
954
- };
955
- var isPageLayout = (file) => {
956
- return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
957
- };
958
- var createRouteEntry = (fileFullpath, {
959
- appRoot,
960
- sourceFolder
961
- }) => {
962
- const resolvedPaths = isRouteFile(fileFullpath, { appRoot, sourceFolder });
963
- if (!resolvedPaths) {
964
- return;
965
- }
966
- const [folder, file] = resolvedPaths;
967
- const id = `${file.replace(/\W+/g, "_")}_${crc5(file)}`;
968
- const name = dirname3(file);
969
- try {
970
- const [pathTokens, pathPattern] = pathTokensFactory(dirname3(file));
971
- return { id, name, folder, file, fileFullpath, pathTokens, pathPattern };
972
- } catch (error) {
973
- console.error(
974
- `\u2757${styleText2("red", "ERROR")}: Failed parsing path for "${styleText2("cyan", file)}"`
975
- );
976
- console.error(error);
977
- return;
978
- }
979
- };
980
- var pageLayoutResolverFactory = () => {
981
- return (entry) => {
982
- const { name } = entry;
983
- const handler = async () => {
984
- return {
985
- kind: "pageLayout",
986
- entry
987
- };
988
- };
989
- return { name, handler };
990
- };
991
- };
992
- var pageRouteResolverFactory = () => {
993
- return (entry) => {
994
- const { id, name, folder, file, fileFullpath, pathTokens, pathPattern } = entry;
995
- const handler = async () => {
996
- const entry2 = {
997
- id,
998
- name,
999
- pathTokens,
1000
- pathPattern,
1001
- params: {
1002
- schema: pathTokens.flatMap((e) => {
1003
- return e.parts.filter((p) => p.type === "param");
1004
- })
1005
- },
1006
- folder,
1007
- file,
1008
- fileFullpath
1009
- };
1010
- return {
1011
- kind: "pageRoute",
1012
- entry: entry2
1013
- };
1014
- };
1015
- return { name, handler };
1016
- };
1017
- };
1018
- var apiUseResolverFactory = () => {
1019
- return (entry) => {
1020
- const { name } = entry;
1021
- const handler = async () => {
1022
- return {
1023
- kind: "apiUse",
1024
- entry
1025
- };
1026
- };
1027
- return { name, handler };
1028
- };
1029
- };
1030
- var apiRouteResolverFactory = (pluginOptions) => {
1031
- const {
1032
- appRoot,
1033
- sourceFolder,
1034
- generators = [],
1035
- refineTypeName
1036
- } = pluginOptions;
1037
- const resolveTypes = generators.some((e) => e.options?.resolveTypes);
1038
- const {
1039
- //
1040
- literalTypesResolver,
1041
- getSourceFile,
1042
- refreshSourceFile
1043
- } = typeResolverFactory(pluginOptions);
1044
- return ({
1045
- id,
1046
- name,
1047
- file,
1048
- folder,
1049
- fileFullpath,
1050
- pathTokens,
1051
- pathPattern
1052
- }) => {
1053
- const handler = async (updatedFile) => {
1054
- const paramsSchema = pathTokens.flatMap(
1055
- (e) => {
1056
- return e.parts.flatMap((p) => {
1057
- return p.type === "param" ? [p] : [];
1058
- });
1059
- }
1060
- );
1061
- const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
1062
- const { getCache, persistCache } = cacheFactory(
1063
- { id, file, fileFullpath },
1064
- {
1065
- appRoot,
1066
- sourceFolder,
1067
- extraContext: { resolveTypes }
1068
- }
1069
- );
1070
- let cache = await getCache({ validate: true });
1071
- if (!cache) {
1072
- if (updatedFile === fileFullpath) {
1073
- await refreshSourceFile(fileFullpath);
1074
- }
1075
- const {
1076
- typeDeclarations,
1077
- paramsRefinements,
1078
- methods,
1079
- validationDefinitions: validationDefinitions2,
1080
- referencedFiles = []
1081
- } = await resolveRouteSignature(
1082
- { id, name, fileFullpath, optionalParams },
1083
- {
1084
- withReferencedFiles: true,
1085
- sourceFile: getSourceFile(fileFullpath),
1086
- relpathResolver(path) {
1087
- return join3(sourceFolder, defaults.apiDir, dirname3(file), path);
1088
- }
1089
- }
1090
- );
1091
- const validationTypes = validationDefinitions2.flatMap((def) => {
1092
- return def.target === "response" ? def.variants.flatMap(({ id: id2, body }) => {
1093
- return body ? [{ id: id2, text: body }] : [];
1094
- }) : [def.schema];
1095
- });
1096
- const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
1097
- if (text === "number") {
1098
- const param = paramsSchema.at(index);
1099
- return param ? [param.name] : [];
1100
- }
1101
- return [];
1102
- }) : [];
1103
- const typesFile = pathResolver({
1104
- appRoot,
1105
- sourceFolder
1106
- }).createPath.libApi(dirname3(file), "types.ts");
1107
- const params = {
1108
- id: ["ParamsT", crc5(name)].join(""),
1109
- schema: paramsSchema,
1110
- resolvedType: void 0
1111
- };
1112
- const typesFileContent = render(types_default, {
1113
- params,
1114
- paramsSchema: paramsSchema.map((param, index) => {
1115
- return {
1116
- ...param,
1117
- isRequired: param.kind === "required",
1118
- isSplat: param.kind === "splat",
1119
- refinement: paramsRefinements?.at(index)
1120
- };
1121
- }),
1122
- typeDeclarations,
1123
- validationTypes
1124
- });
1125
- const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
1126
- stripComments: true,
1127
- overrides: { [refineTypeName]: refineTypeName },
1128
- withProperties: [
1129
- params.id,
1130
- ...validationTypes.flatMap(({ id: id2 }) => id2)
1131
- ]
1132
- }) : void 0;
1133
- await renderToFile(
1134
- typesFile,
1135
- resolvedTypes ? resolved_types_default : typesFileContent,
1136
- { resolvedTypes }
1137
- );
1138
- params.resolvedType = resolvedTypes?.find((e) => e.name === params.id);
1139
- cache = await persistCache({
1140
- params,
1141
- methods,
1142
- typeDeclarations,
1143
- numericParams,
1144
- referencedFiles,
1145
- validationDefinitions: validationDefinitions2.map((def) => {
1146
- return {
1147
- ...def,
1148
- ...def.target === "response" ? {
1149
- variants: def.variants.map((variant) => {
1150
- return {
1151
- ...variant,
1152
- resolvedType: resolvedTypes?.find(
1153
- (e) => e.name === variant.id
1154
- )
1155
- };
1156
- })
1157
- } : {
1158
- schema: {
1159
- ...def.schema,
1160
- resolvedType: resolvedTypes?.find(
1161
- (e) => e.name === def.schema.id
1162
- )
1163
- }
1164
- }
1165
- };
1166
- })
1167
- });
1168
- }
1169
- const validationDefinitions = cache.validationDefinitions.flatMap(
1170
- (def) => {
1171
- let augmentedDef = def;
1172
- if (def.target === "response") {
1173
- augmentedDef = {
1174
- ...def,
1175
- variants: def.variants.flatMap((variant, i) => {
1176
- if (typeof variant.contentType !== "string") {
1177
- return [variant];
1178
- }
1179
- if (variant.contentType.includes("/")) {
1180
- return [variant];
1181
- }
1182
- const contentType = mimeTypes.lookup(variant.contentType);
1183
- if (contentType === false) {
1184
- console.warn(
1185
- styleText2(
1186
- ["bold", "red"],
1187
- "\u2717 Failed resolving Response Content Type"
1188
- )
1189
- );
1190
- console.warn(
1191
- ` Invalid value provided for mime-types lookup - ${variant.contentType}`
1192
- );
1193
- console.warn(
1194
- styleText2(
1195
- ["cyan"],
1196
- ` Response variant #${i} excluded from route schemas`
1197
- )
1198
- );
1199
- console.warn(` Route: ${name}; Method: ${def.method}`);
1200
- console.warn();
1201
- return [];
1202
- }
1203
- return [{ ...variant, contentType }];
1204
- })
1205
- };
1206
- } else if (def.contentType && !def.contentType.includes("/")) {
1207
- const contentType = mimeTypes.lookup(def.contentType);
1208
- if (contentType === false) {
1209
- console.warn(
1210
- styleText2(
1211
- ["bold", "red"],
1212
- "\u2717 Failed resolving Response Content Type"
1213
- )
1214
- );
1215
- console.warn(
1216
- ` Invalid value provided for mime-types lookup - ${def.contentType}`
1217
- );
1218
- console.warn(` Route: ${name}; Method: ${def.method}`);
1219
- console.warn();
1220
- } else {
1221
- augmentedDef = { ...def, contentType };
1222
- }
1223
- }
1224
- return augmentedDef ? [augmentedDef] : [];
1225
- }
1226
- );
1227
- const entry = {
1228
- id,
1229
- name,
1230
- pathTokens,
1231
- pathPattern,
1232
- params: cache.params,
1233
- numericParams: cache.numericParams,
1234
- optionalParams,
1235
- folder,
1236
- file,
1237
- fileFullpath,
1238
- methods: cache.methods,
1239
- typeDeclarations: cache.typeDeclarations,
1240
- validationDefinitions,
1241
- referencedFiles: Object.keys(cache.referencedFiles).map(
1242
- // expand referenced files path,
1243
- // they are stored as relative in cache
1244
- (e) => resolve3(appRoot, e)
1245
- )
1246
- };
1247
- return {
1248
- kind: "apiRoute",
1249
- entry
1250
- };
1251
- };
1252
- return { name, handler };
1253
- };
1254
- };
1255
-
1256
- // src/routes-factory/nesting.ts
1257
- import { basename } from "node:path";
1258
- var nestedRoutesFactory = (routeEntries) => {
1259
- const entryStack = structuredClone(routeEntries).sort(sortRoutes);
1260
- const transformEntries = (entries, parent) => {
1261
- return [...new Set(entries.map((e) => e.name))].flatMap((name) => {
1262
- const nameEntries = entryStack.flatMap(({ fileFullpath, ...entry }) => {
1263
- return entry.name === name ? [entry] : [];
1264
- });
1265
- const index = nameEntries.find(
1266
- (e) => basename(e.file).startsWith(PAGE_INDEX_BASENAME)
1267
- );
1268
- const layout = nameEntries.find(
1269
- (e) => basename(e.file).startsWith(PAGE_LAYOUT_BASENAME)
1270
- );
1271
- if (index || layout) {
1272
- return [
1273
- {
1274
- index: index ? {
1275
- ...index,
1276
- pathTokens: index.pathTokens.slice(
1277
- parent?.pathTokens.length || 0
1278
- )
1279
- } : void 0,
1280
- layout: layout ? {
1281
- ...layout,
1282
- pathTokens: layout.pathTokens.slice(
1283
- parent?.pathTokens.length || 0
1284
- )
1285
- } : void 0,
1286
- parent: parent?.name,
1287
- children: transformEntries(
1288
- findDescendantEntries(index || layout),
1289
- index || layout
1290
- )
1291
- }
1292
- ];
1293
- }
1294
- return [];
1295
- });
1296
- };
1297
- const findDescendantEntries = ({
1298
- name,
1299
- pathTokens
1300
- }) => {
1301
- const potentialChildren = entryStack.filter((entry) => {
1302
- if (entry.pathTokens.length <= pathTokens.length) {
1303
- return false;
1304
- }
1305
- if (!entry.name.startsWith(`${name}/`)) {
1306
- return false;
1307
- }
1308
- return true;
1309
- });
1310
- return potentialChildren.filter((child) => {
1311
- const hasIntermediateRoute = potentialChildren.some((intermediate) => {
1312
- if (intermediate === child) {
1313
- return false;
1314
- }
1315
- if (intermediate.pathTokens.length <= pathTokens.length) {
1316
- return false;
1317
- }
1318
- if (intermediate.pathTokens.length >= child.pathTokens.length) {
1319
- return false;
1320
- }
1321
- return child.name.startsWith(`${intermediate.name}/`);
1322
- });
1323
- return !hasIntermediateRoute;
1324
- });
1325
- };
1326
- const rootEntries = entryStack.filter((entry) => {
1327
- const hasParent = entryStack.some((potential) => {
1328
- if (potential === entry) {
1329
- return false;
1330
- }
1331
- if (potential.pathTokens.length >= entry.pathTokens.length) {
1332
- return false;
1333
- }
1334
- return entry.name.startsWith(`${potential.name}/`);
1335
- });
1336
- return !hasParent;
1337
- });
1338
- return transformEntries(rootEntries);
1339
- };
1340
-
1341
- // src/routes-factory/index.ts
1342
- var routesFactory = async (pluginOptions) => {
1343
- const { appRoot, sourceFolder } = pluginOptions;
1344
- const apiRouteResolver = apiRouteResolverFactory(pluginOptions);
1345
- const apiUseResolver = apiUseResolverFactory(pluginOptions);
1346
- const pageRouteResolver = pageRouteResolverFactory(pluginOptions);
1347
- const pageLayoutResolver = pageLayoutResolverFactory(pluginOptions);
1348
- const resolversFactory = (routeFiles2) => {
1349
- const resolvers = /* @__PURE__ */ new Map();
1350
- const entries = routeFiles2.flatMap((file) => {
1351
- const entry = createRouteEntry(file, pluginOptions);
1352
- return entry ? [entry] : [];
1353
- });
1354
- for (const entry of entries) {
1355
- if (entry.folder === defaults.apiDir) {
1356
- if (isApiRoute(entry.file)) {
1357
- resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
1358
- } else if (isApiUse(entry.file)) {
1359
- resolvers.set(entry.fileFullpath, apiUseResolver(entry));
1360
- }
1361
- } else if (entry.folder === defaults.pagesDir) {
1362
- if (isPageRoute(entry.file)) {
1363
- resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
1364
- } else if (isPageLayout(entry.file)) {
1365
- resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
1366
- }
1367
- }
1368
- }
1369
- return resolvers;
1370
- };
1371
- const routeFiles = await scanRoutes({ appRoot, sourceFolder });
1372
- return {
1373
- resolvers: resolversFactory(routeFiles),
1374
- resolversFactory
1375
- };
1376
- };
1377
-
1378
- // src/base-plugin/api-handler.ts
1379
- import { join as join4, resolve as resolve4 } from "node:path";
1380
- import { styleText as styleText3 } from "node:util";
1381
- import { context } from "esbuild";
1382
- var api_handler_default = async (options) => {
1383
- const { appRoot, sourceFolder, baseurl, apiurl } = options;
1384
- const { createPath } = pathResolver({ appRoot, sourceFolder });
1385
- const outDir = join4(options.outDir, defaults.apiDir);
1386
- const esbuildOptions = await import(resolve4(appRoot, "esbuild.json"), { with: { type: "json" } }).then((e) => e.default);
1387
- let devSetup;
1388
- const watcher = async () => {
1389
- const rebuildPlugin = {
1390
- name: "rebuild",
1391
- setup(build) {
1392
- build.onEnd(async () => {
1393
- if (devSetup) {
1394
- await devSetup.teardownHandler?.();
1395
- }
1396
- try {
1397
- await import(`${outDir}/dev.js?${Date.now()}`).then((e) => {
1398
- devSetup = e.default;
1399
- });
1400
- console.debug(`${styleText3("green", "\u279C")} Api handler ready`);
1401
- } catch (error) {
1402
- console.error(`${styleText3("red", "\u2717")} Api handler error`);
1403
- console.error(error);
1404
- }
1405
- });
1406
- }
1407
- };
1408
- const ctx = await context({
1409
- ...esbuildOptions,
1410
- define: {
1411
- ...esbuildOptions.define,
1412
- PRODUCTION_BUILD: "false"
1413
- },
1414
- logLevel: "error",
1415
- bundle: true,
1416
- entryPoints: [createPath.api("dev.ts")],
1417
- plugins: [rebuildPlugin],
1418
- outdir: outDir
1419
- });
1420
- return {
1421
- async start() {
1422
- await ctx.watch({
1423
- // waits this many milliseconds before rebuilding after a change is detected
1424
- delay: options.watcher.delay
1425
- });
1426
- },
1427
- async stop() {
1428
- await ctx.dispose();
1429
- }
1430
- };
1431
- };
1432
- const devMiddleware = async (req, res, next) => {
1433
- const {
1434
- requestMatcher = () => {
1435
- return new RegExp(`^${join4(baseurl, apiurl)}($|/)`).test(
1436
- req.url
1437
- );
1438
- },
1439
- requestHandler
1440
- } = { ...devSetup };
1441
- if (!requestMatcher(req)) {
1442
- return next();
1443
- }
1444
- const handler = requestHandler?.();
1445
- return handler ? handler(req, res) : next();
1446
- };
1447
- return {
1448
- watcher,
1449
- devMiddleware
1450
- };
1451
- };
1452
-
1453
- // src/base-plugin/spinner.ts
1454
- import { Spinner } from "@topcli/spinner";
1455
- var spinnerFactory = (startText) => {
1456
- const spinner = new Spinner().start(startText);
1457
- let _text = startText;
1458
- return {
1459
- text(text) {
1460
- _text = text;
1461
- spinner.text = text;
1462
- },
1463
- append(text) {
1464
- spinner.text = `${_text} \u203A ${text}`;
1465
- },
1466
- succeed(text) {
1467
- if (text) {
1468
- this.append(text);
1469
- } else {
1470
- this.text(_text);
1471
- }
1472
- spinner.succeed();
1473
- },
1474
- failed(text) {
1475
- if (text) {
1476
- this.text([_text, text].join("\n"));
1477
- }
1478
- spinner.failed();
1479
- }
1480
- };
1481
- };
1482
- var withSpinner = (text, pipe, spinner) => pipe(spinner || spinnerFactory(text));
1483
-
1484
- // src/base-plugin/index.ts
1485
- var base_plugin_default = (apiurl, pluginOptions) => {
1486
- const outDirSuffix = "client";
1487
- const store = {};
1488
- const createWorker = () => {
1489
- const { generators = [], ...restOptions } = store.resolvedOptions;
1490
- const generatorModules = generators.map(
1491
- (e) => [e.moduleImport, e.moduleConfig]
1492
- );
1493
- const workerData = {
1494
- ...restOptions,
1495
- generatorModules
1496
- };
1497
- return new Worker(resolve5(import.meta.dirname, "base-plugin/worker.js"), {
1498
- workerData,
1499
- env: {
1500
- ...process.env,
1501
- FORCE_COLOR: "1"
1502
- }
1503
- });
1504
- };
1505
- const workerHandler = (onReady, onExit) => {
1506
- const worker = createWorker();
1507
- const spinnerMap = /* @__PURE__ */ new Map();
1508
- worker.on("error", async (error) => {
1509
- console.error(error);
1510
- });
1511
- worker.on("exit", async () => {
1512
- await onExit?.();
1513
- });
1514
- worker.on(
1515
- "message",
1516
- async (msg) => {
1517
- if (msg?.spinner) {
1518
- const { id, startText, method, text } = msg.spinner;
1519
- withSpinner(
1520
- startText,
1521
- (spinner) => {
1522
- spinnerMap.set(id, spinner);
1523
- spinner[method](text || "");
1524
- if (method === "succeed" || method === "failed") {
1525
- spinnerMap.delete(id);
1526
- }
1527
- },
1528
- spinnerMap.get(id)
1529
- );
1530
- } else if (msg?.error) {
1531
- const { error } = msg;
1532
- if (error.stack) {
1533
- const [message, ...stack] = error.stack.split("\n");
1534
- console.error(styleText4("red", message));
1535
- console.error(stack.join("\n"));
1536
- } else if (error?.message) {
1537
- console.error(`${styleText4("red", error?.name)}: ${error.message}`);
1538
- } else {
1539
- console.error(error);
1540
- }
1541
- }
1542
- }
1543
- );
1544
- const readyHandler = async (msg) => {
1545
- if (msg === "ready") {
1546
- worker.off("message", readyHandler);
1547
- await onReady?.();
1548
- }
1549
- };
1550
- worker.on("message", readyHandler);
1551
- return async () => {
1552
- await worker.terminate();
1553
- };
1554
- };
1555
- return {
1556
- name: "@kosmojs:basePlugin",
1557
- config(config) {
1558
- if (!config.build?.outDir) {
1559
- throw new Error("Incomplete config, missing build.outDir");
1560
- }
1561
- return {
1562
- build: {
1563
- outDir: join5(config.build.outDir, outDirSuffix),
1564
- manifest: true
1565
- }
1566
- };
1567
- },
1568
- async configResolved(_config) {
1569
- store.config = _config;
1570
- const appRoot = resolve5(store.config.root, "../..");
1571
- const sourceFolder = basename2(store.config.root);
1572
- const outDir = resolve5(appRoot, resolve5(store.config.build.outDir, ".."));
1573
- const { stabilityThreshold = 1e3 } = typeof store.config.server.watch?.awaitWriteFinish === "object" ? store.config.server.watch.awaitWriteFinish : {};
1574
- const watcher = {
1575
- delay: stabilityThreshold,
1576
- ...store.config.server.watch ? { options: store.config.server.watch } : {}
1577
- };
1578
- {
1579
- const { generators = [], refineTypeName = "TRefine" } = {
1580
- ...pluginOptions
1581
- };
1582
- const apiGenerator = generators.find((e) => e.slot === "api");
1583
- const fetchGenerator = generators.find((e) => e.slot === "fetch");
1584
- const ssrGenerator = generators.find((e) => e.slot === "ssr");
1585
- store.resolvedOptions = {
1586
- ...pluginOptions,
1587
- command: store.config.command,
1588
- watcher,
1589
- generators: [
1590
- // 1. stub generator should run first
1591
- stubGenerator(),
1592
- // 2. then api generator
1593
- ...apiGenerator ? [apiGenerator] : [],
1594
- // 3. then fetch generator, only if api generator also enabled
1595
- ...fetchGenerator && apiGenerator ? [fetchGenerator] : [],
1596
- // 4. user generators in the order they were added
1597
- ...generators.filter((e) => {
1598
- return e.slot ? !["api", "fetch", "ssr"].includes(e.slot) : true;
1599
- }),
1600
- // 5. ssr generator should run last
1601
- ...ssrGenerator ? [ssrGenerator] : []
1602
- ],
1603
- refineTypeName,
1604
- baseurl: store.config.base,
1605
- apiurl,
1606
- appRoot,
1607
- sourceFolder,
1608
- outDir
1609
- };
1610
- }
1611
- const packageJsonFile = resolve5(appRoot, "package.json");
1612
- const packageJson = await import(packageJsonFile, {
1613
- with: { type: "json" }
1614
- }).then((e) => e.default);
1615
- const newDependencies = [];
1616
- for (const generator of store.resolvedOptions.generators) {
1617
- for (const key of ["dependencies", "devDependencies"]) {
1618
- for (const [pkg, ver] of Object.entries(generator[key] || {})) {
1619
- if (!packageJson[key]?.[pkg]) {
1620
- newDependencies.push([key, pkg, ver]);
1621
- }
1622
- }
1623
- }
1624
- }
1625
- if (newDependencies.length) {
1626
- console.warn();
1627
- console.warn(
1628
- [
1629
- "\u{1F4A1} ",
1630
- styleText4(["bold", "italic", "red"], "New dependencies added: "),
1631
- styleText4("dim", newDependencies.map(([, pkg]) => pkg).join(", "))
1632
- ].join("")
1633
- );
1634
- console.warn(
1635
- "\u{1F4E6}",
1636
- [
1637
- styleText4(
1638
- ["bold", "blueBright"],
1639
- store.config.command === "build" ? "Install them and run a new build: " : "Install them and restart dev server: "
1640
- ),
1641
- styleText4(
1642
- "dim",
1643
- ["npm", "pnpm", "yarn"].map((e) => `\`${e} install\``).join(" / ")
1644
- )
1645
- ].join("")
1646
- );
1647
- console.warn();
1648
- for (const [key, pkg, ver] of newDependencies) {
1649
- packageJson[key] = { ...packageJson[key], [pkg]: ver };
1650
- }
1651
- await writeFile3(
1652
- packageJsonFile,
1653
- JSON.stringify(packageJson, null, 2),
1654
- "utf8"
1655
- );
1656
- }
1657
- if (store.config.command === "build") {
1658
- const { resolvers } = await routesFactory(store.resolvedOptions);
1659
- const resolvedEntries = [];
1660
- {
1661
- const spinner = spinnerFactory("Resolving Routes");
1662
- for (const { name, handler } of resolvers.values()) {
1663
- spinner.append(
1664
- `[ ${resolvedEntries.length + 1} of ${resolvers.size} ] ${name}`
1665
- );
1666
- resolvedEntries.push(await handler());
1667
- }
1668
- spinner.succeed();
1669
- }
1670
- {
1671
- const spinner = spinnerFactory("Running Generators");
1672
- for (const { name, factory } of store.resolvedOptions.generators) {
1673
- spinner.append(name);
1674
- const { build } = await factory(store.resolvedOptions);
1675
- await build(resolvedEntries);
1676
- }
1677
- spinner.succeed();
1678
- }
1679
- }
1680
- },
1681
- async configureServer(server) {
1682
- if (store.config.command !== "serve") {
1683
- return;
1684
- }
1685
- if (!store.resolvedOptions.generators.find((e) => e.slot === "api")) {
1686
- const stopWorker2 = workerHandler(() => stopWorker2());
1687
- return;
1688
- }
1689
- const apiHandler = await api_handler_default(store.resolvedOptions);
1690
- const apiWatcher = await apiHandler.watcher();
1691
- const stopWorker = workerHandler(
1692
- async () => {
1693
- await apiWatcher.start();
1694
- },
1695
- async () => {
1696
- await apiWatcher.stop();
1697
- }
1698
- );
1699
- server.middlewares.use(apiHandler.devMiddleware);
1700
- server.httpServer?.on("close", stopWorker);
1701
- }
1702
- };
1703
- };
1704
-
1705
- // src/define-plugin/index.ts
1706
- import { readFile as readFile3 } from "node:fs/promises";
1707
- import { parse as dotenv } from "dotenv";
1708
- var define_plugin_default = (entries) => {
1709
- return {
1710
- name: "@kosmojs:definePlugin",
1711
- async config() {
1712
- const define = {};
1713
- for (const { keys, file, defineOn = "process.env", use } of entries) {
1714
- define[defineOn] = {};
1715
- const fileExists = file ? await pathExists(file) : false;
1716
- const env = fileExists ? dotenv(await readFile3(file, "utf8")) : process.env;
1717
- for (const [key, val] of Object.entries(env)) {
1718
- if (keys.includes(key)) {
1719
- define[`${defineOn}.${key}`] = JSON.stringify(val);
1720
- }
1721
- use?.(key, val);
1722
- }
1723
- }
1724
- return { define };
1725
- }
1726
- };
1727
- };
1728
-
1729
- // src/typebox.ts
1730
- var typeboxLiteralText = (text, options) => {
1731
- return [
1732
- // Escape backticks for safe use in template literals
1733
- [/(?<!\\)`/g, "\\`"],
1734
- // Escape $ for safe use in template literals
1735
- [/(?<!\\)\$\{/g, "\\${"],
1736
- /**
1737
- * TypeBox's built-in `Options` type is not configurable.
1738
- * To allow a custom type name, exposing `refineTypeName` option,
1739
- * defaulted to TRefine, then renaming it to `Options`.
1740
- * */
1741
- [new RegExp(`\\b${options.refineTypeName}\\s*<`, "g"), "Options<"]
1742
- ].reduce((text2, [a, b]) => text2.replace(a, b), text);
1
+ // src/index.ts
2
+ import { default as default2 } from "@kosmojs/fetch-generator";
3
+ import { default as default3 } from "@kosmojs/hono-generator";
4
+ import { default as default4 } from "@kosmojs/koa-generator";
5
+ import { default as default5 } from "@kosmojs/openapi-generator";
6
+ import { default as default6 } from "@kosmojs/react-generator";
7
+ import { default as default7 } from "@kosmojs/solid-generator";
8
+ import { default as default8 } from "@kosmojs/ssr-generator";
9
+ import { default as default9 } from "@kosmojs/typebox-generator";
10
+ import { default as default10 } from "@kosmojs/vue-generator";
11
+ var defineConfig = (config) => {
12
+ return config;
1743
13
  };
1744
14
  export {
1745
- API_INDEX_BASENAME,
1746
- API_INDEX_PATTERN,
1747
- API_USE_BASENAME,
1748
- API_USE_PATTERN,
1749
- PAGE_INDEX_BASENAME,
1750
- PAGE_INDEX_PATTERN,
1751
- PAGE_LAYOUT_BASENAME,
1752
- PAGE_LAYOUT_PATTERN,
1753
- alias_plugin_default as aliasPlugin,
1754
- apiRouteResolverFactory,
1755
- apiUseResolverFactory,
1756
- createRouteEntry,
1757
- createTsconfigPaths,
1758
- base_plugin_default as default,
1759
- defaults,
1760
- define_plugin_default as definePlugin,
1761
- isApiRoute,
1762
- isApiUse,
1763
- isPageLayout,
1764
- isPageRoute,
1765
- isRouteFile,
1766
- nestedRoutesFactory,
1767
- normalizeStaticValue,
1768
- pageLayoutResolverFactory,
1769
- pageRouteResolverFactory,
1770
- pathResolver,
1771
- pathTokensFactory,
1772
- render,
1773
- renderFactory,
1774
- renderHelpers,
1775
- renderToFile,
1776
- routesFactory,
1777
- scanRoutes,
1778
- sortRoutes,
1779
- typeboxLiteralText
15
+ defineConfig,
16
+ default2 as fetchGenerator,
17
+ default3 as honoGenerator,
18
+ default4 as koaGenerator,
19
+ default5 as openapiGenerator,
20
+ default6 as reactGenerator,
21
+ default7 as solidGenerator,
22
+ default8 as ssrGenerator,
23
+ default9 as typeboxGenerator,
24
+ default10 as vueGenerator
1780
25
  };
1781
26
  //# sourceMappingURL=index.js.map