@kosmojs/dev 0.1.0 → 0.1.1

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,540 +1,1859 @@
1
- // src/chassis.ts
1
+ import { n as defaults$3 } from "./assets/pkg-BO-iPG8y.js";
2
+ import "node:module";
3
+ 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
15
  import http from "node:http";
3
16
  import net from "node:net";
4
- import { join, resolve as resolve2 } from "node:path";
5
- import { styleText } from "node:util";
6
- import { build, createServer } from "vite";
7
- import {
8
- defaults
9
- } from "@kosmojs/core";
10
- import { pathResolver as pathResolver2, routesFactory, spinnerFactory } from "@kosmojs/lib";
11
- import coreGenerator from "@kosmojs/core-generator";
12
-
13
- // src/cache.ts
14
- import { mkdir, readFile, writeFile } from "node:fs/promises";
15
- import { dirname, resolve } from "node:path";
16
- import crc from "crc/crc32";
17
- import self from "@kosmojs/dev/package.json" with { type: "json" };
18
- import { pathExists, pathResolver } from "@kosmojs/lib";
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
+ });
1384
+ var package_default = {
1385
+ type: "module",
1386
+ name: "@kosmojs/dev",
1387
+ version: "0.1.0",
1388
+ cacheVersion: "001",
1389
+ author: "Slee Woo",
1390
+ license: "MIT",
1391
+ files: ["pkg/*"],
1392
+ exports: {
1393
+ ".": {
1394
+ "types": "./pkg/index.d.ts",
1395
+ "default": "./pkg/index.js"
1396
+ },
1397
+ "./chassis": {
1398
+ "types": "./pkg/chassis.d.ts",
1399
+ "default": "./pkg/chassis.js"
1400
+ },
1401
+ "./package.json": "./package.json"
1402
+ },
1403
+ scripts: { "build": "wsbuild src/index.ts src/chassis.ts" },
1404
+ dependencies: { "crc": "^4.3.2" },
1405
+ devDependencies: {
1406
+ "@kosmojs/core": "workspace:^",
1407
+ "@kosmojs/core-generator": "workspace:^",
1408
+ "@kosmojs/fetch-generator": "workspace:^",
1409
+ "@kosmojs/hono-generator": "workspace:^",
1410
+ "@kosmojs/koa-generator": "workspace:^",
1411
+ "@kosmojs/lib": "workspace:^",
1412
+ "@kosmojs/mdx-generator": "workspace:^",
1413
+ "@kosmojs/openapi-generator": "workspace:^",
1414
+ "@kosmojs/react-generator": "workspace:^",
1415
+ "@kosmojs/solid-generator": "workspace:^",
1416
+ "@kosmojs/ssg-generator": "workspace:^",
1417
+ "@kosmojs/ssr-generator": "workspace:^",
1418
+ "@kosmojs/typebox-generator": "workspace:^",
1419
+ "@kosmojs/vue-generator": "workspace:^"
1420
+ }
1421
+ };
1422
+ //#endregion
1423
+ //#region src/cache.ts
1424
+ /**
1425
+ * Import from published package to ensure correct version at runtime.
1426
+ * Local import would be bundled with pre-bump version; this external
1427
+ * import resolves to the actual published package.json.
1428
+ * */
19
1429
  var cacheFactory = (route, sourceFolder, extraContext) => {
20
- const cacheFile = pathResolver(sourceFolder).createPath.libApi(
21
- dirname(route.file),
22
- "cache.json"
23
- );
24
- const validateCache = async (cache) => {
25
- if (!cache?.hash) {
26
- return;
27
- }
28
- if (!cache.typeDeclarations || !cache.referencedFiles) {
29
- return;
30
- }
31
- const hash = await generateFileHash(route.fileFullpath, {
32
- ...extraContext
33
- });
34
- if (!identicalHashSum(cache.hash, hash)) {
35
- return;
36
- }
37
- for (const [file, hash2] of Object.entries(cache.referencedFiles)) {
38
- if (!identicalHashSum(
39
- hash2,
40
- await generateFileHash(resolve(sourceFolder.root, file))
41
- )) {
42
- return;
43
- }
44
- }
45
- return cache;
46
- };
47
- return {
48
- async get(opt) {
49
- if (await pathExists(cacheFile)) {
50
- try {
51
- const cache = JSON.parse(await readFile(cacheFile, "utf8"));
52
- return opt?.validate ? validateCache(cache) : cache;
53
- } catch (_e) {
54
- }
55
- }
56
- return void 0;
57
- },
58
- async set(cache) {
59
- const hash = await generateFileHash(route.fileFullpath, {
60
- ...extraContext
61
- });
62
- const referencedFiles = {};
63
- for (const file of cache.referencedFiles) {
64
- referencedFiles[
65
- // Strip project root to ensure cached paths are relative
66
- // and portable across environments (CI, local, etc.)
67
- file.replace(`${sourceFolder.root}/`, "")
68
- ] = await generateFileHash(file);
69
- }
70
- const value = { ...cache, hash, referencedFiles };
71
- await mkdir(dirname(cacheFile), { recursive: true });
72
- await writeFile(cacheFile, JSON.stringify(value, null, 2), "utf8");
73
- return value;
74
- }
75
- };
1430
+ const cacheFile = pathResolver$1(sourceFolder).createPath.libApi(dirname(route.file), "cache.json");
1431
+ const validateCache = async (cache) => {
1432
+ if (!cache?.hash) return;
1433
+ if (!cache.typeDeclarations || !cache.referencedFiles) return;
1434
+ const hash = await generateFileHash(route.fileFullpath, { ...extraContext });
1435
+ if (!identicalHashSum(cache.hash, hash)) return;
1436
+ for (const [file, hash] of Object.entries(cache.referencedFiles)) if (!identicalHashSum(hash, await generateFileHash(resolve(sourceFolder.root, file)))) return;
1437
+ return cache;
1438
+ };
1439
+ return {
1440
+ async get(opt) {
1441
+ if (await pathExists$1(cacheFile)) try {
1442
+ const cache = JSON.parse(await readFile(cacheFile, "utf8"));
1443
+ return opt?.validate ? validateCache(cache) : cache;
1444
+ } catch (_e) {}
1445
+ },
1446
+ async set(cache) {
1447
+ const hash = await generateFileHash(route.fileFullpath, { ...extraContext });
1448
+ const referencedFiles = {};
1449
+ for (const file of cache.referencedFiles) referencedFiles[file.replace(`${sourceFolder.root}/`, "")] = await generateFileHash(file);
1450
+ const value = {
1451
+ ...cache,
1452
+ hash,
1453
+ referencedFiles
1454
+ };
1455
+ await mkdir(dirname(cacheFile), { recursive: true });
1456
+ await writeFile(cacheFile, JSON.stringify(value, null, 2), "utf8");
1457
+ return value;
1458
+ }
1459
+ };
76
1460
  };
77
1461
  var generateFileHash = async (file, extraContext) => {
78
- let fileContent;
79
- try {
80
- fileContent = await readFile(file, "utf8");
81
- } catch (_e) {
82
- return 0;
83
- }
84
- return fileContent ? crc(
85
- JSON.stringify({
86
- ...extraContext,
87
- [self.cacheVersion]: fileContent
88
- })
89
- ) : 0;
1462
+ let fileContent;
1463
+ try {
1464
+ fileContent = await readFile(file, "utf8");
1465
+ } catch (_e) {
1466
+ return 0;
1467
+ }
1468
+ return fileContent ? crc(JSON.stringify({
1469
+ ...extraContext,
1470
+ [package_default.cacheVersion]: fileContent
1471
+ })) : 0;
90
1472
  };
91
1473
  var identicalHashSum = (a, b) => {
92
- return a === b;
1474
+ return a === b;
93
1475
  };
94
-
95
- // src/chassis.ts
1476
+ //#endregion
1477
+ //#region src/chassis.ts
96
1478
  var chassis_default = async (projectSettings) => {
97
- const { devPort, command } = projectSettings;
98
- for (const sourceFolder of projectSettings.sourceFolders) {
99
- for (const base of folderGenerators(sourceFolder)) {
100
- if (!base.meta?.name || typeof base.factory !== "function") {
101
- throw new Error(
102
- `${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`
103
- );
104
- }
105
- const factory = base.factory(sourceFolder);
106
- if (!factory.meta?.name) {
107
- throw new Error(
108
- `${sourceFolder.name}: ${base.meta.name} generator is missing meta property`
109
- );
110
- }
111
- try {
112
- await factory.start?.();
113
- } catch (error) {
114
- console.error(
115
- styleText(
116
- "red",
117
- `${sourceFolder.name}: ${base.meta.name} generator failed to initialize`
118
- )
119
- );
120
- throw error;
121
- }
122
- }
123
- }
124
- if (command === "build") {
125
- for (const sourceFolder of projectSettings.sourceFolders) {
126
- const { config, baseurl } = sourceFolder;
127
- const { createPath } = pathResolver2(sourceFolder);
128
- const resolvedRoutes = [];
129
- {
130
- const { resolvers } = await routesFactory(sourceFolder, cacheFactory);
131
- const spinner = spinnerFactory(
132
- `${sourceFolder.name}: resolving routes`
133
- );
134
- for (const { name, handler } of resolvers.values()) {
135
- spinner.append(
136
- `[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`
137
- );
138
- resolvedRoutes.push(await handler());
139
- }
140
- spinner.succeed("ready \u2728");
141
- }
142
- const generators = folderGenerators(sourceFolder);
143
- const plugins = [...config.plugins || []];
144
- for (const base of generators) {
145
- await base.factory(sourceFolder).build?.(resolvedRoutes);
146
- plugins.push(...base.plugins?.(sourceFolder, command) || []);
147
- }
148
- {
149
- const outDir = createPath.distDir("client");
150
- await build({
151
- ...config,
152
- configFile: false,
153
- root: createPath.src(),
154
- base: join(baseurl, "/"),
155
- plugins,
156
- resolve: {
157
- ...config.resolve,
158
- tsconfigPaths: true
159
- },
160
- build: {
161
- ...config?.build,
162
- outDir,
163
- manifest: true,
164
- emptyOutDir: true
165
- },
166
- cacheDir: cacheDir(sourceFolder, command, "client")
167
- });
168
- }
169
- const apiGenerator = generators.find((e) => e.meta.slot === "api");
170
- if (apiGenerator) {
171
- const dir = createPath.distDir("api");
172
- const noExternal = Array.isArray(apiGenerator.options?.noExternal) ? apiGenerator.options.noExternal : generators.flatMap(({ meta }) => {
173
- return Object.keys({
174
- ...meta.dependencies,
175
- ...meta.devDependencies
176
- });
177
- });
178
- await build({
179
- configFile: false,
180
- root: createPath.src(),
181
- appType: "custom",
182
- plugins: [...apiGenerator.plugins?.(sourceFolder, command) || []],
183
- define: {
184
- ...config.define,
185
- KOSMO_PRODUCTION_BUILD: "true"
186
- },
187
- ssr: { noExternal },
188
- resolve: {
189
- ...config.resolve,
190
- tsconfigPaths: true,
191
- conditions: ["node"]
192
- },
193
- build: {
194
- ssr: true,
195
- target: "esnext",
196
- sourcemap: true,
197
- emptyOutDir: true,
198
- rolldownOptions: {
199
- input: [createPath.api("app.ts"), createPath.api("server.ts")],
200
- output: {
201
- dir,
202
- format: "esm"
203
- }
204
- }
205
- },
206
- cacheDir: cacheDir(sourceFolder, command, "backend")
207
- });
208
- }
209
- for (const base of generators) {
210
- await base.factory(sourceFolder).postBuild?.(resolvedRoutes);
211
- }
212
- }
213
- return async () => {
214
- };
215
- }
216
- const requestHandlers = [];
217
- const teardownHandlers = [];
218
- const eventMap = {};
219
- for (const sourceFolder of projectSettings.sourceFolders) {
220
- eventMap[sourceFolder.name] = await eventFactory(sourceFolder);
221
- }
222
- let port = await findFreePort(devPort);
223
- for (const sourceFolder of projectSettings.sourceFolders) {
224
- const { config, baseurl } = sourceFolder;
225
- const { createPath } = pathResolver2(sourceFolder);
226
- const requestMatchers = matchersFactory(sourceFolder);
227
- const generators = folderGenerators(sourceFolder);
228
- const plugins = [...config.plugins || []];
229
- for (const base of generators) {
230
- plugins.push(...base.plugins?.(sourceFolder, command) || []);
231
- }
232
- const viteServer = await createServer({
233
- ...config,
234
- configFile: false,
235
- root: createPath.src(),
236
- base: join(baseurl, "/"),
237
- plugins,
238
- server: {
239
- ...config.server,
240
- port: port++,
241
- middlewareMode: true,
242
- hmr: { port: port++ }
243
- },
244
- resolve: {
245
- ...config.resolve,
246
- tsconfigPaths: true
247
- },
248
- define: {
249
- ...config.define
250
- },
251
- cacheDir: cacheDir(sourceFolder, command, "client")
252
- });
253
- for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) {
254
- viteServer.watcher.on(evt, handler);
255
- }
256
- requestHandlers.push([
257
- [sourceFolder.baseurl],
258
- () => requestMatchers.base,
259
- () => viteServer.middlewares
260
- ]);
261
- teardownHandlers.push(viteServer.close);
262
- }
263
- for (const sourceFolder of projectSettings.sourceFolders) {
264
- const { config } = sourceFolder;
265
- if (!folderGenerators(sourceFolder).find((e) => e.meta.slot === "api")) {
266
- continue;
267
- }
268
- const { createPath } = pathResolver2(sourceFolder);
269
- const requestMatchers = matchersFactory(sourceFolder);
270
- const viteServer = await createServer({
271
- configFile: false,
272
- root: createPath.src(),
273
- appType: "custom",
274
- server: {
275
- port: port++,
276
- middlewareMode: true,
277
- hmr: { port: port++ }
278
- },
279
- resolve: { tsconfigPaths: true },
280
- define: {
281
- ...config?.define,
282
- KOSMO_PRODUCTION_BUILD: "false"
283
- },
284
- environments: {
285
- api: {
286
- resolve: {
287
- conditions: ["node"]
288
- }
289
- }
290
- },
291
- cacheDir: cacheDir(sourceFolder, command, "backend")
292
- });
293
- const env = viteServer.environments.api;
294
- const loadDevSetup = async () => {
295
- env.runner.clearCache();
296
- return env.runner.import(join(defaults.apiDir, "dev.ts")).then((e) => e.default);
297
- };
298
- let devSetup = await loadDevSetup();
299
- for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) {
300
- viteServer.watcher.on(evt, async (file) => {
301
- const mods = env.moduleGraph.getModulesByFile(file);
302
- if (mods?.size) {
303
- await handler(file);
304
- await devSetup?.teardownHandler?.();
305
- devSetup = await loadDevSetup();
306
- }
307
- });
308
- }
309
- requestHandlers.push([
310
- [sourceFolder.baseurl, sourceFolder.apiurl],
311
- () => devSetup.requestMatcher || requestMatchers.api,
312
- () => devSetup.requestHandler()
313
- ]);
314
- teardownHandlers.push(viteServer.close);
315
- }
316
- const requestHandlerWeight = ([
317
- segments
318
- ]) => {
319
- const [base, api] = segments;
320
- const weight = base.split("/").filter(Boolean).length;
321
- return api ? weight + 5 : weight;
322
- };
323
- const handlers = requestHandlers.sort(
324
- (a, b) => requestHandlerWeight(b) - requestHandlerWeight(a)
325
- );
326
- const httpServer = http.createServer((req, res) => {
327
- for (const [, matcherFactory, handlerFactory] of handlers) {
328
- const [matcher, handler] = [matcherFactory(), handlerFactory()];
329
- if (matcher(req)) {
330
- handler(req, res);
331
- return;
332
- }
333
- }
334
- res.writeHead(404, { "Content-Type": "text/html" });
335
- res.end("<h1>404: Not Found</h1>");
336
- });
337
- httpServer.on("error", (error) => {
338
- console.error(
339
- styleText("red", `Failed to start dev server on port ${devPort}`)
340
- );
341
- console.error(error.message);
342
- process.exit(1);
343
- });
344
- httpServer.listen(devPort);
345
- teardownHandlers.push(async () => {
346
- httpServer.close();
347
- });
348
- return async () => {
349
- for (const handler of teardownHandlers) {
350
- await handler().catch(console.error);
351
- }
352
- await new Promise((resolve3) => setTimeout(resolve3, 100));
353
- };
1479
+ const { devPort, command } = projectSettings;
1480
+ for (const sourceFolder of projectSettings.sourceFolders) for (const base of folderGenerators(sourceFolder)) {
1481
+ if (!base.meta?.name || typeof base.factory !== "function") throw new Error(`${sourceFolder.name}: Unrecognized generator - must be created via defineGenerator()`);
1482
+ const factory = base.factory(sourceFolder);
1483
+ if (!factory.meta?.name) throw new Error(`${sourceFolder.name}: ${base.meta.name} generator is missing meta property`);
1484
+ try {
1485
+ await factory.start?.();
1486
+ } catch (error) {
1487
+ console.error(styleText("red", `${sourceFolder.name}: ${base.meta.name} generator failed to initialize`));
1488
+ throw error;
1489
+ }
1490
+ }
1491
+ if (command === "build") {
1492
+ for (const sourceFolder of projectSettings.sourceFolders) {
1493
+ const { config } = sourceFolder;
1494
+ const { createPath } = pathResolver$1(sourceFolder);
1495
+ const resolvedRoutes = [];
1496
+ {
1497
+ const { resolvers } = await routesFactory(sourceFolder, cacheFactory);
1498
+ const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
1499
+ for (const { name, handler } of resolvers.values()) {
1500
+ spinner.append(`[ ${resolvedRoutes.length + 1} of ${resolvers.size} ] ${name}`);
1501
+ resolvedRoutes.push(await handler());
1502
+ }
1503
+ spinner.succeed("ready ✨");
1504
+ }
1505
+ const generators = folderGenerators(sourceFolder);
1506
+ const plugins = [...config.plugins || []];
1507
+ for (const base of generators) {
1508
+ await base.factory(sourceFolder).build?.(resolvedRoutes);
1509
+ plugins.push(...base.plugins?.(sourceFolder, command) || []);
1510
+ }
1511
+ {
1512
+ const outDir = createPath.distDir("client");
1513
+ await build({
1514
+ ...config,
1515
+ configFile: false,
1516
+ root: createPath.src(),
1517
+ base: join(config.base, "/"),
1518
+ plugins,
1519
+ resolve: {
1520
+ ...config.resolve,
1521
+ tsconfigPaths: true
1522
+ },
1523
+ build: {
1524
+ ...config?.build,
1525
+ outDir,
1526
+ manifest: true,
1527
+ emptyOutDir: true
1528
+ },
1529
+ cacheDir: cacheDir(sourceFolder, command, "client")
1530
+ });
1531
+ }
1532
+ const apiGenerator = generators.find((e) => e.meta.slot === "api");
1533
+ if (apiGenerator) {
1534
+ 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
+ });
1541
+ await build({
1542
+ configFile: false,
1543
+ root: createPath.src(),
1544
+ appType: "custom",
1545
+ plugins: [...apiGenerator.plugins?.(sourceFolder, command) || []],
1546
+ define: {
1547
+ ...config.define,
1548
+ KOSMO_PRODUCTION_BUILD: "true"
1549
+ },
1550
+ ssr: { noExternal },
1551
+ resolve: {
1552
+ ...config.resolve,
1553
+ tsconfigPaths: true,
1554
+ conditions: ["node"]
1555
+ },
1556
+ build: {
1557
+ ssr: true,
1558
+ target: "esnext",
1559
+ sourcemap: true,
1560
+ emptyOutDir: true,
1561
+ rolldownOptions: {
1562
+ input: [createPath.api("app.ts"), createPath.api("server.ts")],
1563
+ output: {
1564
+ dir,
1565
+ format: "esm"
1566
+ }
1567
+ }
1568
+ },
1569
+ cacheDir: cacheDir(sourceFolder, command, "backend")
1570
+ });
1571
+ }
1572
+ for (const base of generators) await base.factory(sourceFolder).postBuild?.(resolvedRoutes);
1573
+ }
1574
+ return async () => {};
1575
+ }
1576
+ const requestHandlers = [];
1577
+ const teardownHandlers = [];
1578
+ const eventMap = {};
1579
+ for (const sourceFolder of projectSettings.sourceFolders) eventMap[sourceFolder.name] = await eventFactory(sourceFolder);
1580
+ let port = await findFreePort(devPort);
1581
+ for (const sourceFolder of projectSettings.sourceFolders) {
1582
+ const { config } = sourceFolder;
1583
+ const { createPath } = pathResolver$1(sourceFolder);
1584
+ const requestMatchers = matchersFactory(sourceFolder);
1585
+ const generators = folderGenerators(sourceFolder);
1586
+ const plugins = [...config.plugins || []];
1587
+ for (const base of generators) plugins.push(...base.plugins?.(sourceFolder, command) || []);
1588
+ const viteServer = await createServer({
1589
+ ...config,
1590
+ configFile: false,
1591
+ root: createPath.src(),
1592
+ base: join(config.base, "/"),
1593
+ plugins,
1594
+ server: {
1595
+ ...config.server,
1596
+ port: port++,
1597
+ middlewareMode: true,
1598
+ hmr: { port: port++ }
1599
+ },
1600
+ resolve: {
1601
+ ...config.resolve,
1602
+ tsconfigPaths: true
1603
+ },
1604
+ define: { ...config.define },
1605
+ cacheDir: cacheDir(sourceFolder, command, "client")
1606
+ });
1607
+ for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) viteServer.watcher.on(evt, handler);
1608
+ requestHandlers.push([
1609
+ [sourceFolder.config.base],
1610
+ () => requestMatchers.base,
1611
+ () => viteServer.middlewares
1612
+ ]);
1613
+ teardownHandlers.push(viteServer.close);
1614
+ }
1615
+ for (const sourceFolder of projectSettings.sourceFolders) {
1616
+ const { config } = sourceFolder;
1617
+ if (!folderGenerators(sourceFolder).find((e) => e.meta.slot === "api")) continue;
1618
+ const { createPath } = pathResolver$1(sourceFolder);
1619
+ const requestMatchers = matchersFactory(sourceFolder);
1620
+ const viteServer = await createServer({
1621
+ configFile: false,
1622
+ root: createPath.src(),
1623
+ appType: "custom",
1624
+ server: {
1625
+ port: port++,
1626
+ middlewareMode: true,
1627
+ hmr: { port: port++ }
1628
+ },
1629
+ resolve: { tsconfigPaths: true },
1630
+ define: {
1631
+ ...config?.define,
1632
+ KOSMO_PRODUCTION_BUILD: "false"
1633
+ },
1634
+ environments: { api: { resolve: { conditions: ["node"] } } },
1635
+ cacheDir: cacheDir(sourceFolder, command, "backend")
1636
+ });
1637
+ const env = viteServer.environments.api;
1638
+ const loadDevSetup = async () => {
1639
+ env.runner.clearCache();
1640
+ return env.runner.import(join(defaults$3.apiDir, "dev.ts")).then((e) => e.default);
1641
+ };
1642
+ let devSetup = await loadDevSetup();
1643
+ for (const [evt, handler] of Object.entries(eventMap[sourceFolder.name])) viteServer.watcher.on(evt, async (file) => {
1644
+ if (env.moduleGraph.getModulesByFile(file)?.size) {
1645
+ await handler(file);
1646
+ await devSetup?.teardownHandler?.();
1647
+ devSetup = await loadDevSetup();
1648
+ }
1649
+ });
1650
+ requestHandlers.push([
1651
+ [sourceFolder.config.base, sourceFolder.config.apiBase],
1652
+ () => devSetup.requestMatcher || requestMatchers.api,
1653
+ () => devSetup.requestHandler()
1654
+ ]);
1655
+ teardownHandlers.push(viteServer.close);
1656
+ }
1657
+ /**
1658
+ * Sorting is essential to ensure more specific paths are matched before broader ones.
1659
+ *
1660
+ * Given source folders:
1661
+ * - main app: baseurl=/ apiurl=/api
1662
+ * - admin app: baseurl=/admin apiurl=/admin/api
1663
+ *
1664
+ * Correct sort order:
1665
+ * 1. /admin/api — most specific
1666
+ * 2. /api
1667
+ * 3. /admin
1668
+ * 4. /
1669
+ * */
1670
+ const requestHandlerWeight = ([segments]) => {
1671
+ const [base, api] = segments;
1672
+ /**
1673
+ * set weight to number of non-empty segments:
1674
+ * "/" weight is 0
1675
+ * "/admin" weight is 1
1676
+ * */
1677
+ const weight = base.split("/").filter(Boolean).length;
1678
+ return api ? weight + 5 : weight;
1679
+ };
1680
+ const handlers = requestHandlers.sort((a, b) => requestHandlerWeight(b) - requestHandlerWeight(a));
1681
+ const httpServer = http.createServer((req, res) => {
1682
+ for (const [, matcherFactory, handlerFactory] of handlers) {
1683
+ const [matcher, handler] = [matcherFactory(), handlerFactory()];
1684
+ if (matcher(req)) {
1685
+ handler(req, res);
1686
+ return;
1687
+ }
1688
+ }
1689
+ res.writeHead(404, { "Content-Type": "text/html" });
1690
+ res.end("<h1>404: Not Found</h1>");
1691
+ });
1692
+ httpServer.on("error", (error) => {
1693
+ console.error(styleText("red", `Failed to start dev server on port ${devPort}`));
1694
+ console.error(error.message);
1695
+ process.exit(1);
1696
+ });
1697
+ httpServer.listen(devPort);
1698
+ teardownHandlers.push(async () => {
1699
+ httpServer.close();
1700
+ });
1701
+ return async () => {
1702
+ for (const handler of teardownHandlers) await handler();
1703
+ await new Promise((resolve) => setTimeout(resolve, 100));
1704
+ };
354
1705
  };
355
1706
  var cacheDir = ({ root, name }, command, mode) => {
356
- return resolve2(root, `var/.vite/${name}/${command}/${mode}`);
1707
+ return resolve(root, `var/.vite/${name}/${command}/${mode}`);
357
1708
  };
358
1709
  var folderGenerators = (sourceFolder) => {
359
- const { generators = [] } = sourceFolder.config;
360
- const coreGenerators = {};
361
- const userGenerators = [];
362
- for (const base of generators) {
363
- if (base.meta.slot) {
364
- coreGenerators[base.meta.slot] = base;
365
- } else {
366
- userGenerators.push(base);
367
- }
368
- }
369
- return [
370
- // core generator should run first
371
- coreGenerator(),
372
- // then api generator
373
- ...coreGenerators.api ? [coreGenerators.api] : [],
374
- // then fetch generator, only if api generator also enabled
375
- ...coreGenerators.fetch && coreGenerators.api ? [coreGenerators.fetch] : [],
376
- // then user generators in the order they were added
377
- ...userGenerators,
378
- // then ssr generator should run after user generators
379
- ...coreGenerators.ssr ? [coreGenerators.ssr] : [],
380
- // and ssg generator should run last
381
- ...coreGenerators.ssg ? [coreGenerators.ssg] : []
382
- ];
1710
+ const { generators = [] } = sourceFolder.config;
1711
+ const coreGenerators = {};
1712
+ const userGenerators = [];
1713
+ for (const base of generators) if (base.meta.slot) coreGenerators[base.meta.slot] = base;
1714
+ else userGenerators.push(base);
1715
+ return [
1716
+ src_default(),
1717
+ ...coreGenerators.api ? [coreGenerators.api] : [],
1718
+ ...coreGenerators.fetch && coreGenerators.api ? [coreGenerators.fetch] : [],
1719
+ ...userGenerators,
1720
+ ...coreGenerators.ssr ? [coreGenerators.ssr] : [],
1721
+ ...coreGenerators.ssg ? [coreGenerators.ssg] : []
1722
+ ];
383
1723
  };
384
1724
  var eventFactory = async (sourceFolder) => {
385
- const { resolvers, resolversFactory } = await routesFactory(
386
- sourceFolder,
387
- cacheFactory
388
- );
389
- const { createPath } = pathResolver2(sourceFolder);
390
- const generators = [];
391
- for (const base of folderGenerators(sourceFolder)) {
392
- const factory = base.factory(sourceFolder);
393
- generators.push({ name: base.meta.name, factory });
394
- }
395
- const resolvedRoutes = /* @__PURE__ */ new Map();
396
- const runGenerators = async (event) => {
397
- const entries = Array.from(resolvedRoutes.values());
398
- for (const { name, factory } of generators) {
399
- try {
400
- await factory.watch?.(entries, event);
401
- } catch (error) {
402
- console.error(
403
- styleText("red", `${sourceFolder.name}: ${name} generator failed`)
404
- );
405
- if (event) {
406
- console.error(event);
407
- }
408
- console.error(error);
409
- }
410
- }
411
- };
412
- const updateResolvedEntry = async (file) => {
413
- const resolver = resolvers.get(file);
414
- if (!resolver) {
415
- return;
416
- }
417
- try {
418
- const resolvedEntry = await resolver.handler(file);
419
- resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
420
- return resolvedEntry;
421
- } catch (error) {
422
- const route = file.replace(`${createPath.api()}/`, "");
423
- console.error(
424
- styleText(
425
- "red",
426
- `${sourceFolder.name}: ${route} route resolution failed`
427
- )
428
- );
429
- console.error(error);
430
- return;
431
- }
432
- };
433
- {
434
- const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
435
- for (const { name, handler } of resolvers.values()) {
436
- spinner.append(
437
- `[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`
438
- );
439
- const route = await handler();
440
- resolvedRoutes.set(route.entry.fileFullpath, route);
441
- }
442
- spinner.succeed("ready \u2728");
443
- }
444
- await runGenerators();
445
- return {
446
- async add(file) {
447
- const [resolver] = resolversFactory([file]).values();
448
- if (!resolver) {
449
- return;
450
- }
451
- resolvers.set(file, resolver);
452
- const resolvedEntry = await updateResolvedEntry(file);
453
- if (resolvedEntry) {
454
- await runGenerators({ kind: "create", file });
455
- }
456
- },
457
- async change(file) {
458
- if (resolvedRoutes.has(file)) {
459
- await updateResolvedEntry(file);
460
- } else {
461
- const relatedRoutes = resolvedRoutes.values().flatMap(({ kind, entry }) => {
462
- return kind === "apiRoute" ? entry.referencedFiles.includes(file) ? [entry] : [] : [];
463
- });
464
- for (const route of relatedRoutes) {
465
- await updateResolvedEntry(route.fileFullpath);
466
- }
467
- }
468
- await runGenerators({ kind: "update", file });
469
- },
470
- async unlink(file) {
471
- resolvers.delete(file);
472
- resolvedRoutes.delete(file);
473
- await runGenerators({ kind: "delete", file });
474
- }
475
- };
476
- };
477
- var matchersFactory = ({
478
- baseurl,
479
- apiurl
480
- }) => {
481
- const basePattern = new RegExp(`^${baseurl}($|/*)`);
482
- const apiPattern = new RegExp(`^${join(baseurl, apiurl)}($|/*)`);
483
- return {
484
- base(req) {
485
- return apiPattern.test(req.url) ? false : basePattern.test(req.url);
486
- },
487
- api(req) {
488
- return apiPattern.test(req.url);
489
- }
490
- };
1725
+ const { resolvers, resolversFactory } = await routesFactory(sourceFolder, cacheFactory);
1726
+ const { createPath } = pathResolver$1(sourceFolder);
1727
+ const generators = [];
1728
+ for (const base of folderGenerators(sourceFolder)) {
1729
+ const factory = base.factory(sourceFolder);
1730
+ generators.push({
1731
+ name: base.meta.name,
1732
+ factory
1733
+ });
1734
+ }
1735
+ const resolvedRoutes = /* @__PURE__ */ new Map();
1736
+ const runGenerators = async (event) => {
1737
+ /**
1738
+ * Watch handlers receive the full list of entries
1739
+ * and should process only those whose source file or dependencies were updated.
1740
+ * */
1741
+ const entries = Array.from(resolvedRoutes.values());
1742
+ for (const { name, factory } of generators) try {
1743
+ await factory.watch?.(entries, event);
1744
+ } catch (error) {
1745
+ console.error(styleText("red", `${sourceFolder.name}: ${name} generator failed`));
1746
+ if (event) console.error(event);
1747
+ console.error(error);
1748
+ }
1749
+ };
1750
+ const updateResolvedEntry = async (file) => {
1751
+ const resolver = resolvers.get(file);
1752
+ if (!resolver) return;
1753
+ try {
1754
+ const resolvedEntry = await resolver.handler(file);
1755
+ resolvedRoutes.set(resolvedEntry.entry.fileFullpath, resolvedEntry);
1756
+ return resolvedEntry;
1757
+ } catch (error) {
1758
+ const route = file.replace(`${createPath.api()}/`, "");
1759
+ console.error(styleText("red", `${sourceFolder.name}: ${route} route resolution failed`));
1760
+ console.error(error);
1761
+ return;
1762
+ }
1763
+ };
1764
+ {
1765
+ const spinner = spinnerFactory(`${sourceFolder.name}: resolving routes`);
1766
+ for (const { name, handler } of resolvers.values()) {
1767
+ spinner.append(`[ ${resolvedRoutes.size + 1} of ${resolvers.size} ] ${name}`);
1768
+ const route = await handler();
1769
+ resolvedRoutes.set(route.entry.fileFullpath, route);
1770
+ }
1771
+ spinner.succeed("ready ✨");
1772
+ }
1773
+ await runGenerators();
1774
+ return {
1775
+ async add(file) {
1776
+ const [resolver] = resolversFactory([file]).values();
1777
+ if (!resolver) return;
1778
+ resolvers.set(file, resolver);
1779
+ if (await updateResolvedEntry(file)) await runGenerators({
1780
+ kind: "create",
1781
+ file
1782
+ });
1783
+ },
1784
+ async change(file) {
1785
+ if (resolvedRoutes.has(file)) await updateResolvedEntry(file);
1786
+ else {
1787
+ const relatedRoutes = resolvedRoutes.values().flatMap(({ kind, entry }) => {
1788
+ return kind === "apiRoute" ? entry.referencedFiles.includes(file) ? [entry] : [] : [];
1789
+ });
1790
+ for (const route of relatedRoutes) await updateResolvedEntry(route.fileFullpath);
1791
+ }
1792
+ await runGenerators({
1793
+ kind: "update",
1794
+ file
1795
+ });
1796
+ },
1797
+ async unlink(file) {
1798
+ resolvers.delete(file);
1799
+ resolvedRoutes.delete(file);
1800
+ await runGenerators({
1801
+ kind: "delete",
1802
+ file
1803
+ });
1804
+ }
1805
+ };
1806
+ };
1807
+ var matchersFactory = ({ config }) => {
1808
+ const basePattern = new RegExp(`^${config.base}($|/*)`);
1809
+ const apiPattern = new RegExp(`^${join(config.base, config.apiBase)}($|/*)`);
1810
+ return {
1811
+ base(req) {
1812
+ return apiPattern.test(req.url) ? false : basePattern.test(req.url);
1813
+ },
1814
+ api(req) {
1815
+ return apiPattern.test(req.url);
1816
+ }
1817
+ };
491
1818
  };
492
1819
  var findFreePort = async (devPort) => {
493
- let minPort = 0;
494
- let maxPort = 0;
495
- for (const n of [3, 2, 1, ""]) {
496
- minPort = Number(`${n}${devPort}`) + 100;
497
- maxPort = minPort + 100;
498
- if (maxPort < 65e3) {
499
- break;
500
- }
501
- }
502
- if (maxPort >= 65e3) {
503
- throw new Error("the devPort in package.json should be less than 64000");
504
- }
505
- const range = maxPort - minPort + 1;
506
- const startOffset = Math.floor(Math.random() * range);
507
- const ports = Array.from({ length: range }, (_, i) => {
508
- return minPort + (startOffset + i) % range;
509
- });
510
- const freePort = await ports.reduce(
511
- async (prevPromise, port) => {
512
- const freePort2 = await prevPromise;
513
- if (freePort2) {
514
- return freePort2;
515
- }
516
- const isFree = await isPortFree(port);
517
- return isFree ? port : void 0;
518
- },
519
- Promise.resolve(void 0)
520
- );
521
- if (!freePort) {
522
- throw new Error(`No free ports found in range ${minPort}-${maxPort}`);
523
- }
524
- return freePort;
1820
+ let minPort = 0;
1821
+ let maxPort = 0;
1822
+ for (const n of [
1823
+ 3,
1824
+ 2,
1825
+ 1,
1826
+ ""
1827
+ ]) {
1828
+ minPort = Number(`${n}${devPort}`) + 100;
1829
+ maxPort = minPort + 100;
1830
+ if (maxPort < 65e3) break;
1831
+ }
1832
+ if (maxPort >= 65e3) throw new Error("the devPort in package.json should be less than 64000");
1833
+ const range = maxPort - minPort + 1;
1834
+ const startOffset = Math.floor(Math.random() * range);
1835
+ const freePort = await Array.from({ length: range }, (_, i) => {
1836
+ return minPort + (startOffset + i) % range;
1837
+ }).reduce(async (prevPromise, port) => {
1838
+ const freePort = await prevPromise;
1839
+ if (freePort) return freePort;
1840
+ return await isPortFree(port) ? port : void 0;
1841
+ }, Promise.resolve(void 0));
1842
+ if (!freePort) throw new Error(`No free ports found in range ${minPort}-${maxPort}`);
1843
+ return freePort;
525
1844
  };
526
1845
  var isPortFree = (port) => {
527
- return new Promise((resolve3) => {
528
- const server = net.createServer();
529
- server.once("error", () => resolve3(false));
530
- server.once("listening", () => {
531
- server.close();
532
- resolve3(true);
533
- });
534
- server.listen(port, "127.0.0.1");
535
- });
536
- };
537
- export {
538
- chassis_default as default
539
- };
540
- //# sourceMappingURL=chassis.js.map
1846
+ return new Promise((resolve) => {
1847
+ const server = net.createServer();
1848
+ server.once("error", () => resolve(false));
1849
+ server.once("listening", () => {
1850
+ server.close();
1851
+ resolve(true);
1852
+ });
1853
+ server.listen(port, "127.0.0.1");
1854
+ });
1855
+ };
1856
+ //#endregion
1857
+ export { chassis_default as default };
1858
+
1859
+ //# sourceMappingURL=chassis.js.map