wireweaver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +9 -0
- package/README.md +252 -0
- package/dist/index.cjs +143 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +111 -0
- package/dist/vite-plugin.cjs +652 -0
- package/dist/vite-plugin.d.ts +35 -0
- package/dist/vite-plugin.js +614 -0
- package/package.json +53 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/vite-plugin.ts
|
|
31
|
+
var vite_plugin_exports = {};
|
|
32
|
+
__export(vite_plugin_exports, {
|
|
33
|
+
default: () => vite_plugin_default,
|
|
34
|
+
transformSource: () => transformSource,
|
|
35
|
+
wireWeaverEsbuildPlugin: () => wireWeaverEsbuildPlugin,
|
|
36
|
+
wireWeaverPlugin: () => wireWeaverPlugin
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(vite_plugin_exports);
|
|
39
|
+
var import_typescript = __toESM(require("typescript"), 1);
|
|
40
|
+
var import_promises = require("fs/promises");
|
|
41
|
+
var import_node_path = require("path");
|
|
42
|
+
var import_node_fs = require("fs");
|
|
43
|
+
function wireWeaverPlugin(options = {}) {
|
|
44
|
+
let implementations = /* @__PURE__ */ new Map();
|
|
45
|
+
let beanRegistrations = /* @__PURE__ */ new Map();
|
|
46
|
+
let projectRoot = process.cwd();
|
|
47
|
+
return {
|
|
48
|
+
name: "wireweaver",
|
|
49
|
+
enforce: "pre",
|
|
50
|
+
configResolved(config) {
|
|
51
|
+
projectRoot = config.root;
|
|
52
|
+
},
|
|
53
|
+
buildStart() {
|
|
54
|
+
const extraScanDirs = resolveExtraScanDirs(projectRoot, options.extraScanDirs);
|
|
55
|
+
implementations = discoverInterfaceImplementations(projectRoot, extraScanDirs);
|
|
56
|
+
beanRegistrations = discoverBeanRegistrations(projectRoot, implementations, extraScanDirs);
|
|
57
|
+
},
|
|
58
|
+
transform(code, id) {
|
|
59
|
+
if (!/\.(ts|tsx)$/.test(id) || id.endsWith(".d.ts")) return null;
|
|
60
|
+
return transformSource(code, id, implementations, beanRegistrations);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
var vite_plugin_default = wireWeaverPlugin;
|
|
65
|
+
function wireWeaverEsbuildPlugin(options = {}) {
|
|
66
|
+
let implementations = /* @__PURE__ */ new Map();
|
|
67
|
+
let beanRegistrations = /* @__PURE__ */ new Map();
|
|
68
|
+
return {
|
|
69
|
+
name: "wireweaver",
|
|
70
|
+
setup(build) {
|
|
71
|
+
build.onStart(() => {
|
|
72
|
+
const cwd = process.cwd();
|
|
73
|
+
const extraScanDirs = resolveExtraScanDirs(cwd, options.extraScanDirs);
|
|
74
|
+
implementations = discoverInterfaceImplementations(cwd, extraScanDirs);
|
|
75
|
+
beanRegistrations = discoverBeanRegistrations(cwd, implementations, extraScanDirs);
|
|
76
|
+
});
|
|
77
|
+
build.onLoad({ filter: /\.(ts|tsx)$/ }, async (args) => {
|
|
78
|
+
if (args.path.endsWith(".d.ts")) return void 0;
|
|
79
|
+
const source = await (0, import_promises.readFile)(args.path, "utf8");
|
|
80
|
+
const result = transformSource(source, args.path, implementations, beanRegistrations);
|
|
81
|
+
if (!result) return void 0;
|
|
82
|
+
return { contents: result.code, loader: "ts" };
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function transformSource(code, fileName, implementations = /* @__PURE__ */ new Map(), beanRegistrations = /* @__PURE__ */ new Map()) {
|
|
88
|
+
const sourceFile = import_typescript.default.createSourceFile(
|
|
89
|
+
fileName,
|
|
90
|
+
code,
|
|
91
|
+
import_typescript.default.ScriptTarget.Latest,
|
|
92
|
+
/* setParentNodes */
|
|
93
|
+
true,
|
|
94
|
+
import_typescript.default.ScriptKind.TS
|
|
95
|
+
);
|
|
96
|
+
const replacements = [];
|
|
97
|
+
const importsToInject = /* @__PURE__ */ new Map();
|
|
98
|
+
const sideEffectImports = /* @__PURE__ */ new Set();
|
|
99
|
+
const declaredSymbols = collectDeclaredSymbols(sourceFile);
|
|
100
|
+
const typeOnlySymbols = collectTypeOnlySymbols(sourceFile);
|
|
101
|
+
const localImplementations = discoverLocalImplementations(sourceFile, fileName);
|
|
102
|
+
const localBeanRegistrations = new Map(beanRegistrations);
|
|
103
|
+
collectBeanRegistrationsFromFile(sourceFile, fileName, implementations, localBeanRegistrations);
|
|
104
|
+
function resolveImplementations(typeName) {
|
|
105
|
+
return localImplementations.get(typeName) ?? implementations.get(typeName);
|
|
106
|
+
}
|
|
107
|
+
function visit(node) {
|
|
108
|
+
if (import_typescript.default.isClassDeclaration(node)) {
|
|
109
|
+
const decorators = import_typescript.default.getDecorators(node);
|
|
110
|
+
if (decorators) {
|
|
111
|
+
for (const decorator of decorators) {
|
|
112
|
+
if (!import_typescript.default.isCallExpression(decorator.expression)) continue;
|
|
113
|
+
const callExpr = decorator.expression;
|
|
114
|
+
if (!import_typescript.default.isIdentifier(callExpr.expression)) continue;
|
|
115
|
+
const decoratorName = callExpr.expression.text;
|
|
116
|
+
if (!isDIComponentDecoratorName(decoratorName)) continue;
|
|
117
|
+
if (callExpr.arguments.length !== 0) continue;
|
|
118
|
+
const genericTypeParams = new Set(
|
|
119
|
+
(node.typeParameters ?? []).map((tp) => tp.name.text)
|
|
120
|
+
);
|
|
121
|
+
const dependencyNames = getConstructorDependencies(node).flatMap((dependency) => {
|
|
122
|
+
if (genericTypeParams.has(dependency.typeName)) return [];
|
|
123
|
+
const candidates = resolveImplementations(dependency.typeName);
|
|
124
|
+
if (candidates && candidates.length > 0) {
|
|
125
|
+
const match = pickImplementationForParameter(dependency, candidates);
|
|
126
|
+
if (!match) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`WireWeaver: multiple implementations found for ${dependency.typeName} but none matched constructor parameter '${dependency.paramName ?? "unknown"}' in ${fileName}.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (!declaredSymbols.has(match.className) && normalizeFilePath(fileName) !== normalizeFilePath(match.filePath)) {
|
|
132
|
+
const importPath = match.importSpecifier ?? toImportPath(fileName, match.filePath);
|
|
133
|
+
const names = importsToInject.get(importPath) ?? /* @__PURE__ */ new Set();
|
|
134
|
+
names.add(match.className);
|
|
135
|
+
importsToInject.set(importPath, names);
|
|
136
|
+
}
|
|
137
|
+
const beanFile = localBeanRegistrations.get(match.className);
|
|
138
|
+
if (beanFile && normalizeFilePath(beanFile) !== normalizeFilePath(fileName) && normalizeFilePath(beanFile) !== normalizeFilePath(match.filePath)) {
|
|
139
|
+
sideEffectImports.add(toImportPath(fileName, beanFile));
|
|
140
|
+
}
|
|
141
|
+
return [match.className];
|
|
142
|
+
}
|
|
143
|
+
if (typeOnlySymbols.has(dependency.typeName)) return [];
|
|
144
|
+
const beanFileForConcrete = localBeanRegistrations.get(dependency.typeName);
|
|
145
|
+
if (beanFileForConcrete && normalizeFilePath(beanFileForConcrete) !== normalizeFilePath(fileName)) {
|
|
146
|
+
sideEffectImports.add(toImportPath(fileName, beanFileForConcrete));
|
|
147
|
+
}
|
|
148
|
+
return [dependency.typeName];
|
|
149
|
+
});
|
|
150
|
+
if (dependencyNames.length === 0) continue;
|
|
151
|
+
replacements.push({
|
|
152
|
+
start: callExpr.getStart(sourceFile),
|
|
153
|
+
end: callExpr.getEnd(),
|
|
154
|
+
text: `${decoratorName}([${dependencyNames.join(", ")}])`
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (import_typescript.default.isMethodDeclaration(node)) {
|
|
160
|
+
const isStaticMethod = (import_typescript.default.getModifiers(node) ?? []).some((m) => m.kind === import_typescript.default.SyntaxKind.StaticKeyword);
|
|
161
|
+
if (isStaticMethod) {
|
|
162
|
+
const decorators = import_typescript.default.getDecorators(node);
|
|
163
|
+
if (decorators) {
|
|
164
|
+
for (const decorator of decorators) {
|
|
165
|
+
if (!import_typescript.default.isCallExpression(decorator.expression)) continue;
|
|
166
|
+
const callExpr = decorator.expression;
|
|
167
|
+
if (!import_typescript.default.isIdentifier(callExpr.expression)) continue;
|
|
168
|
+
const decoratorName = callExpr.expression.text;
|
|
169
|
+
if (!isDIBeanDecoratorName(decoratorName)) continue;
|
|
170
|
+
if (callExpr.arguments.length !== 0) continue;
|
|
171
|
+
if (!node.type || !import_typescript.default.isTypeReferenceNode(node.type)) continue;
|
|
172
|
+
if (!import_typescript.default.isIdentifier(node.type.typeName)) continue;
|
|
173
|
+
const returnTypeName = node.type.typeName.text;
|
|
174
|
+
const candidates = resolveImplementations(returnTypeName);
|
|
175
|
+
let registrationClassName;
|
|
176
|
+
if (candidates && candidates.length > 0) {
|
|
177
|
+
if (candidates.length > 1) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`WireWeaver: @${decoratorName}() has multiple implementations for return type ${returnTypeName} in ${fileName}. Specify the class explicitly: @${decoratorName}(ConcreteClass).`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
const match = candidates[0];
|
|
183
|
+
registrationClassName = match.className;
|
|
184
|
+
if (!declaredSymbols.has(match.className) && normalizeFilePath(fileName) !== normalizeFilePath(match.filePath)) {
|
|
185
|
+
const importPath = match.importSpecifier ?? toImportPath(fileName, match.filePath);
|
|
186
|
+
const names = importsToInject.get(importPath) ?? /* @__PURE__ */ new Set();
|
|
187
|
+
names.add(match.className);
|
|
188
|
+
importsToInject.set(importPath, names);
|
|
189
|
+
}
|
|
190
|
+
} else if (!typeOnlySymbols.has(returnTypeName)) {
|
|
191
|
+
registrationClassName = returnTypeName;
|
|
192
|
+
}
|
|
193
|
+
if (registrationClassName) {
|
|
194
|
+
replacements.push({
|
|
195
|
+
start: callExpr.getStart(sourceFile),
|
|
196
|
+
end: callExpr.getEnd(),
|
|
197
|
+
text: `${decoratorName}(${registrationClassName})`
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (import_typescript.default.isCallExpression(node)) {
|
|
205
|
+
const callee = node.expression;
|
|
206
|
+
if (import_typescript.default.isIdentifier(callee) && isDIResolveFunctionName(callee.text)) {
|
|
207
|
+
if (node.typeArguments?.length === 1 && node.arguments.length === 0) {
|
|
208
|
+
const typeArg = node.typeArguments[0];
|
|
209
|
+
if (import_typescript.default.isTypeReferenceNode(typeArg) && import_typescript.default.isIdentifier(typeArg.typeName)) {
|
|
210
|
+
const typeName = typeArg.typeName.text;
|
|
211
|
+
const candidates = resolveImplementations(typeName);
|
|
212
|
+
if (candidates && candidates.length > 0) {
|
|
213
|
+
if (candidates.length > 1) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`WireWeaver: multiple implementations found for ${typeName} in ${fileName}. Cannot disambiguate a ${callee.text}<${typeName}>() call \u2014 use ${callee.text}(ConcreteClass) instead.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const match = candidates[0];
|
|
219
|
+
if (!declaredSymbols.has(match.className) && normalizeFilePath(fileName) !== normalizeFilePath(match.filePath)) {
|
|
220
|
+
const importPath = match.importSpecifier ?? toImportPath(fileName, match.filePath);
|
|
221
|
+
const names = importsToInject.get(importPath) ?? /* @__PURE__ */ new Set();
|
|
222
|
+
names.add(match.className);
|
|
223
|
+
importsToInject.set(importPath, names);
|
|
224
|
+
}
|
|
225
|
+
const beanFile = localBeanRegistrations.get(match.className);
|
|
226
|
+
if (beanFile && normalizeFilePath(beanFile) !== normalizeFilePath(fileName) && normalizeFilePath(beanFile) !== normalizeFilePath(match.filePath)) {
|
|
227
|
+
sideEffectImports.add(toImportPath(fileName, beanFile));
|
|
228
|
+
}
|
|
229
|
+
replacements.push({
|
|
230
|
+
start: node.getStart(sourceFile),
|
|
231
|
+
end: node.getEnd(),
|
|
232
|
+
text: `${callee.text}(${match.className})`
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (node.arguments.length === 1 && (!node.typeArguments || node.typeArguments.length === 0)) {
|
|
238
|
+
const arg = node.arguments[0];
|
|
239
|
+
if (import_typescript.default.isIdentifier(arg)) {
|
|
240
|
+
const beanFile = localBeanRegistrations.get(arg.text);
|
|
241
|
+
if (beanFile && normalizeFilePath(beanFile) !== normalizeFilePath(fileName)) {
|
|
242
|
+
sideEffectImports.add(toImportPath(fileName, beanFile));
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
import_typescript.default.forEachChild(node, visit);
|
|
249
|
+
}
|
|
250
|
+
visit(sourceFile);
|
|
251
|
+
if (replacements.length === 0 && importsToInject.size === 0 && sideEffectImports.size === 0) return null;
|
|
252
|
+
const importInsertions = buildImportInsertions(code, sourceFile, importsToInject, sideEffectImports, declaredSymbols);
|
|
253
|
+
replacements.push(...importInsertions);
|
|
254
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
255
|
+
let result = code;
|
|
256
|
+
for (const r of replacements) {
|
|
257
|
+
result = result.slice(0, r.start) + r.text + result.slice(r.end);
|
|
258
|
+
}
|
|
259
|
+
return { code: result };
|
|
260
|
+
}
|
|
261
|
+
function discoverLocalImplementations(sourceFile, fileName) {
|
|
262
|
+
const localImpls = /* @__PURE__ */ new Map();
|
|
263
|
+
function visitNode(node) {
|
|
264
|
+
if (import_typescript.default.isClassDeclaration(node) && node.name) {
|
|
265
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
266
|
+
if (clause.token !== import_typescript.default.SyntaxKind.ImplementsKeyword) continue;
|
|
267
|
+
for (const type of clause.types) {
|
|
268
|
+
const interfaceName = import_typescript.default.isIdentifier(type.expression) ? type.expression.text : void 0;
|
|
269
|
+
if (!interfaceName) continue;
|
|
270
|
+
const existing = localImpls.get(interfaceName) ?? [];
|
|
271
|
+
existing.push({ className: node.name.text, filePath: fileName });
|
|
272
|
+
localImpls.set(interfaceName, existing);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
import_typescript.default.forEachChild(node, visitNode);
|
|
277
|
+
}
|
|
278
|
+
visitNode(sourceFile);
|
|
279
|
+
return localImpls;
|
|
280
|
+
}
|
|
281
|
+
function discoverBeanRegistrations(root, implementations, extraScanDirs = []) {
|
|
282
|
+
const result = /* @__PURE__ */ new Map();
|
|
283
|
+
collectBeanRegistrationsFromDir(root, implementations, result, false);
|
|
284
|
+
for (const dir of extraScanDirs) {
|
|
285
|
+
collectBeanRegistrationsFromDir(dir, implementations, result, true);
|
|
286
|
+
}
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
function collectBeanRegistrationsFromDir(root, implementations, result, allowNodeModules) {
|
|
290
|
+
const tsconfigPath = import_typescript.default.findConfigFile(root, import_typescript.default.sys.fileExists, "tsconfig.json");
|
|
291
|
+
if (!tsconfigPath) return;
|
|
292
|
+
const configFile = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
|
|
293
|
+
if (configFile.error) return;
|
|
294
|
+
const parsedConfig = import_typescript.default.parseJsonConfigFileContent(configFile.config, import_typescript.default.sys, getDirName(tsconfigPath));
|
|
295
|
+
const program = import_typescript.default.createProgram({ rootNames: parsedConfig.fileNames, options: parsedConfig.options });
|
|
296
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
297
|
+
if (sourceFile.isDeclarationFile) continue;
|
|
298
|
+
if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
|
|
299
|
+
collectBeanRegistrationsFromFile(sourceFile, sourceFile.fileName, implementations, result);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function collectBeanRegistrationsFromFile(sourceFile, filePath, globalImplementations, result) {
|
|
303
|
+
const localImpls = discoverLocalImplementations(sourceFile, filePath);
|
|
304
|
+
const typeOnlySymbols = collectTypeOnlySymbols(sourceFile);
|
|
305
|
+
function resolveToClassName(typeName) {
|
|
306
|
+
const local = localImpls.get(typeName);
|
|
307
|
+
if (local && local.length === 1) return local[0].className;
|
|
308
|
+
const global = globalImplementations.get(typeName);
|
|
309
|
+
if (global && global.length === 1) return global[0].className;
|
|
310
|
+
if (!typeOnlySymbols.has(typeName)) return typeName;
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
function visitNode(node) {
|
|
314
|
+
if (import_typescript.default.isMethodDeclaration(node)) {
|
|
315
|
+
const isStatic = (import_typescript.default.getModifiers(node) ?? []).some((m) => m.kind === import_typescript.default.SyntaxKind.StaticKeyword);
|
|
316
|
+
if (isStatic) {
|
|
317
|
+
for (const decorator of import_typescript.default.getDecorators(node) ?? []) {
|
|
318
|
+
if (!import_typescript.default.isCallExpression(decorator.expression)) continue;
|
|
319
|
+
if (!import_typescript.default.isIdentifier(decorator.expression.expression)) continue;
|
|
320
|
+
if (!isDIBeanDecoratorName(decorator.expression.expression.text)) continue;
|
|
321
|
+
const args = decorator.expression.arguments;
|
|
322
|
+
if (args.length > 0 && import_typescript.default.isIdentifier(args[0])) {
|
|
323
|
+
result.set(args[0].text, filePath);
|
|
324
|
+
} else if (node.type && import_typescript.default.isTypeReferenceNode(node.type) && import_typescript.default.isIdentifier(node.type.typeName)) {
|
|
325
|
+
const className = resolveToClassName(node.type.typeName.text);
|
|
326
|
+
if (className) result.set(className, filePath);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
import_typescript.default.forEachChild(node, visitNode);
|
|
332
|
+
}
|
|
333
|
+
visitNode(sourceFile);
|
|
334
|
+
}
|
|
335
|
+
function discoverInterfaceImplementations(root, extraScanDirs = []) {
|
|
336
|
+
const implementations = /* @__PURE__ */ new Map();
|
|
337
|
+
const allClassHeritage = /* @__PURE__ */ new Map();
|
|
338
|
+
const beanReturnTypes = /* @__PURE__ */ new Set();
|
|
339
|
+
scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, false);
|
|
340
|
+
for (const dir of extraScanDirs) {
|
|
341
|
+
const packageRoot = findPackageRoot(dir);
|
|
342
|
+
scanSourceFilesForImplementations(dir, implementations, allClassHeritage, beanReturnTypes, true, packageRoot);
|
|
343
|
+
}
|
|
344
|
+
for (const typeName of beanReturnTypes) {
|
|
345
|
+
const entries = allClassHeritage.get(typeName);
|
|
346
|
+
if (!entries) continue;
|
|
347
|
+
for (const entry of entries) {
|
|
348
|
+
const existing = implementations.get(entry.interfaceName) ?? [];
|
|
349
|
+
if (existing.some((i) => i.className === typeName && normalizeFilePath(i.filePath) === normalizeFilePath(entry.filePath))) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
existing.push({ className: typeName, filePath: entry.filePath, importSpecifier: entry.importSpecifier });
|
|
353
|
+
implementations.set(entry.interfaceName, existing);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return implementations;
|
|
357
|
+
}
|
|
358
|
+
function scanSourceFilesForImplementations(root, implementations, allClassHeritage, beanReturnTypes, allowNodeModules, packageRoot) {
|
|
359
|
+
const tsconfigPath = import_typescript.default.findConfigFile(root, import_typescript.default.sys.fileExists, "tsconfig.json");
|
|
360
|
+
if (!tsconfigPath) return;
|
|
361
|
+
const configFile = import_typescript.default.readConfigFile(tsconfigPath, import_typescript.default.sys.readFile);
|
|
362
|
+
if (configFile.error) return;
|
|
363
|
+
const parsedConfig = import_typescript.default.parseJsonConfigFileContent(
|
|
364
|
+
configFile.config,
|
|
365
|
+
import_typescript.default.sys,
|
|
366
|
+
getDirName(tsconfigPath)
|
|
367
|
+
);
|
|
368
|
+
const program = import_typescript.default.createProgram({
|
|
369
|
+
rootNames: parsedConfig.fileNames,
|
|
370
|
+
options: parsedConfig.options
|
|
371
|
+
});
|
|
372
|
+
for (const sourceFile of program.getSourceFiles()) {
|
|
373
|
+
if (sourceFile.isDeclarationFile) continue;
|
|
374
|
+
if (!allowNodeModules && sourceFile.fileName.includes("/node_modules/")) continue;
|
|
375
|
+
import_typescript.default.forEachChild(sourceFile, (node) => {
|
|
376
|
+
if (import_typescript.default.isClassDeclaration(node) && node.name) {
|
|
377
|
+
const importSpecifier = packageRoot ? resolvePackageImportSpecifier(node.name.text, packageRoot) : void 0;
|
|
378
|
+
for (const clause of node.heritageClauses ?? []) {
|
|
379
|
+
if (clause.token !== import_typescript.default.SyntaxKind.ImplementsKeyword) continue;
|
|
380
|
+
for (const type of clause.types) {
|
|
381
|
+
const interfaceName = import_typescript.default.isIdentifier(type.expression) ? type.expression.text : void 0;
|
|
382
|
+
if (!interfaceName) continue;
|
|
383
|
+
const heritageEntries = allClassHeritage.get(node.name.text) ?? [];
|
|
384
|
+
if (!heritageEntries.some((e) => e.interfaceName === interfaceName && normalizeFilePath(e.filePath) === normalizeFilePath(sourceFile.fileName))) {
|
|
385
|
+
heritageEntries.push({ interfaceName, filePath: sourceFile.fileName, importSpecifier });
|
|
386
|
+
allClassHeritage.set(node.name.text, heritageEntries);
|
|
387
|
+
}
|
|
388
|
+
if (!hasServiceDecorator(node)) continue;
|
|
389
|
+
const existing = implementations.get(interfaceName) ?? [];
|
|
390
|
+
if (existing.some((impl) => impl.className === node.name.text && normalizeFilePath(impl.filePath) === normalizeFilePath(sourceFile.fileName))) {
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
existing.push({ className: node.name.text, filePath: sourceFile.fileName, importSpecifier });
|
|
394
|
+
implementations.set(interfaceName, existing);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
if (import_typescript.default.isClassDeclaration(node)) {
|
|
399
|
+
for (const member of node.members) {
|
|
400
|
+
if (!import_typescript.default.isMethodDeclaration(member)) continue;
|
|
401
|
+
const isStatic = (import_typescript.default.getModifiers(member) ?? []).some((m) => m.kind === import_typescript.default.SyntaxKind.StaticKeyword);
|
|
402
|
+
if (!isStatic) continue;
|
|
403
|
+
const memberDecs = import_typescript.default.getDecorators(member) ?? member.modifiers?.filter(import_typescript.default.isDecorator) ?? [];
|
|
404
|
+
for (const dec of memberDecs) {
|
|
405
|
+
if (!import_typescript.default.isCallExpression(dec.expression)) continue;
|
|
406
|
+
if (!import_typescript.default.isIdentifier(dec.expression.expression)) continue;
|
|
407
|
+
if (!isDIBeanDecoratorName(dec.expression.expression.text)) continue;
|
|
408
|
+
const args = dec.expression.arguments;
|
|
409
|
+
if (args.length > 0 && import_typescript.default.isIdentifier(args[0])) {
|
|
410
|
+
beanReturnTypes.add(args[0].text);
|
|
411
|
+
} else if (member.type && import_typescript.default.isTypeReferenceNode(member.type) && import_typescript.default.isIdentifier(member.type.typeName)) {
|
|
412
|
+
beanReturnTypes.add(member.type.typeName.text);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function hasServiceDecorator(classNode) {
|
|
421
|
+
const decorators = import_typescript.default.getDecorators(classNode) ?? classNode.modifiers?.filter(import_typescript.default.isDecorator) ?? [];
|
|
422
|
+
for (const decorator of decorators) {
|
|
423
|
+
if (!import_typescript.default.isCallExpression(decorator.expression)) continue;
|
|
424
|
+
if (!import_typescript.default.isIdentifier(decorator.expression.expression)) continue;
|
|
425
|
+
if (isDIComponentDecoratorName(decorator.expression.expression.text)) return true;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
function isDIComponentDecoratorName(name) {
|
|
430
|
+
return name === "Service" || name === "Component";
|
|
431
|
+
}
|
|
432
|
+
function isDIResolveFunctionName(name) {
|
|
433
|
+
return name === "resolve" || name === "getService";
|
|
434
|
+
}
|
|
435
|
+
function isDIBeanDecoratorName(name) {
|
|
436
|
+
return name === "Bean" || name === "Instance";
|
|
437
|
+
}
|
|
438
|
+
function collectTypeOnlySymbols(sourceFile) {
|
|
439
|
+
const symbols = /* @__PURE__ */ new Set();
|
|
440
|
+
for (const statement of sourceFile.statements) {
|
|
441
|
+
if (import_typescript.default.isImportDeclaration(statement) && statement.importClause) {
|
|
442
|
+
const isEntirelyTypeOnly = statement.importClause.isTypeOnly;
|
|
443
|
+
const bindings = statement.importClause.namedBindings;
|
|
444
|
+
if (bindings && import_typescript.default.isNamedImports(bindings)) {
|
|
445
|
+
for (const element of bindings.elements) {
|
|
446
|
+
if (isEntirelyTypeOnly || element.isTypeOnly) {
|
|
447
|
+
symbols.add(element.name.text);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function collectTypeDeclarations(node) {
|
|
454
|
+
if (import_typescript.default.isInterfaceDeclaration(node) && node.name) {
|
|
455
|
+
symbols.add(node.name.text);
|
|
456
|
+
}
|
|
457
|
+
if (import_typescript.default.isTypeAliasDeclaration(node) && node.name) {
|
|
458
|
+
symbols.add(node.name.text);
|
|
459
|
+
}
|
|
460
|
+
import_typescript.default.forEachChild(node, collectTypeDeclarations);
|
|
461
|
+
}
|
|
462
|
+
collectTypeDeclarations(sourceFile);
|
|
463
|
+
return symbols;
|
|
464
|
+
}
|
|
465
|
+
function collectDeclaredSymbols(sourceFile) {
|
|
466
|
+
const symbols = /* @__PURE__ */ new Set();
|
|
467
|
+
for (const statement of sourceFile.statements) {
|
|
468
|
+
if (import_typescript.default.isImportDeclaration(statement) && statement.importClause) {
|
|
469
|
+
if (statement.importClause.name) {
|
|
470
|
+
symbols.add(statement.importClause.name.text);
|
|
471
|
+
}
|
|
472
|
+
const namedBindings = statement.importClause.namedBindings;
|
|
473
|
+
if (namedBindings && import_typescript.default.isNamedImports(namedBindings)) {
|
|
474
|
+
for (const element of namedBindings.elements) {
|
|
475
|
+
symbols.add(element.name.text);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (import_typescript.default.isClassDeclaration(statement) && statement.name) {
|
|
480
|
+
symbols.add(statement.name.text);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return symbols;
|
|
484
|
+
}
|
|
485
|
+
function buildImportInsertions(code, sourceFile, importsToInject, sideEffectImports, declaredSymbols) {
|
|
486
|
+
const replacements = [];
|
|
487
|
+
for (const importPath of sideEffectImports) {
|
|
488
|
+
if (!findImportByModule(sourceFile, importPath)) {
|
|
489
|
+
const insertionPoint = getImportInsertionPoint(sourceFile);
|
|
490
|
+
replacements.push({ start: insertionPoint, end: insertionPoint, text: `import '${importPath}';
|
|
491
|
+
` });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
for (const [importPath, names] of importsToInject.entries()) {
|
|
495
|
+
const unresolved = [...names].filter((name) => !declaredSymbols.has(name));
|
|
496
|
+
if (unresolved.length === 0) continue;
|
|
497
|
+
const existing = findImportByModule(sourceFile, importPath);
|
|
498
|
+
if (existing && existing.importClause?.namedBindings && import_typescript.default.isNamedImports(existing.importClause.namedBindings)) {
|
|
499
|
+
const existingNames = new Set(existing.importClause.namedBindings.elements.map((element) => element.name.text));
|
|
500
|
+
const missing = unresolved.filter((name) => !existingNames.has(name));
|
|
501
|
+
if (missing.length === 0) continue;
|
|
502
|
+
const namedImports = existing.importClause.namedBindings;
|
|
503
|
+
replacements.push({
|
|
504
|
+
start: namedImports.getStart(sourceFile) + 1,
|
|
505
|
+
end: namedImports.getEnd() - 1,
|
|
506
|
+
text: `${[...existingNames, ...missing].sort().join(", ")}`
|
|
507
|
+
});
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
const insertionPoint = getImportInsertionPoint(sourceFile);
|
|
511
|
+
replacements.push({
|
|
512
|
+
start: insertionPoint,
|
|
513
|
+
end: insertionPoint,
|
|
514
|
+
text: `import { ${unresolved.sort().join(", ")} } from '${importPath}';
|
|
515
|
+
`
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
return replacements;
|
|
519
|
+
}
|
|
520
|
+
function findImportByModule(sourceFile, moduleSpecifier) {
|
|
521
|
+
for (const statement of sourceFile.statements) {
|
|
522
|
+
if (!import_typescript.default.isImportDeclaration(statement)) continue;
|
|
523
|
+
if (!import_typescript.default.isStringLiteral(statement.moduleSpecifier)) continue;
|
|
524
|
+
if (statement.moduleSpecifier.text !== moduleSpecifier) continue;
|
|
525
|
+
if (statement.importClause?.isTypeOnly) continue;
|
|
526
|
+
return statement;
|
|
527
|
+
}
|
|
528
|
+
return void 0;
|
|
529
|
+
}
|
|
530
|
+
function getImportInsertionPoint(sourceFile) {
|
|
531
|
+
let end = 0;
|
|
532
|
+
for (const statement of sourceFile.statements) {
|
|
533
|
+
if (!import_typescript.default.isImportDeclaration(statement)) break;
|
|
534
|
+
end = statement.getEnd();
|
|
535
|
+
}
|
|
536
|
+
if (end === 0) return 0;
|
|
537
|
+
const fileText = sourceFile.getFullText();
|
|
538
|
+
if (fileText[end] === "\r" && fileText[end + 1] === "\n") return end + 2;
|
|
539
|
+
if (fileText[end] === "\n") return end + 1;
|
|
540
|
+
return end;
|
|
541
|
+
}
|
|
542
|
+
function toImportPath(fromFile, toFile) {
|
|
543
|
+
const fromDir = getDirName(fromFile);
|
|
544
|
+
const target = normalizeFilePath(toFile);
|
|
545
|
+
let relativePath = getRelativePath(fromDir, target).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
546
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
547
|
+
return relativePath;
|
|
548
|
+
}
|
|
549
|
+
function normalizeFilePath(filePath) {
|
|
550
|
+
return filePath.replace(/\\/g, "/");
|
|
551
|
+
}
|
|
552
|
+
function getDirName(filePath) {
|
|
553
|
+
const normalized = normalizeFilePath(filePath);
|
|
554
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
555
|
+
return slashIndex >= 0 ? normalized.slice(0, slashIndex) : ".";
|
|
556
|
+
}
|
|
557
|
+
function getRelativePath(fromDir, toPath) {
|
|
558
|
+
const fromParts = normalizeFilePath(fromDir).split("/").filter(Boolean);
|
|
559
|
+
const toParts = normalizeFilePath(toPath).split("/").filter(Boolean);
|
|
560
|
+
if (fromParts[0]?.endsWith(":") && toParts[0]?.endsWith(":") && fromParts[0] !== toParts[0]) {
|
|
561
|
+
return normalizeFilePath(toPath);
|
|
562
|
+
}
|
|
563
|
+
let shared = 0;
|
|
564
|
+
while (shared < fromParts.length && shared < toParts.length && fromParts[shared] === toParts[shared]) {
|
|
565
|
+
shared += 1;
|
|
566
|
+
}
|
|
567
|
+
const up = new Array(Math.max(fromParts.length - shared, 0)).fill("..");
|
|
568
|
+
const down = toParts.slice(shared);
|
|
569
|
+
const relative = [...up, ...down].join("/");
|
|
570
|
+
return relative || ".";
|
|
571
|
+
}
|
|
572
|
+
function getConstructorDependencies(classNode) {
|
|
573
|
+
const constructorDeclaration = classNode.members.find(import_typescript.default.isConstructorDeclaration);
|
|
574
|
+
if (!constructorDeclaration) return [];
|
|
575
|
+
const dependencies = [];
|
|
576
|
+
for (const parameter of constructorDeclaration.parameters) {
|
|
577
|
+
if (!parameter.type || !import_typescript.default.isTypeReferenceNode(parameter.type)) continue;
|
|
578
|
+
if (!import_typescript.default.isIdentifier(parameter.type.typeName)) continue;
|
|
579
|
+
dependencies.push({
|
|
580
|
+
typeName: parameter.type.typeName.text,
|
|
581
|
+
paramName: import_typescript.default.isIdentifier(parameter.name) ? parameter.name.text : void 0
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
return dependencies;
|
|
585
|
+
}
|
|
586
|
+
function pickImplementationForParameter(dependency, candidates) {
|
|
587
|
+
if (candidates.length === 1) return candidates[0];
|
|
588
|
+
if (!dependency.paramName) return void 0;
|
|
589
|
+
const normalizedParameterName = normalizeSymbolName(dependency.paramName);
|
|
590
|
+
for (const candidate of candidates) {
|
|
591
|
+
if (normalizeSymbolName(candidate.className) === normalizedParameterName) return candidate;
|
|
592
|
+
if (normalizeSymbolName(toCamelCase(candidate.className)) === normalizedParameterName) return candidate;
|
|
593
|
+
const parameterWithoutServiceSuffix = stripServiceSuffix(normalizedParameterName);
|
|
594
|
+
const candidateWithoutServiceSuffix = stripServiceSuffix(normalizeSymbolName(toCamelCase(candidate.className)));
|
|
595
|
+
if (candidateWithoutServiceSuffix === parameterWithoutServiceSuffix) return candidate;
|
|
596
|
+
}
|
|
597
|
+
return void 0;
|
|
598
|
+
}
|
|
599
|
+
function toCamelCase(name) {
|
|
600
|
+
if (name.length === 0) return name;
|
|
601
|
+
return name[0].toLowerCase() + name.slice(1);
|
|
602
|
+
}
|
|
603
|
+
function normalizeSymbolName(name) {
|
|
604
|
+
return name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
605
|
+
}
|
|
606
|
+
function stripServiceSuffix(name) {
|
|
607
|
+
return name.endsWith("service") ? name.slice(0, -"service".length) : name;
|
|
608
|
+
}
|
|
609
|
+
function resolveExtraScanDirs(root, extraScanDirs) {
|
|
610
|
+
if (!extraScanDirs || extraScanDirs.length === 0) return [];
|
|
611
|
+
return extraScanDirs.map((dir) => (0, import_node_path.isAbsolute)(dir) ? dir : (0, import_node_path.resolve)(root, dir));
|
|
612
|
+
}
|
|
613
|
+
function findPackageRoot(startDir) {
|
|
614
|
+
let current = startDir;
|
|
615
|
+
while (true) {
|
|
616
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(current, "package.json"))) return current;
|
|
617
|
+
const parent = (0, import_node_path.dirname)(current);
|
|
618
|
+
if (parent === current) return void 0;
|
|
619
|
+
current = parent;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function resolvePackageImportSpecifier(className, packageRoot) {
|
|
623
|
+
try {
|
|
624
|
+
const pkgJsonPath = (0, import_node_path.join)(packageRoot, "package.json");
|
|
625
|
+
if (!(0, import_node_fs.existsSync)(pkgJsonPath)) return void 0;
|
|
626
|
+
const pkgJson = JSON.parse((0, import_node_fs.readFileSync)(pkgJsonPath, "utf-8"));
|
|
627
|
+
const packageName = pkgJson.name;
|
|
628
|
+
if (!packageName) return void 0;
|
|
629
|
+
const exports2 = pkgJson.exports;
|
|
630
|
+
if (!exports2) return void 0;
|
|
631
|
+
for (const [subPath, entry] of Object.entries(exports2)) {
|
|
632
|
+
const typesFile = typeof entry === "string" ? entry : entry?.types;
|
|
633
|
+
if (!typesFile || !typesFile.endsWith(".d.ts")) continue;
|
|
634
|
+
const absTypesFile = (0, import_node_path.join)(packageRoot, typesFile);
|
|
635
|
+
if (!(0, import_node_fs.existsSync)(absTypesFile)) continue;
|
|
636
|
+
const content = (0, import_node_fs.readFileSync)(absTypesFile, "utf-8");
|
|
637
|
+
if (new RegExp(`\\b${className}\\b`).test(content)) {
|
|
638
|
+
const normalizedSub = subPath === "." ? "" : subPath.replace(/^\./, "");
|
|
639
|
+
return packageName + normalizedSub;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
} catch {
|
|
643
|
+
}
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
647
|
+
0 && (module.exports = {
|
|
648
|
+
transformSource,
|
|
649
|
+
wireWeaverEsbuildPlugin,
|
|
650
|
+
wireWeaverPlugin
|
|
651
|
+
});
|
|
652
|
+
if (typeof module.exports.default === "function") module.exports = Object.assign(module.exports.default, module.exports);
|