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