alloy-di 1.2.0 → 1.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/dist/plugins/core/codegen.js +42 -4
- package/dist/plugins/core/lazy.js +54 -7
- package/dist/plugins/core/scanner.js +78 -8
- package/dist/plugins/core/types.d.ts +33 -14
- package/dist/plugins/rollup-plugin/index.js +68 -39
- package/dist/plugins/vite-plugin/container-loader.js +92 -0
- package/dist/plugins/vite-plugin/discovery-runtime.js +76 -0
- package/dist/plugins/vite-plugin/index.d.ts +1 -11
- package/dist/plugins/vite-plugin/index.js +37 -124
- package/dist/plugins/vite-plugin/manifest-utils.js +174 -75
- package/dist/plugins/vite-plugin/visualization-utils.d.ts +15 -0
- package/dist/plugins/vite-plugin/visualization-utils.js +28 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -264,7 +264,7 @@ function generateManifestTypeDefinition(manifests) {
|
|
|
264
264
|
declare module "${m.packageName}/manifest" {
|
|
265
265
|
type ServiceScope = "singleton" | "transient";
|
|
266
266
|
|
|
267
|
-
interface
|
|
267
|
+
interface ManifestLegacyLazyDependency {
|
|
268
268
|
exportName: string;
|
|
269
269
|
importPath: string;
|
|
270
270
|
retry?: {
|
|
@@ -274,22 +274,60 @@ declare module "${m.packageName}/manifest" {
|
|
|
274
274
|
};
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
interface ManifestLegacyTokenDependency {
|
|
278
|
+
exportName: string;
|
|
279
|
+
importPath: string;
|
|
280
|
+
symbolKey?: string;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
interface ManifestClassDependency {
|
|
284
|
+
kind: "class";
|
|
285
|
+
exportName: string;
|
|
286
|
+
}
|
|
287
|
+
|
|
277
288
|
interface ManifestTokenDependency {
|
|
289
|
+
kind: "token";
|
|
278
290
|
exportName: string;
|
|
279
291
|
importPath: string;
|
|
280
292
|
symbolKey?: string;
|
|
281
293
|
}
|
|
282
294
|
|
|
283
|
-
interface
|
|
295
|
+
interface ManifestLazyDependency {
|
|
296
|
+
kind: "lazy";
|
|
297
|
+
exportName: string;
|
|
298
|
+
importPath: string;
|
|
299
|
+
retry?: {
|
|
300
|
+
retries: number;
|
|
301
|
+
backoffMs?: number;
|
|
302
|
+
factor?: number;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
type ManifestDependency =
|
|
307
|
+
| ManifestClassDependency
|
|
308
|
+
| ManifestTokenDependency
|
|
309
|
+
| ManifestLazyDependency;
|
|
310
|
+
|
|
311
|
+
interface ManifestServiceV1 {
|
|
284
312
|
exportName: string;
|
|
285
313
|
importPath: string;
|
|
286
314
|
symbolKey: string;
|
|
287
315
|
scope: ServiceScope;
|
|
288
316
|
deps: string[];
|
|
289
|
-
lazyDeps:
|
|
290
|
-
tokenDeps?:
|
|
317
|
+
lazyDeps: ManifestLegacyLazyDependency[];
|
|
318
|
+
tokenDeps?: ManifestLegacyTokenDependency[];
|
|
291
319
|
}
|
|
292
320
|
|
|
321
|
+
interface ManifestServiceV2 {
|
|
322
|
+
exportName: string;
|
|
323
|
+
importPath: string;
|
|
324
|
+
symbolKey: string;
|
|
325
|
+
scope: ServiceScope;
|
|
326
|
+
deps: ManifestDependency[];
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
type ManifestService = ManifestServiceV1 | ManifestServiceV2;
|
|
330
|
+
|
|
293
331
|
interface LibraryManifest {
|
|
294
332
|
schemaVersion: number;
|
|
295
333
|
packageName: string;
|
|
@@ -12,12 +12,20 @@ const RESOLVED_EXTENSIONS = [
|
|
|
12
12
|
".cts"
|
|
13
13
|
];
|
|
14
14
|
function processLazyCall(node, fileId, sourceFile, localLazyRefs) {
|
|
15
|
-
if (node
|
|
16
|
-
const
|
|
17
|
-
if (!
|
|
18
|
-
for (const key of classKeys) localLazyRefs.add(key);
|
|
15
|
+
if (!isLazyCall(node, sourceFile)) return;
|
|
16
|
+
const parsed = resolveLazyDependency(node, fileId);
|
|
17
|
+
if (!parsed) return;
|
|
18
|
+
for (const key of parsed.classKeys) localLazyRefs.add(key);
|
|
19
19
|
}
|
|
20
|
-
function
|
|
20
|
+
function parseLazyDependencyExpression(expression, fileId) {
|
|
21
|
+
const statement = ts.createSourceFile(fileId, `const __alloyLazy = (${expression});`, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS).statements[0];
|
|
22
|
+
if (!statement || !ts.isVariableStatement(statement)) return;
|
|
23
|
+
const initializer = statement.declarationList.declarations[0]?.initializer;
|
|
24
|
+
const call = initializer ? unwrapExpression(initializer) : void 0;
|
|
25
|
+
if (!call || !ts.isCallExpression(call)) return;
|
|
26
|
+
return resolveLazyDependency(call, fileId);
|
|
27
|
+
}
|
|
28
|
+
function resolveLazyDependency(node, fileId) {
|
|
21
29
|
const factory = getLazyFactory(node.arguments[0]);
|
|
22
30
|
if (!factory) return;
|
|
23
31
|
const body = getReturnedExpression(factory);
|
|
@@ -25,14 +33,49 @@ function resolveLazyTarget(node, fileId) {
|
|
|
25
33
|
const importInfo = extractImportInfo(body);
|
|
26
34
|
const exportName = importInfo?.exportName;
|
|
27
35
|
if (!importInfo || !exportName) return;
|
|
36
|
+
const retry = extractRetryOptions(node.arguments[1]);
|
|
28
37
|
const resolvedPaths = resolveModuleSpecifierCandidates(fileId, importInfo.specifier);
|
|
29
38
|
if (!resolvedPaths.length) return;
|
|
30
|
-
return
|
|
39
|
+
return {
|
|
40
|
+
specifier: importInfo.specifier,
|
|
41
|
+
exportName,
|
|
42
|
+
retry,
|
|
43
|
+
importPath: importInfo.specifier,
|
|
44
|
+
classKeys: resolvedPaths.map((candidate) => createClassKey(candidate, exportName))
|
|
45
|
+
};
|
|
31
46
|
}
|
|
32
47
|
function getLazyFactory(arg) {
|
|
33
48
|
if (!arg) return;
|
|
34
49
|
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) return arg;
|
|
35
50
|
}
|
|
51
|
+
function extractRetryOptions(expr) {
|
|
52
|
+
if (!expr || !ts.isObjectLiteralExpression(expr)) return;
|
|
53
|
+
let retries;
|
|
54
|
+
let backoffMs;
|
|
55
|
+
let factor;
|
|
56
|
+
for (const prop of expr.properties) {
|
|
57
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) continue;
|
|
58
|
+
const value = extractNumberLiteral(prop.initializer);
|
|
59
|
+
if (typeof value !== "number") continue;
|
|
60
|
+
if (prop.name.text === "retries") retries = value;
|
|
61
|
+
else if (prop.name.text === "backoffMs") backoffMs = value;
|
|
62
|
+
else if (prop.name.text === "factor") factor = value;
|
|
63
|
+
}
|
|
64
|
+
if (typeof retries !== "number") return;
|
|
65
|
+
return {
|
|
66
|
+
retries,
|
|
67
|
+
...typeof backoffMs === "number" ? { backoffMs } : {},
|
|
68
|
+
...typeof factor === "number" ? { factor } : {}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function extractNumberLiteral(expr) {
|
|
72
|
+
if (ts.isNumericLiteral(expr)) return Number(expr.text);
|
|
73
|
+
if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.MinusToken && ts.isNumericLiteral(expr.operand)) return -Number(expr.operand.text);
|
|
74
|
+
}
|
|
75
|
+
function unwrapExpression(expr) {
|
|
76
|
+
if (ts.isParenthesizedExpression(expr) || ts.isAsExpression(expr) || ts.isTypeAssertionExpression(expr) || ts.isNonNullExpression(expr)) return unwrapExpression(expr.expression);
|
|
77
|
+
return expr;
|
|
78
|
+
}
|
|
36
79
|
function getReturnedExpression(fn) {
|
|
37
80
|
if (ts.isBlock(fn.body)) {
|
|
38
81
|
for (const statement of fn.body.statements) if (ts.isReturnStatement(statement) && statement.expression) return statement.expression;
|
|
@@ -61,6 +104,10 @@ function extractImportInfo(expr) {
|
|
|
61
104
|
function isDynamicImport(node) {
|
|
62
105
|
return node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
63
106
|
}
|
|
107
|
+
function isLazyCall(node, sourceFile) {
|
|
108
|
+
const text = node.expression.getText(sourceFile);
|
|
109
|
+
return text === "Lazy" || text.endsWith(".Lazy");
|
|
110
|
+
}
|
|
64
111
|
function getImportSpecifier(node) {
|
|
65
112
|
return node && ts.isStringLiteralLike(node) ? node.text : void 0;
|
|
66
113
|
}
|
|
@@ -86,4 +133,4 @@ function resolveModuleSpecifierCandidates(fromId, specifier) {
|
|
|
86
133
|
return [...fileCandidates, ...indexCandidates];
|
|
87
134
|
}
|
|
88
135
|
//#endregion
|
|
89
|
-
export { processLazyCall };
|
|
136
|
+
export { parseLazyDependencyExpression, processLazyCall, resolveModuleSpecifierCandidates };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createClassKey, createSymbolKey } from "./utils.js";
|
|
2
2
|
import { extractServiceMetadata } from "./decorators.js";
|
|
3
|
-
import { processLazyCall } from "./lazy.js";
|
|
3
|
+
import { processLazyCall, resolveModuleSpecifierCandidates } from "./lazy.js";
|
|
4
|
+
import fs from "node:fs";
|
|
4
5
|
import ts, { SyntaxKind } from "typescript";
|
|
5
6
|
//#region src/plugins/core/scanner.ts
|
|
7
|
+
const ALLOY_RUNTIME_MODULE = "alloy-di/runtime";
|
|
6
8
|
function collectFileImports(sourceFile) {
|
|
7
9
|
const imports = /* @__PURE__ */ new Map();
|
|
8
10
|
for (const statement of sourceFile.statements) if (ts.isImportDeclaration(statement) && statement.importClause && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
@@ -39,12 +41,14 @@ function scanSource(code, id) {
|
|
|
39
41
|
const discovered = /* @__PURE__ */ new Map();
|
|
40
42
|
const lazyRefs = /* @__PURE__ */ new Set();
|
|
41
43
|
const fileImports = collectFileImports(sourceFile);
|
|
44
|
+
const decoratorResolutionCache = /* @__PURE__ */ new Map();
|
|
42
45
|
const visit = (node) => {
|
|
43
46
|
if (ts.isClassDeclaration(node)) handleClassDeclaration(node, {
|
|
44
47
|
id,
|
|
45
48
|
sourceFile,
|
|
46
49
|
fileImports,
|
|
47
|
-
discovered
|
|
50
|
+
discovered,
|
|
51
|
+
decoratorResolutionCache
|
|
48
52
|
});
|
|
49
53
|
else if (ts.isCallExpression(node)) processLazyCall(node, id, sourceFile, lazyRefs);
|
|
50
54
|
ts.forEachChild(node, visit);
|
|
@@ -57,9 +61,9 @@ function scanSource(code, id) {
|
|
|
57
61
|
}
|
|
58
62
|
function handleClassDeclaration(node, context) {
|
|
59
63
|
if (!node.name) return;
|
|
60
|
-
const
|
|
61
|
-
if (!
|
|
62
|
-
const decoratorName =
|
|
64
|
+
const decoratorMatch = findServiceDecorator(node, context.sourceFile, context.fileImports, context.id, context.decoratorResolutionCache);
|
|
65
|
+
if (!decoratorMatch) return;
|
|
66
|
+
const { decoratorCall, decoratorName } = decoratorMatch;
|
|
63
67
|
const className = node.name.getText(context.sourceFile);
|
|
64
68
|
const metadata = extractServiceMetadata(decoratorName, decoratorCall, context.sourceFile);
|
|
65
69
|
const referencedImports = collectReferencedImports(metadata, context.fileImports);
|
|
@@ -72,15 +76,81 @@ function handleClassDeclaration(node, context) {
|
|
|
72
76
|
referencedImports
|
|
73
77
|
});
|
|
74
78
|
}
|
|
75
|
-
function findServiceDecorator(node, sourceFile) {
|
|
79
|
+
function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache) {
|
|
76
80
|
const decorators = ts.getDecorators ? ts.getDecorators(node) : void 0;
|
|
77
81
|
if (!decorators?.length) return;
|
|
78
82
|
for (const decorator of decorators) {
|
|
79
83
|
if (!ts.isCallExpression(decorator.expression)) continue;
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
84
|
+
const decoratorName = resolveDecoratorName(decorator.expression.expression, fileImports, id, new Set([id]), resolutionCache);
|
|
85
|
+
if (decoratorName) return {
|
|
86
|
+
decoratorCall: decorator.expression,
|
|
87
|
+
decoratorName
|
|
88
|
+
};
|
|
82
89
|
}
|
|
83
90
|
}
|
|
91
|
+
function resolveDecoratorName(expression, fileImports, id, visitedModules, resolutionCache) {
|
|
92
|
+
if (ts.isIdentifier(expression)) {
|
|
93
|
+
const importInfo = fileImports.get(expression.text);
|
|
94
|
+
if (!importInfo || importInfo.isTypeOnly) return;
|
|
95
|
+
return resolveImportedDecorator(importInfo.path, importInfo.originalName ?? expression.text, id, visitedModules, resolutionCache);
|
|
96
|
+
}
|
|
97
|
+
if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)) {
|
|
98
|
+
const importInfo = fileImports.get(expression.expression.text);
|
|
99
|
+
if (!importInfo || importInfo.isTypeOnly || importInfo.originalName !== "*") return;
|
|
100
|
+
return resolveImportedDecorator(importInfo.path, expression.name.text, id, visitedModules, resolutionCache);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function resolveImportedDecorator(importPath, requestedName, fromId, visitedModules, resolutionCache) {
|
|
104
|
+
if (importPath === ALLOY_RUNTIME_MODULE) return isAlloyDecoratorName(requestedName) ? requestedName : void 0;
|
|
105
|
+
if (!importPath.startsWith(".")) return;
|
|
106
|
+
for (const candidate of resolveModuleSpecifierCandidates(fromId, importPath)) {
|
|
107
|
+
if (visitedModules.has(candidate) || !fs.existsSync(candidate)) continue;
|
|
108
|
+
const cacheKey = `${candidate}:${requestedName}`;
|
|
109
|
+
const cached = resolutionCache.get(cacheKey);
|
|
110
|
+
if (cached) return cached;
|
|
111
|
+
if (cached === null) continue;
|
|
112
|
+
visitedModules.add(candidate);
|
|
113
|
+
try {
|
|
114
|
+
const source = fs.readFileSync(candidate, "utf8");
|
|
115
|
+
const sourceFile = ts.createSourceFile(candidate, source, ts.ScriptTarget.ESNext, true);
|
|
116
|
+
const resolved = resolveDecoratorExport(requestedName, sourceFile, collectFileImports(sourceFile), candidate, visitedModules, resolutionCache);
|
|
117
|
+
resolutionCache.set(cacheKey, resolved ?? null);
|
|
118
|
+
if (resolved) return resolved;
|
|
119
|
+
} catch {
|
|
120
|
+
continue;
|
|
121
|
+
} finally {
|
|
122
|
+
visitedModules.delete(candidate);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function resolveDecoratorExport(requestedName, sourceFile, fileImports, id, visitedModules, resolutionCache) {
|
|
127
|
+
for (const statement of sourceFile.statements) {
|
|
128
|
+
if (!ts.isExportDeclaration(statement)) continue;
|
|
129
|
+
const moduleSpecifier = statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : void 0;
|
|
130
|
+
if (!statement.exportClause) {
|
|
131
|
+
if (!moduleSpecifier) continue;
|
|
132
|
+
const resolved = resolveImportedDecorator(moduleSpecifier, requestedName, id, visitedModules, resolutionCache);
|
|
133
|
+
if (resolved) return resolved;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!ts.isNamedExports(statement.exportClause)) continue;
|
|
137
|
+
for (const element of statement.exportClause.elements) {
|
|
138
|
+
if (element.name.text !== requestedName) continue;
|
|
139
|
+
const resolved = resolveNamedExportElement(element, moduleSpecifier, fileImports, id, visitedModules, resolutionCache);
|
|
140
|
+
if (resolved) return resolved;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function resolveNamedExportElement(element, moduleSpecifier, fileImports, id, visitedModules, resolutionCache) {
|
|
145
|
+
const sourceName = element.propertyName?.text ?? element.name.text;
|
|
146
|
+
if (moduleSpecifier) return resolveImportedDecorator(moduleSpecifier, sourceName, id, visitedModules, resolutionCache);
|
|
147
|
+
const importInfo = fileImports.get(sourceName);
|
|
148
|
+
if (!importInfo || importInfo.isTypeOnly) return;
|
|
149
|
+
return resolveImportedDecorator(importInfo.path, importInfo.originalName ?? sourceName, id, visitedModules, resolutionCache);
|
|
150
|
+
}
|
|
151
|
+
function isAlloyDecoratorName(name) {
|
|
152
|
+
return name === "Injectable" || name === "Singleton";
|
|
153
|
+
}
|
|
84
154
|
function collectReferencedImports(metadata, fileImports) {
|
|
85
155
|
const referenced = [];
|
|
86
156
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1,7 +1,31 @@
|
|
|
1
1
|
import { ServiceScope } from "../../lib/scope.js";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/core/types.d.ts
|
|
4
|
-
interface
|
|
4
|
+
interface ManifestTokenDependency {
|
|
5
|
+
exportName: string;
|
|
6
|
+
importPath: string;
|
|
7
|
+
}
|
|
8
|
+
interface ManifestLazyDependency {
|
|
9
|
+
exportName: string;
|
|
10
|
+
importPath: string;
|
|
11
|
+
retry?: {
|
|
12
|
+
retries: number;
|
|
13
|
+
backoffMs?: number;
|
|
14
|
+
factor?: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
interface ManifestClassDependencyEntry {
|
|
18
|
+
kind: "class";
|
|
19
|
+
exportName: string;
|
|
20
|
+
}
|
|
21
|
+
interface ManifestTokenDependencyEntry extends ManifestTokenDependency {
|
|
22
|
+
kind: "token";
|
|
23
|
+
}
|
|
24
|
+
interface ManifestLazyDependencyEntry extends ManifestLazyDependency {
|
|
25
|
+
kind: "lazy";
|
|
26
|
+
}
|
|
27
|
+
type ManifestDependencyEntry = ManifestClassDependencyEntry | ManifestTokenDependencyEntry | ManifestLazyDependencyEntry;
|
|
28
|
+
interface ManifestServiceDescriptorBase {
|
|
5
29
|
exportName: string;
|
|
6
30
|
importPath: string;
|
|
7
31
|
/**
|
|
@@ -10,22 +34,17 @@ interface ManifestServiceDescriptor {
|
|
|
10
34
|
*/
|
|
11
35
|
symbolKey: string;
|
|
12
36
|
scope: ServiceScope;
|
|
37
|
+
}
|
|
38
|
+
interface ManifestServiceDescriptorV1 extends ManifestServiceDescriptorBase {
|
|
13
39
|
deps: string[];
|
|
14
40
|
/** Token dependencies (non-service identifiers) exported publicly by the package. */
|
|
15
|
-
tokenDeps?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
exportName: string;
|
|
21
|
-
importPath: string;
|
|
22
|
-
retry?: {
|
|
23
|
-
retries: number;
|
|
24
|
-
backoffMs?: number;
|
|
25
|
-
factor?: number;
|
|
26
|
-
};
|
|
27
|
-
}[];
|
|
41
|
+
tokenDeps?: ManifestTokenDependency[];
|
|
42
|
+
lazyDeps: ManifestLazyDependency[];
|
|
43
|
+
}
|
|
44
|
+
interface ManifestServiceDescriptorV2 extends ManifestServiceDescriptorBase {
|
|
45
|
+
deps: ManifestDependencyEntry[];
|
|
28
46
|
}
|
|
47
|
+
type ManifestServiceDescriptor = ManifestServiceDescriptorV1 | ManifestServiceDescriptorV2;
|
|
29
48
|
interface AlloyManifest {
|
|
30
49
|
schemaVersion: number;
|
|
31
50
|
packageName: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ServiceScope } from "../../lib/scope.js";
|
|
2
|
+
import { parseLazyDependencyExpression, resolveModuleSpecifierCandidates } from "../core/lazy.js";
|
|
2
3
|
import { createDiscoveryStore } from "../core/discovery-store.js";
|
|
3
4
|
import { determineBuildMode, hasPreserveModules, resolveImportPathForBuild } from "./build-utils.js";
|
|
4
5
|
import path from "node:path";
|
|
@@ -9,6 +10,31 @@ import ts from "typescript";
|
|
|
9
10
|
function hasExportModifier(node) {
|
|
10
11
|
return !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
11
12
|
}
|
|
13
|
+
function getDependencyImports(meta, dep) {
|
|
14
|
+
const imports = meta.referencedImports ?? [];
|
|
15
|
+
if (imports.length === 0 || dep.referencedIdentifiers.length === 0) return [];
|
|
16
|
+
const identifiers = new Set(dep.referencedIdentifiers);
|
|
17
|
+
return imports.filter((entry) => identifiers.has(entry.name));
|
|
18
|
+
}
|
|
19
|
+
function getDependencyReferenceName(expression) {
|
|
20
|
+
const statement = ts.createSourceFile("dependency.ts", `const __dep = (${expression});`, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS).statements[0];
|
|
21
|
+
if (!statement || !ts.isVariableStatement(statement)) return;
|
|
22
|
+
const initializer = statement.declarationList.declarations[0]?.initializer;
|
|
23
|
+
if (!initializer) return;
|
|
24
|
+
return extractReferenceNameFromExpression(initializer);
|
|
25
|
+
}
|
|
26
|
+
function extractReferenceNameFromExpression(expression) {
|
|
27
|
+
if (ts.isIdentifier(expression)) return expression.text;
|
|
28
|
+
if (ts.isPropertyAccessExpression(expression)) return expression.name.text;
|
|
29
|
+
if (ts.isAsExpression(expression) || ts.isParenthesizedExpression(expression) || ts.isNonNullExpression(expression) || ts.isTypeAssertionExpression(expression)) return extractReferenceNameFromExpression(expression.expression);
|
|
30
|
+
}
|
|
31
|
+
function resolveClassDependencyName(dep, meta, knownServiceNames) {
|
|
32
|
+
const imports = getDependencyImports(meta, dep);
|
|
33
|
+
for (const entry of imports) if (entry.originalName && knownServiceNames.has(entry.originalName)) return entry.originalName;
|
|
34
|
+
const referenceName = getDependencyReferenceName(dep.expression);
|
|
35
|
+
if (referenceName && knownServiceNames.has(referenceName)) return referenceName;
|
|
36
|
+
return dep.referencedIdentifiers.find((name) => knownServiceNames.has(name));
|
|
37
|
+
}
|
|
12
38
|
function alloy(options = {}) {
|
|
13
39
|
const fileName = options.fileName ?? "alloy.manifest.mjs";
|
|
14
40
|
const packageJsonFile = options.packageJsonPath ? path.isAbsolute(options.packageJsonPath) ? options.packageJsonPath : path.resolve(process.cwd(), options.packageJsonPath) : path.resolve(process.cwd(), "package.json");
|
|
@@ -83,6 +109,38 @@ function alloy(options = {}) {
|
|
|
83
109
|
function resolveImportPath(targetPath, buildMode) {
|
|
84
110
|
return resolveImportPathForBuild(targetPath, packageName, buildMode);
|
|
85
111
|
}
|
|
112
|
+
function resolveDependencyImportPath(specifier, sourceFilePath, buildMode) {
|
|
113
|
+
if (specifier.startsWith(".")) return resolveImportPath(resolveModuleSpecifierCandidates(sourceFilePath, specifier)[0] ?? path.resolve(path.dirname(sourceFilePath), specifier), buildMode);
|
|
114
|
+
if (path.isAbsolute(specifier)) return resolveImportPath(specifier, buildMode);
|
|
115
|
+
return specifier;
|
|
116
|
+
}
|
|
117
|
+
function createTokenDependency(dep, meta, buildMode) {
|
|
118
|
+
const preferredImport = getDependencyImports(meta, dep).find((entry) => entry.originalName !== "*");
|
|
119
|
+
return {
|
|
120
|
+
kind: "token",
|
|
121
|
+
exportName: preferredImport?.originalName ?? getDependencyReferenceName(dep.expression) ?? dep.referencedIdentifiers[0] ?? dep.expression,
|
|
122
|
+
importPath: preferredImport ? resolveDependencyImportPath(preferredImport.path, meta.filePath, buildMode) : packageName
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function createManifestDependency(dep, meta, knownServiceNames, buildMode) {
|
|
126
|
+
if (dep.isLazy) {
|
|
127
|
+
const parsedLazy = parseLazyDependencyExpression(dep.expression, meta.filePath);
|
|
128
|
+
if (!parsedLazy) return null;
|
|
129
|
+
const entry = {
|
|
130
|
+
kind: "lazy",
|
|
131
|
+
exportName: parsedLazy.exportName,
|
|
132
|
+
importPath: resolveDependencyImportPath(parsedLazy.specifier, meta.filePath, buildMode)
|
|
133
|
+
};
|
|
134
|
+
if (parsedLazy.retry) entry.retry = parsedLazy.retry;
|
|
135
|
+
return entry;
|
|
136
|
+
}
|
|
137
|
+
const className = resolveClassDependencyName(dep, meta, knownServiceNames);
|
|
138
|
+
if (className) return {
|
|
139
|
+
kind: "class",
|
|
140
|
+
exportName: className
|
|
141
|
+
};
|
|
142
|
+
return createTokenDependency(dep, meta, buildMode);
|
|
143
|
+
}
|
|
86
144
|
return {
|
|
87
145
|
name: "alloy-manifest",
|
|
88
146
|
transform(code, id) {
|
|
@@ -108,55 +166,26 @@ function alloy(options = {}) {
|
|
|
108
166
|
importPath,
|
|
109
167
|
symbolKey,
|
|
110
168
|
scope,
|
|
111
|
-
deps: []
|
|
112
|
-
lazyDeps: []
|
|
169
|
+
deps: []
|
|
113
170
|
});
|
|
114
171
|
if (buildMode !== "preserve-modules" && !exportedNames.has(meta.className)) missingExports.push(meta.className);
|
|
115
172
|
}
|
|
116
173
|
const serviceByName = /* @__PURE__ */ new Map();
|
|
117
|
-
|
|
118
|
-
const
|
|
174
|
+
const knownServiceNames = /* @__PURE__ */ new Set();
|
|
175
|
+
for (const service of services) {
|
|
176
|
+
serviceByName.set(service.exportName, service);
|
|
177
|
+
knownServiceNames.add(service.exportName);
|
|
178
|
+
}
|
|
119
179
|
for (const metas of discovery.fileMetas.values()) for (const meta of metas) {
|
|
120
180
|
const svc = serviceByName.get(meta.className);
|
|
121
181
|
if (!svc) continue;
|
|
122
182
|
for (const dep of meta.metadata.dependencies) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
else {
|
|
126
|
-
const set = serviceTokenDeps.get(svc.exportName) ?? /* @__PURE__ */ new Set();
|
|
127
|
-
set.add(name);
|
|
128
|
-
serviceTokenDeps.set(svc.exportName, set);
|
|
129
|
-
}
|
|
183
|
+
const manifestDep = createManifestDependency(dep, meta, knownServiceNames, buildMode);
|
|
184
|
+
if (manifestDep) svc.deps.push(manifestDep);
|
|
130
185
|
}
|
|
131
186
|
}
|
|
132
|
-
for (const [id, refs] of discovery.fileLazyRefs.entries()) {
|
|
133
|
-
const metas = discovery.fileMetas.get(id) ?? [];
|
|
134
|
-
for (const meta of metas) {
|
|
135
|
-
const svc = serviceByName.get(meta.className);
|
|
136
|
-
if (!svc) continue;
|
|
137
|
-
const firstForExport = /* @__PURE__ */ new Map();
|
|
138
|
-
for (const key of refs) {
|
|
139
|
-
const [targetPath, exportName] = key.split("::");
|
|
140
|
-
if (!targetPath || !exportName) continue;
|
|
141
|
-
const importPath = resolveImportPath(targetPath, buildMode);
|
|
142
|
-
if (!firstForExport.has(exportName)) firstForExport.set(exportName, importPath);
|
|
143
|
-
}
|
|
144
|
-
for (const [exportName, importPath] of firstForExport.entries()) svc.lazyDeps.push({
|
|
145
|
-
exportName,
|
|
146
|
-
importPath
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
for (const [exportName, tokens] of serviceTokenDeps.entries()) {
|
|
151
|
-
const svc = serviceByName.get(exportName);
|
|
152
|
-
if (!svc || !tokens.size) continue;
|
|
153
|
-
svc.tokenDeps = Array.from(tokens).map((t) => ({
|
|
154
|
-
exportName: t,
|
|
155
|
-
importPath: packageName
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
187
|
const manifest = {
|
|
159
|
-
schemaVersion:
|
|
188
|
+
schemaVersion: 2,
|
|
160
189
|
packageName,
|
|
161
190
|
buildMode,
|
|
162
191
|
services,
|
|
@@ -187,7 +216,7 @@ function alloy(options = {}) {
|
|
|
187
216
|
}
|
|
188
217
|
manifest.providers = resolvedProviders;
|
|
189
218
|
}
|
|
190
|
-
const code = `// Generated Alloy manifest (
|
|
219
|
+
const code = `// Generated Alloy manifest (v2)\nexport const manifest = ${JSON.stringify(manifest, null, 2)};\n`;
|
|
191
220
|
const identifiersCode = ["// Generated Alloy Service Identifiers", ...services.map((s) => `export const ${s.exportName}Identifier = Symbol.for("${s.symbolKey}");`)].join("\n");
|
|
192
221
|
if (this.emitFile) {
|
|
193
222
|
this.emitFile({
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { normalizeImportPath } from "../core/utils.js";
|
|
2
|
+
import { IdentifierResolver } from "../core/identifier-resolver.js";
|
|
3
|
+
import { generateContainerModule, generateContainerTypeDefinition, generateManifestTypeDefinition } from "../core/codegen.js";
|
|
4
|
+
import { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest } from "./manifest-utils.js";
|
|
5
|
+
import { generateMermaidDiagram } from "./visualizer.js";
|
|
6
|
+
import { ensureDirectoryForFile } from "./visualization-utils.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
//#region src/plugins/vite-plugin/container-loader.ts
|
|
10
|
+
async function loadVirtualContainerModule(options) {
|
|
11
|
+
const metas = options.localMetas;
|
|
12
|
+
assignIdentifierKeys(metas, options.packageName, options.resolvedRoot);
|
|
13
|
+
const manifestData = await readManifests(options.manifests);
|
|
14
|
+
const manifestServices = manifestData.services;
|
|
15
|
+
const loadedManifests = manifestData.loadedManifests;
|
|
16
|
+
assertNoDuplicateManifestServices(metas, manifestServices);
|
|
17
|
+
const combinedMetas = [...metas, ...manifestServices.map((svc) => ({
|
|
18
|
+
className: svc.exportName,
|
|
19
|
+
filePath: svc.importPath,
|
|
20
|
+
metadata: {
|
|
21
|
+
scope: svc.scope,
|
|
22
|
+
dependencies: []
|
|
23
|
+
}
|
|
24
|
+
}))];
|
|
25
|
+
const resolver = new IdentifierResolver(combinedMetas);
|
|
26
|
+
const metasByName = groupMetasByName(combinedMetas);
|
|
27
|
+
for (const svc of manifestServices) metas.push(toMetaFromManifest(svc, metasByName, resolver, options.lazyReferencedClassKeys));
|
|
28
|
+
const providerImports = Array.from(new Set([...options.providerImportPaths, ...manifestData.providers]));
|
|
29
|
+
const eagerReferencedNames = collectEagerReferencedNames(metas);
|
|
30
|
+
reconcileLazySet(metas, options.lazyReferencedClassKeys, eagerReferencedNames);
|
|
31
|
+
augmentFactoryLazyServices(metas, options.lazyServiceKeys);
|
|
32
|
+
const code = generateContainerModule(metas, new Set(options.lazyReferencedClassKeys), providerImports);
|
|
33
|
+
writeTypeDefinitions(metas, loadedManifests, options.resolvedRoot, options.containerDeclarationDir);
|
|
34
|
+
writeVisualizationArtifact(metas, options.lazyReferencedClassKeys, options.resolvedVisualization);
|
|
35
|
+
return {
|
|
36
|
+
code,
|
|
37
|
+
moduleType: "js"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function assignIdentifierKeys(metas, packageName, resolvedRoot) {
|
|
41
|
+
for (const meta of metas) {
|
|
42
|
+
const normalizedMetaPath = normalizeImportPath(meta.filePath);
|
|
43
|
+
const trimmedNormalizedMetaPath = normalizedMetaPath.replaceAll(/^\/+/g, "");
|
|
44
|
+
const looksRootRelative = normalizedMetaPath === "/src" || normalizedMetaPath.startsWith("/src/");
|
|
45
|
+
let relPath = path.relative(resolvedRoot, meta.filePath);
|
|
46
|
+
if (path.sep === "\\") relPath = relPath.split(path.sep).join("/");
|
|
47
|
+
if (looksRootRelative || !relPath || relPath.startsWith("..") || relPath.startsWith("\\")) relPath = trimmedNormalizedMetaPath || normalizedMetaPath.replaceAll(/^\/+/g, "");
|
|
48
|
+
meta.identifierKey = `alloy:${packageName}/${relPath}#${meta.className}`;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function assertNoDuplicateManifestServices(metas, manifestServices) {
|
|
52
|
+
if (!metas.length || !manifestServices.length) return;
|
|
53
|
+
const duplicates = findDuplicateManifestServices(metas, manifestServices);
|
|
54
|
+
if (!duplicates.length) return;
|
|
55
|
+
const details = duplicates.map((d) => `- ${d.exportName}: local [${d.localPaths.join(", ")}] vs manifest '${d.manifestImport}'`).join("\n");
|
|
56
|
+
throw new Error([
|
|
57
|
+
"[alloy] Duplicate service registrations detected.",
|
|
58
|
+
details,
|
|
59
|
+
"Resolve by removing one source (local or manifest) to avoid ambiguous DI keys."
|
|
60
|
+
].join("\n"));
|
|
61
|
+
}
|
|
62
|
+
function writeTypeDefinitions(metas, loadedManifests, resolvedRoot, containerDeclarationDir) {
|
|
63
|
+
const dtsDir = path.resolve(resolvedRoot, containerDeclarationDir ?? "./src");
|
|
64
|
+
const dtsContent = generateContainerTypeDefinition(metas, (filePath) => resolveDeclarationImportPath(dtsDir, filePath));
|
|
65
|
+
if (!fs.existsSync(dtsDir)) fs.mkdirSync(dtsDir, { recursive: true });
|
|
66
|
+
fs.writeFileSync(path.join(dtsDir, "alloy-container.d.ts"), dtsContent);
|
|
67
|
+
if (loadedManifests.length === 0) return;
|
|
68
|
+
const manifestsDts = generateManifestTypeDefinition(loadedManifests.map((m) => ({
|
|
69
|
+
packageName: m.packageName,
|
|
70
|
+
services: m.services
|
|
71
|
+
})));
|
|
72
|
+
fs.writeFileSync(path.join(dtsDir, "alloy-manifests.d.ts"), manifestsDts);
|
|
73
|
+
}
|
|
74
|
+
function resolveDeclarationImportPath(dtsDir, filePath) {
|
|
75
|
+
if (!path.isAbsolute(filePath)) return filePath;
|
|
76
|
+
let rel = path.relative(dtsDir, filePath);
|
|
77
|
+
rel = rel.split(path.sep).join(path.posix.sep);
|
|
78
|
+
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
79
|
+
return rel;
|
|
80
|
+
}
|
|
81
|
+
function writeVisualizationArtifact(metas, lazyReferencedClassKeys, resolvedVisualization) {
|
|
82
|
+
if (!resolvedVisualization) return;
|
|
83
|
+
const artifact = generateMermaidDiagram({
|
|
84
|
+
metas,
|
|
85
|
+
lazyClassKeys: new Set(lazyReferencedClassKeys),
|
|
86
|
+
options: resolvedVisualization.mermaidOptions
|
|
87
|
+
});
|
|
88
|
+
ensureDirectoryForFile(resolvedVisualization.outputPath);
|
|
89
|
+
fs.writeFileSync(resolvedVisualization.outputPath, `${artifact.diagram}\n`);
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { loadVirtualContainerModule };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createClassKey } from "../core/utils.js";
|
|
2
|
+
import { createDiscoveryStore } from "../core/discovery-store.js";
|
|
3
|
+
//#region src/plugins/vite-plugin/discovery-runtime.ts
|
|
4
|
+
/** Files the discovery scanner processes (mirrors the transform hook filter). */
|
|
5
|
+
function isDiscoverableFile(file) {
|
|
6
|
+
return /\.tsx?$/i.test(file) && !/\.d\.ts$/i.test(file) && !file.includes("node_modules");
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Serializes the codegen-relevant fields of a file's discovered metas so two
|
|
10
|
+
* scans can be compared. Changes here (added/removed services, scope, deps,
|
|
11
|
+
* factory, or resolved imports) mean the generated container must be rebuilt;
|
|
12
|
+
* edits that leave them untouched (e.g. a method body) should not.
|
|
13
|
+
*/
|
|
14
|
+
function metasSignature(metas) {
|
|
15
|
+
return JSON.stringify(metas.map((m) => ({
|
|
16
|
+
className: m.className,
|
|
17
|
+
filePath: m.filePath,
|
|
18
|
+
scope: m.metadata.scope,
|
|
19
|
+
factory: m.metadata.factory?.expression ?? null,
|
|
20
|
+
dependencies: m.metadata.dependencies.map((d) => ({
|
|
21
|
+
expression: d.expression,
|
|
22
|
+
isLazy: d.isLazy,
|
|
23
|
+
referencedIdentifiers: d.referencedIdentifiers
|
|
24
|
+
})),
|
|
25
|
+
referencedImports: (m.referencedImports ?? []).map((r) => ({
|
|
26
|
+
name: r.name,
|
|
27
|
+
path: r.path,
|
|
28
|
+
originalName: r.originalName ?? null,
|
|
29
|
+
isTypeOnly: Boolean(r.isTypeOnly)
|
|
30
|
+
}))
|
|
31
|
+
})));
|
|
32
|
+
}
|
|
33
|
+
function lazyKeysSignature(keys) {
|
|
34
|
+
if (!keys || keys.size === 0) return "";
|
|
35
|
+
return Array.from(keys).toSorted().join("|");
|
|
36
|
+
}
|
|
37
|
+
function createDiscoveryRuntime() {
|
|
38
|
+
const discovery = createDiscoveryStore();
|
|
39
|
+
const discoveredClasses = /* @__PURE__ */ new Map();
|
|
40
|
+
const lazyReferencedClassKeys = /* @__PURE__ */ new Set();
|
|
41
|
+
return {
|
|
42
|
+
discoveredClasses,
|
|
43
|
+
lazyReferencedClassKeys,
|
|
44
|
+
processUpdate(id, code) {
|
|
45
|
+
const { metas, lazyClassKeys, previousMetas, previousLazyClassKeys } = discovery.updateFile(id, code);
|
|
46
|
+
if (previousMetas) for (const meta of previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
|
|
47
|
+
for (const meta of metas) discoveredClasses.set(createClassKey(meta.filePath, meta.className), meta);
|
|
48
|
+
if (previousLazyClassKeys) for (const key of previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
|
|
49
|
+
if (lazyClassKeys.size) for (const key of lazyClassKeys) lazyReferencedClassKeys.add(key);
|
|
50
|
+
return metasSignature(previousMetas ?? []) !== metasSignature(metas) || lazyKeysSignature(previousLazyClassKeys) !== lazyKeysSignature(lazyClassKeys);
|
|
51
|
+
},
|
|
52
|
+
removeDiscoveredFile(file) {
|
|
53
|
+
const removed = discovery.removeFile(file);
|
|
54
|
+
if (removed.previousMetas) for (const meta of removed.previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
|
|
55
|
+
if (removed.previousLazyClassKeys) for (const key of removed.previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
|
|
56
|
+
return Boolean(removed.previousMetas?.length || removed.previousLazyClassKeys?.size);
|
|
57
|
+
},
|
|
58
|
+
clear() {
|
|
59
|
+
discovery.clear();
|
|
60
|
+
discoveredClasses.clear();
|
|
61
|
+
lazyReferencedClassKeys.clear();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Invalidate the generated container module in every environment's module
|
|
67
|
+
* graph so its `load` hook re-runs and regenerates from current discovery.
|
|
68
|
+
*/
|
|
69
|
+
function invalidateContainerModule(server, resolvedVirtualModuleId) {
|
|
70
|
+
for (const environment of Object.values(server.environments)) {
|
|
71
|
+
const mod = environment.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
72
|
+
if (mod) environment.moduleGraph.invalidateModule(mod);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
export { createDiscoveryRuntime, invalidateContainerModule, isDiscoverableFile };
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { ServiceIdentifier } from "../../lib/service-identifiers.js";
|
|
2
2
|
import { AlloyManifest } from "../core/types.js";
|
|
3
|
-
import {
|
|
3
|
+
import { AlloyMermaidVisualizerOptions, AlloyVisualizationOptions } from "./visualization-utils.js";
|
|
4
4
|
import { Plugin } from "vite";
|
|
5
5
|
|
|
6
6
|
//#region src/plugins/vite-plugin/index.d.ts
|
|
7
|
-
interface AlloyMermaidVisualizerOptions extends MermaidDiagramOptions {
|
|
8
|
-
outputPath?: string;
|
|
9
|
-
}
|
|
10
|
-
interface AlloyVisualizationOptions {
|
|
11
|
-
/**
|
|
12
|
-
* Configure Mermaid diagram emission. Use `true` for defaults or provide
|
|
13
|
-
* overrides for layout, colors, or output path.
|
|
14
|
-
*/
|
|
15
|
-
mermaid?: boolean | AlloyMermaidVisualizerOptions;
|
|
16
|
-
}
|
|
17
7
|
interface AlloyPluginOptions {
|
|
18
8
|
providers?: string[];
|
|
19
9
|
/** Optional list of manifest objects to ingest */
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest } from "./manifest-utils.js";
|
|
6
|
-
import { generateMermaidDiagram } from "./visualizer.js";
|
|
1
|
+
import { normalizeImportPath, walkSync } from "../core/utils.js";
|
|
2
|
+
import { resolveVisualizationOptions } from "./visualization-utils.js";
|
|
3
|
+
import { loadVirtualContainerModule } from "./container-loader.js";
|
|
4
|
+
import { createDiscoveryRuntime, invalidateContainerModule, isDiscoverableFile } from "./discovery-runtime.js";
|
|
7
5
|
import path from "node:path";
|
|
8
6
|
import fs from "node:fs";
|
|
9
7
|
//#region src/plugins/vite-plugin/index.ts
|
|
10
|
-
const DEFAULT_MERMAID_FILENAME = "alloy-di.mmd";
|
|
11
8
|
function toLazyServiceKey(identifier) {
|
|
12
9
|
const description = identifier.description;
|
|
13
10
|
if (!description || !description.startsWith("alloy:")) throw new Error("[alloy] lazyServices entries must be serviceIdentifiers exported by Alloy manifests.");
|
|
@@ -26,18 +23,10 @@ function alloy(options = {}) {
|
|
|
26
23
|
let packageName = "UNKNOWN_PACKAGE";
|
|
27
24
|
let resolvedVisualization = null;
|
|
28
25
|
const lazyServiceKeys = new Set((options.lazyServices ?? []).map(toLazyServiceKey));
|
|
29
|
-
const
|
|
30
|
-
const discoveredClasses = /* @__PURE__ */ new Map();
|
|
31
|
-
const lazyReferencedClassKeys = /* @__PURE__ */ new Set();
|
|
32
|
-
const processUpdate = (id, code) => {
|
|
33
|
-
const { metas, lazyClassKeys, previousMetas, previousLazyClassKeys } = discovery.updateFile(id, code);
|
|
34
|
-
if (previousMetas) for (const meta of previousMetas) discoveredClasses.delete(createClassKey(meta.filePath, meta.className));
|
|
35
|
-
for (const meta of metas) discoveredClasses.set(createClassKey(meta.filePath, meta.className), meta);
|
|
36
|
-
if (previousLazyClassKeys) for (const key of previousLazyClassKeys) lazyReferencedClassKeys.delete(key);
|
|
37
|
-
if (lazyClassKeys.size) for (const key of lazyClassKeys) lazyReferencedClassKeys.add(key);
|
|
38
|
-
};
|
|
26
|
+
const discoveryRuntime = createDiscoveryRuntime();
|
|
39
27
|
return {
|
|
40
28
|
name: "vite-plugin-alloy",
|
|
29
|
+
enforce: "pre",
|
|
41
30
|
configResolved(config) {
|
|
42
31
|
resolvedRoot = config.root ?? process.cwd();
|
|
43
32
|
try {
|
|
@@ -67,133 +56,57 @@ function alloy(options = {}) {
|
|
|
67
56
|
exclude: [/\.d\.ts$/i, /node_modules/]
|
|
68
57
|
} },
|
|
69
58
|
handler(code, id) {
|
|
70
|
-
processUpdate(id, code);
|
|
59
|
+
discoveryRuntime.processUpdate(id, code);
|
|
71
60
|
return null;
|
|
72
61
|
}
|
|
73
62
|
},
|
|
74
|
-
hotUpdate(ctx) {
|
|
63
|
+
async hotUpdate(ctx) {
|
|
75
64
|
if (this.environment.name !== "client") return;
|
|
76
|
-
const file = ctx
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
const { file } = ctx;
|
|
66
|
+
if (!isDiscoverableFile(file)) return;
|
|
67
|
+
let discoveryChanged;
|
|
68
|
+
if (ctx.type === "delete") discoveryChanged = discoveryRuntime.removeDiscoveredFile(file);
|
|
69
|
+
else {
|
|
70
|
+
let code;
|
|
71
|
+
try {
|
|
72
|
+
code = await ctx.read();
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
discoveryChanged = discoveryRuntime.processUpdate(file, code);
|
|
82
77
|
}
|
|
83
|
-
return
|
|
78
|
+
if (!discoveryChanged) return;
|
|
79
|
+
invalidateContainerModule(ctx.server, resolvedVirtualModuleId);
|
|
80
|
+
this.environment.hot.send({ type: "full-reload" });
|
|
81
|
+
return [];
|
|
84
82
|
},
|
|
85
83
|
buildStart() {
|
|
86
|
-
|
|
87
|
-
discoveredClasses.clear();
|
|
88
|
-
lazyReferencedClassKeys.clear();
|
|
84
|
+
discoveryRuntime.clear();
|
|
89
85
|
for (const ref of providerModuleRefs) this.addWatchFile(ref.absPath);
|
|
90
86
|
const files = walkSync(path.join(resolvedRoot, "src"));
|
|
91
87
|
for (const file of files) if (/\.(tsx?|ts)$/i.test(file) && !file.endsWith(".d.ts")) try {
|
|
92
|
-
|
|
88
|
+
const code = fs.readFileSync(file, "utf-8");
|
|
89
|
+
discoveryRuntime.processUpdate(file, code);
|
|
93
90
|
} catch {}
|
|
94
91
|
},
|
|
95
92
|
load: {
|
|
96
93
|
filter: { id: { include: [/^\0virtual:alloy-container$/] } },
|
|
97
94
|
async handler(id) {
|
|
98
95
|
if (id !== resolvedVirtualModuleId) return;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const manifestData = await readManifests(options.manifests ?? []);
|
|
110
|
-
const manifestServices = manifestData.services;
|
|
111
|
-
const loadedManifests = manifestData.loadedManifests;
|
|
112
|
-
if (metas.length && manifestServices.length) {
|
|
113
|
-
const duplicates = findDuplicateManifestServices(metas, manifestServices);
|
|
114
|
-
if (duplicates.length) {
|
|
115
|
-
const details = duplicates.map((d) => `- ${d.exportName}: local [${d.localPaths.join(", ")}] vs manifest '${d.manifestImport}'`).join("\n");
|
|
116
|
-
throw new Error([
|
|
117
|
-
"[alloy] Duplicate service registrations detected.",
|
|
118
|
-
details,
|
|
119
|
-
"Resolve by removing one source (local or manifest) to avoid ambiguous DI keys."
|
|
120
|
-
].join("\n"));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
const combinedMetas = [...metas, ...manifestServices.map((svc) => ({
|
|
124
|
-
className: svc.exportName,
|
|
125
|
-
filePath: svc.importPath,
|
|
126
|
-
metadata: {
|
|
127
|
-
scope: svc.scope,
|
|
128
|
-
dependencies: []
|
|
129
|
-
}
|
|
130
|
-
}))];
|
|
131
|
-
const resolver = new IdentifierResolver(combinedMetas);
|
|
132
|
-
const metasByName = groupMetasByName(combinedMetas);
|
|
133
|
-
for (const svc of manifestServices) metas.push(toMetaFromManifest(svc, metasByName, resolver, lazyReferencedClassKeys));
|
|
134
|
-
const providerImports = Array.from(new Set([...providerModuleRefs.map((ref) => ref.importPath), ...manifestData.providers]));
|
|
135
|
-
reconcileLazySet(metas, lazyReferencedClassKeys, collectEagerReferencedNames(metas));
|
|
136
|
-
augmentFactoryLazyServices(metas, lazyServiceKeys);
|
|
137
|
-
const code = generateContainerModule(metas, new Set(lazyReferencedClassKeys), providerImports);
|
|
138
|
-
const dtsDir = path.resolve(resolvedRoot, options.containerDeclarationDir ?? "./src");
|
|
139
|
-
const dtsContent = generateContainerTypeDefinition(metas, (filePath) => {
|
|
140
|
-
if (path.isAbsolute(filePath)) {
|
|
141
|
-
let rel = path.relative(dtsDir, filePath);
|
|
142
|
-
rel = rel.split(path.sep).join(path.posix.sep);
|
|
143
|
-
if (!rel.startsWith(".")) rel = "./" + rel;
|
|
144
|
-
return rel;
|
|
145
|
-
}
|
|
146
|
-
return filePath;
|
|
96
|
+
return loadVirtualContainerModule({
|
|
97
|
+
localMetas: Array.from(discoveryRuntime.discoveredClasses.values()),
|
|
98
|
+
lazyReferencedClassKeys: discoveryRuntime.lazyReferencedClassKeys,
|
|
99
|
+
manifests: options.manifests ?? [],
|
|
100
|
+
providerImportPaths: providerModuleRefs.map((ref) => ref.importPath),
|
|
101
|
+
lazyServiceKeys,
|
|
102
|
+
packageName,
|
|
103
|
+
resolvedRoot,
|
|
104
|
+
containerDeclarationDir: options.containerDeclarationDir,
|
|
105
|
+
resolvedVisualization
|
|
147
106
|
});
|
|
148
|
-
if (!fs.existsSync(dtsDir)) fs.mkdirSync(dtsDir, { recursive: true });
|
|
149
|
-
const dtsPath = path.join(dtsDir, "alloy-container.d.ts");
|
|
150
|
-
fs.writeFileSync(dtsPath, dtsContent);
|
|
151
|
-
if (loadedManifests && loadedManifests.length > 0) {
|
|
152
|
-
const manifestsDts = generateManifestTypeDefinition(loadedManifests.map((m) => ({
|
|
153
|
-
packageName: m.packageName,
|
|
154
|
-
services: m.services
|
|
155
|
-
})));
|
|
156
|
-
const manifestsDtsPath = path.join(dtsDir, "alloy-manifests.d.ts");
|
|
157
|
-
fs.writeFileSync(manifestsDtsPath, manifestsDts);
|
|
158
|
-
}
|
|
159
|
-
if (resolvedVisualization) {
|
|
160
|
-
const artifact = generateMermaidDiagram({
|
|
161
|
-
metas,
|
|
162
|
-
lazyClassKeys: new Set(lazyReferencedClassKeys),
|
|
163
|
-
options: resolvedVisualization.mermaidOptions
|
|
164
|
-
});
|
|
165
|
-
ensureDirectoryForFile(resolvedVisualization.outputPath);
|
|
166
|
-
fs.writeFileSync(resolvedVisualization.outputPath, `${artifact.diagram}\n`);
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
code,
|
|
170
|
-
moduleType: "js"
|
|
171
|
-
};
|
|
172
107
|
}
|
|
173
108
|
}
|
|
174
109
|
};
|
|
175
110
|
}
|
|
176
|
-
function resolveVisualizationOptions(input, projectRoot) {
|
|
177
|
-
if (!input) return null;
|
|
178
|
-
if (typeof input === "boolean") return {
|
|
179
|
-
outputPath: path.resolve(projectRoot, DEFAULT_MERMAID_FILENAME),
|
|
180
|
-
mermaidOptions: void 0
|
|
181
|
-
};
|
|
182
|
-
const mermaidConfig = input.mermaid;
|
|
183
|
-
if (!mermaidConfig) return null;
|
|
184
|
-
if (mermaidConfig === true) return {
|
|
185
|
-
outputPath: path.resolve(projectRoot, DEFAULT_MERMAID_FILENAME),
|
|
186
|
-
mermaidOptions: void 0
|
|
187
|
-
};
|
|
188
|
-
const { outputPath, ...rest } = mermaidConfig;
|
|
189
|
-
return {
|
|
190
|
-
outputPath: path.resolve(projectRoot, outputPath ?? DEFAULT_MERMAID_FILENAME),
|
|
191
|
-
mermaidOptions: Object.keys(rest).length > 0 ? rest : void 0
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
function ensureDirectoryForFile(filePath) {
|
|
195
|
-
const dir = path.dirname(filePath);
|
|
196
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
197
|
-
}
|
|
198
111
|
//#endregion
|
|
199
112
|
export { alloy };
|
|
@@ -7,45 +7,72 @@ import { z } from "zod";
|
|
|
7
7
|
* @param inputs Direct manifest objects.
|
|
8
8
|
* @returns Aggregated arrays of service descriptors and provider specifiers.
|
|
9
9
|
*/
|
|
10
|
+
const retrySchema = z.object({
|
|
11
|
+
retries: z.number(),
|
|
12
|
+
backoffMs: z.number().optional(),
|
|
13
|
+
factor: z.number().optional()
|
|
14
|
+
});
|
|
15
|
+
const manifestServiceSchemaV1 = z.object({
|
|
16
|
+
importPath: z.string(),
|
|
17
|
+
exportName: z.string(),
|
|
18
|
+
symbolKey: z.string(),
|
|
19
|
+
scope: z.enum(["singleton", "transient"]),
|
|
20
|
+
deps: z.array(z.string()).default([]),
|
|
21
|
+
tokenDeps: z.array(z.object({
|
|
22
|
+
exportName: z.string(),
|
|
23
|
+
importPath: z.string()
|
|
24
|
+
})).default([]),
|
|
25
|
+
lazyDeps: z.array(z.object({
|
|
26
|
+
importPath: z.string(),
|
|
27
|
+
exportName: z.string(),
|
|
28
|
+
retry: retrySchema.optional()
|
|
29
|
+
})).default([])
|
|
30
|
+
});
|
|
31
|
+
const manifestServiceSchemaV2 = z.object({
|
|
32
|
+
importPath: z.string(),
|
|
33
|
+
exportName: z.string(),
|
|
34
|
+
symbolKey: z.string(),
|
|
35
|
+
scope: z.enum(["singleton", "transient"]),
|
|
36
|
+
deps: z.array(z.discriminatedUnion("kind", [
|
|
37
|
+
z.object({
|
|
38
|
+
kind: z.literal("class"),
|
|
39
|
+
exportName: z.string()
|
|
40
|
+
}),
|
|
41
|
+
z.object({
|
|
42
|
+
kind: z.literal("token"),
|
|
43
|
+
exportName: z.string(),
|
|
44
|
+
importPath: z.string()
|
|
45
|
+
}),
|
|
46
|
+
z.object({
|
|
47
|
+
kind: z.literal("lazy"),
|
|
48
|
+
exportName: z.string(),
|
|
49
|
+
importPath: z.string(),
|
|
50
|
+
retry: retrySchema.optional()
|
|
51
|
+
})
|
|
52
|
+
])).default([])
|
|
53
|
+
});
|
|
54
|
+
const manifestSchemaV1 = z.object({
|
|
55
|
+
schemaVersion: z.number().optional(),
|
|
56
|
+
packageName: z.string(),
|
|
57
|
+
services: z.array(manifestServiceSchemaV1).default([]),
|
|
58
|
+
providers: z.array(z.string()).default([])
|
|
59
|
+
});
|
|
60
|
+
const manifestSchemaV2 = z.object({
|
|
61
|
+
schemaVersion: z.literal(2),
|
|
62
|
+
packageName: z.string(),
|
|
63
|
+
services: z.array(manifestServiceSchemaV2).default([]),
|
|
64
|
+
providers: z.array(z.string()).default([])
|
|
65
|
+
});
|
|
10
66
|
async function readManifests(inputs) {
|
|
11
67
|
const services = [];
|
|
12
68
|
const providers = [];
|
|
13
69
|
const loadedManifests = [];
|
|
14
|
-
const manifestSchema = z.object({
|
|
15
|
-
schemaVersion: z.number().optional(),
|
|
16
|
-
packageName: z.string(),
|
|
17
|
-
services: z.array(z.object({
|
|
18
|
-
importPath: z.string(),
|
|
19
|
-
exportName: z.string(),
|
|
20
|
-
symbolKey: z.string(),
|
|
21
|
-
scope: z.enum(["singleton", "transient"]),
|
|
22
|
-
deps: z.array(z.string()).default([]),
|
|
23
|
-
tokenDeps: z.array(z.object({
|
|
24
|
-
exportName: z.string(),
|
|
25
|
-
importPath: z.string()
|
|
26
|
-
})).default([]),
|
|
27
|
-
lazyDeps: z.array(z.object({
|
|
28
|
-
importPath: z.string(),
|
|
29
|
-
exportName: z.string(),
|
|
30
|
-
retry: z.object({
|
|
31
|
-
retries: z.number(),
|
|
32
|
-
backoffMs: z.number().optional(),
|
|
33
|
-
factor: z.number().optional()
|
|
34
|
-
}).optional()
|
|
35
|
-
})).default([])
|
|
36
|
-
})).default([]),
|
|
37
|
-
providers: z.array(z.string()).default([])
|
|
38
|
-
});
|
|
39
70
|
for (const manifest of inputs) {
|
|
40
|
-
const parsed =
|
|
41
|
-
if (!parsed
|
|
42
|
-
loadedManifests.push(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
providers: parsed.data.providers
|
|
46
|
-
});
|
|
47
|
-
for (const svc of parsed.data.services) services.push(svc);
|
|
48
|
-
for (const p of parsed.data.providers) providers.push(p);
|
|
71
|
+
const parsed = readManifestByVersion(manifest);
|
|
72
|
+
if (!parsed) continue;
|
|
73
|
+
loadedManifests.push(parsed);
|
|
74
|
+
for (const svc of parsed.services) services.push(svc);
|
|
75
|
+
for (const p of parsed.providers) providers.push(p);
|
|
49
76
|
}
|
|
50
77
|
return Promise.resolve({
|
|
51
78
|
services,
|
|
@@ -53,6 +80,35 @@ async function readManifests(inputs) {
|
|
|
53
80
|
loadedManifests
|
|
54
81
|
});
|
|
55
82
|
}
|
|
83
|
+
function readManifestByVersion(manifest) {
|
|
84
|
+
return (manifest.schemaVersion ?? 1) === 2 ? readManifestV2(manifest) : readManifestV1(manifest);
|
|
85
|
+
}
|
|
86
|
+
function readManifestV1(manifest) {
|
|
87
|
+
const parsed = manifestSchemaV1.safeParse(manifest);
|
|
88
|
+
if (!parsed.success) return null;
|
|
89
|
+
return {
|
|
90
|
+
schemaVersion: 1,
|
|
91
|
+
packageName: parsed.data.packageName,
|
|
92
|
+
services: parsed.data.services.map((svc) => ({
|
|
93
|
+
...svc,
|
|
94
|
+
schemaVersion: 1
|
|
95
|
+
})),
|
|
96
|
+
providers: parsed.data.providers
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function readManifestV2(manifest) {
|
|
100
|
+
const parsed = manifestSchemaV2.safeParse(manifest);
|
|
101
|
+
if (!parsed.success) return null;
|
|
102
|
+
return {
|
|
103
|
+
schemaVersion: 2,
|
|
104
|
+
packageName: parsed.data.packageName,
|
|
105
|
+
services: parsed.data.services.map((svc) => ({
|
|
106
|
+
...svc,
|
|
107
|
+
schemaVersion: 2
|
|
108
|
+
})),
|
|
109
|
+
providers: parsed.data.providers
|
|
110
|
+
};
|
|
111
|
+
}
|
|
56
112
|
/**
|
|
57
113
|
* Groups metas by class name to support resolving dependencies that reference classes with collisions.
|
|
58
114
|
*
|
|
@@ -98,21 +154,22 @@ function selectMetaForDep(metasByName, depName, currentImportPath) {
|
|
|
98
154
|
function toMetaFromManifest(svc, metasByName, resolver, lazySet) {
|
|
99
155
|
const deps = [];
|
|
100
156
|
const referencedImports = [];
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
157
|
+
if (svc.schemaVersion === 2) appendDependenciesFromManifestV2(svc, deps, referencedImports, metasByName, resolver, lazySet);
|
|
158
|
+
else appendDependenciesFromManifestV1(svc, deps, referencedImports, metasByName, resolver, lazySet);
|
|
159
|
+
const metadata = {
|
|
160
|
+
scope: svc.scope,
|
|
161
|
+
dependencies: deps
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
className: svc.exportName,
|
|
165
|
+
filePath: svc.importPath,
|
|
166
|
+
identifierKey: svc.symbolKey,
|
|
167
|
+
metadata,
|
|
168
|
+
referencedImports
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function appendDependenciesFromManifestV1(svc, deps, referencedImports, metasByName, resolver, lazySet) {
|
|
172
|
+
for (const depName of svc.deps ?? []) deps.push(createClassOrTokenDependency(depName, svc.importPath, metasByName, resolver));
|
|
116
173
|
if (Array.isArray(svc.tokenDeps)) for (const tok of svc.tokenDeps) {
|
|
117
174
|
deps.push({
|
|
118
175
|
expression: tok.exportName,
|
|
@@ -126,31 +183,63 @@ function toMetaFromManifest(svc, metasByName, resolver, lazySet) {
|
|
|
126
183
|
});
|
|
127
184
|
}
|
|
128
185
|
for (const lazy of svc.lazyDeps ?? []) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
186
|
+
deps.push(createLazyDependencyDescriptor(lazy));
|
|
187
|
+
lazySet.add(createClassKey(lazy.importPath, lazy.exportName));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function appendDependenciesFromManifestV2(svc, deps, referencedImports, metasByName, resolver, lazySet) {
|
|
191
|
+
for (const dep of svc.deps) appendV2Dependency(dep, svc.importPath, deps, referencedImports, metasByName, resolver, lazySet);
|
|
192
|
+
}
|
|
193
|
+
function appendV2Dependency(dep, currentImportPath, deps, referencedImports, metasByName, resolver, lazySet) {
|
|
194
|
+
if (dep.kind === "class") {
|
|
195
|
+
deps.push(createClassOrTokenDependency(dep.exportName, currentImportPath, metasByName, resolver));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (dep.kind === "token") {
|
|
137
199
|
deps.push({
|
|
138
|
-
expression:
|
|
139
|
-
referencedIdentifiers: [],
|
|
140
|
-
isLazy:
|
|
200
|
+
expression: dep.exportName,
|
|
201
|
+
referencedIdentifiers: [dep.exportName],
|
|
202
|
+
isLazy: false
|
|
141
203
|
});
|
|
142
|
-
|
|
204
|
+
referencedImports.push({
|
|
205
|
+
name: dep.exportName,
|
|
206
|
+
path: dep.importPath,
|
|
207
|
+
originalName: dep.exportName
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
143
210
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
211
|
+
deps.push(createLazyDependencyDescriptor(dep));
|
|
212
|
+
lazySet.add(createClassKey(dep.importPath, dep.exportName));
|
|
213
|
+
}
|
|
214
|
+
function createClassOrTokenDependency(depName, currentImportPath, metasByName, resolver) {
|
|
215
|
+
const targetMeta = selectMetaForDep(metasByName, depName, currentImportPath);
|
|
216
|
+
if (targetMeta) {
|
|
217
|
+
const expression = resolver.resolve(targetMeta.className, targetMeta.filePath);
|
|
218
|
+
return {
|
|
219
|
+
expression,
|
|
220
|
+
referencedIdentifiers: [expression],
|
|
221
|
+
isLazy: false
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
expression: depName,
|
|
226
|
+
referencedIdentifiers: [depName],
|
|
227
|
+
isLazy: false
|
|
147
228
|
};
|
|
229
|
+
}
|
|
230
|
+
function createLazyDependencyDescriptor(lazy) {
|
|
231
|
+
const importer = `() => import('${lazy.importPath}').then(m => m.${lazy.exportName})`;
|
|
232
|
+
let expression = `Lazy(${importer})`;
|
|
233
|
+
if (lazy.retry) {
|
|
234
|
+
const opts = [`retries: ${lazy.retry.retries}`];
|
|
235
|
+
if (typeof lazy.retry.backoffMs === "number") opts.push(`backoffMs: ${lazy.retry.backoffMs}`);
|
|
236
|
+
if (typeof lazy.retry.factor === "number") opts.push(`factor: ${lazy.retry.factor}`);
|
|
237
|
+
expression = `Lazy(${importer}, { ${opts.join(", ")} })`;
|
|
238
|
+
}
|
|
148
239
|
return {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
metadata,
|
|
153
|
-
referencedImports
|
|
240
|
+
expression,
|
|
241
|
+
referencedIdentifiers: [],
|
|
242
|
+
isLazy: true
|
|
154
243
|
};
|
|
155
244
|
}
|
|
156
245
|
/**
|
|
@@ -200,12 +289,22 @@ function augmentFactoryLazyServices(metas, lazyServiceKeys) {
|
|
|
200
289
|
* Returns structured info for error reporting.
|
|
201
290
|
*/
|
|
202
291
|
function findDuplicateManifestServices(localMetas, manifestServices) {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
292
|
+
const localMetasByIdentifier = /* @__PURE__ */ new Map();
|
|
293
|
+
for (const meta of localMetas) {
|
|
294
|
+
const identifierKey = meta.identifierKey ?? createSymbolKey(meta.filePath, meta.className);
|
|
295
|
+
const matches = localMetasByIdentifier.get(identifierKey) ?? [];
|
|
296
|
+
matches.push(meta);
|
|
297
|
+
localMetasByIdentifier.set(identifierKey, matches);
|
|
298
|
+
}
|
|
299
|
+
return manifestServices.flatMap((svc) => {
|
|
300
|
+
const matches = localMetasByIdentifier.get(svc.symbolKey);
|
|
301
|
+
if (!matches?.length) return [];
|
|
302
|
+
return [{
|
|
303
|
+
exportName: svc.exportName,
|
|
304
|
+
localPaths: matches.map((m) => normalizeImportPath(m.filePath)),
|
|
305
|
+
manifestImport: svc.importPath
|
|
306
|
+
}];
|
|
307
|
+
});
|
|
209
308
|
}
|
|
210
309
|
//#endregion
|
|
211
310
|
export { augmentFactoryLazyServices, collectEagerReferencedNames, findDuplicateManifestServices, groupMetasByName, readManifests, reconcileLazySet, toMetaFromManifest };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { MermaidDiagramOptions } from "./visualizer.js";
|
|
2
|
+
|
|
3
|
+
//#region src/plugins/vite-plugin/visualization-utils.d.ts
|
|
4
|
+
interface AlloyMermaidVisualizerOptions extends MermaidDiagramOptions {
|
|
5
|
+
outputPath?: string;
|
|
6
|
+
}
|
|
7
|
+
interface AlloyVisualizationOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Configure Mermaid diagram emission. Use `true` for defaults or provide
|
|
10
|
+
* overrides for layout, colors, or output path.
|
|
11
|
+
*/
|
|
12
|
+
mermaid?: boolean | AlloyMermaidVisualizerOptions;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { AlloyMermaidVisualizerOptions, AlloyVisualizationOptions };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
//#region src/plugins/vite-plugin/visualization-utils.ts
|
|
4
|
+
const DEFAULT_MERMAID_FILENAME = "alloy-di.mmd";
|
|
5
|
+
function resolveVisualizationOptions(input, projectRoot) {
|
|
6
|
+
if (!input) return null;
|
|
7
|
+
if (typeof input === "boolean") return {
|
|
8
|
+
outputPath: path.resolve(projectRoot, DEFAULT_MERMAID_FILENAME),
|
|
9
|
+
mermaidOptions: void 0
|
|
10
|
+
};
|
|
11
|
+
const mermaidConfig = input.mermaid;
|
|
12
|
+
if (!mermaidConfig) return null;
|
|
13
|
+
if (mermaidConfig === true) return {
|
|
14
|
+
outputPath: path.resolve(projectRoot, DEFAULT_MERMAID_FILENAME),
|
|
15
|
+
mermaidOptions: void 0
|
|
16
|
+
};
|
|
17
|
+
const { outputPath, ...rest } = mermaidConfig;
|
|
18
|
+
return {
|
|
19
|
+
outputPath: path.resolve(projectRoot, outputPath ?? "alloy-di.mmd"),
|
|
20
|
+
mermaidOptions: Object.keys(rest).length > 0 ? rest : void 0
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function ensureDirectoryForFile(filePath) {
|
|
24
|
+
const dir = path.dirname(filePath);
|
|
25
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ensureDirectoryForFile, resolveVisualizationOptions };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["../src/entry-points.test.ts","../src/rollup.ts","../src/runtime.ts","../src/test.ts","../src/vite.ts","../src/lib/container.identifiers.test.ts","../src/lib/container.internals.test.ts","../src/lib/container.test.ts","../src/lib/container.testing-features.test.ts","../src/lib/container.ts","../src/lib/decorators.runtime.test.ts","../src/lib/decorators.test-d.ts","../src/lib/decorators.ts","../src/lib/dependency-error.ts","../src/lib/env-detection.test.ts","../src/lib/env-detection.ts","../src/lib/lazy-retry.test.ts","../src/lib/lazy.ts","../src/lib/providers.test.ts","../src/lib/providers.ts","../src/lib/scope.ts","../src/lib/service-identifiers.test.ts","../src/lib/service-identifiers.ts","../src/lib/tokens.test.ts","../src/lib/types.ts","../src/lib/testing/mocking.test.ts","../src/lib/testing/mocking.ts","../src/lib/testing/registry.test.ts","../src/lib/testing/registry.ts","../src/plugins/core/codegen.test.ts","../src/plugins/core/codegen.ts","../src/plugins/core/decorators.helpers.test.ts","../src/plugins/core/decorators.ts","../src/plugins/core/discovery-store.ts","../src/plugins/core/identifier-resolver.test.ts","../src/plugins/core/identifier-resolver.ts","../src/plugins/core/lazy-utils.ts","../src/plugins/core/lazy.helpers.test.ts","../src/plugins/core/lazy.ts","../src/plugins/core/scanner.test.ts","../src/plugins/core/scanner.ts","../src/plugins/core/types.ts","../src/plugins/core/utils.ts","../src/plugins/rollup-plugin/build-utils.ts","../src/plugins/rollup-plugin/index.ts","../src/plugins/rollup-plugin/rollup-plugin.test.ts","../src/plugins/vite-plugin/codegen.specifier.test.ts","../src/plugins/vite-plugin/duplicate-registrations.test.ts","../src/plugins/vite-plugin/fixture-subpaths.test.ts","../src/plugins/vite-plugin/index.ts","../src/plugins/vite-plugin/lazy-services.test.ts","../src/plugins/vite-plugin/lifecycle-and-hmr.test.ts","../src/plugins/vite-plugin/manifest-utils.ts","../src/plugins/vite-plugin/manifest-utils.validation.test.ts","../src/plugins/vite-plugin/module-generation.test.ts","../src/plugins/vite-plugin/test-utils.ts","../src/plugins/vite-plugin/transform-guards.test.ts","../src/plugins/vite-plugin/visualization-option.test.ts","../src/plugins/vite-plugin/visualizer.test.ts","../src/plugins/vite-plugin/visualizer.ts"],"version":"6.0.3"}
|
|
1
|
+
{"root":["../src/entry-points.test.ts","../src/rollup.ts","../src/runtime.ts","../src/test.ts","../src/vite.ts","../src/lib/container.identifiers.test.ts","../src/lib/container.internals.test.ts","../src/lib/container.test.ts","../src/lib/container.testing-features.test.ts","../src/lib/container.ts","../src/lib/decorators.runtime.test.ts","../src/lib/decorators.test-d.ts","../src/lib/decorators.ts","../src/lib/dependency-error.ts","../src/lib/env-detection.test.ts","../src/lib/env-detection.ts","../src/lib/lazy-retry.test.ts","../src/lib/lazy.ts","../src/lib/providers.test.ts","../src/lib/providers.ts","../src/lib/scope.ts","../src/lib/service-identifiers.test.ts","../src/lib/service-identifiers.ts","../src/lib/tokens.test.ts","../src/lib/types.ts","../src/lib/testing/mocking.test.ts","../src/lib/testing/mocking.ts","../src/lib/testing/registry.test.ts","../src/lib/testing/registry.ts","../src/plugins/core/codegen.test.ts","../src/plugins/core/codegen.ts","../src/plugins/core/decorators.helpers.test.ts","../src/plugins/core/decorators.ts","../src/plugins/core/discovery-store.ts","../src/plugins/core/identifier-resolver.test.ts","../src/plugins/core/identifier-resolver.ts","../src/plugins/core/lazy-utils.ts","../src/plugins/core/lazy.helpers.test.ts","../src/plugins/core/lazy.ts","../src/plugins/core/scanner.test.ts","../src/plugins/core/scanner.ts","../src/plugins/core/types.ts","../src/plugins/core/utils.ts","../src/plugins/rollup-plugin/build-utils.ts","../src/plugins/rollup-plugin/index.ts","../src/plugins/rollup-plugin/rollup-plugin.test.ts","../src/plugins/vite-plugin/codegen.specifier.test.ts","../src/plugins/vite-plugin/container-loader.ts","../src/plugins/vite-plugin/discovery-runtime.ts","../src/plugins/vite-plugin/duplicate-registrations.test.ts","../src/plugins/vite-plugin/fixture-subpaths.test.ts","../src/plugins/vite-plugin/index.ts","../src/plugins/vite-plugin/lazy-services.test.ts","../src/plugins/vite-plugin/lifecycle-and-hmr.test.ts","../src/plugins/vite-plugin/manifest-utils.ts","../src/plugins/vite-plugin/manifest-utils.validation.test.ts","../src/plugins/vite-plugin/module-generation.test.ts","../src/plugins/vite-plugin/test-utils.ts","../src/plugins/vite-plugin/transform-guards.test.ts","../src/plugins/vite-plugin/visualization-option.test.ts","../src/plugins/vite-plugin/visualization-utils.ts","../src/plugins/vite-plugin/visualizer.test.ts","../src/plugins/vite-plugin/visualizer.ts"],"version":"6.0.3"}
|