@hexaijs/plugin-contracts-generator 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/README.md +33 -25
  2. package/dist/ast-utils.d.ts +6 -0
  3. package/dist/ast-utils.d.ts.map +1 -0
  4. package/dist/ast-utils.js +73 -0
  5. package/dist/ast-utils.js.map +1 -0
  6. package/dist/class-analyzer.d.ts +16 -0
  7. package/dist/class-analyzer.d.ts.map +1 -0
  8. package/dist/class-analyzer.js +114 -0
  9. package/dist/class-analyzer.js.map +1 -0
  10. package/dist/cli.d.ts +32 -1
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +226 -2480
  13. package/dist/cli.js.map +1 -1
  14. package/dist/config-loader.d.ts +24 -0
  15. package/dist/config-loader.d.ts.map +1 -0
  16. package/dist/config-loader.js +126 -0
  17. package/dist/config-loader.js.map +1 -0
  18. package/dist/context-config.d.ts +44 -0
  19. package/dist/context-config.d.ts.map +1 -0
  20. package/dist/context-config.js +140 -0
  21. package/dist/context-config.js.map +1 -0
  22. package/dist/domain/index.d.ts +2 -0
  23. package/dist/domain/index.d.ts.map +1 -0
  24. package/dist/domain/index.js +2 -0
  25. package/dist/domain/index.js.map +1 -0
  26. package/dist/domain/types.d.ts +182 -0
  27. package/dist/domain/types.d.ts.map +1 -0
  28. package/dist/domain/types.js +49 -0
  29. package/dist/domain/types.js.map +1 -0
  30. package/dist/errors.d.ts +72 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +112 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/file-copier.d.ts +84 -0
  35. package/dist/file-copier.d.ts.map +1 -0
  36. package/dist/file-copier.js +721 -0
  37. package/dist/file-copier.js.map +1 -0
  38. package/dist/file-graph-resolver.d.ts +42 -0
  39. package/dist/file-graph-resolver.d.ts.map +1 -0
  40. package/dist/file-graph-resolver.js +138 -0
  41. package/dist/file-graph-resolver.js.map +1 -0
  42. package/dist/file-system.d.ts +26 -0
  43. package/dist/file-system.d.ts.map +1 -0
  44. package/dist/file-system.js +30 -0
  45. package/dist/file-system.js.map +1 -0
  46. package/dist/hexai-plugin.d.ts +16 -0
  47. package/dist/hexai-plugin.d.ts.map +1 -0
  48. package/dist/hexai-plugin.js +56 -0
  49. package/dist/hexai-plugin.js.map +1 -0
  50. package/dist/import-analyzer.d.ts +6 -0
  51. package/dist/import-analyzer.d.ts.map +1 -0
  52. package/dist/import-analyzer.js +39 -0
  53. package/dist/import-analyzer.js.map +1 -0
  54. package/dist/index.d.ts +39 -531
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +56 -2739
  57. package/dist/index.js.map +1 -1
  58. package/dist/logger.d.ts +21 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +43 -0
  61. package/dist/logger.js.map +1 -0
  62. package/dist/parser.d.ts +32 -0
  63. package/dist/parser.d.ts.map +1 -0
  64. package/dist/parser.js +202 -0
  65. package/dist/parser.js.map +1 -0
  66. package/dist/pipeline.d.ts +57 -0
  67. package/dist/pipeline.d.ts.map +1 -0
  68. package/dist/pipeline.js +148 -0
  69. package/dist/pipeline.js.map +1 -0
  70. package/dist/reexport-generator.d.ts +81 -0
  71. package/dist/reexport-generator.d.ts.map +1 -0
  72. package/dist/reexport-generator.js +171 -0
  73. package/dist/reexport-generator.js.map +1 -0
  74. package/dist/registry-generator.d.ts +27 -0
  75. package/dist/registry-generator.d.ts.map +1 -0
  76. package/dist/registry-generator.js +104 -0
  77. package/dist/registry-generator.js.map +1 -0
  78. package/dist/runtime/index.d.ts +2 -23
  79. package/dist/runtime/index.d.ts.map +1 -0
  80. package/dist/runtime/index.js +1 -38
  81. package/dist/runtime/index.js.map +1 -1
  82. package/dist/runtime/message-registry.d.ts +23 -0
  83. package/dist/runtime/message-registry.d.ts.map +1 -0
  84. package/dist/runtime/message-registry.js +35 -0
  85. package/dist/runtime/message-registry.js.map +1 -0
  86. package/dist/scanner.d.ts +21 -0
  87. package/dist/scanner.d.ts.map +1 -0
  88. package/dist/scanner.js +49 -0
  89. package/dist/scanner.js.map +1 -0
  90. package/dist/test-utils.d.ts +23 -0
  91. package/dist/test-utils.d.ts.map +1 -0
  92. package/dist/test-utils.js +188 -0
  93. package/dist/test-utils.js.map +1 -0
  94. package/package.json +69 -64
  95. package/dist/cli-CPg-O4OY.d.ts +0 -214
  96. package/dist/decorators/index.d.ts +0 -144
  97. package/dist/decorators/index.js +0 -28
  98. package/dist/decorators/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,2744 +1,61 @@
1
- import 'reflect-metadata';
2
- import { glob } from 'glob';
3
- import { readFile, readdir, writeFile, mkdir, access, stat } from 'fs/promises';
4
- import { constants } from 'fs';
5
- import * as ts6 from 'typescript';
6
- import ts6__default from 'typescript';
7
- import * as path3 from 'path';
8
- import path3__default, { resolve, dirname, basename, relative, join } from 'path';
9
- import { minimatch } from 'minimatch';
10
- import { fileURLToPath } from 'url';
11
-
12
- // src/domain/types.ts
13
- var DEFAULT_DECORATOR_NAMES = {
14
- event: "PublicEvent",
15
- command: "PublicCommand",
16
- query: "PublicQuery"
17
- };
18
- function mergeDecoratorNames(partial) {
19
- return {
20
- ...DEFAULT_DECORATOR_NAMES,
21
- ...partial
22
- };
23
- }
24
- function isPrimitiveType(type) {
25
- return type.kind === "primitive";
26
- }
27
- function isArrayType(type) {
28
- return type.kind === "array";
29
- }
30
- function isObjectType(type) {
31
- return type.kind === "object";
32
- }
33
- function isUnionType(type) {
34
- return type.kind === "union";
35
- }
36
- function isIntersectionType(type) {
37
- return type.kind === "intersection";
38
- }
39
- function isReferenceType(type) {
40
- return type.kind === "reference";
41
- }
42
- function isLiteralType(type) {
43
- return type.kind === "literal";
44
- }
45
- function isTupleType(type) {
46
- return type.kind === "tuple";
47
- }
48
- function isFunctionType(type) {
49
- return type.kind === "function";
50
- }
51
- function isDomainEvent(message) {
52
- return message.messageType === "event";
53
- }
54
- function isCommand(message) {
55
- return message.messageType === "command";
56
- }
57
- var PUBLIC_EVENT_METADATA = /* @__PURE__ */ Symbol("publicEvent");
58
- var PUBLIC_COMMAND_METADATA = /* @__PURE__ */ Symbol("publicCommand");
59
- var PUBLIC_QUERY_METADATA = /* @__PURE__ */ Symbol("publicQuery");
60
- function PublicEvent(options = {}) {
61
- return (target) => {
62
- Reflect.defineMetadata(PUBLIC_EVENT_METADATA, options, target);
63
- return target;
64
- };
65
- }
66
- function PublicCommand(options = {}) {
67
- return (target) => {
68
- Reflect.defineMetadata(PUBLIC_COMMAND_METADATA, options, target);
69
- return target;
70
- };
71
- }
72
- function PublicQuery(options = {}) {
73
- return (target) => {
74
- Reflect.defineMetadata(PUBLIC_QUERY_METADATA, options, target);
75
- return target;
76
- };
77
- }
78
-
79
- // src/errors.ts
80
- var MessageParserError = class extends Error {
81
- constructor(message, options) {
82
- super(message, options);
83
- this.name = "MessageParserError";
84
- }
85
- };
86
- var ConfigurationError = class extends MessageParserError {
87
- constructor(message, options) {
88
- super(message, options);
89
- this.name = "ConfigurationError";
90
- }
91
- };
92
- var ConfigLoadError = class extends ConfigurationError {
93
- constructor(message, options) {
94
- super(message, options);
95
- this.name = "ConfigLoadError";
96
- }
97
- };
98
- var FileSystemError = class extends MessageParserError {
99
- path;
100
- constructor(message, path5, options) {
101
- super(message, options);
102
- this.name = "FileSystemError";
103
- this.path = path5;
104
- }
105
- };
106
- var FileNotFoundError = class extends FileSystemError {
107
- constructor(path5, options) {
108
- super(`File not found: ${path5}`, path5, options);
109
- this.name = "FileNotFoundError";
110
- }
111
- };
112
- var FileReadError = class extends FileSystemError {
113
- constructor(path5, options) {
114
- super(`Failed to read file: ${path5}`, path5, options);
115
- this.name = "FileReadError";
116
- }
117
- };
118
- var FileWriteError = class extends FileSystemError {
119
- constructor(path5, options) {
120
- super(`Failed to write file: ${path5}`, path5, options);
121
- this.name = "FileWriteError";
122
- }
123
- };
124
- var ParseError = class extends MessageParserError {
125
- constructor(message, options) {
126
- super(message, options);
127
- this.name = "ParseError";
128
- }
129
- };
130
- var JsonParseError = class extends ParseError {
131
- filePath;
132
- constructor(message, filePath, options) {
133
- const fullMessage = filePath ? `Failed to parse JSON in ${filePath}: ${message}` : `Failed to parse JSON: ${message}`;
134
- super(fullMessage, options);
135
- this.name = "JsonParseError";
136
- this.filePath = filePath;
137
- }
138
- };
139
- var ResolutionError = class extends MessageParserError {
140
- constructor(message, options) {
141
- super(message, options);
142
- this.name = "ResolutionError";
143
- }
144
- };
145
- var ModuleResolutionError = class extends ResolutionError {
146
- moduleSpecifier;
147
- fromFile;
148
- constructor(moduleSpecifier, fromFile, options) {
149
- super(
150
- `Failed to resolve module '${moduleSpecifier}' from '${fromFile}'`,
151
- options
152
- );
153
- this.name = "ModuleResolutionError";
154
- this.moduleSpecifier = moduleSpecifier;
155
- this.fromFile = fromFile;
156
- }
157
- };
158
- var NodeFileSystem = class {
159
- async readFile(path5) {
160
- return readFile(path5, "utf-8");
161
- }
162
- async readdir(path5) {
163
- return readdir(path5);
164
- }
165
- async writeFile(path5, content) {
166
- await writeFile(path5, content);
167
- }
168
- async mkdir(path5, options) {
169
- await mkdir(path5, options);
170
- }
171
- async exists(path5) {
172
- try {
173
- await access(path5, constants.F_OK);
174
- return true;
175
- } catch {
176
- return false;
177
- }
178
- }
179
- async stat(path5) {
180
- return stat(path5);
181
- }
182
- };
183
- var nodeFileSystem = new NodeFileSystem();
184
-
185
- // src/scanner.ts
186
- var DEFAULT_EXCLUDE_PATTERNS = [
187
- "**/node_modules/**",
188
- "**/dist/**",
189
- "**/*.d.ts",
190
- "**/*.test.ts",
191
- "**/*.spec.ts"
192
- ];
193
- var Scanner = class {
194
- exclude;
195
- fs;
196
- decoratorPatterns;
197
- constructor(options = {}) {
198
- this.exclude = options.exclude ?? DEFAULT_EXCLUDE_PATTERNS;
199
- this.fs = options.fileSystem ?? nodeFileSystem;
200
- const names = mergeDecoratorNames(options.decoratorNames);
201
- const messageTypes = options.messageTypes ?? ["event", "command", "query"];
202
- this.decoratorPatterns = messageTypes.map((type) => {
203
- const decoratorName = names[type];
204
- return `@${decoratorName}(`;
205
- });
206
- }
207
- async scan(sourceDir) {
208
- const files = await glob(`${sourceDir}/**/*.ts`, {
209
- ignore: this.exclude
210
- });
211
- const result = [];
212
- for (const file of files) {
213
- let content;
214
- try {
215
- content = await this.fs.readFile(file);
216
- } catch (error) {
217
- throw new FileReadError(file, { cause: error });
218
- }
219
- if (this.containsPublicDecorator(content)) {
220
- result.push(file);
221
- }
222
- }
223
- return result;
224
- }
225
- containsPublicDecorator(content) {
226
- return this.decoratorPatterns.some((pattern) => content.includes(pattern));
227
- }
228
- };
229
- function parseTypeNode(typeNode) {
230
- if (typeNode.kind === ts6.SyntaxKind.StringKeyword) {
231
- return { kind: "primitive", name: "string" };
232
- }
233
- if (typeNode.kind === ts6.SyntaxKind.NumberKeyword) {
234
- return { kind: "primitive", name: "number" };
235
- }
236
- if (typeNode.kind === ts6.SyntaxKind.BooleanKeyword) {
237
- return { kind: "primitive", name: "boolean" };
238
- }
239
- if (ts6.isArrayTypeNode(typeNode)) {
240
- const elementType = parseTypeNode(typeNode.elementType);
241
- return { kind: "array", elementType };
242
- }
243
- if (ts6.isTypeReferenceNode(typeNode)) {
244
- const name = typeNode.typeName.getText();
245
- const typeArguments = typeNode.typeArguments ? typeNode.typeArguments.map((t) => parseTypeNode(t)) : void 0;
246
- return { kind: "reference", name, typeArguments };
247
- }
248
- if (ts6.isIntersectionTypeNode(typeNode)) {
249
- const types = typeNode.types.map((t) => parseTypeNode(t));
250
- return { kind: "intersection", types };
251
- }
252
- if (ts6.isUnionTypeNode(typeNode)) {
253
- const types = typeNode.types.map((t) => parseTypeNode(t));
254
- return { kind: "union", types };
255
- }
256
- if (ts6.isTypeLiteralNode(typeNode)) {
257
- const fields = extractFieldsFromMembers(typeNode.members);
258
- return { kind: "object", fields };
259
- }
260
- return { kind: "reference", name: typeNode.getText() };
261
- }
262
- function extractFieldsFromMembers(members) {
263
- const fields = [];
264
- for (const member of members) {
265
- if (ts6.isPropertySignature(member) && member.name) {
266
- const fieldName = member.name.getText();
267
- const fieldType = member.type ? parseTypeNode(member.type) : { kind: "primitive", name: "any" };
268
- const optional = !!member.questionToken;
269
- const readonly = member.modifiers?.some(
270
- (m) => m.kind === ts6.SyntaxKind.ReadonlyKeyword
271
- ) ?? false;
272
- fields.push({
273
- name: fieldName,
274
- type: fieldType,
275
- optional,
276
- readonly
277
- });
278
- }
279
- }
280
- return fields;
281
- }
282
- function hasDecorator(node, decoratorName) {
283
- const decorators = ts6.getDecorators(node);
284
- if (!decorators) return false;
285
- return decorators.some((decorator) => {
286
- if (ts6.isCallExpression(decorator.expression)) {
287
- const expr = decorator.expression.expression;
288
- if (ts6.isIdentifier(expr)) {
289
- return expr.text === decoratorName;
290
- }
291
- }
292
- return false;
293
- });
294
- }
295
- function getDecoratorOptions(node, decoratorName) {
296
- const decorators = ts6.getDecorators(node);
297
- if (!decorators) return void 0;
298
- for (const decorator of decorators) {
299
- if (!ts6.isCallExpression(decorator.expression)) continue;
300
- const expr = decorator.expression.expression;
301
- if (!ts6.isIdentifier(expr) || expr.text !== decoratorName) continue;
302
- const args = decorator.expression.arguments;
303
- if (args.length === 0) return {};
304
- const firstArg = args[0];
305
- if (!ts6.isObjectLiteralExpression(firstArg)) return {};
306
- const options = {};
307
- for (const prop of firstArg.properties) {
308
- if (!ts6.isPropertyAssignment(prop)) continue;
309
- if (!ts6.isIdentifier(prop.name)) continue;
310
- const propName = prop.name.text;
311
- if (propName === "response" && ts6.isStringLiteral(prop.initializer)) {
312
- options.response = prop.initializer.text;
313
- } else if (propName === "context" && ts6.isStringLiteral(prop.initializer)) {
314
- options.context = prop.initializer.text;
315
- }
316
- }
317
- return options;
318
- }
319
- return void 0;
320
- }
321
- function hasExportModifier(node) {
322
- const modifiers = ts6.canHaveModifiers(node) ? ts6.getModifiers(node) : void 0;
323
- return modifiers?.some((m) => m.kind === ts6.SyntaxKind.ExportKeyword) ?? false;
324
- }
325
- function extractClassSourceText(node, sourceCode) {
326
- const fullStart = node.getFullStart();
327
- const end = node.getEnd();
328
- let sourceText = sourceCode.slice(fullStart, end);
329
- sourceText = sourceText.replace(/^\s*\n/, "");
330
- return sourceText;
331
- }
332
- function getBaseClassName(node) {
333
- if (!node.heritageClauses) return void 0;
334
- for (const clause of node.heritageClauses) {
335
- if (clause.token === ts6.SyntaxKind.ExtendsKeyword) {
336
- const firstType = clause.types[0];
337
- if (firstType && ts6.isExpressionWithTypeArguments(firstType)) {
338
- const expr = firstType.expression;
339
- if (ts6.isIdentifier(expr)) {
340
- return expr.text;
341
- }
342
- }
343
- }
344
- }
345
- return void 0;
346
- }
347
- function isExternalModule(moduleSpecifier) {
348
- return !moduleSpecifier.startsWith(".");
349
- }
350
- function extractImports(sourceFile) {
351
- const imports = [];
352
- ts6.forEachChild(sourceFile, (node) => {
353
- if (ts6.isImportDeclaration(node) && node.importClause) {
354
- const moduleSpecifier = node.moduleSpecifier.text;
355
- const isExternal = isExternalModule(moduleSpecifier);
356
- const isTypeOnly = node.importClause.isTypeOnly ?? false;
357
- const importedNames = extractImportedNames(node.importClause);
358
- if (importedNames.length > 0) {
359
- imports.push({
360
- names: importedNames,
361
- source: moduleSpecifier,
362
- isTypeOnly,
363
- isExternal
364
- });
365
- }
366
- }
367
- });
368
- return imports;
369
- }
370
- function extractImportedNames(importClause) {
371
- const names = [];
372
- if (importClause.name) {
373
- names.push(importClause.name.text);
374
- }
375
- if (importClause.namedBindings) {
376
- if (ts6.isNamedImports(importClause.namedBindings)) {
377
- for (const element of importClause.namedBindings.elements) {
378
- names.push(element.name.text);
379
- }
380
- }
381
- }
382
- return names;
383
- }
384
-
385
- // src/parser.ts
386
- var PAYLOAD_TYPE_ARGUMENT_INDEX = 0;
387
- function buildDecoratorMappings(decoratorNames) {
388
- return [
389
- { decorator: decoratorNames.event, messageType: "event" },
390
- { decorator: decoratorNames.command, messageType: "command" },
391
- { decorator: decoratorNames.query, messageType: "query" }
392
- ];
393
- }
394
- function extractTypeParameterNames(typeParameters) {
395
- if (!typeParameters) return void 0;
396
- return typeParameters.map((tp) => tp.name.text);
397
- }
398
- var Parser = class {
399
- decoratorMappings;
400
- responseNamingConventions;
401
- messageTypes;
402
- constructor(options = {}) {
403
- const names = mergeDecoratorNames(options.decoratorNames);
404
- this.decoratorMappings = buildDecoratorMappings(names);
405
- this.responseNamingConventions = options.responseNamingConventions ?? [];
406
- this.messageTypes = options.messageTypes;
407
- }
408
- parse(sourceCode, sourceFileInfo) {
409
- const tsSourceFile = ts6__default.createSourceFile(
410
- sourceFileInfo.absolutePath,
411
- sourceCode,
412
- ts6__default.ScriptTarget.Latest,
413
- true
414
- );
415
- const events = [];
416
- const commands = [];
417
- const queries = [];
418
- const typeDefinitions = [];
419
- const messageCollectors = {
420
- event: (m) => events.push(m),
421
- command: (m) => commands.push(m),
422
- query: (m) => queries.push(m)
423
- };
424
- const visit = (node) => {
425
- if (ts6__default.isClassDeclaration(node) && node.name) {
426
- this.collectMessagesFromClass(node, sourceCode, tsSourceFile, sourceFileInfo, messageCollectors);
427
- }
428
- if (ts6__default.isTypeAliasDeclaration(node) && node.name) {
429
- typeDefinitions.push(this.extractTypeDefinition(node, sourceFileInfo));
430
- }
431
- if (ts6__default.isInterfaceDeclaration(node) && node.name) {
432
- typeDefinitions.push(this.extractInterfaceDefinition(node, sourceFileInfo));
433
- }
434
- ts6__default.forEachChild(node, visit);
435
- };
436
- visit(tsSourceFile);
437
- this.applyNamingConventionMatching(commands, typeDefinitions);
438
- this.applyNamingConventionMatching(queries, typeDefinitions);
439
- return { events, commands, queries, typeDefinitions };
440
- }
441
- collectMessagesFromClass(node, sourceCode, tsSourceFile, sourceFileInfo, collectors) {
442
- for (const { decorator, messageType } of this.decoratorMappings) {
443
- if (!hasDecorator(node, decorator)) continue;
444
- if (this.messageTypes && !this.messageTypes.includes(messageType)) continue;
445
- const message = this.buildMessage(node, sourceCode, tsSourceFile, sourceFileInfo, messageType, decorator);
446
- collectors[messageType](message);
447
- }
448
- }
449
- applyNamingConventionMatching(messages, typeDefinitions) {
450
- for (const message of messages) {
451
- const hasExplicitResultType = Boolean(message.resultType);
452
- if (hasExplicitResultType) continue;
453
- const matchedTypeName = this.findMatchingResponseType(message.name, typeDefinitions);
454
- if (matchedTypeName) {
455
- message.resultType = {
456
- kind: "reference",
457
- name: matchedTypeName
458
- };
459
- }
460
- }
461
- }
462
- findMatchingResponseType(messageName, typeDefinitions) {
463
- for (const convention of this.responseNamingConventions) {
464
- const hasSuffix = messageName.endsWith(convention.messageSuffix);
465
- if (!hasSuffix) continue;
466
- const messagePrefix = messageName.slice(0, -convention.messageSuffix.length);
467
- const expectedResponseName = messagePrefix + convention.responseSuffix;
468
- const matchingType = typeDefinitions.find((t) => t.name === expectedResponseName);
469
- if (matchingType) return matchingType.name;
470
- }
471
- return void 0;
472
- }
473
- buildMessage(node, sourceCode, tsSourceFile, sourceFileInfo, messageType, decoratorName) {
474
- const extracted = this.extractMessageDetails(node, sourceCode, tsSourceFile);
475
- const decoratorOptions = getDecoratorOptions(node, decoratorName);
476
- const baseMessage = {
477
- name: node.name.text,
478
- messageType,
479
- sourceFile: sourceFileInfo,
480
- fields: extracted.fields,
481
- payloadType: extracted.payloadType,
482
- sourceText: extracted.sourceText,
483
- imports: extracted.imports,
484
- baseClass: extracted.baseClass
485
- };
486
- const explicitResponseName = decoratorOptions?.response;
487
- const supportsResultType = messageType === "command" || messageType === "query";
488
- if (!supportsResultType || !explicitResponseName) {
489
- return baseMessage;
490
- }
491
- return {
492
- ...baseMessage,
493
- resultType: {
494
- kind: "reference",
495
- name: explicitResponseName
496
- }
497
- };
498
- }
499
- extractMessageDetails(node, sourceCode, tsSourceFile) {
500
- const { fields, payloadType } = this.extractPayload(node);
501
- const sourceText = extractClassSourceText(node, sourceCode);
502
- const imports = extractImports(tsSourceFile);
503
- const baseClass = getBaseClassName(node);
504
- return {
505
- fields,
506
- payloadType,
507
- sourceText,
508
- imports,
509
- baseClass
510
- };
511
- }
512
- extractPayload(node) {
513
- const emptyPayload = { fields: [] };
514
- if (!node.heritageClauses) return emptyPayload;
515
- const extendsClause = node.heritageClauses.find(
516
- (clause) => clause.token === ts6__default.SyntaxKind.ExtendsKeyword
517
- );
518
- if (!extendsClause) return emptyPayload;
519
- for (const type of extendsClause.types) {
520
- const typeArgs = type.typeArguments;
521
- if (!typeArgs || typeArgs.length === 0) continue;
522
- const payloadTypeArg = typeArgs[PAYLOAD_TYPE_ARGUMENT_INDEX];
523
- const extractedPayload = this.parsePayloadTypeArgument(payloadTypeArg);
524
- if (extractedPayload) return extractedPayload;
525
- }
526
- return emptyPayload;
527
- }
528
- parsePayloadTypeArgument(typeArg) {
529
- if (ts6__default.isTypeReferenceNode(typeArg) && ts6__default.isIdentifier(typeArg.typeName)) {
530
- return {
531
- fields: [],
532
- payloadType: parseTypeNode(typeArg)
533
- };
534
- }
535
- if (ts6__default.isTypeLiteralNode(typeArg)) {
536
- return {
537
- fields: extractFieldsFromMembers(typeArg.members)
538
- };
539
- }
540
- if (ts6__default.isIntersectionTypeNode(typeArg)) {
541
- const parsedType = parseTypeNode(typeArg);
542
- return {
543
- fields: this.flattenIntersectionToFields(parsedType),
544
- payloadType: parsedType
545
- };
546
- }
547
- return void 0;
548
- }
549
- flattenIntersectionToFields(intersection) {
550
- const fields = [];
551
- for (const type of intersection.types) {
552
- if (type.kind === "object") {
553
- fields.push(...type.fields);
554
- }
555
- }
556
- return fields;
557
- }
558
- extractTypeDefinition(node, sourceFileInfo) {
559
- return this.buildTypeDefinition(
560
- node.name.text,
561
- "type",
562
- sourceFileInfo,
563
- parseTypeNode(node.type),
564
- node.typeParameters,
565
- node
566
- );
567
- }
568
- extractInterfaceDefinition(node, sourceFileInfo) {
569
- const body = {
570
- kind: "object",
571
- fields: extractFieldsFromMembers(node.members)
572
- };
573
- return this.buildTypeDefinition(
574
- node.name.text,
575
- "interface",
576
- sourceFileInfo,
577
- body,
578
- node.typeParameters,
579
- node
580
- );
581
- }
582
- buildTypeDefinition(name, kind, sourceFile, body, typeParameters, node) {
583
- return {
584
- name,
585
- kind,
586
- sourceFile,
587
- body,
588
- typeParameters: extractTypeParameterNames(typeParameters),
589
- exported: hasExportModifier(node)
590
- };
591
- }
592
- };
593
- var TYPESCRIPT_EXTENSIONS = [".ts", ".tsx"];
594
- var INDEX_FILE = "index.ts";
595
- var FileGraphResolver = class _FileGraphResolver {
596
- contextConfig;
597
- fs;
598
- excludeDependencies;
599
- constructor(contextConfig, fileSystem, excludeDependencies) {
600
- this.contextConfig = contextConfig;
601
- this.fs = fileSystem;
602
- this.excludeDependencies = excludeDependencies;
603
- }
604
- static create(options) {
605
- const fs = options.fileSystem ?? nodeFileSystem;
606
- const excludeDependencies = options.excludeDependencies ?? [];
607
- return new _FileGraphResolver(options.contextConfig, fs, excludeDependencies);
608
- }
609
- async buildGraph(entryPoints, sourceRoot) {
610
- const nodes = /* @__PURE__ */ new Map();
611
- const entryPointSet = new Set(entryPoints);
612
- const excludedPaths = /* @__PURE__ */ new Set();
613
- const visited = /* @__PURE__ */ new Set();
614
- const queue = [...entryPoints];
615
- while (queue.length > 0) {
616
- const filePath = queue.shift();
617
- if (visited.has(filePath)) {
618
- continue;
619
- }
620
- visited.add(filePath);
621
- const imports = await this.extractImports(filePath);
622
- const node = {
623
- absolutePath: filePath,
624
- relativePath: path3__default.relative(sourceRoot, filePath),
625
- imports,
626
- isEntryPoint: entryPointSet.has(filePath)
627
- };
628
- nodes.set(filePath, node);
629
- this.queueUnvisitedLocalDependencies(imports, visited, queue, excludedPaths);
630
- }
631
- return {
632
- nodes,
633
- entryPoints: entryPointSet,
634
- excludedPaths
635
- };
636
- }
637
- async extractImports(filePath) {
638
- const sourceCode = await this.readSourceFile(filePath);
639
- const sourceFile = ts6__default.createSourceFile(
640
- filePath,
641
- sourceCode,
642
- ts6__default.ScriptTarget.Latest,
643
- true
644
- );
645
- const imports = [];
646
- for (const node of sourceFile.statements) {
647
- const moduleSpecifier = this.extractModuleSpecifier(node);
648
- if (!moduleSpecifier) {
649
- continue;
650
- }
651
- const resolution = await this.resolveModule(moduleSpecifier, filePath);
652
- const importedNames = this.extractImportedNamesFromNode(node);
653
- imports.push({
654
- moduleSpecifier,
655
- resolvedPath: resolution.resolvedPath,
656
- isExternal: resolution.isExternal,
657
- importedNames
658
- });
659
- }
660
- return imports;
661
- }
662
- async readSourceFile(filePath) {
663
- try {
664
- return await this.fs.readFile(filePath);
665
- } catch (error) {
666
- throw new FileReadError(filePath, { cause: error });
667
- }
668
- }
669
- extractModuleSpecifier(node) {
670
- if (ts6__default.isImportDeclaration(node)) {
671
- return node.moduleSpecifier.text;
672
- }
673
- if (ts6__default.isExportDeclaration(node) && node.moduleSpecifier) {
674
- return node.moduleSpecifier.text;
675
- }
676
- return null;
677
- }
678
- extractImportedNamesFromNode(node) {
679
- if (ts6__default.isImportDeclaration(node) && node.importClause) {
680
- return extractImportedNames(node.importClause);
681
- }
682
- return [];
683
- }
684
- async resolveModule(moduleSpecifier, fromFile) {
685
- if (moduleSpecifier.startsWith(".")) {
686
- const resolvedPath = await this.resolveRelativeModule(
687
- moduleSpecifier,
688
- fromFile
689
- );
690
- return { resolvedPath, isExternal: false };
691
- }
692
- return this.contextConfig.resolvePath(moduleSpecifier);
693
- }
694
- queueUnvisitedLocalDependencies(imports, visited, queue, excludedPaths) {
695
- const localImports = imports.filter(
696
- (importInfo) => !importInfo.isExternal && importInfo.resolvedPath !== null
697
- );
698
- for (const importInfo of localImports) {
699
- const resolvedPath = importInfo.resolvedPath;
700
- if (this.shouldExclude(resolvedPath)) {
701
- excludedPaths.add(resolvedPath);
702
- continue;
703
- }
704
- if (!visited.has(resolvedPath)) {
705
- queue.push(resolvedPath);
706
- }
707
- }
708
- }
709
- shouldExclude(filePath) {
710
- if (this.excludeDependencies.length === 0) {
711
- return false;
712
- }
713
- return this.excludeDependencies.some(
714
- (pattern) => minimatch(filePath, pattern, { matchBase: true })
715
- );
716
- }
717
- async resolveRelativeModule(moduleSpecifier, fromFile) {
718
- const dir = path3__default.dirname(fromFile);
719
- const basePath = path3__default.resolve(dir, moduleSpecifier);
720
- return this.tryResolveWithExtensions(basePath);
721
- }
722
- async tryResolveWithExtensions(basePath) {
723
- const extensionCandidates = TYPESCRIPT_EXTENSIONS.map((ext) => basePath + ext);
724
- const indexCandidate = path3__default.join(basePath, INDEX_FILE);
725
- const candidates = [...extensionCandidates, indexCandidate];
726
- for (const candidate of candidates) {
727
- if (await this.fs.exists(candidate)) {
728
- return candidate;
729
- }
730
- }
731
- return null;
732
- }
733
- };
734
- var CONTRACTS_GENERATOR_MODULE = "@hexaijs/plugin-contracts-generator";
735
- var CONTRACT_DECORATORS = /* @__PURE__ */ new Set([
736
- "PublicCommand",
737
- "PublicEvent",
738
- "PublicQuery"
739
- ]);
740
- var TS_EXTENSION_PATTERN = /\.ts$/;
741
- var REQUEST_SUFFIX = "Request";
742
- var QUERY_SUFFIX = "Query";
743
- var FileCopier = class {
744
- fs;
745
- constructor(options = {}) {
746
- this.fs = options.fileSystem ?? nodeFileSystem;
747
- }
748
- async copyFiles(options) {
749
- const {
750
- sourceRoot,
751
- outputDir,
752
- fileGraph,
753
- pathAliasRewrites,
754
- removeDecorators,
755
- responseTypesToExport,
756
- messageTypes,
757
- decoratorNames
758
- } = options;
759
- const copiedFiles = [];
760
- const rewrittenImports = /* @__PURE__ */ new Map();
761
- const { entryContents, usedLocalImports } = await this.preprocessEntryFiles(
762
- fileGraph,
763
- messageTypes,
764
- decoratorNames,
765
- sourceRoot
766
- );
767
- this.expandTransitiveDependencies(usedLocalImports, fileGraph);
768
- for (const node of fileGraph.nodes.values()) {
769
- const content = await this.resolveNodeContent(
770
- node,
771
- entryContents,
772
- usedLocalImports,
773
- messageTypes
774
- );
775
- if (content === null) {
776
- continue;
777
- }
778
- const transformedContent = this.applyTransformations(
779
- content,
780
- node,
781
- fileGraph,
782
- sourceRoot,
783
- removeDecorators,
784
- responseTypesToExport,
785
- pathAliasRewrites
786
- );
787
- const outputPath = await this.writeOutputFile(
788
- outputDir,
789
- node.relativePath,
790
- transformedContent.content
791
- );
792
- copiedFiles.push(outputPath);
793
- if (transformedContent.rewrites.length > 0) {
794
- rewrittenImports.set(outputPath, transformedContent.rewrites);
795
- }
796
- }
797
- return { copiedFiles, rewrittenImports };
798
- }
799
- async preprocessEntryFiles(fileGraph, messageTypes, decoratorNames, sourceRoot) {
800
- const entryContents = /* @__PURE__ */ new Map();
801
- const usedLocalImports = /* @__PURE__ */ new Set();
802
- if (!messageTypes?.length) {
803
- return { entryContents, usedLocalImports };
804
- }
805
- for (const node of fileGraph.nodes.values()) {
806
- if (!node.isEntryPoint) {
807
- continue;
808
- }
809
- const rawContent = await this.readFileContent(node.absolutePath);
810
- const { content: extractedContent, usedModuleSpecifiers } = this.extractSymbolsFromEntry(
811
- rawContent,
812
- node.absolutePath,
1
+ /**
2
+ * Contracts Generator
3
+ *
4
+ * Extract Domain Events and Commands from TypeScript source code using decorators.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { extract } from '@hexaijs/plugin-contracts-generator';
9
+ *
10
+ * const result = await extract({
11
+ * sourceDir: 'packages',
12
+ * outputDir: 'packages/contracts',
13
+ * });
14
+ *
15
+ * console.log(`Extracted ${result.events.length} events and ${result.commands.length} commands`);
16
+ * ```
17
+ */
18
+ export { isPrimitiveType, isArrayType, isObjectType, isUnionType, isIntersectionType, isReferenceType, isLiteralType, isTupleType, isFunctionType, isDomainEvent, isCommand, } from "./domain/types.js";
19
+ export { PublicEvent, PublicCommand, PublicQuery, } from "@hexaijs/contracts/decorators";
20
+ export { Scanner } from "./scanner.js";
21
+ export { Parser } from "./parser.js";
22
+ export { FileGraphResolver } from "./file-graph-resolver.js";
23
+ export { FileCopier } from "./file-copier.js";
24
+ export { ConfigLoader, resolveContextEntries } from "./config-loader.js";
25
+ export { MessageParserError, ConfigurationError, ConfigLoadError, FileSystemError, FileNotFoundError, FileReadError, FileWriteError, ParseError, JsonParseError, ResolutionError, ModuleResolutionError, } from "./errors.js";
26
+ export { RegistryGenerator, } from "./registry-generator.js";
27
+ export { ReexportGenerator, } from "./reexport-generator.js";
28
+ import { nodeFileSystem } from "./file-system.js";
29
+ import { noopLogger } from "./logger.js";
30
+ import { ContractsPipeline } from "./pipeline.js";
31
+ export { nodeFileSystem } from "./file-system.js";
32
+ export { ConsoleLogger, noopLogger } from "./logger.js";
33
+ export { ContractsPipeline, } from "./pipeline.js";
34
+ export { ContextConfig } from "./context-config.js";
35
+ import { ContextConfig } from "./context-config.js";
36
+ export async function processContext(options) {
37
+ const { contextName, path: contextPath, sourceDir, outputDir, pathAliasRewrites, tsconfigPath, responseNamingConventions, removeDecorators, messageTypes, fileSystem = nodeFileSystem, logger = noopLogger, } = options;
38
+ const contextConfig = await ContextConfig.create({
39
+ name: contextName,
40
+ path: contextPath,
41
+ sourceDir,
42
+ tsconfigPath,
43
+ responseNamingConventions,
44
+ }, process.cwd(), fileSystem);
45
+ return ContractsPipeline.create({
46
+ contextConfig,
47
+ responseNamingConventions,
813
48
  messageTypes,
814
- decoratorNames
815
- );
816
- entryContents.set(node.absolutePath, extractedContent);
817
- for (const specifier of usedModuleSpecifiers) {
818
- const importInfo = node.imports.find(
819
- (i) => i.moduleSpecifier === specifier
820
- );
821
- if (importInfo?.resolvedPath && !importInfo.isExternal) {
822
- usedLocalImports.add(importInfo.resolvedPath);
823
- }
824
- }
825
- }
826
- return { entryContents, usedLocalImports };
827
- }
828
- expandTransitiveDependencies(usedLocalImports, fileGraph) {
829
- let changed = true;
830
- while (changed) {
831
- changed = false;
832
- for (const importPath of usedLocalImports) {
833
- const node = fileGraph.nodes.get(importPath);
834
- if (!node) continue;
835
- for (const imp of node.imports) {
836
- const isUnusedLocalImport = imp.resolvedPath && !imp.isExternal && !usedLocalImports.has(imp.resolvedPath);
837
- if (isUnusedLocalImport) {
838
- usedLocalImports.add(imp.resolvedPath);
839
- changed = true;
840
- }
841
- }
842
- }
843
- }
844
- }
845
- async resolveNodeContent(node, entryContents, usedLocalImports, messageTypes) {
846
- if (node.isEntryPoint && messageTypes?.length) {
847
- return entryContents.get(node.absolutePath) ?? await this.readFileContent(node.absolutePath);
848
- }
849
- const isUnusedDependency = messageTypes?.length && !usedLocalImports.has(node.absolutePath);
850
- if (isUnusedDependency) {
851
- return null;
852
- }
853
- return await this.readFileContent(node.absolutePath);
854
- }
855
- applyTransformations(content, node, fileGraph, sourceRoot, removeDecorators, responseTypesToExport, pathAliasRewrites) {
856
- const rewrites = [];
857
- let transformedContent = content;
858
- transformedContent = this.processExcludedImports(
859
- transformedContent,
860
- node,
861
- fileGraph.excludedPaths,
862
- rewrites
863
- );
864
- transformedContent = this.processDecoratorRemoval(
865
- transformedContent,
866
- node.absolutePath,
867
- removeDecorators,
868
- rewrites
869
- );
870
- transformedContent = this.processTypeExports(
871
- transformedContent,
872
- node.absolutePath,
873
- responseTypesToExport?.get(node.absolutePath),
874
- rewrites
875
- );
876
- transformedContent = this.processInternalPathAliases(
877
- transformedContent,
878
- node,
879
- fileGraph,
880
- sourceRoot,
881
- rewrites
882
- );
883
- transformedContent = this.processExternalPathAliases(
884
- transformedContent,
885
- pathAliasRewrites,
886
- rewrites
887
- );
888
- return { content: transformedContent, rewrites };
889
- }
890
- generateBarrelExport(copiedFiles, outputDir) {
891
- const lines = [];
892
- for (const filePath of copiedFiles) {
893
- const relativePath = path3.relative(outputDir, filePath);
894
- lines.push(this.createExportStatement(relativePath));
895
- }
896
- return lines.join("\n");
897
- }
898
- async readFileContent(absolutePath) {
899
- try {
900
- return await this.fs.readFile(absolutePath);
901
- } catch (error) {
902
- throw new FileReadError(absolutePath, { cause: error });
903
- }
904
- }
905
- async writeOutputFile(outputDir, relativePath, content) {
906
- const outputPath = path3.join(outputDir, relativePath);
907
- const outputDirPath = path3.dirname(outputPath);
908
- await this.fs.mkdir(outputDirPath, { recursive: true });
909
- try {
910
- await this.fs.writeFile(outputPath, content);
911
- } catch (error) {
912
- throw new FileWriteError(outputPath, { cause: error });
913
- }
914
- return outputPath;
915
- }
916
- processExcludedImports(content, node, excludedPaths, rewrites) {
917
- if (excludedPaths.size === 0) {
918
- return content;
919
- }
920
- const excludedResult = this.removeExcludedImports(
921
- content,
922
- node,
923
- excludedPaths
924
- );
925
- rewrites.push(...excludedResult.changes);
926
- return excludedResult.content;
927
- }
928
- processDecoratorRemoval(content, filePath, removeDecorators, rewrites) {
929
- if (!removeDecorators) {
930
- return content;
931
- }
932
- const decoratorResult = this.removeContractDecorators(
933
- content,
934
- filePath
935
- );
936
- rewrites.push(...decoratorResult.changes);
937
- return decoratorResult.content;
938
- }
939
- processTypeExports(content, filePath, typesToExport, rewrites) {
940
- if (!typesToExport || typesToExport.length === 0) {
941
- return content;
942
- }
943
- const exportResult = this.addExportToTypes(
944
- content,
945
- filePath,
946
- typesToExport
947
- );
948
- rewrites.push(...exportResult.changes);
949
- return exportResult.content;
950
- }
951
- processInternalPathAliases(content, node, fileGraph, sourceRoot, rewrites) {
952
- const internalResult = this.rewriteInternalPathAliases(
953
- content,
954
- node,
955
- fileGraph,
956
- sourceRoot
957
- );
958
- rewrites.push(...internalResult.rewrites);
959
- return internalResult.content;
960
- }
961
- processExternalPathAliases(content, pathAliasRewrites, rewrites) {
962
- if (!pathAliasRewrites) {
963
- return content;
964
- }
965
- const aliasResult = this.applyPathAliasRewrites(
966
- content,
967
- pathAliasRewrites
968
- );
969
- rewrites.push(...aliasResult.rewrites);
970
- return aliasResult.content;
971
- }
972
- createExportStatement(relativePath) {
973
- const exportPath = "./" + relativePath.replace(TS_EXTENSION_PATTERN, "");
974
- return `export * from '${exportPath}'`;
975
- }
976
- rewriteInternalPathAliases(content, node, fileGraph, sourceRoot) {
977
- let transformedContent = content;
978
- const appliedRewrites = [];
979
- for (const importInfo of node.imports) {
980
- if (this.isExternalOrRelativeImport(importInfo)) {
981
- continue;
982
- }
983
- const targetNode = this.resolveInternalImport(
984
- importInfo,
985
- fileGraph
986
- );
987
- if (!targetNode) {
988
- continue;
989
- }
990
- const relativePath = this.computeRelativePath(
991
- node.relativePath,
992
- targetNode.relativePath
993
- );
994
- const rewriteResult = this.rewriteModuleSpecifier(
995
- transformedContent,
996
- importInfo.moduleSpecifier,
997
- relativePath
998
- );
999
- if (rewriteResult.wasRewritten) {
1000
- transformedContent = rewriteResult.content;
1001
- appliedRewrites.push(
1002
- `${importInfo.moduleSpecifier} \u2192 ${relativePath}`
1003
- );
1004
- }
1005
- }
1006
- return { content: transformedContent, rewrites: appliedRewrites };
1007
- }
1008
- isExternalOrRelativeImport(importInfo) {
1009
- return importInfo.isExternal || importInfo.moduleSpecifier.startsWith(".");
1010
- }
1011
- resolveInternalImport(importInfo, fileGraph) {
1012
- if (!importInfo.resolvedPath || !fileGraph.nodes.has(importInfo.resolvedPath)) {
1013
- return void 0;
1014
- }
1015
- return fileGraph.nodes.get(importInfo.resolvedPath);
1016
- }
1017
- rewriteModuleSpecifier(content, originalSpecifier, newSpecifier) {
1018
- const importPattern = new RegExp(
1019
- `(from\\s+['"])${this.escapeRegex(originalSpecifier)}(['"])`,
1020
- "g"
1021
- );
1022
- if (!importPattern.test(content)) {
1023
- return { content, wasRewritten: false };
1024
- }
1025
- const rewrittenContent = content.replace(
1026
- importPattern,
1027
- `$1${newSpecifier}$2`
1028
- );
1029
- return { content: rewrittenContent, wasRewritten: true };
1030
- }
1031
- computeRelativePath(fromRelative, toRelative) {
1032
- const fromDir = path3.dirname(fromRelative);
1033
- let relativePath = path3.relative(fromDir, toRelative);
1034
- relativePath = relativePath.replace(TS_EXTENSION_PATTERN, "");
1035
- if (!relativePath.startsWith(".")) {
1036
- relativePath = "./" + relativePath;
1037
- }
1038
- return relativePath;
1039
- }
1040
- applyPathAliasRewrites(content, rewrites) {
1041
- let transformedContent = content;
1042
- const appliedRewrites = [];
1043
- for (const [from, to] of rewrites) {
1044
- if (transformedContent.includes(from)) {
1045
- transformedContent = transformedContent.replace(
1046
- new RegExp(this.escapeRegex(from), "g"),
1047
- to
1048
- );
1049
- appliedRewrites.push(`${from} \u2192 ${to}`);
1050
- }
1051
- }
1052
- return { content: transformedContent, rewrites: appliedRewrites };
1053
- }
1054
- escapeRegex(str) {
1055
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1056
- }
1057
- removeExcludedImports(content, node, excludedPaths) {
1058
- const excludedModuleSpecifiers = this.findExcludedModuleSpecifiers(
1059
- node,
1060
- excludedPaths
1061
- );
1062
- if (excludedModuleSpecifiers.size === 0) {
1063
- return { content, changes: [] };
1064
- }
1065
- const changes = Array.from(excludedModuleSpecifiers).map(
1066
- (specifier) => `removed import: ${specifier}`
1067
- );
1068
- const visitorFactory = this.createExcludedImportsVisitor(
1069
- excludedModuleSpecifiers
1070
- );
1071
- const transformedContent = this.transformSourceFile(
1072
- content,
1073
- node.absolutePath,
1074
- visitorFactory
1075
- );
1076
- return { content: transformedContent, changes };
1077
- }
1078
- findExcludedModuleSpecifiers(node, excludedPaths) {
1079
- const excludedModuleSpecifiers = /* @__PURE__ */ new Set();
1080
- for (const importInfo of node.imports) {
1081
- if (importInfo.resolvedPath && excludedPaths.has(importInfo.resolvedPath)) {
1082
- excludedModuleSpecifiers.add(importInfo.moduleSpecifier);
1083
- }
1084
- }
1085
- return excludedModuleSpecifiers;
1086
- }
1087
- createExcludedImportsVisitor(excludedModuleSpecifiers) {
1088
- return (context) => {
1089
- const visit = (node) => {
1090
- if (this.isImportFromExcludedModule(
1091
- node,
1092
- excludedModuleSpecifiers
1093
- )) {
1094
- return void 0;
1095
- }
1096
- if (this.isExportFromExcludedModule(
1097
- node,
1098
- excludedModuleSpecifiers
1099
- )) {
1100
- return void 0;
1101
- }
1102
- return ts6.visitEachChild(node, visit, context);
1103
- };
1104
- return visit;
1105
- };
1106
- }
1107
- isImportFromExcludedModule(node, excludedModules) {
1108
- if (!ts6.isImportDeclaration(node)) {
1109
- return false;
1110
- }
1111
- const moduleSpecifier = node.moduleSpecifier.text;
1112
- return excludedModules.has(moduleSpecifier);
1113
- }
1114
- isExportFromExcludedModule(node, excludedModules) {
1115
- if (!ts6.isExportDeclaration(node) || !node.moduleSpecifier) {
1116
- return false;
1117
- }
1118
- const moduleSpecifier = node.moduleSpecifier.text;
1119
- return excludedModules.has(moduleSpecifier);
1120
- }
1121
- removeContractDecorators(content, filePath) {
1122
- const changes = [];
1123
- const visitorFactory = this.createDecoratorRemovalVisitor(changes);
1124
- const transformedContent = this.transformSourceFile(
1125
- content,
1126
- filePath,
1127
- visitorFactory
1128
- );
1129
- return { content: transformedContent, changes };
1130
- }
1131
- createDecoratorRemovalVisitor(changes) {
1132
- return (context) => {
1133
- const visit = (node) => {
1134
- if (ts6.isImportDeclaration(node)) {
1135
- return this.processContractGeneratorImport(node, changes);
1136
- }
1137
- if (ts6.isClassDeclaration(node) && node.modifiers) {
1138
- return this.removeContractDecoratorsFromClass(
1139
- node,
1140
- changes
1141
- );
1142
- }
1143
- return ts6.visitEachChild(node, visit, context);
1144
- };
1145
- return visit;
1146
- };
1147
- }
1148
- processContractGeneratorImport(node, changes) {
1149
- const moduleSpecifier = node.moduleSpecifier.text;
1150
- const isContractGeneratorModule = moduleSpecifier === CONTRACTS_GENERATOR_MODULE || moduleSpecifier.startsWith(CONTRACTS_GENERATOR_MODULE + "/");
1151
- if (!isContractGeneratorModule) {
1152
- return node;
1153
- }
1154
- const namedBindings = node.importClause?.namedBindings;
1155
- if (!namedBindings || !ts6.isNamedImports(namedBindings)) {
1156
- return node;
1157
- }
1158
- const remainingElements = namedBindings.elements.filter(
1159
- (el) => !CONTRACT_DECORATORS.has(el.name.text)
1160
- );
1161
- if (remainingElements.length === 0) {
1162
- changes.push(`removed import: ${moduleSpecifier}`);
1163
- return void 0;
1164
- }
1165
- if (remainingElements.length < namedBindings.elements.length) {
1166
- changes.push(`removed decorators from import: ${moduleSpecifier}`);
1167
- return this.createImportWithFilteredBindings(
1168
- node,
1169
- remainingElements
1170
- );
1171
- }
1172
- return node;
1173
- }
1174
- createImportWithFilteredBindings(originalImport, remainingElements) {
1175
- const newNamedImports = ts6.factory.createNamedImports(remainingElements);
1176
- const newImportClause = ts6.factory.createImportClause(
1177
- originalImport.importClause.isTypeOnly,
1178
- originalImport.importClause.name,
1179
- newNamedImports
1180
- );
1181
- return ts6.factory.createImportDeclaration(
1182
- originalImport.modifiers,
1183
- newImportClause,
1184
- originalImport.moduleSpecifier,
1185
- originalImport.attributes
1186
- );
1187
- }
1188
- removeContractDecoratorsFromClass(node, changes) {
1189
- const filteredModifiers = node.modifiers.filter((modifier) => {
1190
- if (!ts6.isDecorator(modifier)) {
1191
- return true;
1192
- }
1193
- const decoratorName = this.extractDecoratorName(modifier);
1194
- if (decoratorName && CONTRACT_DECORATORS.has(decoratorName)) {
1195
- const decoratorSuffix = this.isDecoratorCallExpression(modifier) ? "()" : "";
1196
- changes.push(
1197
- `removed decorator: @${decoratorName}${decoratorSuffix}`
1198
- );
1199
- return false;
1200
- }
1201
- return true;
1202
- });
1203
- if (filteredModifiers.length === node.modifiers.length) {
1204
- return node;
1205
- }
1206
- return ts6.factory.createClassDeclaration(
1207
- filteredModifiers,
1208
- node.name,
1209
- node.typeParameters,
1210
- node.heritageClauses,
1211
- node.members
1212
- );
1213
- }
1214
- extractDecoratorName(decorator) {
1215
- const expression = decorator.expression;
1216
- if (ts6.isCallExpression(expression) && ts6.isIdentifier(expression.expression)) {
1217
- return expression.expression.text;
1218
- }
1219
- if (ts6.isIdentifier(expression)) {
1220
- return expression.text;
1221
- }
1222
- return void 0;
1223
- }
1224
- isDecoratorCallExpression(decorator) {
1225
- return ts6.isCallExpression(decorator.expression);
1226
- }
1227
- hasExportModifier(node) {
1228
- return node.modifiers?.some(
1229
- (m) => m.kind === ts6.SyntaxKind.ExportKeyword
1230
- ) ?? false;
1231
- }
1232
- prependExportModifier(modifiers) {
1233
- const exportModifier = ts6.factory.createModifier(
1234
- ts6.SyntaxKind.ExportKeyword
1235
- );
1236
- return modifiers ? [exportModifier, ...modifiers] : [exportModifier];
1237
- }
1238
- addExportToTypes(content, filePath, typeNames) {
1239
- const changes = [];
1240
- const typeNamesSet = new Set(typeNames);
1241
- const visitorFactory = this.createExportAdditionVisitor(
1242
- typeNamesSet,
1243
- changes
1244
- );
1245
- const transformedContent = this.transformSourceFile(
1246
- content,
1247
- filePath,
1248
- visitorFactory
1249
- );
1250
- return { content: transformedContent, changes };
1251
- }
1252
- createExportAdditionVisitor(typeNamesSet, changes) {
1253
- return (context) => {
1254
- const visit = (node) => {
1255
- if (ts6.isTypeAliasDeclaration(node)) {
1256
- return this.addExportToTypeAlias(
1257
- node,
1258
- typeNamesSet,
1259
- changes
1260
- );
1261
- }
1262
- if (ts6.isInterfaceDeclaration(node)) {
1263
- return this.addExportToInterface(
1264
- node,
1265
- typeNamesSet,
1266
- changes
1267
- );
1268
- }
1269
- return ts6.visitEachChild(node, visit, context);
1270
- };
1271
- return visit;
1272
- };
1273
- }
1274
- addExportToTypeAlias(node, typeNamesSet, changes) {
1275
- const typeName = node.name.text;
1276
- if (!typeNamesSet.has(typeName) || this.hasExportModifier(node)) {
1277
- return node;
1278
- }
1279
- changes.push(`added export: type ${typeName}`);
1280
- return ts6.factory.createTypeAliasDeclaration(
1281
- this.prependExportModifier(node.modifiers),
1282
- node.name,
1283
- node.typeParameters,
1284
- node.type
1285
- );
1286
- }
1287
- addExportToInterface(node, typeNamesSet, changes) {
1288
- const typeName = node.name.text;
1289
- if (!typeNamesSet.has(typeName) || this.hasExportModifier(node)) {
1290
- return node;
1291
- }
1292
- changes.push(`added export: interface ${typeName}`);
1293
- return ts6.factory.createInterfaceDeclaration(
1294
- this.prependExportModifier(node.modifiers),
1295
- node.name,
1296
- node.typeParameters,
1297
- node.heritageClauses,
1298
- node.members
1299
- );
1300
- }
1301
- transformSourceFile(content, filePath, visitorFactory) {
1302
- const sourceFile = ts6.createSourceFile(
1303
- filePath,
1304
- content,
1305
- ts6.ScriptTarget.Latest,
1306
- true,
1307
- ts6.ScriptKind.TS
1308
- );
1309
- const transformer = (context) => {
1310
- return (sf) => {
1311
- const visitor = visitorFactory(context);
1312
- return ts6.visitNode(sf, visitor);
1313
- };
1314
- };
1315
- const result = ts6.transform(sourceFile, [transformer]);
1316
- const transformedSourceFile = result.transformed[0];
1317
- const printer = ts6.createPrinter({ newLine: ts6.NewLineKind.LineFeed });
1318
- const transformedContent = printer.printFile(transformedSourceFile);
1319
- result.dispose();
1320
- return transformedContent;
1321
- }
1322
- extractSymbolsFromEntry(content, filePath, messageTypes, decoratorNames) {
1323
- const sourceFile = ts6.createSourceFile(
1324
- filePath,
1325
- content,
1326
- ts6.ScriptTarget.Latest,
1327
- true,
1328
- ts6.ScriptKind.TS
1329
- );
1330
- const decoratorToMessageType = this.buildDecoratorToMessageTypeMap(decoratorNames);
1331
- const context = {
1332
- sourceFile,
1333
- content,
1334
- messageTypes,
1335
- decoratorToMessageType
1336
- };
1337
- const { targetClassNames, targetClasses } = this.findTargetClasses(context);
1338
- if (targetClasses.length === 0) {
1339
- return { content, usedModuleSpecifiers: /* @__PURE__ */ new Set() };
1340
- }
1341
- const relatedTypeNames = this.computeRelatedTypeNames(targetClassNames);
1342
- const usedIdentifiers = this.collectUsedIdentifiers(targetClasses);
1343
- const localTypeDeclarations = this.collectLocalTypeDeclarations(sourceFile);
1344
- const includedLocalTypes = this.resolveIncludedLocalTypes(
1345
- usedIdentifiers,
1346
- relatedTypeNames,
1347
- localTypeDeclarations
1348
- );
1349
- const extractedSymbols = {
1350
- targetClassNames,
1351
- targetClasses,
1352
- usedIdentifiers,
1353
- includedLocalTypes,
1354
- localTypeDeclarations
1355
- };
1356
- return this.generateExtractedOutput(context, extractedSymbols);
1357
- }
1358
- buildDecoratorToMessageTypeMap(decoratorNames) {
1359
- const names = { ...DEFAULT_DECORATOR_NAMES, ...decoratorNames };
1360
- return {
1361
- [names.event]: "event",
1362
- [names.command]: "command",
1363
- [names.query]: "query"
1364
- };
1365
- }
1366
- findTargetClasses(context) {
1367
- const targetClassNames = /* @__PURE__ */ new Set();
1368
- const targetClasses = [];
1369
- const findClasses = (node) => {
1370
- if (ts6.isClassDeclaration(node) && node.name) {
1371
- for (const [decoratorName, messageType] of Object.entries(
1372
- context.decoratorToMessageType
1373
- )) {
1374
- if (hasDecorator(node, decoratorName) && context.messageTypes.includes(messageType)) {
1375
- targetClassNames.add(node.name.text);
1376
- targetClasses.push(node);
1377
- break;
1378
- }
1379
- }
1380
- }
1381
- ts6.forEachChild(node, findClasses);
1382
- };
1383
- findClasses(context.sourceFile);
1384
- return { targetClassNames, targetClasses };
1385
- }
1386
- computeRelatedTypeNames(targetClassNames) {
1387
- const relatedTypeNames = /* @__PURE__ */ new Set();
1388
- for (const className of targetClassNames) {
1389
- if (className.endsWith(REQUEST_SUFFIX)) {
1390
- const baseName = className.slice(0, -REQUEST_SUFFIX.length);
1391
- relatedTypeNames.add(baseName + "Response");
1392
- relatedTypeNames.add(baseName + "Payload");
1393
- } else if (className.endsWith(QUERY_SUFFIX)) {
1394
- const baseName = className.slice(0, -QUERY_SUFFIX.length);
1395
- relatedTypeNames.add(baseName + "QueryResult");
1396
- relatedTypeNames.add(baseName + "Payload");
1397
- }
1398
- relatedTypeNames.add(className + "Response");
1399
- relatedTypeNames.add(className + "Payload");
1400
- }
1401
- return relatedTypeNames;
1402
- }
1403
- collectUsedIdentifiers(targetClasses) {
1404
- const usedIdentifiers = /* @__PURE__ */ new Set();
1405
- const collectIdentifiers = (node) => {
1406
- if (ts6.isTypeReferenceNode(node) && ts6.isIdentifier(node.typeName)) {
1407
- usedIdentifiers.add(node.typeName.text);
1408
- }
1409
- if (ts6.isExpressionWithTypeArguments(node) && ts6.isIdentifier(node.expression)) {
1410
- usedIdentifiers.add(node.expression.text);
1411
- }
1412
- if (ts6.isIdentifier(node) && node.parent) {
1413
- const isHeritageOrTypeRef = ts6.isHeritageClause(node.parent.parent) || ts6.isTypeReferenceNode(node.parent);
1414
- if (isHeritageOrTypeRef) {
1415
- usedIdentifiers.add(node.text);
1416
- }
1417
- }
1418
- if (ts6.isDecorator(node)) {
1419
- this.collectDecoratorIdentifier(node, usedIdentifiers);
1420
- }
1421
- if (ts6.isCallExpression(node) && ts6.isIdentifier(node.expression)) {
1422
- usedIdentifiers.add(node.expression.text);
1423
- }
1424
- ts6.forEachChild(node, collectIdentifiers);
1425
- };
1426
- for (const cls of targetClasses) {
1427
- collectIdentifiers(cls);
1428
- }
1429
- return usedIdentifiers;
1430
- }
1431
- collectDecoratorIdentifier(decorator, usedIdentifiers) {
1432
- const expr = decorator.expression;
1433
- if (ts6.isIdentifier(expr)) {
1434
- usedIdentifiers.add(expr.text);
1435
- } else if (ts6.isCallExpression(expr) && ts6.isIdentifier(expr.expression)) {
1436
- usedIdentifiers.add(expr.expression.text);
1437
- }
1438
- }
1439
- collectLocalTypeDeclarations(sourceFile) {
1440
- const localTypeDeclarations = /* @__PURE__ */ new Map();
1441
- const collectLocalTypes = (node) => {
1442
- if (ts6.isInterfaceDeclaration(node) && node.name) {
1443
- this.addToDeclarationMap(
1444
- localTypeDeclarations,
1445
- node.name.text,
1446
- node
1447
- );
1448
- }
1449
- if (ts6.isTypeAliasDeclaration(node) && node.name) {
1450
- this.addToDeclarationMap(
1451
- localTypeDeclarations,
1452
- node.name.text,
1453
- node
1454
- );
1455
- }
1456
- if (ts6.isVariableStatement(node)) {
1457
- for (const decl of node.declarationList.declarations) {
1458
- if (ts6.isIdentifier(decl.name)) {
1459
- this.addToDeclarationMap(
1460
- localTypeDeclarations,
1461
- decl.name.text,
1462
- node
1463
- );
1464
- }
1465
- }
1466
- }
1467
- ts6.forEachChild(node, collectLocalTypes);
1468
- };
1469
- collectLocalTypes(sourceFile);
1470
- return localTypeDeclarations;
1471
- }
1472
- addToDeclarationMap(map, name, node) {
1473
- const existing = map.get(name) ?? [];
1474
- existing.push(node);
1475
- map.set(name, existing);
1476
- }
1477
- resolveIncludedLocalTypes(usedIdentifiers, relatedTypeNames, localTypeDeclarations) {
1478
- const includedLocalTypes = /* @__PURE__ */ new Set();
1479
- const queue = [...usedIdentifiers, ...relatedTypeNames];
1480
- while (queue.length > 0) {
1481
- const identifier = queue.shift();
1482
- if (includedLocalTypes.has(identifier)) continue;
1483
- const typeNodes = localTypeDeclarations.get(identifier);
1484
- if (!typeNodes || typeNodes.length === 0) continue;
1485
- includedLocalTypes.add(identifier);
1486
- const typeIdentifiers = this.collectTypeIdentifiersFromNodes(typeNodes);
1487
- for (const id of typeIdentifiers) {
1488
- if (!includedLocalTypes.has(id)) {
1489
- queue.push(id);
1490
- usedIdentifiers.add(id);
1491
- }
1492
- }
1493
- }
1494
- return includedLocalTypes;
1495
- }
1496
- collectTypeIdentifiersFromNodes(nodes) {
1497
- const typeIdentifiers = /* @__PURE__ */ new Set();
1498
- const collectFromType = (node) => {
1499
- if (ts6.isTypeReferenceNode(node) && ts6.isIdentifier(node.typeName)) {
1500
- typeIdentifiers.add(node.typeName.text);
1501
- }
1502
- if (ts6.isExpressionWithTypeArguments(node) && ts6.isIdentifier(node.expression)) {
1503
- typeIdentifiers.add(node.expression.text);
1504
- }
1505
- if (ts6.isComputedPropertyName(node)) {
1506
- this.collectComputedPropertyIdentifier(node, typeIdentifiers);
1507
- }
1508
- ts6.forEachChild(node, collectFromType);
1509
- };
1510
- for (const typeNode of nodes) {
1511
- collectFromType(typeNode);
1512
- }
1513
- return typeIdentifiers;
1514
- }
1515
- collectComputedPropertyIdentifier(node, typeIdentifiers) {
1516
- const expr = node.expression;
1517
- if (ts6.isPropertyAccessExpression(expr) && ts6.isIdentifier(expr.expression)) {
1518
- typeIdentifiers.add(expr.expression.text);
1519
- }
1520
- if (ts6.isIdentifier(expr)) {
1521
- typeIdentifiers.add(expr.text);
1522
- }
1523
- }
1524
- generateExtractedOutput(context, symbols) {
1525
- const output = [];
1526
- const importMap = this.buildImportMap(context.sourceFile);
1527
- const filteredImports = this.filterImports(
1528
- symbols.usedIdentifiers,
1529
- importMap,
1530
- symbols.includedLocalTypes
1531
- );
1532
- this.appendImportStatements(output, filteredImports);
1533
- this.appendLocalTypeDeclarations(output, context, symbols);
1534
- this.appendTargetClasses(output, context, symbols.targetClasses);
1535
- const usedModuleSpecifiers = new Set(filteredImports.keys());
1536
- return {
1537
- content: output.join("\n"),
1538
- usedModuleSpecifiers
1539
- };
1540
- }
1541
- buildImportMap(sourceFile) {
1542
- const importMap = /* @__PURE__ */ new Map();
1543
- const collectImports = (node) => {
1544
- if (ts6.isImportDeclaration(node)) {
1545
- const moduleSpecifier = node.moduleSpecifier.text;
1546
- const namedBindings = node.importClause?.namedBindings;
1547
- const isTypeOnly = node.importClause?.isTypeOnly ?? false;
1548
- if (namedBindings && ts6.isNamedImports(namedBindings)) {
1549
- for (const element of namedBindings.elements) {
1550
- importMap.set(element.name.text, {
1551
- moduleSpecifier,
1552
- isTypeOnly
1553
- });
1554
- }
1555
- }
1556
- }
1557
- ts6.forEachChild(node, collectImports);
1558
- };
1559
- collectImports(sourceFile);
1560
- return importMap;
1561
- }
1562
- filterImports(usedIdentifiers, importMap, includedLocalTypes) {
1563
- const filteredImports = /* @__PURE__ */ new Map();
1564
- for (const identifier of usedIdentifiers) {
1565
- const importInfo = importMap.get(identifier);
1566
- if (!importInfo || includedLocalTypes.has(identifier)) {
1567
- continue;
1568
- }
1569
- const existing = filteredImports.get(importInfo.moduleSpecifier);
1570
- if (existing) {
1571
- existing.identifiers.add(identifier);
1572
- } else {
1573
- filteredImports.set(importInfo.moduleSpecifier, {
1574
- identifiers: /* @__PURE__ */ new Set([identifier]),
1575
- isTypeOnly: importInfo.isTypeOnly
1576
- });
1577
- }
1578
- }
1579
- return filteredImports;
1580
- }
1581
- appendImportStatements(output, filteredImports) {
1582
- for (const [moduleSpecifier, info] of filteredImports) {
1583
- const identifiers = [...info.identifiers].sort().join(", ");
1584
- const typeOnlyPrefix = info.isTypeOnly ? "type " : "";
1585
- output.push(
1586
- `import ${typeOnlyPrefix}{ ${identifiers} } from "${moduleSpecifier}";`
1587
- );
1588
- }
1589
- if (output.length > 0) {
1590
- output.push("");
1591
- }
1592
- }
1593
- appendLocalTypeDeclarations(output, context, symbols) {
1594
- const outputNodes = /* @__PURE__ */ new Set();
1595
- for (const typeName of symbols.includedLocalTypes) {
1596
- const typeNodes = symbols.localTypeDeclarations.get(typeName);
1597
- if (!typeNodes) continue;
1598
- for (const typeNode of typeNodes) {
1599
- if (outputNodes.has(typeNode)) continue;
1600
- outputNodes.add(typeNode);
1601
- this.appendNodeWithExport(output, context, typeNode);
1602
- }
1603
- }
1604
- }
1605
- appendTargetClasses(output, context, targetClasses) {
1606
- for (const cls of targetClasses) {
1607
- this.appendNodeWithExport(output, context, cls);
1608
- }
1609
- }
1610
- appendNodeWithExport(output, context, node) {
1611
- const nodeText = context.content.substring(node.getStart(context.sourceFile), node.end).trim();
1612
- const hasExport = this.nodeHasExportKeyword(node);
1613
- if (hasExport) {
1614
- output.push(nodeText);
1615
- } else {
1616
- output.push("export " + nodeText);
1617
- }
1618
- output.push("");
1619
- }
1620
- nodeHasExportKeyword(node) {
1621
- const isExportableDeclaration = ts6.isInterfaceDeclaration(node) || ts6.isTypeAliasDeclaration(node) || ts6.isVariableStatement(node) || ts6.isClassDeclaration(node);
1622
- if (!isExportableDeclaration) {
1623
- return false;
1624
- }
1625
- const declarationNode = node;
1626
- return declarationNode.modifiers?.some(
1627
- (m) => m.kind === ts6.SyntaxKind.ExportKeyword
1628
- ) ?? false;
1629
- }
1630
- };
1631
- var TYPESCRIPT_EXTENSIONS2 = [".ts", ".tsx", ".d.ts"];
1632
- var INDEX_FILE2 = "index.ts";
1633
- var Tsconfig = class _Tsconfig {
1634
- constructor(paths) {
1635
- this.paths = paths;
1636
- }
1637
- static NONE = new _Tsconfig(/* @__PURE__ */ new Map());
1638
- static async load(tsconfigPath, fs) {
1639
- const absolutePath = path3__default.resolve(tsconfigPath);
1640
- const configDir = path3__default.dirname(absolutePath);
1641
- const content = await fs.readFile(absolutePath);
1642
- const { config, error } = ts6__default.parseConfigFileTextToJson(absolutePath, content);
1643
- if (error) {
1644
- throw new Error(
1645
- `Failed to parse tsconfig: ${ts6__default.flattenDiagnosticMessageText(error.messageText, "\n")}`
1646
- );
1647
- }
1648
- const parsed = ts6__default.parseJsonConfigFileContent(config, ts6__default.sys, configDir);
1649
- const baseUrl = parsed.options.baseUrl ?? configDir;
1650
- const paths = /* @__PURE__ */ new Map();
1651
- if (parsed.options.paths) {
1652
- for (const [alias, targets] of Object.entries(parsed.options.paths)) {
1653
- const resolvedTargets = targets.map(
1654
- (target) => path3__default.join(baseUrl, target)
1655
- );
1656
- paths.set(alias, resolvedTargets);
1657
- }
1658
- }
1659
- return new _Tsconfig(paths);
1660
- }
1661
- /**
1662
- * Pure string transformation: resolves path alias to potential file paths.
1663
- * Returns null if no alias matches.
1664
- */
1665
- resolvePath(importPath) {
1666
- for (const [pattern, targets] of this.paths) {
1667
- const wildcardMatch = this.matchPathPattern(importPath, pattern);
1668
- if (wildcardMatch === null) {
1669
- continue;
1670
- }
1671
- return targets.map((target) => target.replace("*", wildcardMatch));
1672
- }
1673
- return null;
1674
- }
1675
- matchPathPattern(moduleSpecifier, pattern) {
1676
- if (pattern.endsWith("*")) {
1677
- const prefix = pattern.slice(0, -1);
1678
- if (moduleSpecifier.startsWith(prefix)) {
1679
- return moduleSpecifier.slice(prefix.length);
1680
- }
1681
- } else if (moduleSpecifier === pattern) {
1682
- return "";
1683
- }
1684
- return null;
1685
- }
1686
- };
1687
- var ContextConfig = class _ContextConfig {
1688
- fs;
1689
- tsconfig;
1690
- name;
1691
- sourceDir;
1692
- responseNamingConventions;
1693
- constructor(name, sourceDir, tsconfig, fs, responseNamingConventions) {
1694
- this.name = name;
1695
- this.sourceDir = sourceDir;
1696
- this.tsconfig = tsconfig;
1697
- this.fs = fs;
1698
- this.responseNamingConventions = responseNamingConventions;
1699
- }
1700
- /**
1701
- * Factory method to create ContextConfig with properly loaded tsconfig.
1702
- */
1703
- static async create(input, configDir, fs = nodeFileSystem) {
1704
- if (!input.name) {
1705
- throw new Error("ContextConfig requires 'name'");
1706
- }
1707
- if (!input.path) {
1708
- throw new Error(`ContextConfig '${input.name}' requires 'path'`);
1709
- }
1710
- const basePath = path3__default.resolve(configDir, input.path);
1711
- const sourceDir = path3__default.resolve(basePath, input.sourceDir ?? "src");
1712
- const tsconfig = await this.loadTsconfig(basePath, input.tsconfigPath, fs);
1713
- return new _ContextConfig(
1714
- input.name,
1715
- sourceDir,
1716
- tsconfig,
1717
- fs,
1718
- input.responseNamingConventions
1719
- );
1720
- }
1721
- static async loadTsconfig(basePath, inputPath, fs) {
1722
- const tsconfigPath = path3__default.resolve(basePath, inputPath ?? "tsconfig.json");
1723
- if (!await fs.exists(tsconfigPath)) {
1724
- return Tsconfig.NONE;
1725
- }
1726
- return Tsconfig.load(tsconfigPath, fs);
1727
- }
1728
- /**
1729
- * Creates a ContextConfig without async loading (for cases where tsconfig is not needed
1730
- * or already handled externally).
1731
- */
1732
- static createSync(name, sourceDir, fs = nodeFileSystem, responseNamingConventions) {
1733
- return new _ContextConfig(
1734
- name,
1735
- sourceDir,
1736
- Tsconfig.NONE,
1737
- fs,
1738
- responseNamingConventions
1739
- );
1740
- }
1741
- /**
1742
- * Resolves a module specifier (path alias) to actual file path.
1743
- * Only handles non-relative imports (path aliases).
1744
- *
1745
- * @param moduleSpecifier - The import path to resolve (e.g., "@/utils/helper")
1746
- * @returns Object with resolvedPath (null if external) and isExternal flag
1747
- */
1748
- async resolvePath(moduleSpecifier) {
1749
- const resolvedPaths = this.tsconfig.resolvePath(moduleSpecifier);
1750
- if (!resolvedPaths) {
1751
- return { resolvedPath: null, isExternal: true };
1752
- }
1753
- for (const resolvedPath of resolvedPaths) {
1754
- const filePath = await this.tryResolveWithExtensions(resolvedPath);
1755
- if (filePath && filePath.startsWith(this.sourceDir)) {
1756
- return { resolvedPath: filePath, isExternal: false };
1757
- }
1758
- }
1759
- return { resolvedPath: null, isExternal: true };
1760
- }
1761
- async tryResolveWithExtensions(basePath) {
1762
- const extensionCandidates = TYPESCRIPT_EXTENSIONS2.map((ext) => basePath + ext);
1763
- const indexCandidate = path3__default.join(basePath, INDEX_FILE2);
1764
- const candidates = [...extensionCandidates, indexCandidate];
1765
- for (const candidate of candidates) {
1766
- if (await this.fs.exists(candidate)) {
1767
- return candidate;
1768
- }
1769
- }
1770
- return null;
1771
- }
1772
- };
1773
-
1774
- // src/config-loader.ts
1775
- var SUPPORTED_GLOB_PARTS_COUNT = 2;
1776
- var ConfigLoader = class {
1777
- fs;
1778
- constructor(options = {}) {
1779
- this.fs = options.fileSystem ?? nodeFileSystem;
1780
- }
1781
- async load(configPath) {
1782
- const absolutePath = resolve(configPath);
1783
- const config = await this.loadTypeScriptConfig(absolutePath);
1784
- return this.extractContractsConfig(config, dirname(absolutePath));
1785
- }
1786
- async loadTypeScriptConfig(absolutePath) {
1787
- const source = await this.fs.readFile(absolutePath);
1788
- const result = ts6__default.transpileModule(source, {
1789
- compilerOptions: {
1790
- module: ts6__default.ModuleKind.CommonJS,
1791
- target: ts6__default.ScriptTarget.ES2020,
1792
- esModuleInterop: true
1793
- }
1794
- });
1795
- const exports$1 = {};
1796
- const moduleWrapper = new Function("exports", result.outputText);
1797
- moduleWrapper(exports$1);
1798
- return exports$1.default ?? exports$1;
1799
- }
1800
- async extractContractsConfig(config, configDir) {
1801
- const contracts = config.contracts;
1802
- if (!contracts) {
1803
- throw new ConfigLoadError("Missing 'contracts' section in config");
1804
- }
1805
- if (!contracts.contexts || !Array.isArray(contracts.contexts)) {
1806
- throw new ConfigLoadError("Missing 'contracts.contexts' in config");
1807
- }
1808
- const contexts = await this.resolveContexts(contracts.contexts, configDir);
1809
- if (contexts.length === 0) {
1810
- throw new ConfigLoadError("No contexts found from 'contexts'");
1811
- }
1812
- const decoratorNames = mergeDecoratorNames(contracts.decoratorNames);
1813
- return {
1814
- contexts,
1815
- pathAliasRewrites: contracts.pathAliasRewrites,
1816
- externalDependencies: contracts.externalDependencies,
1817
- decoratorNames,
1818
- responseNamingConventions: contracts.responseNamingConventions,
1819
- removeDecorators: contracts.removeDecorators ?? true
1820
- };
1821
- }
1822
- async resolveContexts(contextsConfig, configDir) {
1823
- const contexts = [];
1824
- for (let i = 0; i < contextsConfig.length; i++) {
1825
- const item = contextsConfig[i];
1826
- if (typeof item === "string") {
1827
- const resolvedContexts = await this.resolveStringContext(item, configDir);
1828
- contexts.push(...resolvedContexts);
1829
- } else {
1830
- const contextConfig = await this.createObjectContext(item, i, configDir);
1831
- contexts.push(contextConfig);
1832
- }
1833
- }
1834
- return contexts;
1835
- }
1836
- async resolveStringContext(contextPath, configDir) {
1837
- if (contextPath.includes("*")) {
1838
- return this.expandGlobPattern(contextPath, configDir);
1839
- }
1840
- const basePath = resolve(configDir, contextPath);
1841
- const name = basename(basePath);
1842
- return [await ContextConfig.create(
1843
- { name, path: contextPath },
1844
- configDir,
1845
- this.fs
1846
- )];
1847
- }
1848
- async expandGlobPattern(pattern, configDir) {
1849
- const packageDirs = await this.matchGlobPattern(pattern, configDir);
1850
- return Promise.all(
1851
- packageDirs.map((dir) => {
1852
- const name = basename(dir);
1853
- const relativePath = relative(configDir, dir);
1854
- return ContextConfig.create(
1855
- { name, path: relativePath },
1856
- configDir,
1857
- this.fs
1858
- );
1859
- })
1860
- );
1861
- }
1862
- async createObjectContext(ctx, index, configDir) {
1863
- if (!ctx.name || typeof ctx.name !== "string") {
1864
- throw new ConfigLoadError(
1865
- `Invalid context at index ${index}: missing 'name'`
1866
- );
1867
- }
1868
- if (!ctx.path || typeof ctx.path !== "string") {
1869
- throw new ConfigLoadError(
1870
- `Invalid context at index ${index}: missing 'path'`
1871
- );
1872
- }
1873
- return ContextConfig.create(ctx, configDir, this.fs);
1874
- }
1875
- async matchGlobPattern(pattern, configDir) {
1876
- const globParts = pattern.split("*");
1877
- if (globParts.length !== SUPPORTED_GLOB_PARTS_COUNT) {
1878
- throw new ConfigLoadError(
1879
- `Invalid glob pattern: "${pattern}". Only single wildcard patterns like "packages/*" are supported.`
1880
- );
1881
- }
1882
- const [prefix, suffix] = globParts;
1883
- const baseDir = resolve(configDir, prefix);
1884
- if (!await this.fs.exists(baseDir)) {
1885
- return [];
1886
- }
1887
- const entries = await this.fs.readdir(baseDir);
1888
- const matchedDirs = [];
1889
- for (const entry of entries) {
1890
- const fullPath = resolve(baseDir, entry);
1891
- const stats = await this.fs.stat(fullPath);
1892
- if (!stats.isDirectory()) {
1893
- continue;
1894
- }
1895
- if (suffix) {
1896
- const suffixPath = resolve(fullPath, suffix.replace(/^\//, ""));
1897
- if (await this.fs.exists(suffixPath)) {
1898
- matchedDirs.push(fullPath);
1899
- }
1900
- } else {
1901
- matchedDirs.push(fullPath);
1902
- }
1903
- }
1904
- return matchedDirs.sort();
1905
- }
1906
- };
1907
-
1908
- // src/registry-generator.ts
1909
- var DEFAULT_OPTIONS = {
1910
- messageRegistryImport: "@hexaijs/plugin-contracts-generator/runtime"
1911
- };
1912
- function hasMessages(ctx) {
1913
- return ctx.events.length > 0 || ctx.commands.length > 0 || (ctx.queries?.length ?? 0) > 0;
1914
- }
1915
- function getAllMessages(ctx) {
1916
- return [...ctx.events, ...ctx.commands, ...ctx.queries ?? []];
1917
- }
1918
- var RegistryGenerator = class {
1919
- options;
1920
- constructor(options = {}) {
1921
- this.options = { ...DEFAULT_OPTIONS, ...options };
1922
- }
1923
- generate(contexts) {
1924
- const allMessages = contexts.flatMap(
1925
- (ctx) => getAllMessages(ctx).map((message) => ({
1926
- message,
1927
- contextName: ctx.contextName
1928
- }))
1929
- );
1930
- if (allMessages.length === 0) {
1931
- return this.generateEmptyRegistry();
1932
- }
1933
- if (this.options.useNamespace) {
1934
- return this.generateWithNamespace(contexts, allMessages);
1935
- }
1936
- const imports = this.generateImports(contexts);
1937
- const registrations = this.generateRegistrations(allMessages);
1938
- return [
1939
- imports,
1940
- "",
1941
- "export const messageRegistry = new MessageRegistry()",
1942
- registrations
1943
- ].join("\n");
1944
- }
1945
- generateEmptyRegistry() {
1946
- return [
1947
- `import { MessageRegistry } from "${this.options.messageRegistryImport}";`,
1948
- "",
1949
- "export const messageRegistry = new MessageRegistry();",
1950
- ""
1951
- ].join("\n");
1952
- }
1953
- generateWithNamespace(contexts, allMessages) {
1954
- const imports = this.generateNamespaceImports(contexts);
1955
- const exports$1 = this.generateNamespaceExports(contexts);
1956
- const registrations = this.generateNamespaceRegistrations(allMessages);
1957
- return [
1958
- imports,
1959
- "",
1960
- exports$1,
1961
- "",
1962
- "export const messageRegistry = new MessageRegistry()",
1963
- registrations
1964
- ].join("\n");
1965
- }
1966
- getNamespaceInfos(contexts) {
1967
- return contexts.filter(hasMessages).map((ctx) => ({
1968
- importPath: ctx.importPath ?? `./${ctx.contextName}`,
1969
- namespace: this.toNamespace(ctx.contextName)
1970
- }));
1971
- }
1972
- generateNamespaceImports(contexts) {
1973
- return [
1974
- `import { MessageRegistry } from "${this.options.messageRegistryImport}";`,
1975
- ...this.getNamespaceInfos(contexts).map(
1976
- ({ importPath, namespace }) => `import * as ${namespace} from "${importPath}";`
1977
- )
1978
- ].join("\n");
1979
- }
1980
- generateNamespaceExports(contexts) {
1981
- return this.getNamespaceInfos(contexts).map(
1982
- ({ importPath, namespace }) => `export * as ${namespace} from "${importPath}";`
1983
- ).join("\n");
1984
- }
1985
- generateNamespaceRegistrations(messages) {
1986
- const lines = messages.map(({ message, contextName }) => {
1987
- const namespace = this.toNamespace(contextName);
1988
- return ` .register(${namespace}.${message.name})`;
1989
- });
1990
- return lines.join("\n") + ";\n";
1991
- }
1992
- toNamespace(contextName) {
1993
- return contextName.replace(
1994
- /-([a-z])/g,
1995
- (_, letter) => letter.toUpperCase()
1996
- );
1997
- }
1998
- generateImports(contexts) {
1999
- const lines = [
2000
- `import { MessageRegistry } from "${this.options.messageRegistryImport}";`
2001
- ];
2002
- for (const ctx of contexts) {
2003
- const messageNames = getAllMessages(ctx).map((m) => m.name);
2004
- if (messageNames.length > 0) {
2005
- const importPath = ctx.importPath ?? `./${ctx.contextName}`;
2006
- lines.push(
2007
- `import { ${messageNames.join(", ")} } from "${importPath}";`
2008
- );
2009
- }
2010
- }
2011
- return lines.join("\n");
2012
- }
2013
- generateRegistrations(messages) {
2014
- const lines = messages.map(
2015
- ({ message }) => ` .register(${message.name})`
2016
- );
2017
- return lines.join("\n") + ";\n";
2018
- }
2019
- };
2020
- var ReexportGenerator = class {
2021
- fs;
2022
- constructor(options = {}) {
2023
- this.fs = options.fileSystem ?? nodeFileSystem;
2024
- }
2025
- /**
2026
- * Analyzes files to find imports that match pathAliasRewrites
2027
- * and groups them by rewritten path
2028
- */
2029
- async analyze(options) {
2030
- const { files, pathAliasRewrites } = options;
2031
- const rewrittenToOriginal = /* @__PURE__ */ new Map();
2032
- for (const [original, rewritten] of pathAliasRewrites) {
2033
- rewrittenToOriginal.set(rewritten, original);
2034
- }
2035
- const allImports = [];
2036
- for (const file of files) {
2037
- const content = await this.fs.readFile(file);
2038
- const imports = this.extractRewrittenImports(content, rewrittenToOriginal);
2039
- allImports.push(...imports);
2040
- }
2041
- return this.groupImportsByPath(allImports);
2042
- }
2043
- /**
2044
- * Generates re-export files
2045
- */
2046
- async generate(options) {
2047
- const { outputDir, reexportFiles } = options;
2048
- const generatedFiles = [];
2049
- for (const reexport of reexportFiles) {
2050
- const filePath = path3.join(outputDir, reexport.relativePath);
2051
- const content = this.generateReexportContent(reexport);
2052
- await this.fs.mkdir(path3.dirname(filePath), { recursive: true });
2053
- await this.fs.writeFile(filePath, content);
2054
- generatedFiles.push(filePath);
2055
- }
2056
- return generatedFiles;
2057
- }
2058
- /**
2059
- * Extracts imports from source content that match rewritten prefixes
2060
- */
2061
- extractRewrittenImports(content, rewrittenToOriginal) {
2062
- const sourceFile = ts6.createSourceFile(
2063
- "temp.ts",
2064
- content,
2065
- ts6.ScriptTarget.Latest,
2066
- true
2067
- );
2068
- const imports = [];
2069
- ts6.forEachChild(sourceFile, (node) => {
2070
- if (!ts6.isImportDeclaration(node) || !node.importClause) {
2071
- return;
2072
- }
2073
- const moduleSpecifier = node.moduleSpecifier.text;
2074
- for (const [rewrittenPrefix, originalPrefix] of rewrittenToOriginal) {
2075
- if (moduleSpecifier.startsWith(rewrittenPrefix)) {
2076
- const symbols = this.extractSymbolNames(node.importClause);
2077
- if (symbols.length > 0) {
2078
- const suffix = moduleSpecifier.slice(rewrittenPrefix.length);
2079
- const originalPath = originalPrefix + suffix;
2080
- imports.push({
2081
- rewrittenPath: moduleSpecifier,
2082
- originalPath,
2083
- symbols,
2084
- isTypeOnly: node.importClause.isTypeOnly ?? false
2085
- });
2086
- }
2087
- break;
2088
- }
2089
- }
2090
- });
2091
- return imports;
2092
- }
2093
- /**
2094
- * Extracts symbol names from import clause
2095
- */
2096
- extractSymbolNames(importClause) {
2097
- const names = [];
2098
- if (importClause.name) {
2099
- names.push(importClause.name.text);
2100
- }
2101
- if (importClause.namedBindings && ts6.isNamedImports(importClause.namedBindings)) {
2102
- for (const element of importClause.namedBindings.elements) {
2103
- const originalName = element.propertyName?.text ?? element.name.text;
2104
- names.push(originalName);
2105
- }
2106
- }
2107
- return names;
2108
- }
2109
- /**
2110
- * Groups imports by rewritten path and merges symbols
2111
- */
2112
- groupImportsByPath(imports) {
2113
- const grouped = /* @__PURE__ */ new Map();
2114
- for (const imp of imports) {
2115
- const existing = grouped.get(imp.rewrittenPath);
2116
- if (existing) {
2117
- for (const symbol of imp.symbols) {
2118
- existing.symbols.add(symbol);
2119
- }
2120
- if (!imp.isTypeOnly) {
2121
- existing.hasValueImport = true;
2122
- }
2123
- } else {
2124
- grouped.set(imp.rewrittenPath, {
2125
- originalPath: imp.originalPath,
2126
- symbols: new Set(imp.symbols),
2127
- hasValueImport: !imp.isTypeOnly
2128
- });
2129
- }
2130
- }
2131
- const result = [];
2132
- for (const [rewrittenPath, data] of grouped) {
2133
- const relativePath = this.rewrittenPathToRelativePath(rewrittenPath);
2134
- result.push({
2135
- relativePath,
2136
- originalModule: data.originalPath,
2137
- symbols: Array.from(data.symbols).sort(),
2138
- isTypeOnly: !data.hasValueImport
2139
- });
2140
- }
2141
- return result.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
2142
- }
2143
- /**
2144
- * Converts a rewritten package path to a relative file path
2145
- * e.g., "@libera/contracts/common/request" with prefix "@libera/contracts"
2146
- * -> "common/request.ts"
2147
- */
2148
- rewrittenPathToRelativePath(rewrittenPath) {
2149
- const parts = rewrittenPath.split("/");
2150
- let subpathStart;
2151
- if (parts[0].startsWith("@")) {
2152
- subpathStart = 2;
2153
- } else {
2154
- subpathStart = 1;
2155
- }
2156
- const subpath = parts.slice(subpathStart).join("/");
2157
- if (!subpath) {
2158
- return "index.ts";
2159
- }
2160
- return subpath + ".ts";
2161
- }
2162
- /**
2163
- * Generates the content for a re-export file
2164
- */
2165
- generateReexportContent(reexport) {
2166
- const exportKeyword = reexport.isTypeOnly ? "export type" : "export";
2167
- const symbols = reexport.symbols.join(", ");
2168
- return `${exportKeyword} { ${symbols} } from "${reexport.originalModule}";
2169
- `;
2170
- }
2171
- };
2172
-
2173
- // src/logger.ts
2174
- var LOG_LEVEL_PRIORITY = {
2175
- debug: 0,
2176
- info: 1,
2177
- warn: 2,
2178
- error: 3
2179
- };
2180
- var ConsoleLogger = class {
2181
- level;
2182
- constructor(options = {}) {
2183
- this.level = options.level ?? "info";
2184
- }
2185
- debug(message) {
2186
- if (this.shouldLog("debug")) {
2187
- console.debug(message);
2188
- }
2189
- }
2190
- info(message) {
2191
- if (this.shouldLog("info")) {
2192
- console.info(message);
2193
- }
2194
- }
2195
- warn(message) {
2196
- if (this.shouldLog("warn")) {
2197
- console.warn(message);
2198
- }
2199
- }
2200
- error(message) {
2201
- if (this.shouldLog("error")) {
2202
- console.error(message);
2203
- }
2204
- }
2205
- shouldLog(level) {
2206
- return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.level];
2207
- }
2208
- };
2209
- var NoopLogger = class {
2210
- debug() {
2211
- }
2212
- info() {
2213
- }
2214
- warn() {
2215
- }
2216
- error() {
2217
- }
2218
- };
2219
- var noopLogger = new NoopLogger();
2220
- var DEFAULT_EXCLUDE_DEPENDENCIES = [
2221
- "**/*.test.ts",
2222
- "**/*.spec.ts",
2223
- "**/*.eh.ts",
2224
- "**/db.ts",
2225
- "**/infra/**"
2226
- ];
2227
- var ContractsPipeline = class _ContractsPipeline {
2228
- constructor(deps, messageTypes) {
2229
- this.deps = deps;
2230
- this.messageTypes = messageTypes;
2231
- }
2232
- messageTypes;
2233
- static create(options) {
2234
- const fileSystem = options.fileSystem ?? nodeFileSystem;
2235
- const logger = options.logger ?? noopLogger;
2236
- const excludeDependencies = options.excludeDependencies ?? DEFAULT_EXCLUDE_DEPENDENCIES;
2237
- const scanner = new Scanner({ fileSystem, messageTypes: options.messageTypes });
2238
- const parser = new Parser({
2239
- responseNamingConventions: options.responseNamingConventions ?? options.contextConfig.responseNamingConventions,
2240
- messageTypes: options.messageTypes
2241
- });
2242
- const fileGraphResolver = FileGraphResolver.create({
2243
- contextConfig: options.contextConfig,
2244
- fileSystem,
2245
- excludeDependencies
2246
- });
2247
- const fileCopier = new FileCopier({ fileSystem });
2248
- return new _ContractsPipeline(
2249
- {
2250
- scanner,
2251
- parser,
2252
- fileGraphResolver,
2253
- fileCopier,
2254
49
  fileSystem,
2255
- logger
2256
- },
2257
- options.messageTypes
2258
- );
2259
- }
2260
- static fromDependencies(deps) {
2261
- return new _ContractsPipeline(deps);
2262
- }
2263
- async execute(options) {
2264
- const { contextName, sourceDir, outputDir, pathAliasRewrites, removeDecorators } = options;
2265
- const contextOutputDir = join(outputDir, contextName);
2266
- this.deps.logger.info(`Processing context: ${contextName}`);
2267
- this.deps.logger.debug(` Source: ${sourceDir}`);
2268
- this.deps.logger.debug(` Output: ${contextOutputDir}`);
2269
- const decoratedFiles = await this.scan(sourceDir);
2270
- const messages = await this.parse(decoratedFiles, sourceDir);
2271
- const fileGraph = await this.resolve(decoratedFiles, sourceDir);
2272
- const responseTypesToExport = this.collectResponseTypesToExport(messages);
2273
- const copiedFiles = await this.copy(fileGraph, sourceDir, contextOutputDir, pathAliasRewrites, responseTypesToExport, removeDecorators, this.messageTypes);
2274
- await this.exportBarrel(copiedFiles, contextOutputDir);
2275
- this.deps.logger.info(`Completed context: ${contextName} (${messages.events.length} events, ${messages.commands.length} commands, ${messages.queries.length} queries, ${copiedFiles.length} files)`);
2276
- return {
2277
- events: messages.events,
2278
- commands: messages.commands,
2279
- queries: messages.queries,
2280
- copiedFiles
2281
- };
2282
- }
2283
- collectResponseTypesToExport(messages) {
2284
- const result = /* @__PURE__ */ new Map();
2285
- const allMessages = [...messages.commands, ...messages.queries];
2286
- for (const message of allMessages) {
2287
- if (message.resultType?.kind !== "reference") continue;
2288
- const typeName = message.resultType.name;
2289
- const sourceFile = message.sourceFile.absolutePath;
2290
- const typeDef = messages.typeDefinitions.find(
2291
- (t) => t.name === typeName && t.sourceFile.absolutePath === sourceFile && !t.exported
2292
- );
2293
- if (typeDef) {
2294
- const existing = result.get(sourceFile) ?? [];
2295
- if (!existing.includes(typeName)) {
2296
- existing.push(typeName);
2297
- result.set(sourceFile, existing);
2298
- }
2299
- }
2300
- }
2301
- if (result.size > 0) {
2302
- this.deps.logger.debug(`Found ${result.size} file(s) with unexported response types`);
2303
- }
2304
- return result;
2305
- }
2306
- async scan(sourceDir) {
2307
- this.deps.logger.debug(`Scanning for decorated files in ${sourceDir}`);
2308
- const files = await this.deps.scanner.scan(sourceDir);
2309
- this.deps.logger.debug(`Found ${files.length} decorated file(s)`);
2310
- return files;
2311
- }
2312
- async parse(files, sourceRoot) {
2313
- this.deps.logger.debug(`Parsing ${files.length} file(s)`);
2314
- const events = [];
2315
- const commands = [];
2316
- const queries = [];
2317
- const typeDefinitions = [];
2318
- for (const file of files) {
2319
- const content = await this.deps.fileSystem.readFile(file);
2320
- const sourceFileInfo = {
2321
- absolutePath: file,
2322
- relativePath: relative(sourceRoot, file)
2323
- };
2324
- const result = this.deps.parser.parse(content, sourceFileInfo);
2325
- events.push(...result.events);
2326
- commands.push(...result.commands);
2327
- queries.push(...result.queries);
2328
- typeDefinitions.push(...result.typeDefinitions);
2329
- }
2330
- this.deps.logger.debug(`Parsed ${events.length} event(s), ${commands.length} command(s), ${queries.length} query(s), ${typeDefinitions.length} type(s)`);
2331
- return { events, commands, queries, typeDefinitions };
2332
- }
2333
- async resolve(entryPoints, sourceRoot) {
2334
- this.deps.logger.debug(`Resolving dependencies for ${entryPoints.length} entry point(s)`);
2335
- const graph = await this.deps.fileGraphResolver.buildGraph(entryPoints, sourceRoot);
2336
- this.deps.logger.debug(`Resolved ${graph.nodes.size} file(s) in dependency graph`);
2337
- return graph;
2338
- }
2339
- async copy(fileGraph, sourceRoot, outputDir, pathAliasRewrites, responseTypesToExport, removeDecorators, messageTypes) {
2340
- this.deps.logger.debug(`Copying files to ${outputDir}`);
2341
- await this.deps.fileSystem.mkdir(outputDir, { recursive: true });
2342
- const result = await this.deps.fileCopier.copyFiles({
2343
- sourceRoot,
2344
- outputDir,
2345
- fileGraph,
2346
- pathAliasRewrites,
2347
- responseTypesToExport,
2348
- removeDecorators,
2349
- messageTypes
2350
- });
2351
- this.deps.logger.debug(`Copied ${result.copiedFiles.length} file(s)`);
2352
- return result.copiedFiles;
2353
- }
2354
- async exportBarrel(copiedFiles, outputDir) {
2355
- this.deps.logger.debug(`Generating barrel export at ${outputDir}/index.ts`);
2356
- const indexContent = this.deps.fileCopier.generateBarrelExport(copiedFiles, outputDir);
2357
- await this.deps.fileSystem.writeFile(join(outputDir, "index.ts"), indexContent);
2358
- }
2359
- };
2360
- var DEFAULT_CONFIG_PATH = "application.config.ts";
2361
- var EXIT_CODE_ERROR = 1;
2362
- var VALID_MESSAGE_TYPES = ["event", "command", "query"];
2363
- var CLI_OPTIONS = {
2364
- config: { short: "-c", long: "--config"},
2365
- outputDir: { short: "-o", long: "--output-dir"},
2366
- messageTypes: { short: "-m", long: "--message-types"},
2367
- generateMessageRegistry: { short: null, long: "--generate-message-registry"},
2368
- help: { short: "-h", long: "--help"}
2369
- };
2370
- function parseMessageTypes(value) {
2371
- const types = value.split(",").map((type) => type.trim().toLowerCase());
2372
- const invalidTypes = types.filter((type) => !VALID_MESSAGE_TYPES.includes(type));
2373
- if (invalidTypes.length > 0) {
2374
- throw new Error(
2375
- `Invalid message type(s): ${invalidTypes.join(", ")}. Valid types are: ${VALID_MESSAGE_TYPES.join(", ")}`
2376
- );
2377
- }
2378
- return types;
2379
- }
2380
- function extractOptionValue(args, currentIndex, optionName) {
2381
- const currentArg = args[currentIndex];
2382
- const equalsIndex = currentArg.indexOf("=");
2383
- if (equalsIndex !== -1) {
2384
- return {
2385
- value: currentArg.slice(equalsIndex + 1),
2386
- nextIndex: currentIndex
2387
- };
2388
- }
2389
- const nextValue = args[currentIndex + 1];
2390
- if (!nextValue) {
2391
- throw new Error(`Missing value for ${optionName} option`);
2392
- }
2393
- return {
2394
- value: nextValue,
2395
- nextIndex: currentIndex + 1
2396
- };
2397
- }
2398
- function matchesOption(arg, option) {
2399
- const matchesShortOrLong = arg === option.short || arg === option.long;
2400
- const matchesLongWithValue = option.long !== null && arg.startsWith(`${option.long}=`);
2401
- return matchesShortOrLong || matchesLongWithValue;
2402
- }
2403
- function parseArgs(args) {
2404
- const options = {
2405
- config: DEFAULT_CONFIG_PATH
2406
- };
2407
- for (let i = 0; i < args.length; i++) {
2408
- const arg = args[i];
2409
- if (matchesOption(arg, CLI_OPTIONS.config)) {
2410
- const { value, nextIndex } = extractOptionValue(args, i, "--config");
2411
- options.config = value;
2412
- i = nextIndex;
2413
- } else if (matchesOption(arg, CLI_OPTIONS.outputDir)) {
2414
- const { value, nextIndex } = extractOptionValue(args, i, "--output-dir");
2415
- options.outputDir = value;
2416
- i = nextIndex;
2417
- } else if (matchesOption(arg, CLI_OPTIONS.messageTypes)) {
2418
- const { value, nextIndex } = extractOptionValue(args, i, "--message-types");
2419
- options.messageTypes = parseMessageTypes(value);
2420
- i = nextIndex;
2421
- } else if (matchesOption(arg, CLI_OPTIONS.generateMessageRegistry)) {
2422
- options.generateMessageRegistry = true;
2423
- } else if (matchesOption(arg, CLI_OPTIONS.help)) {
2424
- printHelp();
2425
- process.exit(0);
2426
- }
2427
- }
2428
- if (!options.outputDir) {
2429
- throw new Error("Missing required option: --output-dir");
2430
- }
2431
- return options;
2432
- }
2433
- function printHelp() {
2434
- console.log(`
2435
- contracts-generator - Extract domain events and commands from TypeScript source
2436
-
2437
- Usage:
2438
- contracts-generator --output-dir <path> [options]
2439
-
2440
- Required:
2441
- -o, --output-dir <path> Output directory for generated contracts
2442
-
2443
- Options:
2444
- -c, --config <path> Path to config file (default: ${DEFAULT_CONFIG_PATH})
2445
- -m, --message-types <types> Filter message types to extract (comma-separated)
2446
- Valid types: ${VALID_MESSAGE_TYPES.join(", ")}
2447
- Default: all types
2448
- --generate-message-registry Generate message registry index.ts file
2449
- Default: not generated
2450
- -h, --help Show this help message
2451
-
2452
- Config file format:
2453
- export default {
2454
- contracts: {
2455
- contexts: [
2456
- {
2457
- name: "lecture",
2458
- sourceDir: "packages/lecture/src",
2459
- },
2460
- ],
2461
- pathAliasRewrites: {
2462
- "@/": "@libera/",
2463
- },
2464
- },
2465
- };
2466
-
2467
- Examples:
2468
- # Extract all message types
2469
- contracts-generator --output-dir packages/contracts/src
2470
-
2471
- # Extract only commands and queries
2472
- contracts-generator -o packages/contracts/requests -m command,query
2473
-
2474
- # Extract only events
2475
- contracts-generator -o packages/contracts/events --message-types event
2476
-
2477
- # Generate with message registry (index.ts)
2478
- contracts-generator -o packages/contracts/src --generate-message-registry
2479
-
2480
- # Use with custom config
2481
- contracts-generator -c app.config.ts -o packages/contracts/src
2482
- `);
2483
- }
2484
- function calculateSummaryTotals(results) {
2485
- return results.reduce(
2486
- (totals, contextResult) => ({
2487
- events: totals.events + contextResult.result.events.length,
2488
- commands: totals.commands + contextResult.result.commands.length,
2489
- files: totals.files + contextResult.result.copiedFiles.length
2490
- }),
2491
- { events: 0, commands: 0, files: 0 }
2492
- );
2493
- }
2494
- function countTotalMessages(totals) {
2495
- return totals.events + totals.commands;
2496
- }
2497
- function logSummary(logger, totals) {
2498
- logger.info("\n--- Summary ---");
2499
- logger.info(`Total events: ${totals.events}`);
2500
- logger.info(`Total commands: ${totals.commands}`);
2501
- logger.info(`Total files copied: ${totals.files}`);
2502
- }
2503
- async function run(args) {
2504
- const options = parseArgs(args);
2505
- const configPath = resolve(options.config);
2506
- const configDir = dirname(configPath);
2507
- const outputDir = resolve(configDir, options.outputDir);
2508
- const logger = new ConsoleLogger({ level: "info" });
2509
- logger.info(`Loading config from: ${configPath}`);
2510
- const configLoader = new ConfigLoader();
2511
- const config = await configLoader.load(configPath);
2512
- logger.info(`Found ${config.contexts.length} context(s) to process`);
2513
- logger.info(`Output directory: ${outputDir}`);
2514
- if (options.messageTypes) {
2515
- logger.info(`Message types filter: ${options.messageTypes.join(", ")}`);
2516
- }
2517
- const pathAliasRewrites = config.pathAliasRewrites ? new Map(Object.entries(config.pathAliasRewrites)) : void 0;
2518
- const results = [];
2519
- for (const contextConfig of config.contexts) {
2520
- const pipeline = ContractsPipeline.create({
2521
- contextConfig,
2522
- messageTypes: options.messageTypes,
2523
- logger
2524
- });
2525
- const result = await pipeline.execute({
2526
- contextName: contextConfig.name,
2527
- sourceDir: contextConfig.sourceDir,
2528
- outputDir,
2529
- pathAliasRewrites,
2530
- removeDecorators: config.removeDecorators
2531
- });
2532
- results.push({ name: contextConfig.name, result, outputDir });
2533
- }
2534
- const totals = calculateSummaryTotals(results);
2535
- logSummary(logger, totals);
2536
- if (options.generateMessageRegistry) {
2537
- await generateRegistry(outputDir, results, totals, logger);
2538
- }
2539
- if (config.pathAliasRewrites) {
2540
- await generateReexports(config, outputDir, results, logger);
2541
- }
2542
- }
2543
- async function toContractsConfig(pluginConfig) {
2544
- const contexts = await Promise.all(
2545
- pluginConfig.contexts.map(
2546
- (ctx) => ContextConfig.create(
2547
- {
2548
- name: ctx.name,
2549
- path: ctx.path,
2550
- sourceDir: ctx.sourceDir,
2551
- tsconfigPath: ctx.tsconfigPath
2552
- },
2553
- process.cwd(),
2554
- nodeFileSystem
2555
- )
2556
- )
2557
- );
2558
- return {
2559
- contexts,
2560
- pathAliasRewrites: pluginConfig.pathAliasRewrites,
2561
- externalDependencies: pluginConfig.externalDependencies,
2562
- decoratorNames: mergeDecoratorNames(pluginConfig.decoratorNames),
2563
- responseNamingConventions: pluginConfig.responseNamingConventions,
2564
- removeDecorators: pluginConfig.removeDecorators ?? true
2565
- };
2566
- }
2567
- async function runWithConfig(options, pluginConfig) {
2568
- const outputDir = resolve(options.outputDir);
2569
- const logger = new ConsoleLogger({ level: "info" });
2570
- const config = await toContractsConfig(pluginConfig);
2571
- logger.info(`Found ${config.contexts.length} context(s) to process`);
2572
- logger.info(`Output directory: ${outputDir}`);
2573
- if (options.messageTypes) {
2574
- logger.info(`Message types filter: ${options.messageTypes.join(", ")}`);
2575
- }
2576
- const pathAliasRewrites = config.pathAliasRewrites ? new Map(Object.entries(config.pathAliasRewrites)) : void 0;
2577
- const results = [];
2578
- for (const contextConfig of config.contexts) {
2579
- const pipeline = ContractsPipeline.create({
2580
- contextConfig,
2581
- messageTypes: options.messageTypes,
2582
- logger
2583
- });
2584
- const result = await pipeline.execute({
2585
- contextName: contextConfig.name,
2586
- sourceDir: contextConfig.sourceDir,
2587
- outputDir,
2588
- pathAliasRewrites,
2589
- removeDecorators: config.removeDecorators
50
+ logger,
51
+ }).execute({
52
+ contextName,
53
+ sourceDir: contextConfig.sourceDir,
54
+ outputDir,
55
+ pathAliasRewrites,
56
+ removeDecorators,
2590
57
  });
2591
- results.push({ name: contextConfig.name, result, outputDir });
2592
- }
2593
- const totals = calculateSummaryTotals(results);
2594
- logSummary(logger, totals);
2595
- if (options.generateMessageRegistry) {
2596
- await generateRegistry(outputDir, results, totals, logger);
2597
- }
2598
- if (config.pathAliasRewrites) {
2599
- await generateReexports(config, outputDir, results, logger);
2600
- }
2601
- }
2602
- async function generateRegistry(outputDir, results, totals, logger) {
2603
- const contextMessages = results.map((contextResult) => {
2604
- const contextOutputDir = join(contextResult.outputDir, contextResult.name);
2605
- const importPath = "./" + relative(outputDir, contextOutputDir).replace(/\\/g, "/");
2606
- return {
2607
- contextName: contextResult.name,
2608
- events: contextResult.result.events,
2609
- commands: contextResult.result.commands,
2610
- queries: contextResult.result.queries,
2611
- importPath
2612
- };
2613
- });
2614
- const generator = new RegistryGenerator({ useNamespace: true });
2615
- const registryContent = generator.generate(contextMessages);
2616
- await nodeFileSystem.mkdir(outputDir, { recursive: true });
2617
- const indexPath = join(outputDir, "index.ts");
2618
- await nodeFileSystem.writeFile(indexPath, registryContent);
2619
- logger.info(` Generated index.ts with ${countTotalMessages(totals)} message(s)`);
2620
- }
2621
- async function generateReexports(config, outputDir, results, logger) {
2622
- logger.info("\n--- Generating re-exports for pathAliasRewrites ---");
2623
- const pathAliasRewrites = new Map(Object.entries(config.pathAliasRewrites));
2624
- const allCopiedFiles = results.flatMap((contextResult) => contextResult.result.copiedFiles);
2625
- const generator = new ReexportGenerator({ fileSystem: nodeFileSystem });
2626
- const reexportFiles = await generator.analyze({
2627
- files: allCopiedFiles,
2628
- pathAliasRewrites
2629
- });
2630
- if (reexportFiles.length === 0) {
2631
- logger.info(" No re-export files needed");
2632
- return;
2633
- }
2634
- const generatedFiles = await generator.generate({
2635
- outputDir,
2636
- reexportFiles
2637
- });
2638
- logger.info(` Generated ${generatedFiles.length} re-export file(s):`);
2639
- for (const file of generatedFiles) {
2640
- logger.info(` - ${relative(outputDir, file)}`);
2641
- }
2642
- }
2643
- async function main() {
2644
- try {
2645
- await run(process.argv.slice(2));
2646
- } catch (error) {
2647
- if (error instanceof Error) {
2648
- console.error(`Error: ${error.message}`);
2649
- } else {
2650
- console.error("Unknown error occurred");
2651
- }
2652
- process.exit(EXIT_CODE_ERROR);
2653
- }
2654
- }
2655
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
2656
- main();
2657
- }
2658
-
2659
- // src/hexai-plugin.ts
2660
- var VALID_MESSAGE_TYPES2 = ["event", "command", "query"];
2661
- function parseMessageTypes2(value) {
2662
- const types = value.split(",").map((type) => type.trim().toLowerCase());
2663
- const invalidTypes = types.filter(
2664
- (type) => !VALID_MESSAGE_TYPES2.includes(type)
2665
- );
2666
- if (invalidTypes.length > 0) {
2667
- throw new Error(
2668
- `Invalid message type(s): ${invalidTypes.join(", ")}. Valid types are: ${VALID_MESSAGE_TYPES2.join(", ")}`
2669
- );
2670
- }
2671
- return types;
2672
- }
2673
- var cliPlugin = {
2674
- name: "generate-contracts",
2675
- description: "Extract domain events, commands, and queries from bounded contexts",
2676
- options: [
2677
- {
2678
- flags: "-o, --output-dir <path>",
2679
- description: "Output directory for generated contracts",
2680
- required: true
2681
- },
2682
- {
2683
- flags: "-m, --message-types <types>",
2684
- description: "Filter message types (comma-separated: event,command,query)"
2685
- },
2686
- {
2687
- flags: "--generate-message-registry",
2688
- description: "Generate message registry index.ts file"
2689
- }
2690
- ],
2691
- run: async (args, config) => {
2692
- const options = {
2693
- outputDir: String(args.outputDir),
2694
- messageTypes: args.messageTypes ? parseMessageTypes2(String(args.messageTypes)) : void 0,
2695
- generateMessageRegistry: args.generateMessageRegistry === true
2696
- };
2697
- await runWithConfig(options, config);
2698
- }
2699
- };
2700
-
2701
- // src/index.ts
2702
- async function processContext(options) {
2703
- const {
2704
- contextName,
2705
- path: contextPath,
2706
- sourceDir,
2707
- outputDir,
2708
- pathAliasRewrites,
2709
- tsconfigPath,
2710
- responseNamingConventions,
2711
- removeDecorators,
2712
- messageTypes,
2713
- fileSystem = nodeFileSystem,
2714
- logger = noopLogger
2715
- } = options;
2716
- const contextConfig = await ContextConfig.create(
2717
- {
2718
- name: contextName,
2719
- path: contextPath,
2720
- sourceDir,
2721
- tsconfigPath,
2722
- responseNamingConventions
2723
- },
2724
- process.cwd(),
2725
- fileSystem
2726
- );
2727
- return ContractsPipeline.create({
2728
- contextConfig,
2729
- responseNamingConventions,
2730
- messageTypes,
2731
- fileSystem,
2732
- logger
2733
- }).execute({
2734
- contextName,
2735
- sourceDir: contextConfig.sourceDir,
2736
- outputDir,
2737
- pathAliasRewrites,
2738
- removeDecorators
2739
- });
2740
58
  }
2741
-
2742
- export { ConfigLoadError, ConfigLoader, ConfigurationError, ConsoleLogger, ContextConfig, ContractsPipeline, FileCopier, FileGraphResolver, FileNotFoundError, FileReadError, FileSystemError, FileWriteError, JsonParseError, MessageParserError, ModuleResolutionError, ParseError, Parser, PublicCommand, PublicEvent, PublicQuery, ReexportGenerator, RegistryGenerator, ResolutionError, Scanner, cliPlugin, isArrayType, isCommand, isDomainEvent, isFunctionType, isIntersectionType, isLiteralType, isObjectType, isPrimitiveType, isReferenceType, isTupleType, isUnionType, nodeFileSystem, noopLogger, processContext };
2743
- //# sourceMappingURL=index.js.map
59
+ // Hexai CLI plugin integration
60
+ export { cliPlugin } from "./hexai-plugin.js";
2744
61
  //# sourceMappingURL=index.js.map