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