@kosmojs/dev 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/pkg/chassis.js CHANGED
@@ -1,1390 +1,17 @@
1
- import { n as defaults$3 } from "./assets/pkg-BO-iPG8y.js";
2
- import "node:module";
3
1
  import { dirname, join, resolve } from "node:path";
4
- import crc from "crc/crc32";
5
- import { flattener } from "tfusion";
6
- import { Project, SyntaxKind } from "ts-morph";
7
- import { access, constants, mkdir, readFile, writeFile } from "node:fs/promises";
8
- import handlebars from "handlebars";
9
- import { parse } from "path-to-regexp";
10
- import mimeTypes from "mime-types";
11
- import { Spinner } from "@topcli/spinner";
12
- import picomatch from "picomatch";
13
- import { build, createServer } from "vite";
14
- import { styleText } from "node:util";
2
+ import { defaults } from "@kosmojs/core";
15
3
  import http from "node:http";
16
4
  import net from "node:net";
17
- import { glob } from "tinyglobby";
18
- //#region ../lib/pkg/index.js
19
- var defaults$2 = {
20
- appPrefix: "@",
21
- srcPrefix: "~",
22
- libPrefix: "_",
23
- coreDir: "core",
24
- srcDir: "src",
25
- libDir: "lib",
26
- configDir: "config",
27
- apiDir: "api",
28
- pagesDir: "pages",
29
- entryDir: "entry",
30
- fetchDir: "fetch",
31
- refineTypeName: "VRefine"
32
- };
33
- /**
34
- * Request metadata validation targets.
35
- * */
36
- var RequestMetadataTargets$2 = {
37
- query: "URL query parameters",
38
- headers: "HTTP request headers",
39
- cookies: "HTTP cookies"
40
- };
41
- /**
42
- * Request body validation targets.
43
- *
44
- * Body formats are mutually exclusive - only one should be specified per handler.
45
- *
46
- * **Development behavior:**
47
- * - If multiple formats are defined, the builder displays a warning and
48
- * disables validation schemas for the affected handler.
49
- * - If an unsuitable target is defined (e.g., `json`, `form`)
50
- * for a method without a body like GET, HEAD), a warning is displayed and
51
- * validation schemas are disabled for that handler.
52
- *
53
- * This ensures misconfigurations are detected during development
54
- * for runtime to execute without false positive validation failures.
55
- *
56
- * Always define exactly one target that is suitable for current handler.
57
- * */
58
- var RequestBodyTargets$2 = {
59
- json: "JSON request body",
60
- form: "URL-encoded or Multipart form",
61
- raw: "Raw body format (string/Buffer/ArrayBuffer/Blob)"
62
- };
63
- var RequestValidationTargets = {
64
- ...RequestMetadataTargets$2,
65
- ...RequestBodyTargets$2
66
- };
67
- var HTTPMethods = /* @__PURE__ */ function(HTTPMethods) {
68
- HTTPMethods["HEAD"] = "HEAD";
69
- HTTPMethods["OPTIONS"] = "OPTIONS";
70
- HTTPMethods["GET"] = "GET";
71
- HTTPMethods["PUT"] = "PUT";
72
- HTTPMethods["PATCH"] = "PATCH";
73
- HTTPMethods["POST"] = "POST";
74
- HTTPMethods["DELETE"] = "DELETE";
75
- return HTTPMethods;
76
- }({});
77
- var astFactory = () => {
78
- const createProject = (opts) => new Project(opts);
79
- const resolveRouteSignature = async (route, opts) => {
80
- const { sourceFile = createProject().addSourceFileAtPath(route.fileFullpath) } = { ...opts };
81
- const [typeDeclarations, referencedFiles] = extractTypeDeclarations(sourceFile, opts);
82
- const defaultExport = extractDefaultExport(sourceFile);
83
- const paramsRefinements = defaultExport ? extractParamsRefinements(defaultExport) : void 0;
84
- const methods = defaultExport ? extractRouteMethods(route, defaultExport) : [];
85
- return {
86
- typeDeclarations,
87
- paramsRefinements,
88
- methods: methods.map((e) => e.method),
89
- validationDefinitions: methods.flatMap((e) => e.validationDefinitions),
90
- referencedFiles
91
- };
92
- };
93
- const extractDefaultExport = (sourceFile) => {
94
- const [defaultExport] = sourceFile.getExportAssignments().flatMap((exportAssignment) => {
95
- if (exportAssignment.isExportEquals()) return [];
96
- const callExpression = exportAssignment.getExpression();
97
- return callExpression.isKind(SyntaxKind.CallExpression) ? [callExpression] : [];
98
- });
99
- return defaultExport;
100
- };
101
- const extractParamsRefinements = (callExpression) => {
102
- const [_routeName, paramsGeneric] = extractGenerics(callExpression);
103
- if (!paramsGeneric?.isKind(SyntaxKind.TupleType)) return;
104
- return paramsGeneric.getElements().map((node, index) => {
105
- return {
106
- index,
107
- text: node.getText()
108
- };
109
- });
110
- };
111
- const extractRouteMethods = (route, callExpression) => {
112
- const funcDeclaration = callExpression.getFirstChildByKind(SyntaxKind.ArrowFunction) || callExpression.getFirstChildByKind(SyntaxKind.FunctionExpression);
113
- if (!funcDeclaration) return [];
114
- const arrayLiteralExpression = funcDeclaration.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
115
- if (!arrayLiteralExpression) return [];
116
- const callExpressions = [];
117
- for (const e of arrayLiteralExpression.getChildrenOfKind(SyntaxKind.CallExpression)) {
118
- const name = e.getExpression().getText();
119
- if (HTTPMethods[name]) callExpressions.push([e, name]);
120
- }
121
- const methods = [];
122
- for (const [callExpression, method] of callExpressions) {
123
- const [vDefs, vOpts] = extractGenerics(callExpression);
124
- methods.push({
125
- method,
126
- validationDefinitions: extractValidationDefinitions(route, method, vDefs, vOpts)
127
- });
128
- }
129
- return methods;
130
- };
131
- /** Parse a boolean literal type node */
132
- const parseRuntimeValidation = (typeNode) => {
133
- if (typeNode.isKind(SyntaxKind.LiteralType)) {
134
- const literal = typeNode.getFirstChild();
135
- if (literal?.isKind(SyntaxKind.TrueKeyword)) return true;
136
- else if (literal?.isKind(SyntaxKind.FalseKeyword)) return false;
137
- }
138
- };
139
- const extractResponseVariant = (typeNode) => {
140
- if (!typeNode.isKind(SyntaxKind.TupleType)) return;
141
- let status = 200;
142
- let contentType;
143
- let body;
144
- const [statusNode, contentTypeNode, bodyNode] = typeNode.getElements();
145
- if (statusNode?.isKind(SyntaxKind.LiteralType)) {
146
- const literal = statusNode.getFirstChildByKind(SyntaxKind.NumericLiteral);
147
- if (literal) status = Number(literal.getText());
148
- }
149
- if (contentTypeNode) contentType = extractStringLiteral(contentTypeNode);
150
- if (bodyNode) {
151
- body = bodyNode.getText();
152
- if (["object"].includes(body)) body = "{}";
153
- }
154
- return {
155
- status,
156
- contentType,
157
- body
158
- };
159
- };
160
- /** Parse opts TypeLiteral into a map keyed by target */
161
- const parseValidationOptions = (typeNode) => {
162
- const opts = {};
163
- if (!typeNode?.isKind(SyntaxKind.TypeLiteral)) return opts;
164
- for (const prop of typeNode.getMembers()) {
165
- if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
166
- const target = prop.getName();
167
- const typeNode = prop.getTypeNodeOrThrow();
168
- if (!typeNode.isKind(SyntaxKind.TypeLiteral)) continue;
169
- let contentType;
170
- let runtimeValidation;
171
- const customErrors = {};
172
- for (const member of typeNode.getMembers()) {
173
- if (!member.isKind(SyntaxKind.PropertySignature)) continue;
174
- const nameNode = member.getNameNode();
175
- const valueNode = member.getTypeNodeOrThrow();
176
- const name = nameNode.isKind(SyntaxKind.StringLiteral) ? nameNode.getLiteralText() : nameNode.getText();
177
- if (name === "contentType") contentType = extractStringLiteral(valueNode);
178
- else if (name === "runtimeValidation") runtimeValidation = parseRuntimeValidation(valueNode);
179
- else if (name.startsWith("error")) {
180
- const literal = extractStringLiteral(valueNode);
181
- if (literal) customErrors[name] = literal;
182
- }
183
- }
184
- opts[target] = {
185
- contentType,
186
- runtimeValidation,
187
- customErrors
188
- };
189
- }
190
- return opts;
191
- };
192
- const extractStringLiteral = (typeNode) => {
193
- const literal = typeNode.isKind(SyntaxKind.LiteralType) ? typeNode.getFirstChildByKind(SyntaxKind.StringLiteral) : void 0;
194
- return literal ? literal.getLiteralText() : void 0;
195
- };
196
- /**
197
- * Extract validation definitions from route handler generics.
198
- * Merges defs (schemas) and opts (validation options) into a flat array.
199
- * */
200
- const extractValidationDefinitions = (route, method, defsNode, optsNode) => {
201
- const definitions = [];
202
- if (!defsNode?.isKind(SyntaxKind.TypeLiteral)) return definitions;
203
- const optsMap = parseValidationOptions(optsNode);
204
- const createId = (target, hash) => {
205
- return [
206
- target.replace(/^./, (c) => c.toUpperCase()),
207
- "T",
208
- method,
209
- crc(route.id + hash)
210
- ].join("");
211
- };
212
- for (const prop of defsNode.getMembers()) {
213
- if (!prop.isKind(SyntaxKind.PropertySignature)) continue;
214
- const target = prop.getName();
215
- const typeNode = prop.getTypeNodeOrThrow();
216
- if (target === "response") {
217
- const variants = typeNode.isKind(SyntaxKind.UnionType) ? typeNode.getChildrenOfKind(SyntaxKind.TupleType) : [typeNode];
218
- definitions.push({
219
- ...optsMap[target],
220
- method,
221
- target,
222
- variants: variants.flatMap((e, i) => {
223
- const { status, contentType, body } = extractResponseVariant(e) || {};
224
- if (!status) return [];
225
- if (contentType && typeof contentType !== "string") {
226
- console.warn(styleText(["bold", "red"], `✗ The second element of a response variant should specify the Response Content Type`));
227
- console.warn(styleText(["blue"], ` Example: [200, "json", Schema]`));
228
- console.warn(` Route: ${route.name}; Method: ${method}; Response Variant: #${i}`);
229
- console.warn();
230
- }
231
- return [{
232
- id: createId(target, JSON.stringify([
233
- status,
234
- contentType,
235
- body
236
- ])),
237
- status,
238
- contentType,
239
- body
240
- }];
241
- })
242
- });
243
- } else if (Object.keys(RequestValidationTargets).includes(target)) definitions.push({
244
- ...optsMap[target],
245
- method,
246
- target,
247
- schema: {
248
- id: createId(target),
249
- text: typeNode.getText()
250
- }
251
- });
252
- }
253
- return definitions;
254
- };
255
- const extractTypeDeclarations = (sourceFile, opts) => {
256
- const declarations = [];
257
- const referencedFiles = opts?.withReferencedFiles ? [] : void 0;
258
- for (const declaration of sourceFile.getImportDeclarations()) {
259
- const modulePath = declaration.getModuleSpecifierValue();
260
- const path = /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath;
261
- const typeOnlyDeclaration = declaration.isTypeOnly();
262
- const defaultImport = typeOnlyDeclaration ? declaration.getDefaultImport() : void 0;
263
- if (defaultImport) {
264
- const name = defaultImport.getText();
265
- const text = `import type ${name} from "${path}";`;
266
- declarations.push({
267
- importDeclaration: {
268
- name,
269
- path
270
- },
271
- text
272
- });
273
- if (referencedFiles) referencedFiles.push(...getReferencedFiles(defaultImport));
274
- }
275
- const namespaceImport = typeOnlyDeclaration ? declaration.getNamespaceImport() : void 0;
276
- if (namespaceImport) {
277
- const name = namespaceImport.getText();
278
- const text = `import type * as ${name} from "${path}";`;
279
- declarations.push({
280
- importDeclaration: {
281
- name,
282
- path
283
- },
284
- text
285
- });
286
- if (referencedFiles) referencedFiles.push(...getReferencedFiles(namespaceImport));
287
- }
288
- for (const namedImport of declaration.getNamedImports()) if (namedImport.isTypeOnly() || typeOnlyDeclaration) {
289
- const nameNode = namedImport.getNameNode();
290
- const name = nameNode.getText();
291
- const alias = namedImport.getAliasNode()?.getText();
292
- const nameText = alias ? `${name} as ${alias}` : name;
293
- declarations.push({
294
- importDeclaration: {
295
- name,
296
- alias,
297
- path
298
- },
299
- text: `import type { ${nameText} } from "${path}";`
300
- });
301
- if (referencedFiles) {
302
- if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
303
- }
304
- }
305
- }
306
- for (const declaration of sourceFile.getTypeAliases()) {
307
- const name = declaration.getName();
308
- const text = declaration.getFullText().trim();
309
- declarations.push({
310
- typeAliasDeclaration: { name },
311
- text
312
- });
313
- }
314
- for (const declaration of sourceFile.getInterfaces()) {
315
- const name = declaration.getName();
316
- const text = declaration.getFullText().trim();
317
- declarations.push({
318
- interfaceDeclaration: { name },
319
- text
320
- });
321
- }
322
- for (const declaration of sourceFile.getEnums()) {
323
- const name = declaration.getName();
324
- const text = declaration.getFullText().trim();
325
- declarations.push({
326
- enumDeclaration: { name },
327
- text
328
- });
329
- }
330
- for (const declaration of sourceFile.getExportDeclarations()) {
331
- const typeOnlyDeclaration = declaration.isTypeOnly();
332
- const modulePath = declaration.getModuleSpecifierValue();
333
- const path = modulePath ? /^\.\.?\/?/.test(modulePath) ? opts?.relpathResolver ? opts.relpathResolver(modulePath) : modulePath : modulePath : void 0;
334
- for (const namedExport of declaration.getNamedExports()) if (namedExport.isTypeOnly() || typeOnlyDeclaration) {
335
- const nameNode = namedExport.getNameNode();
336
- const name = nameNode.getText();
337
- const alias = namedExport.getAliasNode()?.getText();
338
- const nameText = alias ? `${name} as ${alias}` : name;
339
- declarations.push({
340
- exportDeclaration: {
341
- name,
342
- alias: alias ?? name,
343
- path
344
- },
345
- text: path ? `export type { ${nameText} } from "${path}";` : `export type { ${nameText} };`
346
- });
347
- if (referencedFiles) {
348
- if (nameNode.isKind(SyntaxKind.Identifier)) referencedFiles.push(...getReferencedFiles(nameNode));
349
- }
350
- }
351
- }
352
- return referencedFiles ? [declarations, [...new Set(referencedFiles)]] : [declarations];
353
- };
354
- const getReferencedFiles = (importIdentifier) => {
355
- return (importIdentifier?.getSymbol()?.getAliasedSymbol()?.getDeclarations() || []).flatMap((e) => {
356
- const sourceFile = e.getSourceFile();
357
- return sourceFile ? [sourceFile.getFilePath()] : [];
358
- });
359
- };
360
- const extractGenerics = (callExpression) => {
361
- return callExpression.getTypeArguments();
362
- };
363
- const typeResolverFactory = ({ root, name }) => {
364
- const project = createProject({
365
- tsConfigFilePath: resolve(root, join(defaults$2.srcDir, name, "tsconfig.json")),
366
- skipAddingFilesFromTsConfig: true
367
- });
368
- const literalTypesResolver = (literalTypes, options) => {
369
- const sourceFile = project.createSourceFile(`${crc(literalTypes)}-${Date.now()}.ts`, literalTypes, { overwrite: true });
370
- const resolvedTypes = flattener(project, sourceFile, {
371
- ...options,
372
- stripComments: true
373
- });
374
- project.removeSourceFile(sourceFile);
375
- return resolvedTypes;
376
- };
377
- return {
378
- getSourceFile: (fileFullpath) => {
379
- return project.getSourceFile(fileFullpath) || project.addSourceFileAtPath(fileFullpath);
380
- },
381
- refreshSourceFile: async (fileFullpath) => {
382
- const sourceFile = project.getSourceFile(fileFullpath);
383
- if (sourceFile) await sourceFile.refreshFromFileSystem();
384
- },
385
- literalTypesResolver
386
- };
387
- };
388
- return {
389
- createProject,
390
- extractDefaultExport,
391
- extractParamsRefinements,
392
- extractRouteMethods,
393
- extractTypeDeclarations,
394
- resolveRouteSignature,
395
- typeResolverFactory
396
- };
397
- };
398
- var pathResolver$1 = (sourceFolder) => {
399
- const createPath = (...a) => {
400
- return sourceFolder.root ? resolve(sourceFolder.root, join(...a)) : join(...a);
401
- };
402
- const createImport = {
403
- src(a, { origin }) {
404
- return origin === "src" ? join(defaults$2.srcPrefix, ...a) : join(defaults$2.appPrefix, defaults$2.srcDir, sourceFolder.name, ...a);
405
- },
406
- api(a, o) {
407
- return this.src([defaults$2.apiDir, ...a], o);
408
- },
409
- pages(a, o) {
410
- return this.src([defaults$2.pagesDir, ...a], o);
411
- },
412
- lib(a, { origin }) {
413
- return origin === "src" ? join(defaults$2.libPrefix, ...a) : join(defaults$2.appPrefix, defaults$2.libDir, sourceFolder.name, ...a);
414
- },
415
- libCore(a, o) {
416
- return this.lib(["core", ...a], o);
417
- },
418
- libApi(a, o) {
419
- return this.lib([defaults$2.apiDir, ...a], o);
420
- },
421
- libEntry(a, o) {
422
- return this.lib([defaults$2.entryDir, ...a], o);
423
- }
424
- };
425
- return {
426
- createPath: {
427
- src(...a) {
428
- return createPath(defaults$2.srcDir, sourceFolder.name, ...a);
429
- },
430
- api(...a) {
431
- return this.src(defaults$2.apiDir, ...a);
432
- },
433
- pages(...a) {
434
- return this.src(defaults$2.pagesDir, ...a);
435
- },
436
- entry(...a) {
437
- return this.src(defaults$2.entryDir, ...a);
438
- },
439
- lib(...a) {
440
- return createPath(defaults$2.libDir, sourceFolder.name, ...a);
441
- },
442
- libCore(...a) {
443
- return this.lib("core", ...a);
444
- },
445
- libApi(...a) {
446
- return this.lib(defaults$2.apiDir, ...a);
447
- },
448
- libEntry(...a) {
449
- return this.lib(defaults$2.entryDir, ...a);
450
- },
451
- libPages(...a) {
452
- return this.lib(defaults$2.pagesDir, ...a);
453
- },
454
- distDir(...a) {
455
- return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
456
- }
457
- },
458
- createImport,
459
- createImportHelpers(o) {
460
- if (!["src", "lib"].includes(o?.origin)) throw new Error(`createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`);
461
- return { createImport(key, ...a) {
462
- return createImport[key](a.slice(0, -1), o);
463
- } };
464
- }
465
- };
466
- };
467
- var pathExists$1 = async (path) => {
468
- try {
469
- await access(path, constants.F_OK);
470
- return true;
471
- } catch {
472
- return false;
473
- }
474
- };
475
- var render$1 = (template, context, options) => {
476
- const { noEscape = true, renderer = handlebars } = { ...options };
477
- return renderer.compile(template, { noEscape })(context);
478
- };
479
- var renderToFile$1 = async (file, template, context, options) => {
480
- const content = render$1(template, context, options);
481
- /**
482
- * Two fs calls (exists + read) are worth it to avoid touching the file
483
- * and triggering watchers unnecessarily.
484
- * */
485
- if (await pathExists$1(file)) {
486
- const { overwrite = true } = { ...options };
487
- if (overwrite === false) return;
488
- const fileContent = await readFile(file, "utf8");
489
- if (typeof overwrite === "function" && !overwrite(fileContent)) return;
490
- if (crc(content) === crc(fileContent)) return;
491
- }
492
- await mkdir(dirname(file), { recursive: true });
493
- await writeFile(file, content, "utf8");
494
- };
495
- /**
496
- * Parse a filesystem route path into structured PathToken array.
497
- *
498
- * Uses path-to-regexp v8 AST for parsing, with directory-friendly syntax:
499
- * - [param] => required param - :param
500
- * - {param} => optional param - {:param}
501
- * - {...param} => splat - {*param}
502
- *
503
- * Direct :param syntax is prohibited outside {} to avoid ambiguity.
504
- * Inside {} it is treated as path-to-regexp power syntax and used as-is.
505
- *
506
- * Examples:
507
- * Required: [id], [name]
508
- * Optional: {name}, {format}
509
- * Splat: {...path}
510
- * Mixed segments: shop/[id]-{name}
511
- * Power syntax: {-v:version{-:pre}}, :name{@:version{.:min}}.js
512
- * */
513
- var pathTokensFactory = (path, { transformStaticValue = normalizeStaticValue } = {}) => {
514
- /**
515
- * Recursively extract parts from path-to-regexp AST tokens.
516
- * A param inside a group is optional; top-level params are required.
517
- * Wildcard tokens are always splat.
518
- * Slash-only text nodes (restored for parsing) are skipped.
519
- * */
520
- const extractParts = (tokens, createConst, insideGroup = false) => {
521
- const parts = [];
522
- for (const token of tokens) switch (token.type) {
523
- case "text":
524
- if (token.value !== "/") parts.push({
525
- type: "static",
526
- value: transformStaticValue(token.value)
527
- });
528
- break;
529
- case "param":
530
- parts.push({
531
- type: "param",
532
- kind: insideGroup ? "optional" : "required",
533
- name: token.name,
534
- const: createConst(token.name)
535
- });
536
- break;
537
- case "wildcard":
538
- parts.push({
539
- type: "param",
540
- kind: "splat",
541
- name: token.name,
542
- const: createConst(token.name)
543
- });
544
- break;
545
- case "group":
546
- parts.push(...extractParts(token.tokens, createConst, true));
547
- break;
548
- }
549
- return parts;
550
- };
551
- const patternTransforms = [
552
- (s) => s.replace(/\[(\w+)\]/g, ":$1"),
553
- (s) => s.replace(/\{(\w+)\}/g, "{:$1}"),
554
- (s) => s.replace(/\{\.\.\./g, "{*"),
555
- (s) => {
556
- return s.startsWith("{") ? s.replace(/^\{/, "{/") : s;
557
- }
558
- ];
559
- const detectBareParams = (s) => {
560
- let depth = 0;
561
- for (const [i, ch] of [...s].entries()) if (ch === "{") depth += 1;
562
- else if (ch === "}") depth -= 1;
563
- else if (ch === ":" && depth === 0) return s.slice(i + 1).match(/^\w+/)?.[0] || ":";
564
- };
565
- return path.replace(/^index\/?/, "").split("/").flatMap((orig) => {
566
- if (!orig.length) return [];
567
- const bareParam = detectBareParams(orig);
568
- if (bareParam === ":") throw new Error(`${path} contains colons outside braces, use : only within {}`);
569
- else if (bareParam) throw new Error(`${path} contains bare params, use [${bareParam}] instead of :${bareParam}`);
570
- const pattern = patternTransforms.reduce((src, fn) => fn(src), orig);
571
- const { tokens } = parse(pattern.replace(/\{[^}]*\}|(\+)/g, (m, p1) => p1 ? "\\+" : m));
572
- const parts = extractParts(tokens, (val) => {
573
- return /\W/.test(val) || /^\d/.test(val) ? [val.replace(/^\d+|\W/g, "_"), crc(orig)].join("_") : val;
574
- });
575
- const isStatic = parts.length === 1 ? parts[0].type === "static" : false;
576
- const isParam = parts.length === 1 ? parts[0].type === "param" : false;
577
- return [{
578
- kind: isStatic ? "static" : isParam ? "param" : "mixed",
579
- orig,
580
- pattern,
581
- parts
582
- }];
583
- });
584
- };
585
- var createPathPattern = (tokens) => {
586
- return tokens.map(({ pattern }, i) => {
587
- const next = tokens[i + 1];
588
- if (!next || next.pattern.includes("/")) return pattern;
589
- return tokens.slice(i + 1).some((e) => {
590
- return e.parts.some((e) => {
591
- return e.type === "static" || e.kind === "required";
592
- });
593
- }) ? `${pattern}/` : pattern;
594
- }).join("");
595
- };
596
- var createHonoPattern = (tokens) => {
597
- const staticValue = ({ value }) => {
598
- return normalizeStaticValue(value);
599
- };
600
- const paramValue = (p) => {
601
- if (p.kind === "splat") return `*`;
602
- if (p.kind === "optional") return `:${p.name}?`;
603
- return `:${p.name}`;
604
- };
605
- const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
606
- const tokensToRegex = (tokens) => {
607
- return tokens.flatMap((t) => {
608
- if (t.type === "text") return t.value === "/" ? [] : [escapeRegex(t.value)];
609
- if (t.type === "wildcard") return [".+"];
610
- if (t.type === "param") return ["[^/]+"];
611
- if (t.type === "group") return [`(?:${tokensToRegex(t.tokens)})?`];
612
- return [];
613
- }).join("");
614
- };
615
- return tokens.flatMap((token, i) => {
616
- if (token.kind === "static") return [staticValue(token.parts[0])];
617
- if (token.kind === "param") return [paramValue(token.parts[0])];
618
- const { tokens } = parse(token.pattern.replace(/\//g, ""));
619
- return [`:_${i}{${tokensToRegex(tokens)}}`];
620
- }).join("/");
621
- };
622
- var normalizeStaticValue = (value) => {
623
- return value.replace(/\+/g, "\\\\+");
624
- };
625
- var API_INDEX_PATTERN = `index.ts`;
626
- var API_USE_PATTERN = `use.ts`;
627
- var PAGE_INDEX_PATTERN = `index.{tsx,vue,mdx,md}`;
628
- var PAGE_LAYOUT_PATTERN = `layout.{tsx,vue,mdx}`;
629
- var ROUTE_FILE_PATTERNS = [
630
- `${defaults$2.apiDir}/**/${API_INDEX_PATTERN}`,
631
- `${defaults$2.apiDir}/**/${API_USE_PATTERN}`,
632
- `${defaults$2.pagesDir}/**/${PAGE_INDEX_PATTERN}`,
633
- `${defaults$2.pagesDir}/**/${PAGE_LAYOUT_PATTERN}`
634
- ];
635
- var scanRoutes = async (sourceFolder) => {
636
- const { createPath } = pathResolver$1(sourceFolder);
637
- return glob(ROUTE_FILE_PATTERNS, {
638
- cwd: createPath.src(),
639
- absolute: true,
640
- onlyFiles: true,
641
- followSymbolicLinks: false,
642
- ignore: [
643
- `${defaults$2.apiDir}/${API_INDEX_PATTERN}`,
644
- `${defaults$2.apiDir}/${API_USE_PATTERN}`,
645
- `${defaults$2.pagesDir}/${PAGE_INDEX_PATTERN}`,
646
- `${defaults$2.pagesDir}/${PAGE_LAYOUT_PATTERN}`
647
- ]
648
- });
649
- };
650
- var isRouteFile = (file, sourceFolder) => {
651
- const [_sourceFolder, _folder, ...rest] = resolve(sourceFolder.root, file).replace(`${sourceFolder.root}/${defaults$2.srcDir}/`, "").split("/");
652
- /**
653
- * Ensure the file:
654
- * - is under the correct source root
655
- * - belongs to a known route folder (`api/` or `pages/`)
656
- * - is nested at least one level deep (not a direct child of the folder)
657
- * */
658
- if (!_folder || _sourceFolder !== sourceFolder.name || rest.length < 2) return false;
659
- return picomatch.isMatch(join(_folder, ...rest), ROUTE_FILE_PATTERNS) ? [_folder, rest.join("/")] : false;
660
- };
661
- var isApiRoute = (file) => {
662
- return picomatch.matchBase(file, `**/${API_INDEX_PATTERN}`);
663
- };
664
- var isApiUse = (file) => {
665
- return picomatch.matchBase(file, `**/${API_USE_PATTERN}`);
666
- };
667
- var isPageRoute = (file) => {
668
- return picomatch.matchBase(file, `**/${PAGE_INDEX_PATTERN}`);
669
- };
670
- var isPageLayout = (file) => {
671
- return picomatch.matchBase(file, `**/${PAGE_LAYOUT_PATTERN}`);
672
- };
673
- var createRouteEntry = (fileFullpath, sourceFolder) => {
674
- const resolvedPaths = isRouteFile(fileFullpath, sourceFolder);
675
- if (!resolvedPaths) return;
676
- const [folder, file] = resolvedPaths;
677
- const id = `${file.replace(/\W+/g, "_")}_${crc(file)}`;
678
- const name = dirname(file);
679
- try {
680
- const pathTokens = pathTokensFactory(dirname(file));
681
- return {
682
- id,
683
- name,
684
- folder,
685
- file,
686
- fileFullpath,
687
- pathTokens,
688
- pathPattern: createPathPattern(pathTokens),
689
- honoPattern: createHonoPattern(pathTokens)
690
- };
691
- } catch (error) {
692
- console.error(`❗${styleText("red", "ERROR")}: Failed parsing path for "${styleText("cyan", file)}"`);
693
- console.error(error);
694
- return;
695
- }
696
- };
697
- var resolved_types_default = "{{#each resolvedTypes}}\nexport type {{name}} = {{text}};\n{{/each}}\n";
698
- 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";
699
- var resolverFactory = (sourceFolder, cacheFactory) => {
700
- const { generators = [], refineTypeName = defaults$2.refineTypeName } = sourceFolder.config;
701
- const { typeResolverFactory, resolveRouteSignature } = astFactory();
702
- const resolveTypes = generators.some(({ meta }) => meta.resolveTypes);
703
- const { literalTypesResolver, getSourceFile, refreshSourceFile } = typeResolverFactory(sourceFolder);
704
- return {
705
- pageLayoutResolver(entry) {
706
- const { name } = entry;
707
- const handler = async () => {
708
- return {
709
- kind: "pageLayout",
710
- entry
711
- };
712
- };
713
- return {
714
- name,
715
- handler
716
- };
717
- },
718
- pageRouteResolver(entry) {
719
- const { id, name, folder, file, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
720
- const handler = async () => {
721
- return {
722
- kind: "pageRoute",
723
- entry: {
724
- id,
725
- name,
726
- pathTokens,
727
- pathPattern,
728
- honoPattern,
729
- params: { schema: pathTokens.flatMap((e) => {
730
- return e.parts.filter((p) => p.type === "param");
731
- }) },
732
- folder,
733
- file,
734
- fileFullpath
735
- }
736
- };
737
- };
738
- return {
739
- name,
740
- handler
741
- };
742
- },
743
- apiUseResolver(entry) {
744
- const { name } = entry;
745
- const handler = async () => {
746
- return {
747
- kind: "apiUse",
748
- entry
749
- };
750
- };
751
- return {
752
- name,
753
- handler
754
- };
755
- },
756
- apiRouteResolver(entry) {
757
- const { id, name, file, folder, fileFullpath, pathTokens, pathPattern, honoPattern } = entry;
758
- const handler = async (updatedFile) => {
759
- const paramsSchema = pathTokens.flatMap((e) => {
760
- return e.parts.flatMap((p) => {
761
- return p.type === "param" ? [p] : [];
762
- });
763
- });
764
- const optionalParams = paramsSchema.length ? paramsSchema.filter((e) => e.kind === "required").length === 0 : true;
765
- const cacheStore = cacheFactory ? cacheFactory({
766
- id,
767
- file,
768
- fileFullpath
769
- }, sourceFolder, { resolveTypes }) : void 0;
770
- let cache = cacheStore ? await cacheStore.get({ validate: true }) : void 0;
771
- if (!cache) {
772
- if (updatedFile === fileFullpath) await refreshSourceFile(fileFullpath);
773
- const { typeDeclarations, paramsRefinements, methods, validationDefinitions, referencedFiles = [] } = await resolveRouteSignature({
774
- id,
775
- name,
776
- fileFullpath,
777
- optionalParams
778
- }, {
779
- withReferencedFiles: true,
780
- sourceFile: getSourceFile(fileFullpath),
781
- relpathResolver(path) {
782
- return join(sourceFolder.name, defaults$2.apiDir, dirname(file), path);
783
- }
784
- });
785
- const validationTypes = validationDefinitions.flatMap((def) => {
786
- return def.target === "response" ? def.variants.flatMap(({ id, body }) => {
787
- return body ? [{
788
- id,
789
- text: body
790
- }] : [];
791
- }) : [def.schema];
792
- });
793
- const numericParams = paramsRefinements ? paramsRefinements.flatMap(({ text, index }) => {
794
- if (text === "number") {
795
- const param = paramsSchema.at(index);
796
- return param ? [param.name] : [];
797
- }
798
- return [];
799
- }) : [];
800
- const typesFile = pathResolver$1(sourceFolder).createPath.libApi(dirname(file), "types.ts");
801
- const params = {
802
- id: ["ParamsT", crc(name)].join(""),
803
- schema: paramsSchema,
804
- resolvedType: void 0
805
- };
806
- const typesFileContent = render$1(types_default, {
807
- params,
808
- paramsSchema: paramsSchema.map((param, index) => {
809
- return {
810
- ...param,
811
- isRequired: param.kind === "required",
812
- isSplat: param.kind === "splat",
813
- refinement: paramsRefinements?.at(index)
814
- };
815
- }),
816
- typeDeclarations,
817
- validationTypes
818
- });
819
- const resolvedTypes = resolveTypes ? literalTypesResolver(typesFileContent, {
820
- stripComments: true,
821
- overrides: { [refineTypeName]: refineTypeName },
822
- withProperties: [params.id, ...validationTypes.flatMap(({ id }) => id)]
823
- }) : void 0;
824
- /**
825
- * Deploy types.ts file; required by core generators (like fetch).
826
- * If types resolved, write resolved types;
827
- * otherwise write original types extracted from API route.
828
- * */
829
- await renderToFile$1(typesFile, resolvedTypes ? resolved_types_default : typesFileContent, { resolvedTypes });
830
- params.resolvedType = resolvedTypes?.find((e) => e.name === params.id);
831
- cache = {
832
- hash: 0,
833
- referencedFiles: {},
834
- params,
835
- methods,
836
- typeDeclarations,
837
- numericParams,
838
- validationDefinitions: validationDefinitions.map((def) => {
839
- return {
840
- ...def,
841
- ...def.target === "response" ? { variants: def.variants.map((variant) => {
842
- return {
843
- ...variant,
844
- resolvedType: resolvedTypes?.find((e) => e.name === variant.id)
845
- };
846
- }) } : { schema: {
847
- ...def.schema,
848
- resolvedType: resolvedTypes?.find((e) => e.name === def.schema.id)
849
- } }
850
- };
851
- })
852
- };
853
- if (cacheStore) {
854
- const { referencedFiles: _void, ...rest } = cache;
855
- cache = await cacheStore.set({
856
- ...rest,
857
- referencedFiles
858
- });
859
- }
860
- }
861
- const validationDefinitions = cache.validationDefinitions.flatMap((def) => {
862
- let augmentedDef = def;
863
- if (def.target === "response") augmentedDef = {
864
- ...def,
865
- variants: def.variants.flatMap((variant, i) => {
866
- if (typeof variant.contentType !== "string") return [variant];
867
- if (variant.contentType.includes("/")) return [variant];
868
- const contentType = mimeTypes.lookup(variant.contentType);
869
- if (contentType === false) {
870
- console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
871
- console.warn(` Invalid value provided for mime-types lookup - ${variant.contentType}`);
872
- console.warn(styleText(["cyan"], ` Response variant #${i} excluded from route schemas`));
873
- console.warn(` Route: ${name}; Method: ${def.method}`);
874
- console.warn();
875
- return [];
876
- }
877
- return [{
878
- ...variant,
879
- contentType
880
- }];
881
- })
882
- };
883
- else if (def.contentType && !def.contentType.includes("/")) {
884
- const contentType = mimeTypes.lookup(def.contentType);
885
- if (contentType === false) {
886
- console.warn(styleText(["bold", "red"], "✗ Failed resolving Response Content Type"));
887
- console.warn(` Invalid value provided for mime-types lookup - ${def.contentType}`);
888
- console.warn(` Route: ${name}; Method: ${def.method}`);
889
- console.warn();
890
- } else augmentedDef = {
891
- ...def,
892
- contentType
893
- };
894
- }
895
- return augmentedDef ? [augmentedDef] : [];
896
- });
897
- return {
898
- kind: "apiRoute",
899
- entry: {
900
- id,
901
- name,
902
- pathTokens,
903
- pathPattern,
904
- honoPattern,
905
- params: cache.params,
906
- numericParams: cache.numericParams,
907
- optionalParams,
908
- folder,
909
- file,
910
- fileFullpath,
911
- methods: cache.methods,
912
- typeDeclarations: cache.typeDeclarations,
913
- validationDefinitions,
914
- referencedFiles: Object.keys(cache.referencedFiles).map((e) => resolve(sourceFolder.root, e))
915
- }
916
- };
917
- };
918
- return {
919
- name,
920
- handler
921
- };
922
- }
923
- };
924
- };
925
- var routesFactory = async (sourceFolder, cacheFactory) => {
926
- const { apiRouteResolver, apiUseResolver, pageRouteResolver, pageLayoutResolver } = resolverFactory(sourceFolder, cacheFactory);
927
- const resolversFactory = (routeFiles) => {
928
- const resolvers = /* @__PURE__ */ new Map();
929
- const entries = routeFiles.flatMap((file) => {
930
- const entry = createRouteEntry(file, sourceFolder);
931
- return entry ? [entry] : [];
932
- });
933
- for (const entry of entries) if (entry.folder === defaults$2.apiDir) {
934
- if (isApiRoute(entry.file)) resolvers.set(entry.fileFullpath, apiRouteResolver(entry));
935
- else if (isApiUse(entry.file)) resolvers.set(entry.fileFullpath, apiUseResolver(entry));
936
- } else if (entry.folder === defaults$2.pagesDir) {
937
- if (isPageRoute(entry.file)) resolvers.set(entry.fileFullpath, pageRouteResolver(entry));
938
- else if (isPageLayout(entry.file)) resolvers.set(entry.fileFullpath, pageLayoutResolver(entry));
939
- }
940
- return resolvers;
941
- };
942
- return {
943
- resolvers: resolversFactory(await scanRoutes(sourceFolder)),
944
- resolversFactory
945
- };
946
- };
947
- var spinnerFactory = (startText) => {
948
- const spinner = new Spinner().start(startText);
949
- let _text = startText;
950
- return {
951
- text(text) {
952
- _text = text;
953
- spinner.text = text;
954
- },
955
- append(text) {
956
- spinner.text = `${_text} › ${text}`;
957
- },
958
- succeed(text) {
959
- if (text) this.append(text);
960
- else this.text(_text);
961
- spinner.succeed();
962
- },
963
- failed(text) {
964
- if (text) this.text([_text, text].join("\n"));
965
- spinner.failed();
966
- }
967
- };
968
- };
969
- //#endregion
970
- //#region ../../generators/core-generator/pkg/index.js
971
- var routeRenderHelpers = () => {
972
- return {
973
- pageLinkBase({ name, pathPattern, params }) {
974
- return JSON.stringify({
975
- name,
976
- pathPattern,
977
- params
978
- });
979
- },
980
- serializeParamsTupleElements: (route) => {
981
- return route.params.schema.map((p, i) => {
982
- if (p.kind === "splat") {
983
- const suffix = route.params.schema[i + 1] ? "" : "?";
984
- return `${p.const + suffix}: Array<string | number>`;
985
- }
986
- return p.kind === "optional" ? `${p.const}?: string | number` : `${p.const}: string | number`;
987
- }).join(", ");
988
- },
989
- serializeParamsLiteral(route) {
990
- return `{ ${route.params.schema.map((e) => {
991
- return [[e.name, e.kind === "required" ? "" : "?"].join(""), e.kind === "splat" ? "Array<string>" : "string"].join(": ");
992
- }).join("; ")} }`;
993
- }
994
- };
995
- };
996
- var defaults$1 = {
997
- appPrefix: "@",
998
- srcPrefix: "~",
999
- libPrefix: "_",
1000
- coreDir: "core",
1001
- srcDir: "src",
1002
- libDir: "lib",
1003
- configDir: "config",
1004
- apiDir: "api",
1005
- pagesDir: "pages",
1006
- entryDir: "entry",
1007
- fetchDir: "fetch",
1008
- refineTypeName: "VRefine"
1009
- };
1010
- /**
1011
- * Request metadata validation targets.
1012
- * */
1013
- var RequestMetadataTargets$1 = {
1014
- query: "URL query parameters",
1015
- headers: "HTTP request headers",
1016
- cookies: "HTTP cookies"
1017
- };
1018
- /**
1019
- * Request body validation targets.
1020
- *
1021
- * Body formats are mutually exclusive - only one should be specified per handler.
1022
- *
1023
- * **Development behavior:**
1024
- * - If multiple formats are defined, the builder displays a warning and
1025
- * disables validation schemas for the affected handler.
1026
- * - If an unsuitable target is defined (e.g., `json`, `form`)
1027
- * for a method without a body like GET, HEAD), a warning is displayed and
1028
- * validation schemas are disabled for that handler.
1029
- *
1030
- * This ensures misconfigurations are detected during development
1031
- * for runtime to execute without false positive validation failures.
1032
- *
1033
- * Always define exactly one target that is suitable for current handler.
1034
- * */
1035
- var RequestBodyTargets$1 = {
1036
- json: "JSON request body",
1037
- form: "URL-encoded or Multipart form",
1038
- raw: "Raw body format (string/Buffer/ArrayBuffer/Blob)"
1039
- };
1040
- ({
1041
- ...RequestMetadataTargets$1,
1042
- ...RequestBodyTargets$1
1043
- });
1044
- var defineGenerator = (f) => f;
1045
- var defineGeneratorFactory = (f) => f;
1046
- var pathResolver = (sourceFolder) => {
1047
- const createPath = (...a) => {
1048
- return sourceFolder.root ? resolve(sourceFolder.root, join(...a)) : join(...a);
1049
- };
1050
- const createImport = {
1051
- src(a, { origin }) {
1052
- return origin === "src" ? join(defaults$1.srcPrefix, ...a) : join(defaults$1.appPrefix, defaults$1.srcDir, sourceFolder.name, ...a);
1053
- },
1054
- api(a, o) {
1055
- return this.src([defaults$1.apiDir, ...a], o);
1056
- },
1057
- pages(a, o) {
1058
- return this.src([defaults$1.pagesDir, ...a], o);
1059
- },
1060
- lib(a, { origin }) {
1061
- return origin === "src" ? join(defaults$1.libPrefix, ...a) : join(defaults$1.appPrefix, defaults$1.libDir, sourceFolder.name, ...a);
1062
- },
1063
- libCore(a, o) {
1064
- return this.lib(["core", ...a], o);
1065
- },
1066
- libApi(a, o) {
1067
- return this.lib([defaults$1.apiDir, ...a], o);
1068
- },
1069
- libEntry(a, o) {
1070
- return this.lib([defaults$1.entryDir, ...a], o);
1071
- }
1072
- };
1073
- return {
1074
- createPath: {
1075
- src(...a) {
1076
- return createPath(defaults$1.srcDir, sourceFolder.name, ...a);
1077
- },
1078
- api(...a) {
1079
- return this.src(defaults$1.apiDir, ...a);
1080
- },
1081
- pages(...a) {
1082
- return this.src(defaults$1.pagesDir, ...a);
1083
- },
1084
- entry(...a) {
1085
- return this.src(defaults$1.entryDir, ...a);
1086
- },
1087
- lib(...a) {
1088
- return createPath(defaults$1.libDir, sourceFolder.name, ...a);
1089
- },
1090
- libCore(...a) {
1091
- return this.lib("core", ...a);
1092
- },
1093
- libApi(...a) {
1094
- return this.lib(defaults$1.apiDir, ...a);
1095
- },
1096
- libEntry(...a) {
1097
- return this.lib(defaults$1.entryDir, ...a);
1098
- },
1099
- libPages(...a) {
1100
- return this.lib(defaults$1.pagesDir, ...a);
1101
- },
1102
- distDir(...a) {
1103
- return createPath(sourceFolder.distDir, sourceFolder.name, ...a);
1104
- }
1105
- },
1106
- createImport,
1107
- createImportHelpers(o) {
1108
- if (!["src", "lib"].includes(o?.origin)) throw new Error(`createImportHelpers: required exactly one argument of shape { origin: "src|lib" }`);
1109
- return { createImport(key, ...a) {
1110
- return createImport[key](a.slice(0, -1), o);
1111
- } };
1112
- }
1113
- };
1114
- };
1115
- var pathExists = async (path) => {
1116
- try {
1117
- await access(path, constants.F_OK);
1118
- return true;
1119
- } catch {
1120
- return false;
1121
- }
1122
- };
1123
- var render = (template, context, options) => {
1124
- const { noEscape = true, renderer = handlebars } = { ...options };
1125
- return renderer.compile(template, { noEscape })(context);
1126
- };
1127
- var renderToFile = async (file, template, context, options) => {
1128
- const content = render(template, context, options);
1129
- /**
1130
- * Two fs calls (exists + read) are worth it to avoid touching the file
1131
- * and triggering watchers unnecessarily.
1132
- * */
1133
- if (await pathExists(file)) {
1134
- const { overwrite = true } = { ...options };
1135
- if (overwrite === false) return;
1136
- const fileContent = await readFile(file, "utf8");
1137
- if (typeof overwrite === "function" && !overwrite(fileContent)) return;
1138
- if (crc(content) === crc(fileContent)) return;
1139
- }
1140
- await mkdir(dirname(file), { recursive: true });
1141
- await writeFile(file, content, "utf8");
1142
- };
1143
- var renderFactory = (options) => {
1144
- const createRenderer = (selfOoptions) => {
1145
- const renderer = handlebars.create();
1146
- renderer.registerPartial({
1147
- ...options?.partials,
1148
- ...selfOoptions?.partials
1149
- });
1150
- renderer.registerHelper({
1151
- ...options?.helpers,
1152
- ...selfOoptions?.helpers
1153
- });
1154
- return renderer;
1155
- };
1156
- return {
1157
- render(template, context, selfOoptions) {
1158
- return render(template, context, {
1159
- ...options,
1160
- ...selfOoptions,
1161
- renderer: createRenderer(selfOoptions)
1162
- });
1163
- },
1164
- async renderToFile(file, template, context, selfOoptions) {
1165
- return renderToFile(options?.outdir ? join(options.outdir, file) : file, template, context, {
1166
- ...options,
1167
- ...selfOoptions,
1168
- renderer: createRenderer(selfOoptions)
1169
- });
1170
- }
1171
- };
1172
- };
1173
- `${defaults$1.apiDir}`, `${defaults$1.apiDir}`, `${defaults$1.pagesDir}`, `${defaults$1.pagesDir}`;
1174
- var package_default$1 = {
1175
- type: "module",
1176
- "private": true,
1177
- name: "@kosmojs/core-generator",
1178
- version: "0.1.0",
1179
- author: "Slee Woo",
1180
- license: "MIT",
1181
- files: ["pkg/*"],
1182
- imports: { "#templates/*": "./src/templates/*" },
1183
- exports: { ".": {
1184
- "types": "./pkg/index.d.ts",
1185
- "default": "./pkg/index.js"
1186
- } },
1187
- scripts: { "build": "wsbuild src/index.ts" },
1188
- devDependencies: {
1189
- "@kosmojs/core": "workspace:^",
1190
- "@kosmojs/lib": "workspace:^",
1191
- "path-to-regexp": "^8.4.2"
1192
- }
1193
- };
1194
- var config_default = "export const base = \"{{config.base}}\";\nexport const apiBase = \"{{config.apiBase}}\";\n";
1195
- var core_default = "export * from \"./config\";\nexport * from \"./routeMap\";\n";
1196
- var pathMapper_default = "import { compile } from \"path-to-regexp\";\n\nimport type { PathMapperSignature } from \"@kosmojs/core\";\nimport { createHost, join, stringify } from \"@kosmojs/core/fetch\";\n\nexport const pathMapper = <ParamsT extends readonly unknown[]>(\n basePath: string,\n routeName: string,\n pathPattern: string,\n paramsMap: Array<[name: string, kind: string]>,\n numericParams: Array<string>,\n): PathMapperSignature<ParamsT> => {\n const toPath = compile(pathPattern);\n\n const castParam = (name: string, value: unknown) => {\n if (numericParams.includes(name)) {\n const n = Number(value);\n return Number.isFinite(n) ? n : String(value);\n }\n return String(value);\n };\n\n const paramsMapper: PathMapperSignature<ParamsT>[\"paramsMapper\"] = (\n params,\n ) => {\n return paramsMap.reduce<Record<string, unknown>>((map, [name, kind], i) => {\n if (kind === \"splat\") {\n if (Array.isArray(params[i]) && params[i].length) {\n map[name] = params[i].map((v) => castParam(name, v));\n }\n } else if (params[i] !== undefined) {\n map[name] = castParam(name, params[i]);\n }\n return map;\n }, {});\n };\n\n const parametrize: PathMapperSignature<ParamsT>[\"parametrize\"] = (params) => {\n try {\n return toPath(paramsMapper(params) as never);\n } catch (error) {\n console.error(`❗ERROR: Failed building path for ${routeName}`);\n throw error;\n }\n };\n\n const base: PathMapperSignature<ParamsT>[\"base\"] = (params, query) => {\n const path = join(\"/\", parametrize(params));\n return query ? [path, stringify(query)].join(\"?\") : path;\n };\n\n const path: PathMapperSignature<ParamsT>[\"path\"] = (params, query) => {\n return join(basePath, base(params, query));\n };\n\n const href: PathMapperSignature<ParamsT>[\"href\"] = (host, params, query) => {\n return createHost(host) + path(params, query);\n };\n\n return {\n paramsMapper,\n parametrize,\n base,\n path,\n href,\n };\n};\n";
1197
- var routeMap_default = "{{> pathMapper}}\n\nimport { base, apiBase } from \"./config\";\n\n{{#if pageRoutes.length}}\nexport type LinkProps =\n {{#each pageRoutes}}\n | [ \"{{name}}\", {{serializeParamsTupleElements .}} ]\n {{/each}};\n{{else}}\nexport type LinkProps = never;\n{{/if}}\n\nexport const apiRouteMap = {\n {{#each apiRoutes}}\n \"{{name}}\": pathMapper<[{{serializeParamsTupleElements .}}]>(\n apiBase,\n \"{{name}}\",\n \"{{pathPattern}}\",\n [{{#each params.schema}}[\"{{name}}\", \"{{kind}}\"], {{/each}}],\n [{{#each numericParams}}\"{{.}}\", {{/each}}],\n ),\n {{/each}}\n};\n\nexport const pageRouteMap = {\n {{#each pageRoutes}}\n \"{{name}}\": pathMapper<[{{serializeParamsTupleElements .}}]>(\n base,\n \"{{name}}\",\n \"{{pathPattern}}\",\n [{{#each params.schema}}[\"{{name}}\", \"{{kind}}\"], {{/each}}],\n [{{#each numericParams}}\"{{.}}\", {{/each}}],\n ),\n {{/each}}\n};\n";
1198
- var env_d_default = "declare const KOSMO_PRODUCTION_BUILD: boolean;\n\n/**\n * Enhances base TypeScript types with JSON Schema validation constraints.\n * Allows declaring refined types that carry validation metadata for runtime\n * schema validation while maintaining full TypeScript type safety.\n *\n * Useful for generating validation schemas and ensuring\n * data conforms to specific business rules beyond basic type checking.\n * */\ndeclare type VRefine<\n T extends unknown[] | number | string | object,\n _ extends T extends unknown[]\n ? TArrayOptions\n : T extends number\n ? TNumberOptions\n : T extends string\n ? TStringOptions\n : TObjectOptions,\n> = T;\n\n/**\n * Type definitions inspired by and gently adapted from TypeBox.\n * Original TypeBox created by sinclairzx81: https://github.com/sinclairzx81/typebox\n * TypeBox is licensed under MIT: https://github.com/sinclairzx81/typebox/blob/main/license\n *\n * These types provide JSON Schema compatible type refinements for TypeScript.\n * */\ninterface TSchema {}\n\n// ------------------------------------------------------------------\n// ObjectOptions\n// ------------------------------------------------------------------\ninterface TObjectOptions {\n /**\n * Defines whether additional properties are allowed beyond those explicitly defined in `properties`.\n */\n additionalProperties?: TSchema | boolean;\n /**\n * The minimum number of properties required in the object.\n */\n minProperties?: number;\n /**\n * The maximum number of properties allowed in the object.\n */\n maxProperties?: number;\n /**\n * Defines conditional requirements for properties.\n */\n dependencies?: Record<string, boolean | TSchema | string[]>;\n /**\n * Specifies properties that *must* be present if a given property is present.\n */\n dependentRequired?: Record<string, string[]>;\n /**\n * Defines schemas that apply if a specific property is present.\n */\n dependentSchemas?: Record<string, TSchema>;\n /**\n * Maps regular expressions to schemas properties matching a pattern must validate against the schema.\n */\n patternProperties?: Record<string, TSchema>;\n /**\n * A schema that all property names within the object must validate against.\n */\n propertyNames?: TSchema;\n}\n\n// ------------------------------------------------------------------\n// ArrayOptions\n// ------------------------------------------------------------------\ninterface TArrayOptions {\n /**\n * The minimum number of items allowed in the array.\n */\n minItems?: number;\n /**\n * The maximum number of items allowed in the array.\n */\n maxItems?: number;\n /**\n * A schema that at least one item in the array must validate against.\n */\n contains?: TSchema;\n /**\n * The minimum number of array items that must validate against the `contains` schema.\n */\n minContains?: number;\n /**\n * The maximum number of array items that may validate against the `contains` schema.\n */\n maxContains?: number;\n /**\n * An array of schemas, where each schema in `prefixItems` validates against items at corresponding positions from the beginning of the array.\n */\n prefixItems?: TSchema[];\n /**\n * If `true`, all items in the array must be unique.\n */\n uniqueItems?: boolean;\n}\n\n// ------------------------------------------------------------------\n// NumberOptions\n// ------------------------------------------------------------------\ninterface TNumberOptions {\n /**\n * Specifies an exclusive upper limit for the number (number must be less than this value).\n */\n exclusiveMaximum?: number | bigint;\n /**\n * Specifies an exclusive lower limit for the number (number must be greater than this value).\n */\n exclusiveMinimum?: number | bigint;\n /**\n * Specifies an inclusive upper limit for the number (number must be less than or equal to this value).\n */\n maximum?: number | bigint;\n /**\n * Specifies an inclusive lower limit for the number (number must be greater than or equal to this value).\n */\n minimum?: number | bigint;\n /**\n * Specifies that the number must be a multiple of this value.\n */\n multipleOf?: number | bigint;\n}\n\n// ------------------------------------------------------------------\n// StringOptions\n// ------------------------------------------------------------------\ntype TFormat =\n | \"date-time\"\n | \"date\"\n | \"duration\"\n | \"email\"\n | \"hostname\"\n | \"idn-email\"\n | \"idn-hostname\"\n | \"ipv4\"\n | \"ipv6\"\n | \"iri-reference\"\n | \"iri\"\n | \"json-pointer-uri-fragment\"\n | \"json-pointer\"\n | \"json-string\"\n | \"regex\"\n | \"relative-json-pointer\"\n | \"time\"\n | \"uri-reference\"\n | \"uri-template\"\n | \"url\"\n | \"uuid\";\n\ninterface TStringOptions {\n /**\n * Specifies the expected string format.\n *\n * Common values include:\n * - `base64` – Base64-encoded string.\n * - `date-time` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date-time format.\n * - `date` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date (YYYY-MM-DD).\n * - `duration` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) duration format.\n * - `email` – RFC 5321/5322 compliant email address.\n * - `hostname` – RFC 1034/1035 compliant host name.\n * - `idn-email` – Internationalized email address.\n * - `idn-hostname` – Internationalized host name.\n * - `ipv4` – IPv4 address.\n * - `ipv6` – IPv6 address.\n * - `iri` / `iri-reference` – Internationalized Resource Identifier.\n * - `json-pointer` / `json-pointer-uri-fragment` – JSON Pointer format.\n * - `json-string` – String containing valid JSON.\n * - `regex` – Regular expression syntax.\n * - `relative-json-pointer` – Relative JSON Pointer format.\n * - `time` – [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) time (HH:MM:SS).\n * - `uri-reference` / `uri-template` – URI reference or template.\n * - `url` – Web URL format.\n * - `uuid` – RFC 4122 UUID string.\n *\n * May also be a custom format string.\n */\n format?: TFormat;\n /**\n * Specifies the minimum number of characters allowed in the string.\n * Must be a non-negative integer.\n */\n minLength?: number;\n /**\n * Specifies the maximum number of characters allowed in the string.\n * Must be a non-negative integer.\n */\n maxLength?: number;\n /**\n * Specifies a regular expression pattern that the string value must match.\n * Can be provided as a string (ECMA-262 regex syntax) or a `RegExp` object.\n */\n pattern?: string | RegExp;\n}\n";
1199
- var gitignore_default = "# Ignore all files\n*\n\n# But don't ignore directories (so Git can traverse them)\n!*/\n\n# And don't ignore these files at any depth\n!cache.json\n!types.ts\n";
1200
- var schemas_default = "// stub schemas, specialized generators supposed to overwrite this file\nimport type { ValidationSchemas } from \"@kosmojs/core/api\";\nexport type { ValidationSchemas };\nexport const validationSchemas: ValidationSchemas = {};\n";
1201
- var defaults = {
1202
- appPrefix: "@",
1203
- srcPrefix: "~",
1204
- libPrefix: "_",
1205
- coreDir: "core",
1206
- srcDir: "src",
1207
- libDir: "lib",
1208
- configDir: "config",
1209
- apiDir: "api",
1210
- pagesDir: "pages",
1211
- entryDir: "entry",
1212
- fetchDir: "fetch",
1213
- refineTypeName: "VRefine"
1214
- };
1215
- /**
1216
- * Request metadata validation targets.
1217
- * */
1218
- var RequestMetadataTargets = {
1219
- query: "URL query parameters",
1220
- headers: "HTTP request headers",
1221
- cookies: "HTTP cookies"
1222
- };
1223
- /**
1224
- * Request body validation targets.
1225
- *
1226
- * Body formats are mutually exclusive - only one should be specified per handler.
1227
- *
1228
- * **Development behavior:**
1229
- * - If multiple formats are defined, the builder displays a warning and
1230
- * disables validation schemas for the affected handler.
1231
- * - If an unsuitable target is defined (e.g., `json`, `form`)
1232
- * for a method without a body like GET, HEAD), a warning is displayed and
1233
- * validation schemas are disabled for that handler.
1234
- *
1235
- * This ensures misconfigurations are detected during development
1236
- * for runtime to execute without false positive validation failures.
1237
- *
1238
- * Always define exactly one target that is suitable for current handler.
1239
- * */
1240
- var RequestBodyTargets = {
1241
- json: "JSON request body",
1242
- form: "URL-encoded or Multipart form",
1243
- raw: "Raw body format (string/Buffer/ArrayBuffer/Blob)"
1244
- };
1245
- ({
1246
- ...RequestMetadataTargets,
1247
- ...RequestBodyTargets
1248
- });
1249
- var generateTsconfig = (sourceFolder) => {
1250
- const rootDir = "${configDir}";
1251
- const compilerOptions = {
1252
- types: ["@types/node", "vite/client"],
1253
- moduleResolution: "bundler",
1254
- module: "ESNext",
1255
- target: "ESNext",
1256
- strict: true,
1257
- exactOptionalPropertyTypes: true,
1258
- noImplicitAny: true,
1259
- noImplicitThis: true,
1260
- noImplicitOverride: true,
1261
- noImplicitReturns: true,
1262
- noUnusedLocals: false,
1263
- noUnusedParameters: false,
1264
- allowArbitraryExtensions: true,
1265
- allowImportingTsExtensions: true,
1266
- allowUnreachableCode: false,
1267
- allowUnusedLabels: false,
1268
- useUnknownInCatchVariables: true,
1269
- noFallthroughCasesInSwitch: true,
1270
- noUncheckedSideEffectImports: true,
1271
- resolveJsonModule: true,
1272
- esModuleInterop: true,
1273
- verbatimModuleSyntax: true,
1274
- skipLibCheck: true,
1275
- noEmit: true
1276
- };
1277
- if (sourceFolder) return {
1278
- include: [
1279
- `${rootDir}/`,
1280
- `${rootDir}/../../${defaults.libDir}/${sourceFolder}/`,
1281
- `${rootDir}/../../**/*.d.ts`
1282
- ],
1283
- compilerOptions: {
1284
- ...compilerOptions,
1285
- types: [...compilerOptions.types],
1286
- paths: {
1287
- [`${defaults.appPrefix}/*`]: [`${rootDir}/../../*`],
1288
- [`${defaults.srcPrefix}/*`]: [`${rootDir}/*`],
1289
- [`${defaults.libPrefix}/*`]: [`${rootDir}/../../${defaults.libDir}/${sourceFolder}/*`]
1290
- },
1291
- jsx: "preserve"
1292
- }
1293
- };
1294
- return {
1295
- include: [`${rootDir}/`],
1296
- exclude: [`${rootDir}/${defaults.srcDir}/`],
1297
- compilerOptions: {
1298
- ...compilerOptions,
1299
- paths: { [`${defaults.appPrefix}/*`]: [`${rootDir}/*`] }
1300
- }
1301
- };
1302
- };
1303
- var factory = defineGeneratorFactory((meta, sourceFolder) => {
1304
- const { createPath, createImportHelpers } = pathResolver(sourceFolder);
1305
- const start = async () => {
1306
- await renderToFile(createPath.lib("../tsconfig.base.json"), JSON.stringify(generateTsconfig(), void 0, 2), {});
1307
- {
1308
- const tsconfig = generateTsconfig(sourceFolder.name);
1309
- const compilerOptions = {};
1310
- const types = new Set(tsconfig.compilerOptions.types || []);
1311
- for (const { meta } of sourceFolder.config.generators || []) {
1312
- if (meta.jsxImportSource) compilerOptions.jsxImportSource = meta.jsxImportSource;
1313
- for (const type of meta.types || []) types.add(type);
1314
- }
1315
- await renderToFile(createPath.lib("tsconfig.base.json"), JSON.stringify({
1316
- ...tsconfig,
1317
- compilerOptions: {
1318
- ...tsconfig.compilerOptions,
1319
- ...compilerOptions,
1320
- types: [...types.values()]
1321
- }
1322
- }, void 0, 2), {});
1323
- }
1324
- /**
1325
- * expose VRefine as a global type.
1326
- * not supposed to be overriden by generators.
1327
- * */
1328
- await renderToFile(createPath.lib("../env.d.ts"), env_d_default, {});
1329
- /**
1330
- * deploy a default gitignore file that ignore everything,
1331
- * except cache.json files; if file exists, do not override.
1332
- * */
1333
- await renderToFile(createPath.lib("../.gitignore"), gitignore_default, {}, { overwrite: false });
1334
- /**
1335
- * deploy a stub SSG file.
1336
- * generators that support SSG will override it as needed.
1337
- * then SSG generator will import it and generate static files for exported routes.
1338
- * */
1339
- await renderToFile(createPath.lib("ssg.ts"), "export default [];", {});
1340
- };
1341
- const generateLibFiles = async (entries) => {
1342
- const { renderToFile } = renderFactory({
1343
- helpers: {
1344
- ...createImportHelpers({ origin: "lib" }),
1345
- ...routeRenderHelpers()
1346
- },
1347
- partials: { pathMapper: pathMapper_default }
1348
- });
1349
- await renderToFile(createPath.libCore("routeMap.ts"), routeMap_default, {
1350
- apiRoutes: entries.flatMap(({ kind, entry }) => {
1351
- return kind === "apiRoute" ? [entry] : [];
1352
- }),
1353
- pageRoutes: entries.flatMap(({ kind, entry }) => {
1354
- return kind === "pageRoute" ? [entry] : [];
1355
- })
1356
- });
1357
- for (const [file, template] of [["config.ts", config_default], ["index.ts", core_default]]) await renderToFile(createPath.libCore(file), template, sourceFolder);
1358
- for (const { kind, entry } of entries) if (kind === "apiRoute") await renderToFile(createPath.libApi(dirname(entry.file), "schemas.ts"), schemas_default, { route: entry }, { overwrite: false });
1359
- };
1360
- return {
1361
- meta,
1362
- options: void 0,
1363
- start,
1364
- watch: generateLibFiles,
1365
- build: generateLibFiles
1366
- };
1367
- });
1368
- /**
1369
- * Generates stub files required by various generators.
1370
- * Ensures cross-generator dependencies remain resolvable
1371
- * even if specialized generators supposed to generate these files are not installed.
1372
- * */
1373
- var src_default = defineGenerator(() => {
1374
- const meta = {
1375
- name: "Core",
1376
- dependencies: { "path-to-regexp": package_default$1.devDependencies["path-to-regexp"] }
1377
- };
1378
- return {
1379
- meta,
1380
- options: void 0,
1381
- factory: (sourceFolder) => factory(meta, sourceFolder)
1382
- };
1383
- });
5
+ import { styleText } from "node:util";
6
+ import { build, createServer } from "vite";
7
+ import { pathExists, pathResolver, routesFactory, spinnerFactory } from "@kosmojs/lib";
8
+ import coreGenerator from "@kosmojs/core-generator";
9
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
10
+ import crc from "crc/crc32";
1384
11
  var package_default = {
1385
12
  type: "module",
1386
13
  name: "@kosmojs/dev",
1387
- version: "0.1.0",
14
+ version: "0.1.1",
1388
15
  cacheVersion: "001",
1389
16
  author: "Slee Woo",
1390
17
  license: "MIT",
@@ -1427,7 +54,7 @@ var package_default = {
1427
54
  * import resolves to the actual published package.json.
1428
55
  * */
1429
56
  var cacheFactory = (route, sourceFolder, extraContext) => {
1430
- const cacheFile = pathResolver$1(sourceFolder).createPath.libApi(dirname(route.file), "cache.json");
57
+ const cacheFile = pathResolver(sourceFolder).createPath.libApi(dirname(route.file), "cache.json");
1431
58
  const validateCache = async (cache) => {
1432
59
  if (!cache?.hash) return;
1433
60
  if (!cache.typeDeclarations || !cache.referencedFiles) return;
@@ -1438,7 +65,7 @@ var cacheFactory = (route, sourceFolder, extraContext) => {
1438
65
  };
1439
66
  return {
1440
67
  async get(opt) {
1441
- if (await pathExists$1(cacheFile)) try {
68
+ if (await pathExists(cacheFile)) try {
1442
69
  const cache = JSON.parse(await readFile(cacheFile, "utf8"));
1443
70
  return opt?.validate ? validateCache(cache) : cache;
1444
71
  } catch (_e) {}
@@ -1491,7 +118,7 @@ var chassis_default = async (projectSettings) => {
1491
118
  if (command === "build") {
1492
119
  for (const sourceFolder of projectSettings.sourceFolders) {
1493
120
  const { config } = sourceFolder;
1494
- const { createPath } = pathResolver$1(sourceFolder);
121
+ const { createPath } = pathResolver(sourceFolder);
1495
122
  const resolvedRoutes = [];
1496
123
  {
1497
124
  const { resolvers } = await routesFactory(sourceFolder, cacheFactory);
@@ -1532,12 +159,9 @@ var chassis_default = async (projectSettings) => {
1532
159
  const apiGenerator = generators.find((e) => e.meta.slot === "api");
1533
160
  if (apiGenerator) {
1534
161
  const dir = createPath.distDir("api");
1535
- const noExternal = Array.isArray(apiGenerator.options?.noExternal) ? apiGenerator.options.noExternal : generators.flatMap(({ meta }) => {
1536
- return Object.keys({
1537
- ...meta.dependencies,
1538
- ...meta.devDependencies
1539
- });
1540
- });
162
+ const externalizeOptions = apiGenerator.options ? Object.entries(apiGenerator.options).flatMap(([k, v]) => {
163
+ return k === "external" || k === "noExternal" ? [[k, v]] : [];
164
+ }) : [];
1541
165
  await build({
1542
166
  configFile: false,
1543
167
  root: createPath.src(),
@@ -1547,7 +171,7 @@ var chassis_default = async (projectSettings) => {
1547
171
  ...config.define,
1548
172
  KOSMO_PRODUCTION_BUILD: "true"
1549
173
  },
1550
- ssr: { noExternal },
174
+ ssr: externalizeOptions.length ? Object.fromEntries(externalizeOptions) : { external: true },
1551
175
  resolve: {
1552
176
  ...config.resolve,
1553
177
  tsconfigPaths: true,
@@ -1580,7 +204,7 @@ var chassis_default = async (projectSettings) => {
1580
204
  let port = await findFreePort(devPort);
1581
205
  for (const sourceFolder of projectSettings.sourceFolders) {
1582
206
  const { config } = sourceFolder;
1583
- const { createPath } = pathResolver$1(sourceFolder);
207
+ const { createPath } = pathResolver(sourceFolder);
1584
208
  const requestMatchers = matchersFactory(sourceFolder);
1585
209
  const generators = folderGenerators(sourceFolder);
1586
210
  const plugins = [...config.plugins || []];
@@ -1615,7 +239,7 @@ var chassis_default = async (projectSettings) => {
1615
239
  for (const sourceFolder of projectSettings.sourceFolders) {
1616
240
  const { config } = sourceFolder;
1617
241
  if (!folderGenerators(sourceFolder).find((e) => e.meta.slot === "api")) continue;
1618
- const { createPath } = pathResolver$1(sourceFolder);
242
+ const { createPath } = pathResolver(sourceFolder);
1619
243
  const requestMatchers = matchersFactory(sourceFolder);
1620
244
  const viteServer = await createServer({
1621
245
  configFile: false,
@@ -1637,7 +261,7 @@ var chassis_default = async (projectSettings) => {
1637
261
  const env = viteServer.environments.api;
1638
262
  const loadDevSetup = async () => {
1639
263
  env.runner.clearCache();
1640
- return env.runner.import(join(defaults$3.apiDir, "dev.ts")).then((e) => e.default);
264
+ return env.runner.import(join(defaults.apiDir, "dev.ts")).then((e) => e.default);
1641
265
  };
1642
266
  let devSetup = await loadDevSetup();
1643
267
  for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) viteServer.watcher.on(evt, async (file) => {
@@ -1713,7 +337,7 @@ var folderGenerators = (sourceFolder) => {
1713
337
  for (const base of generators) if (base.meta.slot) coreGenerators[base.meta.slot] = base;
1714
338
  else userGenerators.push(base);
1715
339
  return [
1716
- src_default(),
340
+ coreGenerator(),
1717
341
  ...coreGenerators.api ? [coreGenerators.api] : [],
1718
342
  ...coreGenerators.fetch && coreGenerators.api ? [coreGenerators.fetch] : [],
1719
343
  ...userGenerators,
@@ -1723,7 +347,7 @@ var folderGenerators = (sourceFolder) => {
1723
347
  };
1724
348
  var eventFactory = async (sourceFolder) => {
1725
349
  const { resolvers, resolversFactory } = await routesFactory(sourceFolder, cacheFactory);
1726
- const { createPath } = pathResolver$1(sourceFolder);
350
+ const { createPath } = pathResolver(sourceFolder);
1727
351
  const generators = [];
1728
352
  for (const base of folderGenerators(sourceFolder)) {
1729
353
  const factory = base.factory(sourceFolder);