alloy-di 1.1.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/lib/container.d.ts +6 -7
- package/dist/lib/container.js +1 -3
- package/dist/lib/decorators.d.ts +0 -1
- package/dist/lib/decorators.js +5 -7
- package/dist/lib/dependency-error.js +1 -3
- package/dist/lib/env-detection.js +2 -4
- package/dist/lib/lazy.js +1 -2
- package/dist/lib/providers.d.ts +0 -1
- package/dist/lib/providers.js +3 -5
- package/dist/lib/scope.js +1 -2
- package/dist/lib/service-identifiers.d.ts +0 -1
- package/dist/lib/service-identifiers.js +1 -2
- package/dist/lib/testing/mocking.d.ts +1 -3
- package/dist/lib/testing/mocking.js +1 -3
- package/dist/lib/testing/registry.js +1 -3
- package/dist/lib/types.js +1 -2
- package/dist/plugins/core/codegen.js +118 -54
- package/dist/plugins/core/decorators.js +1 -3
- package/dist/plugins/core/discovery-store.js +1 -3
- package/dist/plugins/core/identifier-resolver.js +1 -3
- package/dist/plugins/core/lazy.js +83 -45
- package/dist/plugins/core/scanner.js +121 -42
- package/dist/plugins/core/types.d.ts +33 -15
- package/dist/plugins/core/utils.js +13 -15
- package/dist/plugins/rollup-plugin/build-utils.js +1 -3
- package/dist/plugins/rollup-plugin/index.js +73 -45
- 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 +57 -134
- package/dist/plugins/vite-plugin/manifest-utils.js +176 -79
- package/dist/plugins/vite-plugin/visualization-utils.d.ts +15 -0
- package/dist/plugins/vite-plugin/visualization-utils.js +28 -0
- package/dist/plugins/vite-plugin/visualizer.js +4 -6
- package/dist/rollup.js +2 -4
- package/dist/runtime.js +1 -2
- package/dist/test.js +1 -3
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/vite.js +1 -3
- package/package.json +34 -29
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createClassKey } from "./utils.js";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import path from "path";
|
|
4
|
-
|
|
5
4
|
//#region src/plugins/core/lazy.ts
|
|
6
5
|
const RESOLVED_EXTENSIONS = [
|
|
7
6
|
"",
|
|
@@ -13,23 +12,69 @@ const RESOLVED_EXTENSIONS = [
|
|
|
13
12
|
".cts"
|
|
14
13
|
];
|
|
15
14
|
function processLazyCall(node, fileId, sourceFile, localLazyRefs) {
|
|
16
|
-
if (node
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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) {
|
|
29
|
+
const factory = getLazyFactory(node.arguments[0]);
|
|
30
|
+
if (!factory) return;
|
|
24
31
|
const body = getReturnedExpression(factory);
|
|
25
32
|
if (!body) return;
|
|
26
33
|
const importInfo = extractImportInfo(body);
|
|
27
|
-
|
|
34
|
+
const exportName = importInfo?.exportName;
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
return {
|
|
40
|
+
specifier: importInfo.specifier,
|
|
41
|
+
exportName,
|
|
42
|
+
retry,
|
|
43
|
+
importPath: importInfo.specifier,
|
|
44
|
+
classKeys: resolvedPaths.map((candidate) => createClassKey(candidate, exportName))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function getLazyFactory(arg) {
|
|
48
|
+
if (!arg) return;
|
|
49
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) return arg;
|
|
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;
|
|
33
78
|
}
|
|
34
79
|
function getReturnedExpression(fn) {
|
|
35
80
|
if (ts.isBlock(fn.body)) {
|
|
@@ -39,40 +84,37 @@ function getReturnedExpression(fn) {
|
|
|
39
84
|
return fn.body;
|
|
40
85
|
}
|
|
41
86
|
function extractImportInfo(expr) {
|
|
42
|
-
if (ts.isCallExpression(expr))
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
specifier: spec,
|
|
48
|
-
exportName: void 0
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
if (ts.isPropertyAccessExpression(expr.expression) && expr.expression.name.text === "then") {
|
|
52
|
-
const importCall = expr.expression.expression;
|
|
53
|
-
if (!ts.isCallExpression(importCall) || !isDynamicImport(importCall)) return;
|
|
54
|
-
const spec = getImportSpecifier(importCall.arguments[0]);
|
|
55
|
-
if (!spec) return;
|
|
56
|
-
const callback = expr.arguments[0];
|
|
57
|
-
return {
|
|
58
|
-
specifier: spec,
|
|
59
|
-
exportName: callback ? extractExportName(callback) : void 0
|
|
60
|
-
};
|
|
61
|
-
}
|
|
87
|
+
if (!ts.isCallExpression(expr)) return;
|
|
88
|
+
if (isDynamicImport(expr)) {
|
|
89
|
+
const spec = getImportSpecifier(expr.arguments[0]);
|
|
90
|
+
return spec ? { specifier: spec } : void 0;
|
|
62
91
|
}
|
|
92
|
+
if (!ts.isPropertyAccessExpression(expr.expression)) return;
|
|
93
|
+
if (expr.expression.name.text !== "then") return;
|
|
94
|
+
const importCall = expr.expression.expression;
|
|
95
|
+
if (!ts.isCallExpression(importCall) || !isDynamicImport(importCall)) return;
|
|
96
|
+
const spec = getImportSpecifier(importCall.arguments[0]);
|
|
97
|
+
if (!spec) return;
|
|
98
|
+
const callback = expr.arguments[0];
|
|
99
|
+
return {
|
|
100
|
+
specifier: spec,
|
|
101
|
+
exportName: callback ? extractExportName(callback) : void 0
|
|
102
|
+
};
|
|
63
103
|
}
|
|
64
104
|
function isDynamicImport(node) {
|
|
65
105
|
return node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
66
106
|
}
|
|
107
|
+
function isLazyCall(node, sourceFile) {
|
|
108
|
+
const text = node.expression.getText(sourceFile);
|
|
109
|
+
return text === "Lazy" || text.endsWith(".Lazy");
|
|
110
|
+
}
|
|
67
111
|
function getImportSpecifier(node) {
|
|
68
|
-
|
|
69
|
-
if (ts.isStringLiteralLike(node)) return node.text;
|
|
112
|
+
return node && ts.isStringLiteralLike(node) ? node.text : void 0;
|
|
70
113
|
}
|
|
71
114
|
function extractExportName(callback) {
|
|
72
115
|
if (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) {
|
|
73
116
|
const body = getReturnedExpression(callback);
|
|
74
|
-
|
|
75
|
-
return extractExportNameFromExpression(body);
|
|
117
|
+
return body ? extractExportNameFromExpression(body) : void 0;
|
|
76
118
|
}
|
|
77
119
|
return extractExportNameFromExpression(callback);
|
|
78
120
|
}
|
|
@@ -85,14 +127,10 @@ function resolveModuleSpecifierCandidates(fromId, specifier) {
|
|
|
85
127
|
if (!specifier.startsWith(".")) return [];
|
|
86
128
|
const baseDir = path.dirname(fromId);
|
|
87
129
|
const resolvedBase = path.resolve(baseDir, specifier);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
for (const ext of RESOLVED_EXTENSIONS) candidates.push(path.join(resolvedBase, "index" + ext));
|
|
93
|
-
}
|
|
94
|
-
return candidates;
|
|
130
|
+
if (path.extname(resolvedBase)) return [resolvedBase];
|
|
131
|
+
const fileCandidates = RESOLVED_EXTENSIONS.map((ext) => resolvedBase + ext);
|
|
132
|
+
const indexCandidates = RESOLVED_EXTENSIONS.map((ext) => path.join(resolvedBase, `index${ext}`));
|
|
133
|
+
return [...fileCandidates, ...indexCandidates];
|
|
95
134
|
}
|
|
96
|
-
|
|
97
135
|
//#endregion
|
|
98
|
-
export { processLazyCall };
|
|
136
|
+
export { parseLazyDependencyExpression, processLazyCall, resolveModuleSpecifierCandidates };
|
|
@@ -1,9 +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
6
|
//#region src/plugins/core/scanner.ts
|
|
7
|
+
const ALLOY_RUNTIME_MODULE = "alloy-di/runtime";
|
|
7
8
|
function collectFileImports(sourceFile) {
|
|
8
9
|
const imports = /* @__PURE__ */ new Map();
|
|
9
10
|
for (const statement of sourceFile.statements) if (ts.isImportDeclaration(statement) && statement.importClause && ts.isStringLiteral(statement.moduleSpecifier)) {
|
|
@@ -40,46 +41,16 @@ function scanSource(code, id) {
|
|
|
40
41
|
const discovered = /* @__PURE__ */ new Map();
|
|
41
42
|
const lazyRefs = /* @__PURE__ */ new Set();
|
|
42
43
|
const fileImports = collectFileImports(sourceFile);
|
|
44
|
+
const decoratorResolutionCache = /* @__PURE__ */ new Map();
|
|
43
45
|
const visit = (node) => {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (!ts.isCallExpression(d.expression)) return false;
|
|
51
|
-
const name = d.expression.expression.getText(sourceFile);
|
|
52
|
-
return name.endsWith("Injectable") || name.endsWith("Singleton");
|
|
53
|
-
});
|
|
54
|
-
if (!targetDecorator || !ts.isCallExpression(targetDecorator.expression)) {
|
|
55
|
-
ts.forEachChild(node, visit);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const decoratorName = targetDecorator.expression.expression.getText(sourceFile);
|
|
59
|
-
const className = node.name.getText(sourceFile);
|
|
60
|
-
const callExpression = targetDecorator.expression;
|
|
61
|
-
const metadata = extractServiceMetadata(decoratorName, callExpression, sourceFile);
|
|
62
|
-
const referencedImports = [];
|
|
63
|
-
const seenIdentifiers = /* @__PURE__ */ new Set();
|
|
64
|
-
for (const dep of metadata.dependencies) for (const ident of dep.referencedIdentifiers) {
|
|
65
|
-
if (seenIdentifiers.has(ident)) continue;
|
|
66
|
-
seenIdentifiers.add(ident);
|
|
67
|
-
const importInfo = fileImports.get(ident);
|
|
68
|
-
if (importInfo) referencedImports.push({
|
|
69
|
-
name: ident,
|
|
70
|
-
path: importInfo.path,
|
|
71
|
-
originalName: importInfo.originalName,
|
|
72
|
-
isTypeOnly: importInfo.isTypeOnly
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const classKey = createClassKey(id, className);
|
|
76
|
-
discovered.set(classKey, {
|
|
77
|
-
className,
|
|
78
|
-
filePath: id,
|
|
79
|
-
identifierKey: createSymbolKey(id, className),
|
|
80
|
-
metadata,
|
|
81
|
-
referencedImports
|
|
46
|
+
if (ts.isClassDeclaration(node)) handleClassDeclaration(node, {
|
|
47
|
+
id,
|
|
48
|
+
sourceFile,
|
|
49
|
+
fileImports,
|
|
50
|
+
discovered,
|
|
51
|
+
decoratorResolutionCache
|
|
82
52
|
});
|
|
53
|
+
else if (ts.isCallExpression(node)) processLazyCall(node, id, sourceFile, lazyRefs);
|
|
83
54
|
ts.forEachChild(node, visit);
|
|
84
55
|
};
|
|
85
56
|
ts.forEachChild(sourceFile, visit);
|
|
@@ -88,6 +59,114 @@ function scanSource(code, id) {
|
|
|
88
59
|
lazyClassKeys: lazyRefs
|
|
89
60
|
};
|
|
90
61
|
}
|
|
91
|
-
|
|
62
|
+
function handleClassDeclaration(node, context) {
|
|
63
|
+
if (!node.name) return;
|
|
64
|
+
const decoratorMatch = findServiceDecorator(node, context.sourceFile, context.fileImports, context.id, context.decoratorResolutionCache);
|
|
65
|
+
if (!decoratorMatch) return;
|
|
66
|
+
const { decoratorCall, decoratorName } = decoratorMatch;
|
|
67
|
+
const className = node.name.getText(context.sourceFile);
|
|
68
|
+
const metadata = extractServiceMetadata(decoratorName, decoratorCall, context.sourceFile);
|
|
69
|
+
const referencedImports = collectReferencedImports(metadata, context.fileImports);
|
|
70
|
+
const classKey = createClassKey(context.id, className);
|
|
71
|
+
context.discovered.set(classKey, {
|
|
72
|
+
className,
|
|
73
|
+
filePath: context.id,
|
|
74
|
+
identifierKey: createSymbolKey(context.id, className),
|
|
75
|
+
metadata,
|
|
76
|
+
referencedImports
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function findServiceDecorator(node, sourceFile, fileImports, id, resolutionCache) {
|
|
80
|
+
const decorators = ts.getDecorators ? ts.getDecorators(node) : void 0;
|
|
81
|
+
if (!decorators?.length) return;
|
|
82
|
+
for (const decorator of decorators) {
|
|
83
|
+
if (!ts.isCallExpression(decorator.expression)) continue;
|
|
84
|
+
const decoratorName = resolveDecoratorName(decorator.expression.expression, fileImports, id, new Set([id]), resolutionCache);
|
|
85
|
+
if (decoratorName) return {
|
|
86
|
+
decoratorCall: decorator.expression,
|
|
87
|
+
decoratorName
|
|
88
|
+
};
|
|
89
|
+
}
|
|
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
|
+
}
|
|
154
|
+
function collectReferencedImports(metadata, fileImports) {
|
|
155
|
+
const referenced = [];
|
|
156
|
+
const seen = /* @__PURE__ */ new Set();
|
|
157
|
+
for (const dep of metadata.dependencies) for (const ident of dep.referencedIdentifiers) {
|
|
158
|
+
if (seen.has(ident)) continue;
|
|
159
|
+
seen.add(ident);
|
|
160
|
+
const importInfo = fileImports.get(ident);
|
|
161
|
+
if (!importInfo) continue;
|
|
162
|
+
referenced.push({
|
|
163
|
+
name: ident,
|
|
164
|
+
path: importInfo.path,
|
|
165
|
+
originalName: importInfo.originalName,
|
|
166
|
+
isTypeOnly: importInfo.isTypeOnly
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return referenced;
|
|
170
|
+
}
|
|
92
171
|
//#endregion
|
|
93
|
-
export { scanSource };
|
|
172
|
+
export { scanSource };
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
import { ServiceScope } from "../../lib/scope.js";
|
|
2
2
|
|
|
3
3
|
//#region src/plugins/core/types.d.ts
|
|
4
|
-
|
|
5
|
-
|
|
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 {
|
|
6
29
|
exportName: string;
|
|
7
30
|
importPath: string;
|
|
8
31
|
/**
|
|
@@ -11,22 +34,17 @@ interface ManifestServiceDescriptor {
|
|
|
11
34
|
*/
|
|
12
35
|
symbolKey: string;
|
|
13
36
|
scope: ServiceScope;
|
|
37
|
+
}
|
|
38
|
+
interface ManifestServiceDescriptorV1 extends ManifestServiceDescriptorBase {
|
|
14
39
|
deps: string[];
|
|
15
40
|
/** Token dependencies (non-service identifiers) exported publicly by the package. */
|
|
16
|
-
tokenDeps?:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
exportName: string;
|
|
22
|
-
importPath: string;
|
|
23
|
-
retry?: {
|
|
24
|
-
retries: number;
|
|
25
|
-
backoffMs?: number;
|
|
26
|
-
factor?: number;
|
|
27
|
-
};
|
|
28
|
-
}[];
|
|
41
|
+
tokenDeps?: ManifestTokenDependency[];
|
|
42
|
+
lazyDeps: ManifestLazyDependency[];
|
|
43
|
+
}
|
|
44
|
+
interface ManifestServiceDescriptorV2 extends ManifestServiceDescriptorBase {
|
|
45
|
+
deps: ManifestDependencyEntry[];
|
|
29
46
|
}
|
|
47
|
+
type ManifestServiceDescriptor = ManifestServiceDescriptorV1 | ManifestServiceDescriptorV2;
|
|
30
48
|
interface AlloyManifest {
|
|
31
49
|
schemaVersion: number;
|
|
32
50
|
packageName: string;
|
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
-
|
|
4
3
|
//#region src/plugins/core/utils.ts
|
|
5
4
|
const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:[\\/]/;
|
|
6
5
|
function normalizeImportPath(p) {
|
|
7
6
|
const raw = p.trim();
|
|
8
|
-
if (!raw) return raw;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return
|
|
7
|
+
if (!raw || isBareModuleSpecifier(raw)) return raw;
|
|
8
|
+
return ensureLeadingSlash(normalizeSlashes(raw));
|
|
9
|
+
}
|
|
10
|
+
function isBareModuleSpecifier(raw) {
|
|
11
|
+
return !raw.startsWith("/") && !raw.startsWith("\\") && !raw.startsWith(".") && !raw.startsWith("~") && !WINDOWS_DRIVE_PATTERN.test(raw) && !raw.includes("\\");
|
|
12
|
+
}
|
|
13
|
+
function normalizeSlashes(value) {
|
|
14
|
+
return value.replaceAll("\\", "/").replaceAll(/^\/+/g, "/");
|
|
15
|
+
}
|
|
16
|
+
function ensureLeadingSlash(value) {
|
|
17
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
19
18
|
}
|
|
20
19
|
function hashString(value) {
|
|
21
20
|
let hash = 0;
|
|
22
|
-
for (let i = 0; i < value.length; i++) hash = hash * 31 + value.charCodeAt(i)
|
|
21
|
+
for (let i = 0; i < value.length; i++) hash = Math.trunc(hash * 31 + value.charCodeAt(i));
|
|
23
22
|
return Math.abs(hash).toString(36);
|
|
24
23
|
}
|
|
25
24
|
function createClassKey(filePath, className) {
|
|
@@ -40,6 +39,5 @@ function walkSync(dir, fileList = []) {
|
|
|
40
39
|
});
|
|
41
40
|
return fileList;
|
|
42
41
|
}
|
|
43
|
-
|
|
44
42
|
//#endregion
|
|
45
|
-
export { createAliasName, createClassKey, createSymbolKey, hashString, normalizeImportPath, walkSync };
|
|
43
|
+
export { createAliasName, createClassKey, createSymbolKey, hashString, normalizeImportPath, walkSync };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { normalizeImportPath } from "../core/utils.js";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
4
3
|
//#region src/plugins/rollup-plugin/build-utils.ts
|
|
5
4
|
/**
|
|
6
5
|
* Determines build mode from Rollup/Rolldown output configuration + count of discovered services.
|
|
@@ -44,6 +43,5 @@ function resolveImportPathForBuild(targetPath, packageName, buildMode) {
|
|
|
44
43
|
function hasPreserveModules(o) {
|
|
45
44
|
return typeof o === "object" && o !== null && "preserveModules" in o;
|
|
46
45
|
}
|
|
47
|
-
|
|
48
46
|
//#endregion
|
|
49
|
-
export { determineBuildMode, hasPreserveModules, resolveImportPathForBuild };
|
|
47
|
+
export { determineBuildMode, hasPreserveModules, resolveImportPathForBuild };
|
|
@@ -1,11 +1,40 @@
|
|
|
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";
|
|
5
6
|
import fs from "node:fs";
|
|
6
7
|
import ts from "typescript";
|
|
7
|
-
|
|
8
8
|
//#region src/plugins/rollup-plugin/index.ts
|
|
9
|
+
/** Check if a node has the 'export' modifier */
|
|
10
|
+
function hasExportModifier(node) {
|
|
11
|
+
return !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
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
|
+
}
|
|
9
38
|
function alloy(options = {}) {
|
|
10
39
|
const fileName = options.fileName ?? "alloy.manifest.mjs";
|
|
11
40
|
const packageJsonFile = options.packageJsonPath ? path.isAbsolute(options.packageJsonPath) ? options.packageJsonPath : path.resolve(process.cwd(), options.packageJsonPath) : path.resolve(process.cwd(), "package.json");
|
|
@@ -52,9 +81,6 @@ function alloy(options = {}) {
|
|
|
52
81
|
if (!barrelEntry || !sourceText) return exportedNames;
|
|
53
82
|
const sf = ts.createSourceFile(barrelEntry, sourceText, ts.ScriptTarget.ESNext, true);
|
|
54
83
|
const names = /* @__PURE__ */ new Set();
|
|
55
|
-
const hasExportModifier = (node) => {
|
|
56
|
-
return !!(ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
57
|
-
};
|
|
58
84
|
const visit = (node) => {
|
|
59
85
|
if (ts.isClassDeclaration(node) && node.name && hasExportModifier(node)) names.add(node.name.text);
|
|
60
86
|
if (ts.isVariableStatement(node) && hasExportModifier(node)) {
|
|
@@ -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
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
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
|
-
});
|
|
183
|
+
const manifestDep = createManifestDependency(dep, meta, knownServiceNames, buildMode);
|
|
184
|
+
if (manifestDep) svc.deps.push(manifestDep);
|
|
148
185
|
}
|
|
149
186
|
}
|
|
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({
|
|
@@ -220,6 +249,5 @@ function checkPackageExports(packageJsonPath, manifestFileName) {
|
|
|
220
249
|
if (!hasIdentifiers) console.warn(`[alloy] Warning: service-identifiers.mjs is not exposed in package.json "exports". Consumers may not be able to access the generated identifiers helper.`);
|
|
221
250
|
} catch {}
|
|
222
251
|
}
|
|
223
|
-
|
|
224
252
|
//#endregion
|
|
225
|
-
export { alloy };
|
|
253
|
+
export { alloy as default };
|