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