@kosmojs/dev 0.0.0

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.
Files changed (44) hide show
  1. package/package.json +57 -0
  2. package/pkg/base-plugin/routes.js +618 -0
  3. package/pkg/base-plugin/routes.js.map +7 -0
  4. package/pkg/base-plugin/worker.js +810 -0
  5. package/pkg/base-plugin/worker.js.map +7 -0
  6. package/pkg/index.js +1004 -0
  7. package/pkg/index.js.map +7 -0
  8. package/pkg/src/alias-plugin/index.d.ts +5 -0
  9. package/pkg/src/base-plugin/api-handler.d.ts +10 -0
  10. package/pkg/src/base-plugin/ast.d.ts +49 -0
  11. package/pkg/src/base-plugin/cache.d.ts +20 -0
  12. package/pkg/src/base-plugin/index.d.ts +4 -0
  13. package/pkg/src/base-plugin/routes.d.ts +13 -0
  14. package/pkg/src/base-plugin/spinner.d.ts +8 -0
  15. package/pkg/src/base-plugin/worker.d.ts +17 -0
  16. package/pkg/src/define-plugin/index.d.ts +9 -0
  17. package/pkg/src/index.d.ts +6 -0
  18. package/pkg/src/stub-generator/index.d.ts +8 -0
  19. package/pkg/stub-generator/index.js +51 -0
  20. package/pkg/stub-generator/index.js.map +7 -0
  21. package/pkg/test/@fixtures/app/@src/api/articles/[...path]/index.d.ts +0 -0
  22. package/pkg/test/@fixtures/app/@src/api/books/[category]/[[author]]/index.d.ts +0 -0
  23. package/pkg/test/@fixtures/app/@src/api/books/[category]/index.d.ts +0 -0
  24. package/pkg/test/@fixtures/app/@src/api/books/index.d.ts +0 -0
  25. package/pkg/test/@fixtures/app/@src/api/files/[[folder]]/[[id]].json/index.d.ts +0 -0
  26. package/pkg/test/@fixtures/app/@src/api/files/[[folder]]/index.d.ts +0 -0
  27. package/pkg/test/@fixtures/app/@src/api/index/index.d.ts +0 -0
  28. package/pkg/test/@fixtures/app/@src/api/pages/[...path].html/index.d.ts +0 -0
  29. package/pkg/test/@fixtures/app/@src/api/users/[id].json/index.d.ts +0 -0
  30. package/pkg/test/@fixtures/app/lib/@src/{api}/articles/[...path]/types.d.ts +3 -0
  31. package/pkg/test/@fixtures/app/lib/@src/{api}/books/[category]/[[author]]/types.d.ts +4 -0
  32. package/pkg/test/@fixtures/app/lib/@src/{api}/books/[category]/types.d.ts +3 -0
  33. package/pkg/test/@fixtures/app/lib/@src/{api}/books/types.d.ts +1 -0
  34. package/pkg/test/@fixtures/app/lib/@src/{api}/files/[[folder]]/[[id]].json/types.d.ts +4 -0
  35. package/pkg/test/@fixtures/app/lib/@src/{api}/files/[[folder]]/types.d.ts +3 -0
  36. package/pkg/test/@fixtures/app/lib/@src/{api}/index/types.d.ts +1 -0
  37. package/pkg/test/@fixtures/app/lib/@src/{api}/pages/[...path].html/types.d.ts +3 -0
  38. package/pkg/test/@fixtures/app/lib/@src/{api}/users/[id].json/types.d.ts +3 -0
  39. package/pkg/test/@fixtures/ast/extractTypeDeclarations/exports/with-referenced-files.d.ts +1 -0
  40. package/pkg/test/@fixtures/ast/extractTypeDeclarations/imports/with-referenced-files.d.ts +1 -0
  41. package/pkg/test/ast/extractParamsRefinements.test.d.ts +1 -0
  42. package/pkg/test/ast/extractRouteMethods.test.d.ts +1 -0
  43. package/pkg/test/ast/extractTypeDeclarations.test.d.ts +1 -0
  44. package/pkg/test/routes/resolver.test.d.ts +1 -0
package/pkg/index.js ADDED
@@ -0,0 +1,1004 @@
1
+ // src/index.ts
2
+ import { default as default2 } from "@kosmojs/api-generator";
3
+ import { default as default3 } from "@kosmojs/fetch-generator";
4
+
5
+ // src/alias-plugin/index.ts
6
+ import { glob } from "tinyglobby";
7
+ var alias_plugin_default = (appRoot, opt) => {
8
+ return {
9
+ name: "@kosmojs:aliasPlugin",
10
+ async config() {
11
+ const compilerOptions = await import(`${appRoot}/tsconfig.json`, {
12
+ with: { type: "json" }
13
+ }).then((e) => e.default.compilerOptions);
14
+ const aliasmap = [];
15
+ const pathEntries = Object.entries({ ...compilerOptions?.paths });
16
+ for (const [aliasPattern, pathPatterns] of pathEntries) {
17
+ const alias = aliasPattern.replace("/*", "");
18
+ const paths = pathPatterns.map((e) => e.replace("/*", "")).sort((a, b) => a.split(/\/+/).length - b.split(/\/+/).length);
19
+ if (paths.length === 1) {
20
+ aliasmap.push({
21
+ find: new RegExp(`^${alias}/`),
22
+ replacement: `${appRoot}/${paths[0]}/`
23
+ });
24
+ } else if (paths.length > 1) {
25
+ aliasmap.push({
26
+ find: new RegExp(`^${alias}/`),
27
+ replacement: "",
28
+ async customResolver(_src) {
29
+ const src = _src.replace(/(\$|\^|\+|\(|\)|\[|\])/g, "\\$1");
30
+ const patterns = paths.flatMap((path) => [
31
+ // Case 1: Extension is explicitly provided
32
+ // e.g. import styles from "@admin/{solid}/styles.module.css"
33
+ `${path}/${src}*`,
34
+ // Case 2: No extension provided
35
+ // Match any extension and return the first match
36
+ `${path}/${src}.*`,
37
+ // Case 3: Folder containing an index file of any extension
38
+ `${path}/${src}/index.*`
39
+ ]);
40
+ const [file] = await glob(patterns, {
41
+ cwd: appRoot,
42
+ onlyFiles: true,
43
+ absolute: true,
44
+ dot: true,
45
+ followSymbolicLinks: false,
46
+ braceExpansion: false,
47
+ globstar: false,
48
+ ignore: opt?.ignore || [
49
+ "**/.git/**",
50
+ "**/node_modules/**",
51
+ "**/public/**",
52
+ "**/var/**"
53
+ ]
54
+ });
55
+ return file;
56
+ }
57
+ });
58
+ }
59
+ }
60
+ return {
61
+ resolve: {
62
+ alias: aliasmap
63
+ }
64
+ };
65
+ }
66
+ };
67
+ };
68
+
69
+ // src/base-plugin/index.ts
70
+ import { basename, join as join3, resolve as resolve5 } from "node:path";
71
+ import { styleText as styleText2 } from "node:util";
72
+ import { Worker } from "node:worker_threads";
73
+ import apiGenerator from "@kosmojs/api-generator";
74
+ import stubGenerator from "@kosmojs/dev/stub-generator";
75
+ import fetchGenerator from "@kosmojs/fetch-generator";
76
+
77
+ // src/base-plugin/api-handler.ts
78
+ import { join, resolve } from "node:path";
79
+ import { styleText } from "node:util";
80
+ import { context } from "esbuild";
81
+ import { defaults } from "@kosmojs/devlib";
82
+ var api_handler_default = async (options) => {
83
+ const { appRoot, sourceFolder, baseurl, apiurl } = options;
84
+ const apiDir = join(sourceFolder, defaults.apiDir);
85
+ const outDir = join(options.outDir, defaults.apiDir);
86
+ const esbuildOptions = await import(resolve(appRoot, "esbuild.json"), { with: { type: "json" } }).then((e) => e.default);
87
+ let app;
88
+ let devMiddlewareFactory;
89
+ let teardownHandler;
90
+ const watcher = async () => {
91
+ const outfile = join(outDir, "dev.js");
92
+ const rebuildPlugin = {
93
+ name: "rebuild",
94
+ setup(build) {
95
+ build.onEnd(async () => {
96
+ if (app) {
97
+ await teardownHandler?.(app);
98
+ }
99
+ try {
100
+ const exports = await import([outfile, Date.now()].join("?"));
101
+ devMiddlewareFactory = exports.devMiddlewareFactory;
102
+ teardownHandler = exports.teardownHandler;
103
+ app = await exports.default();
104
+ console.debug(`${styleText("green", "\u279C")} Api handler ready`);
105
+ } catch (error) {
106
+ console.error(`${styleText("red", "\u2717")} Api handler error`);
107
+ console.error(error);
108
+ }
109
+ });
110
+ }
111
+ };
112
+ const ctx = await context({
113
+ ...esbuildOptions,
114
+ logLevel: "error",
115
+ bundle: true,
116
+ entryPoints: [join(apiDir, "app.ts")],
117
+ plugins: [rebuildPlugin],
118
+ outfile
119
+ });
120
+ return {
121
+ async start() {
122
+ await ctx.watch({
123
+ // waits this many milliseconds before rebuilding after a change is detected
124
+ delay: options.watcher.delay
125
+ });
126
+ },
127
+ async stop() {
128
+ await ctx.dispose();
129
+ }
130
+ };
131
+ };
132
+ const devMiddleware = async (req, res, viteHandler) => {
133
+ const next = () => {
134
+ return viteHandler();
135
+ };
136
+ if (devMiddlewareFactory) {
137
+ const handler = devMiddlewareFactory(app);
138
+ await handler(req, res, next);
139
+ } else {
140
+ !req?.url || !new RegExp(`^${join(baseurl, apiurl)}($|/)`).test(req.url) ? next() : await app?.callback()(req, res);
141
+ }
142
+ };
143
+ return {
144
+ watcher,
145
+ devMiddleware
146
+ };
147
+ };
148
+
149
+ // src/base-plugin/routes.ts
150
+ import { dirname, join as join2, resolve as resolve4 } from "node:path";
151
+ import crc3 from "crc/crc32";
152
+ import picomatch from "picomatch";
153
+ import { glob as glob2 } from "tinyglobby";
154
+ import {
155
+ defaults as defaults2,
156
+ pathResolver as pathResolver2,
157
+ pathTokensFactory,
158
+ render,
159
+ renderToFile
160
+ } from "@kosmojs/devlib";
161
+
162
+ // src/base-plugin/ast.ts
163
+ import { resolve as resolve2 } from "node:path";
164
+ import crc from "crc/crc32";
165
+ import { flattener } from "tfusion";
166
+ import {
167
+ Project,
168
+ SyntaxKind
169
+ } from "ts-morph";
170
+ import { HTTPMethods } from "@kosmojs/api";
171
+ var createProject = (opts) => new Project(opts);
172
+ var resolveRouteSignature = async (route, opts) => {
173
+ const {
174
+ sourceFile = createProject().addSourceFileAtPath(route.fileFullpath)
175
+ } = { ...opts };
176
+ const [typeDeclarations, referencedFiles] = extractTypeDeclarations(
177
+ sourceFile,
178
+ opts
179
+ );
180
+ const defaultExport = extractDefaultExport(sourceFile);
181
+ const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
182
+ const methods = defaultExport ? extractRouteMethods(defaultExport, route) : [];
183
+ const payloadTypes = methods.flatMap((e) => {
184
+ return e.payloadType ? [e.payloadType] : [];
185
+ });
186
+ const responseTypes = methods.flatMap((e) => {
187
+ return e.responseType ? [e.responseType] : [];
188
+ });
189
+ return {
190
+ typeDeclarations,
191
+ paramsRefinements,
192
+ methods: methods.map((e) => e.method),
193
+ payloadTypes,
194
+ responseTypes,
195
+ referencedFiles
196
+ };
197
+ };
198
+ var extractDefaultExport = (sourceFile) => {
199
+ const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
200
+ if (exportAssignment.isExportEquals()) {
201
+ return [];
202
+ }
203
+ const callExpression = exportAssignment.getExpression();
204
+ return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
205
+ });
206
+ return defaultExport;
207
+ };
208
+ var extractParamsRefinements = (callExpression) => {
209
+ const [firstGeneric] = extractGenerics(callExpression);
210
+ if (!firstGeneric?.node.isKind(SyntaxKind.TupleType)) {
211
+ return;
212
+ }
213
+ const tupleElements = firstGeneric.node.getElements();
214
+ if (!tupleElements?.length) {
215
+ return;
216
+ }
217
+ return tupleElements.map((node, index) => {
218
+ return {
219
+ index,
220
+ text: node.getText()
221
+ };
222
+ });
223
+ };
224
+ var extractRouteMethods = (callExpression, route) => {
225
+ const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
226
+ if (!funcDeclaration) {
227
+ return [];
228
+ }
229
+ const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(
230
+ SyntaxKind.ArrayLiteralExpression
231
+ );
232
+ if (!arrayLiteralExpression) {
233
+ return [];
234
+ }
235
+ const callExpressions = [];
236
+ for (const e of arrayLiteralExpression.getChildrenOfKind(
237
+ SyntaxKind.CallExpression
238
+ )) {
239
+ const name = e.getExpression().getText();
240
+ if (HTTPMethods[name]) {
241
+ callExpressions.push([e, name]);
242
+ }
243
+ }
244
+ const methods = [];
245
+ const skipValidationFilter = (e) => /@skip-validation/.test(e);
246
+ for (const [callExpression2, method] of callExpressions) {
247
+ const [payloadGeneric, responseGeneric] = extractGenerics(callExpression2);
248
+ const payloadText = payloadGeneric?.node ? payloadGeneric.node.getChildren().length === 0 ? "{}" : payloadGeneric.node.getFullText() : void 0;
249
+ const responseText = responseGeneric?.node.getText();
250
+ const responseType = responseText ? {
251
+ id: ["ResponseT", crc(route.importName + method)].join(""),
252
+ method,
253
+ skipValidation: responseGeneric?.comments ? responseGeneric.comments.some(skipValidationFilter) : false,
254
+ text: ["never", "object"].includes(responseText) ? "{}" : responseText,
255
+ resolvedType: void 0
256
+ } : void 0;
257
+ const payloadType = payloadText ? {
258
+ id: ["PayloadT", crc(route.importName + method)].join(""),
259
+ responseTypeId: responseType?.id,
260
+ method,
261
+ skipValidation: payloadGeneric?.comments ? payloadGeneric.comments.some(skipValidationFilter) : false,
262
+ isOptional: payloadText ? payloadText === "{}" || route.optionalParams : true,
263
+ text: payloadText,
264
+ resolvedType: void 0
265
+ } : void 0;
266
+ methods.push({
267
+ method,
268
+ payloadType,
269
+ responseType
270
+ });
271
+ }
272
+ return methods;
273
+ };
274
+ var extractTypeDeclarations = (sourceFile, opts) => {
275
+ const declarations = [];
276
+ const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
277
+ for (const declaration of sourceFile.getImportDeclarations()) {
278
+ const modulePath = declaration.getModuleSpecifierValue();
279
+ const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
280
+ const typeOnlyDeclaration = declaration.isTypeOnly();
281
+ const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
282
+ if (defaultImport) {
283
+ const name = defaultImport.getText();
284
+ const text = `import type ${name} from "${path}";`;
285
+ declarations.push({
286
+ importDeclaration: {
287
+ name,
288
+ path
289
+ },
290
+ text
291
+ });
292
+ if (referencedFiles) {
293
+ referencedFiles.push(...getReferencedFiles(defaultImport));
294
+ }
295
+ }
296
+ for (const namedImport of declaration.getNamedImports()) {
297
+ if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
298
+ const nameNode = namedImport.getNameNode();
299
+ const name = nameNode.getText();
300
+ const alias = namedImport.getAliasNode()?.getText();
301
+ const nameText = alias ? `${name} as ${alias}` : name;
302
+ declarations.push({
303
+ importDeclaration: {
304
+ name,
305
+ alias,
306
+ path
307
+ },
308
+ text: `import type { ${nameText} } from "${path}";`
309
+ });
310
+ if (referencedFiles) {
311
+ if (nameNode.isKind(SyntaxKind.Identifier)) {
312
+ referencedFiles.push(...getReferencedFiles(nameNode));
313
+ }
314
+ }
315
+ }
316
+ }
317
+ }
318
+ for (const declaration of sourceFile.getTypeAliases()) {
319
+ const name = declaration.getName();
320
+ const text = declaration.getFullText().trim();
321
+ declarations.push({
322
+ typeAliasDeclaration: { name },
323
+ text
324
+ });
325
+ }
326
+ for (const declaration of sourceFile.getInterfaces()) {
327
+ const name = declaration.getName();
328
+ const text = declaration.getFullText().trim();
329
+ declarations.push({
330
+ interfaceDeclaration: { name },
331
+ text
332
+ });
333
+ }
334
+ for (const declaration of sourceFile.getEnums()) {
335
+ const name = declaration.getName();
336
+ const text = declaration.getFullText().trim();
337
+ declarations.push({
338
+ enumDeclaration: { name },
339
+ text
340
+ });
341
+ }
342
+ for (const declaration of sourceFile.getExportDeclarations()) {
343
+ const typeOnlyDeclaration = declaration.isTypeOnly();
344
+ const modulePath = declaration.getModuleSpecifierValue();
345
+ const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
346
+ for (const namedExport of declaration.getNamedExports()) {
347
+ if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
348
+ const nameNode = namedExport.getNameNode();
349
+ const name = nameNode.getText();
350
+ const alias = namedExport.getAliasNode()?.getText();
351
+ const nameText = alias ? `${name} as ${alias}` : name;
352
+ declarations.push({
353
+ exportDeclaration: {
354
+ name,
355
+ alias: alias ?? name,
356
+ path
357
+ },
358
+ text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
359
+ });
360
+ if (referencedFiles) {
361
+ if (nameNode.isKind(SyntaxKind.Identifier)) {
362
+ referencedFiles.push(...getReferencedFiles(nameNode));
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
369
+ };
370
+ var getReferencedFiles = (importIdentifier) => {
371
+ const declarations = importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || [];
372
+ return declarations.flatMap((e) => {
373
+ const sourceFile = e.getSourceFile();
374
+ return sourceFile ? [sourceFile.getFilePath()] : [];
375
+ });
376
+ };
377
+ var extractGenerics = (callExpression) => {
378
+ return callExpression.getTypeArguments().map((node) => {
379
+ return {
380
+ node,
381
+ comments: node.getLeadingCommentRanges().map((range) => range.getText().trim())
382
+ };
383
+ });
384
+ };
385
+ var typeResolverFactory = ({ appRoot }) => {
386
+ const project = createProject({
387
+ tsConfigFilePath: resolve2(appRoot, "tsconfig.json"),
388
+ skipAddingFilesFromTsConfig: true
389
+ });
390
+ const literalTypesResolver = (literalTypes, options) => {
391
+ const sourceFile = project.createSourceFile(
392
+ `${crc(literalTypes)}-${Date.now()}.ts`,
393
+ literalTypes,
394
+ { overwrite: true }
395
+ );
396
+ const resolvedTypes = flattener(project, sourceFile, {
397
+ ...options,
398
+ stripComments: true
399
+ });
400
+ project.removeSourceFile(sourceFile);
401
+ return resolvedTypes;
402
+ };
403
+ return {
404
+ getSourceFile: (fileFullpath) => {
405
+ return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
406
+ },
407
+ refreshSourceFile: async (fileFullpath) => {
408
+ const sourceFile = project.getSourceFile(fileFullpath);
409
+ if (sourceFile) {
410
+ await sourceFile.refreshFromFileSystem();
411
+ }
412
+ },
413
+ literalTypesResolver
414
+ };
415
+ };
416
+
417
+ // src/base-plugin/cache.ts
418
+ import { resolve as resolve3 } from "node:path";
419
+ import crc2 from "crc/crc32";
420
+ import fsx from "fs-extra";
421
+ import pkg from "@kosmojs/dev/package.json" with { type: "json" };
422
+ import { pathResolver } from "@kosmojs/devlib";
423
+ var cacheFactory = (route, {
424
+ appRoot,
425
+ sourceFolder,
426
+ extraContext
427
+ }) => {
428
+ const cacheFile = pathResolver({
429
+ appRoot,
430
+ sourceFolder
431
+ }).resolve("apiLibDir", route.importPath, "cache.json");
432
+ const getCache = async (opt) => {
433
+ if (await fsx.exists(cacheFile)) {
434
+ try {
435
+ const cache = JSON.parse(await fsx.readFile(cacheFile, "utf8"));
436
+ return opt?.validate ? validateCache(cache) : cache;
437
+ } catch (_e) {
438
+ }
439
+ }
440
+ return void 0;
441
+ };
442
+ const persistCache = async ({
443
+ referencedFiles: _referencedFiles,
444
+ ...rest
445
+ }) => {
446
+ const hash = await generateFileHash(route.fileFullpath, {
447
+ ...extraContext
448
+ });
449
+ const referencedFiles = {};
450
+ for (const file of _referencedFiles) {
451
+ referencedFiles[
452
+ // Strip project root to ensure cached paths are relative
453
+ // and portable across environments (CI, local, etc.)
454
+ file.replace(`${appRoot}/`, "")
455
+ ] = await generateFileHash(file);
456
+ }
457
+ const cache = { ...rest, hash, referencedFiles };
458
+ await fsx.outputJson(cacheFile, cache, { spaces: 2 });
459
+ return cache;
460
+ };
461
+ const validateCache = async (cache) => {
462
+ if (!cache?.hash) {
463
+ return;
464
+ }
465
+ if (!cache.typeDeclarations || !cache.referencedFiles) {
466
+ return;
467
+ }
468
+ const hash = await generateFileHash(route.fileFullpath, {
469
+ ...extraContext
470
+ });
471
+ if (!identicalHashSum(cache.hash, hash)) {
472
+ return;
473
+ }
474
+ for (const [file, hash2] of Object.entries(cache.referencedFiles)) {
475
+ if (!identicalHashSum(hash2, await generateFileHash(resolve3(appRoot, file)))) {
476
+ return;
477
+ }
478
+ }
479
+ return cache;
480
+ };
481
+ return {
482
+ getCache,
483
+ validateCache,
484
+ persistCache
485
+ };
486
+ };
487
+ var generateFileHash = async (file, extraContext) => {
488
+ let fileContent;
489
+ try {
490
+ fileContent = await fsx.readFile(file, "utf8");
491
+ } catch (_e) {
492
+ return 0;
493
+ }
494
+ return fileContent ? crc2(
495
+ JSON.stringify({
496
+ ...extraContext,
497
+ [pkg.cacheVersion]: fileContent
498
+ })
499
+ ) : 0;
500
+ };
501
+ var identicalHashSum = (a, b) => {
502
+ return a === b;
503
+ };
504
+
505
+ // src/base-plugin/templates/resolved-types.hbs
506
+ var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
507
+
508
+ // src/base-plugin/templates/types.hbs
509
+ var types_default = '{{#each typeDeclarations}}{{text}}\n{{/each}}\n\nexport type {{params.id}} = {\n {{#each paramsSchema}}\n "{{name}}"{{#unless isRequired}}?{{/unless}}:{{#if isRest}} Array<{{/if}}\n {{#if refinement}}{{refinement.text}}{{else}}string{{/if}}\n {{#if isRest}}>{{/if}}\n {{/each}}\n};\n\n{{#each payloadTypes}}\nexport type {{id}} = {{text}};\n{{/each}}\n\n{{#each responseTypes}}\nexport type {{id}} = {{text}};\n{{/each}}\n';
510
+
511
+ // src/base-plugin/routes.ts
512
+ var routes_default = async (pluginOptions) => {
513
+ const {
514
+ appRoot,
515
+ sourceFolder,
516
+ generators = [],
517
+ formatters = [],
518
+ refineTypeName
519
+ } = pluginOptions;
520
+ let resolveTypes = false;
521
+ for (const { options } of generators) {
522
+ if (options?.resolveTypes) {
523
+ resolveTypes = true;
524
+ }
525
+ }
526
+ const {
527
+ //
528
+ literalTypesResolver,
529
+ getSourceFile,
530
+ refreshSourceFile
531
+ } = typeResolverFactory(pluginOptions);
532
+ const routeFilePatterns = [
533
+ `${defaults2.apiDir}/**/index.ts`,
534
+ `${defaults2.pagesDir}/**/index.{ts,tsx,vue,svelte}`
535
+ ];
536
+ const resolveRouteFile = (file) => {
537
+ const [_sourceFolder, folder, ...rest] = resolve4(appRoot, file).replace(`${appRoot}/`, "").split("/");
538
+ if (!folder || _sourceFolder !== sourceFolder || rest.length < 2) {
539
+ return;
540
+ }
541
+ return picomatch.isMatch(join2(folder, ...rest), routeFilePatterns) ? [folder, rest.join("/")] : void 0;
542
+ };
543
+ const resolversFactory = (routeFiles2) => {
544
+ const resolvers = /* @__PURE__ */ new Map();
545
+ const entries = routeFiles2.flatMap((_file) => {
546
+ const resolvedPaths = resolveRouteFile(_file);
547
+ if (!resolvedPaths) {
548
+ return [];
549
+ }
550
+ const [folder, file] = resolvedPaths;
551
+ const fileFullpath = join2(appRoot, sourceFolder, folder, file);
552
+ const pathTokens = pathTokensFactory(dirname(file));
553
+ const name = pathTokens.map((e) => e.orig).join("/");
554
+ const importPath = dirname(file);
555
+ const importName = [
556
+ importPath.split(/\[/)[0].replace(/^\W+|\W+$/g, "").replace(/\W+/g, "_"),
557
+ crc3(importPath)
558
+ ].join("_");
559
+ return [
560
+ {
561
+ name,
562
+ folder,
563
+ file,
564
+ fileFullpath,
565
+ pathTokens,
566
+ importPath,
567
+ importName
568
+ }
569
+ ];
570
+ });
571
+ for (const entry of entries.filter((e) => e.folder === defaults2.apiDir)) {
572
+ const {
573
+ name,
574
+ file,
575
+ folder,
576
+ fileFullpath,
577
+ pathTokens,
578
+ importPath,
579
+ importName
580
+ } = entry;
581
+ const handler = async (updatedFile) => {
582
+ const paramsSchema = pathTokens.flatMap((e) => {
583
+ return e.param ? [e.param] : [];
584
+ });
585
+ const optionalParams = paramsSchema.length ? !paramsSchema.some((e) => e.isRequired) : true;
586
+ const { getCache, persistCache } = cacheFactory(
587
+ { file, fileFullpath, importName, importPath },
588
+ {
589
+ appRoot,
590
+ sourceFolder,
591
+ extraContext: { resolveTypes }
592
+ }
593
+ );
594
+ let cache = await getCache({ validate: true });
595
+ if (!cache) {
596
+ if (updatedFile === fileFullpath) {
597
+ await refreshSourceFile(fileFullpath);
598
+ }
599
+ const {
600
+ typeDeclarations,
601
+ paramsRefinements,
602
+ methods,
603
+ payloadTypes,
604
+ responseTypes,
605
+ referencedFiles = []
606
+ } = await resolveRouteSignature(
607
+ { importName, fileFullpath, optionalParams },
608
+ {
609
+ withReferencedFiles: true,
610
+ sourceFile: getSourceFile(fileFullpath),
611
+ relpathResolver(path) {
612
+ return join2(sourceFolder, defaults2.apiDir, dirname(file), path);
613
+ }
614
+ }
615
+ );
616
+ const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
617
+ if (text === "number") {
618
+ const param = paramsSchema.at(index);
619
+ return param ? [param.name] : [];
620
+ }
621
+ return [];
622
+ }) : [];
623
+ const typesFile = pathResolver2({ appRoot, sourceFolder }).resolve(
624
+ "apiLibDir",
625
+ importPath,
626
+ "types.ts"
627
+ );
628
+ const params = {
629
+ id: ["ParamsT", crc3(name)].join(""),
630
+ schema: paramsSchema,
631
+ resolvedType: void 0
632
+ };
633
+ const typesFileContent = render(types_default, {
634
+ params,
635
+ paramsSchema: paramsSchema.map((param, index) => {
636
+ return {
637
+ ...param,
638
+ refinement: paramsRefinements?.at(index)
639
+ };
640
+ }),
641
+ typeDeclarations,
642
+ payloadTypes,
643
+ responseTypes
644
+ });
645
+ const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
646
+ overrides: [...payloadTypes, ...responseTypes].reduce(
647
+ (map, { id, skipValidation }) => {
648
+ if (skipValidation) {
649
+ map[id] = "never";
650
+ }
651
+ return map;
652
+ },
653
+ { [refineTypeName]: refineTypeName }
654
+ ),
655
+ withProperties: [params.id, ...payloadTypes.map((e) => e.id)],
656
+ formatters
657
+ }) : void 0;
658
+ await renderToFile(
659
+ typesFile,
660
+ resolvedTypes ? resolved_types_default : typesFileContent,
661
+ { resolvedTypes }
662
+ );
663
+ params.resolvedType = resolvedTypes?.find(
664
+ (e) => e.name === params.id
665
+ );
666
+ cache = await persistCache({
667
+ params,
668
+ methods,
669
+ typeDeclarations,
670
+ numericParams,
671
+ // text was needed at writing types.ts file, dropping from cache
672
+ payloadTypes: payloadTypes.map(({ text, ...rest }) => {
673
+ return {
674
+ ...rest,
675
+ resolvedType: resolvedTypes?.find((e) => e.name === rest.id)
676
+ };
677
+ }),
678
+ responseTypes: responseTypes.map(({ text, ...rest }) => {
679
+ return {
680
+ ...rest,
681
+ resolvedType: resolvedTypes?.find((e) => e.name === rest.id)
682
+ };
683
+ }),
684
+ referencedFiles
685
+ });
686
+ }
687
+ const route = {
688
+ name,
689
+ pathTokens,
690
+ params: cache.params,
691
+ numericParams: cache.numericParams,
692
+ optionalParams,
693
+ importName,
694
+ importPath,
695
+ folder,
696
+ file,
697
+ fileFullpath,
698
+ methods: cache.methods,
699
+ typeDeclarations: cache.typeDeclarations,
700
+ payloadTypes: cache.payloadTypes,
701
+ responseTypes: cache.responseTypes,
702
+ referencedFiles: Object.keys(cache.referencedFiles).map(
703
+ // expand referenced files path,
704
+ // they are stored as relative in cache
705
+ (e) => resolve4(appRoot, e)
706
+ )
707
+ };
708
+ return {
709
+ kind: "api",
710
+ route
711
+ };
712
+ };
713
+ resolvers.set(fileFullpath, { name, handler });
714
+ }
715
+ for (const entry of entries.filter((e) => e.folder === defaults2.pagesDir)) {
716
+ const {
717
+ //
718
+ name,
719
+ folder,
720
+ file,
721
+ fileFullpath,
722
+ pathTokens,
723
+ importPath,
724
+ importName
725
+ } = entry;
726
+ const handler = async () => {
727
+ const route = {
728
+ name,
729
+ pathTokens,
730
+ params: {
731
+ schema: pathTokens.flatMap((e) => e.param ? [e.param] : [])
732
+ },
733
+ folder,
734
+ file,
735
+ fileFullpath,
736
+ importPath,
737
+ importName
738
+ };
739
+ return {
740
+ kind: "page",
741
+ route
742
+ };
743
+ };
744
+ resolvers.set(fileFullpath, { name, handler });
745
+ }
746
+ return resolvers;
747
+ };
748
+ const routeFiles = await glob2(routeFilePatterns, {
749
+ cwd: resolve4(appRoot, sourceFolder),
750
+ absolute: true,
751
+ onlyFiles: true,
752
+ ignore: [
753
+ `${defaults2.apiDir}/index.ts`,
754
+ `${defaults2.pagesDir}/index.ts{x,}`
755
+ ]
756
+ });
757
+ return {
758
+ resolvers: resolversFactory(routeFiles),
759
+ resolversFactory,
760
+ resolveRouteFile
761
+ };
762
+ };
763
+
764
+ // src/base-plugin/spinner.ts
765
+ import ora from "ora";
766
+ var spinnerFactory = (startText) => {
767
+ const spinner = ora().start(startText);
768
+ let _text = startText;
769
+ return {
770
+ text(text) {
771
+ _text = text;
772
+ spinner.text = text;
773
+ },
774
+ append(text) {
775
+ spinner.text = `${_text} \u203A ${text}`;
776
+ },
777
+ succeed(text) {
778
+ if (text) {
779
+ this.append(text);
780
+ } else {
781
+ this.text(_text);
782
+ }
783
+ spinner.succeed();
784
+ },
785
+ failed(text) {
786
+ if (text) {
787
+ this.text([_text, text].join("\n"));
788
+ }
789
+ spinner.fail();
790
+ }
791
+ };
792
+ };
793
+ var withSpinner = (text, pipe, spinner) => pipe(spinner || spinnerFactory(text));
794
+
795
+ // src/base-plugin/index.ts
796
+ var base_plugin_default = (apiurl, pluginOptions) => {
797
+ const outDirSuffix = "client";
798
+ const store = {};
799
+ const createWorker = () => {
800
+ const {
801
+ generators = [],
802
+ formatters = [],
803
+ ...restOptions
804
+ } = store.resolvedOptions;
805
+ const generatorModules = generators.map(
806
+ (e) => [e.moduleImport, e.moduleConfig]
807
+ );
808
+ const formatterModules = pluginOptions?.formatters ? pluginOptions.formatters.map((e) => [e.moduleImport, e.moduleConfig]) : [];
809
+ const workerData = {
810
+ ...restOptions,
811
+ generatorModules,
812
+ formatterModules
813
+ };
814
+ return new Worker(resolve5(import.meta.dirname, "base-plugin/worker.js"), {
815
+ workerData,
816
+ env: {
817
+ ...process.env,
818
+ FORCE_COLOR: "1"
819
+ }
820
+ });
821
+ };
822
+ const workerHandler = (onReady, onExit) => {
823
+ const worker = createWorker();
824
+ const spinnerMap = /* @__PURE__ */ new Map();
825
+ worker.on("error", async (error) => {
826
+ console.error(error);
827
+ });
828
+ worker.on("exit", async () => {
829
+ await onExit?.();
830
+ });
831
+ worker.on(
832
+ "message",
833
+ async (msg) => {
834
+ if (msg?.spinner) {
835
+ const { id, startText, method, text } = msg.spinner;
836
+ withSpinner(
837
+ startText,
838
+ (spinner) => {
839
+ spinnerMap.set(id, spinner);
840
+ spinner[method](text || "");
841
+ if (method === "succeed" || method === "failed") {
842
+ spinnerMap.delete(id);
843
+ }
844
+ },
845
+ spinnerMap.get(id)
846
+ );
847
+ } else if (msg?.error) {
848
+ const { error } = msg;
849
+ if (error.stack) {
850
+ const [message, ...stack] = error.stack.split("\n");
851
+ console.error(styleText2("red", message));
852
+ console.error(stack.join("\n"));
853
+ } else if (error?.message) {
854
+ console.error(`${styleText2("red", error?.name)}: ${error.message}`);
855
+ } else {
856
+ console.error(error);
857
+ }
858
+ }
859
+ }
860
+ );
861
+ const readyHandler = async (msg) => {
862
+ if (msg === "ready") {
863
+ worker.off("message", readyHandler);
864
+ await onReady?.();
865
+ }
866
+ };
867
+ worker.on("message", readyHandler);
868
+ return async () => {
869
+ await worker.terminate();
870
+ };
871
+ };
872
+ return {
873
+ name: "@kosmojs:basePlugin",
874
+ config(config) {
875
+ if (!config.build?.outDir) {
876
+ throw new Error("Incomplete config, missing build.outDir");
877
+ }
878
+ return {
879
+ build: {
880
+ outDir: join3(config.build.outDir, outDirSuffix),
881
+ manifest: true
882
+ }
883
+ };
884
+ },
885
+ async configResolved(_config) {
886
+ store.config = _config;
887
+ const appRoot = resolve5(store.config.root, "..");
888
+ const sourceFolder = basename(store.config.root);
889
+ const outDir = resolve5(appRoot, resolve5(store.config.build.outDir, ".."));
890
+ const { stabilityThreshold = 1e3 } = typeof store.config.server.watch?.awaitWriteFinish === "object" ? store.config.server.watch.awaitWriteFinish : {};
891
+ const watcher = {
892
+ delay: stabilityThreshold,
893
+ ...store.config.server.watch ? { options: store.config.server.watch } : {}
894
+ };
895
+ {
896
+ const {
897
+ generators = [],
898
+ formatters = [],
899
+ refineTypeName = "TRefine"
900
+ } = { ...pluginOptions };
901
+ const _apiGenerator = generators.find((e) => e.kind === "api");
902
+ const _fetchGenerator = generators.find((e) => e.kind === "fetch");
903
+ const _ssrGenerator = generators.find((e) => e.kind === "ssr");
904
+ store.resolvedOptions = {
905
+ ...pluginOptions,
906
+ command: store.config.command,
907
+ watcher,
908
+ generators: [
909
+ // 1. stub generator should run first
910
+ stubGenerator(),
911
+ // 2. then api generator
912
+ _apiGenerator || apiGenerator(),
913
+ // 3. then fetch generator
914
+ _fetchGenerator || fetchGenerator(),
915
+ // 4. user generators in the order they were added
916
+ ...generators.filter((e) => {
917
+ return e.kind ? !["api", "fetch", "ssr"].includes(e.kind) : true;
918
+ }),
919
+ // 5. ssr generator should run last
920
+ ..._ssrGenerator ? [_ssrGenerator] : []
921
+ ],
922
+ formatters: formatters.map((e) => e.formatter),
923
+ refineTypeName,
924
+ baseurl: store.config.base,
925
+ apiurl,
926
+ appRoot,
927
+ sourceFolder,
928
+ outDir
929
+ };
930
+ }
931
+ if (store.config.command === "build") {
932
+ const { resolvers } = await routes_default(store.resolvedOptions);
933
+ const resolvedRoutes = [];
934
+ {
935
+ const spinner = spinnerFactory("Resolving Routes");
936
+ for (const { name, handler } of resolvers.values()) {
937
+ spinner.append(
938
+ `[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`
939
+ );
940
+ resolvedRoutes.push(await handler());
941
+ }
942
+ spinner.succeed();
943
+ }
944
+ {
945
+ const spinner = spinnerFactory("Running Generators");
946
+ for (const { name, factory } of store.resolvedOptions.generators) {
947
+ spinner.append(name);
948
+ const { watchHandler } = await factory(store.resolvedOptions);
949
+ await watchHandler(resolvedRoutes);
950
+ }
951
+ spinner.succeed();
952
+ }
953
+ }
954
+ },
955
+ async configureServer(server) {
956
+ if (store.config.command !== "serve") {
957
+ return;
958
+ }
959
+ const apiHandler = await api_handler_default(store.resolvedOptions);
960
+ const apiWatcher = await apiHandler.watcher();
961
+ const stopWorker = workerHandler(
962
+ async () => {
963
+ await apiWatcher.start();
964
+ },
965
+ async () => {
966
+ await apiWatcher.stop();
967
+ }
968
+ );
969
+ server.middlewares.use(apiHandler.devMiddleware);
970
+ server.httpServer?.on("close", stopWorker);
971
+ }
972
+ };
973
+ };
974
+
975
+ // src/define-plugin/index.ts
976
+ import { parse as dotenv } from "dotenv";
977
+ import fsx2 from "fs-extra";
978
+ var define_plugin_default = (entries) => {
979
+ return {
980
+ name: "@kosmojs:definePlugin",
981
+ async config() {
982
+ const define = {};
983
+ for (const { keys, file, defineOn = "process.env", use } of entries) {
984
+ define[defineOn] = {};
985
+ const env = file && await fsx2.pathExists(file) ? dotenv(await fsx2.readFile(file, "utf8")) : process.env;
986
+ for (const [key, val] of Object.entries(env)) {
987
+ if (keys.includes(key)) {
988
+ define[`${defineOn}.${key}`] = JSON.stringify(val);
989
+ }
990
+ use?.(key, val);
991
+ }
992
+ }
993
+ return { define };
994
+ }
995
+ };
996
+ };
997
+ export {
998
+ alias_plugin_default as aliasPlugin,
999
+ default2 as apiGenerator,
1000
+ base_plugin_default as default,
1001
+ define_plugin_default as definePlugin,
1002
+ default3 as fetchGenerator
1003
+ };
1004
+ //# sourceMappingURL=index.js.map