entkapp 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.scaffold-ignore +22 -0
- package/LICENSE +211 -0
- package/NOTICE +13 -0
- package/README.md +33 -0
- package/bin/cli.js +174 -0
- package/entkapp/config.json +42 -0
- package/entkapp/plugins/README.md +19 -0
- package/index.js +2254 -0
- package/logo.png +0 -0
- package/package.json +96 -0
- package/src/EngineContext.js +185 -0
- package/src/api/HeadlessAPI.js +376 -0
- package/src/api/PluginSDK.js +299 -0
- package/src/ast/ASTAnalyzer.js +492 -0
- package/src/ast/BarrelParser.js +221 -0
- package/src/ast/DeadCodeDetector.js +73 -0
- package/src/ast/MagicDetector.js +203 -0
- package/src/ast/OxcAnalyzer.js +337 -0
- package/src/ast/SecretScanner.js +304 -0
- package/src/healing/GitSandbox.js +82 -0
- package/src/healing/SelfHealer.js +52 -0
- package/src/index.js +723 -0
- package/src/performance/GraphCache.js +87 -0
- package/src/performance/SupplyChainGuard.js +92 -0
- package/src/performance/WorkerPool.js +109 -0
- package/src/plugins/BasePlugin.js +71 -0
- package/src/plugins/KnipAdapter.js +106 -0
- package/src/plugins/PluginRegistry.js +197 -0
- package/src/plugins/ecosystems/BackendServices.js +61 -0
- package/src/plugins/ecosystems/GenericPlugins.js +64 -0
- package/src/plugins/ecosystems/ModernFrameworks.js +159 -0
- package/src/plugins/ecosystems/MorePlugins.js +184 -0
- package/src/plugins/ecosystems/NextJsPlugin.js +33 -0
- package/src/plugins/ecosystems/PluginLoader.js +20 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.js +56 -0
- package/src/refractor/ImpactAnalyzer.js +92 -0
- package/src/refractor/SourceRewriter.js +86 -0
- package/src/refractor/TransactionManager.js +138 -0
- package/src/refractor/TypeIntegrity.js +75 -0
- package/src/resolution/CircularDetector.js +91 -0
- package/src/resolution/ConfigLoader.js +87 -0
- package/src/resolution/DepencyResolver.js +133 -0
- package/src/resolution/DependencyProfiler.js +268 -0
- package/src/resolution/PathMapper.js +125 -0
- package/src/resolution/WorkSpaceGraph.js +474 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enterprise Wildcard Optimization & Flattening Layer for Barrel Operations
|
|
6
|
+
* Traces unreferenced paths through redistribution entries.
|
|
7
|
+
*/
|
|
8
|
+
export class BarrelParser {
|
|
9
|
+
constructor(context, resolver) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.resolver = resolver;
|
|
12
|
+
this.cachedSpecifications = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compiles the structural export layout for a specific index or barrel target.
|
|
17
|
+
* @param {string} filePath - Absolute path to on-disk target element
|
|
18
|
+
*/
|
|
19
|
+
async parseBarrelSpecification(filePath) {
|
|
20
|
+
if (this.cachedSpecifications.has(filePath)) {
|
|
21
|
+
return this.cachedSpecifications.get(filePath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const specification = {
|
|
25
|
+
isBarrelInstance: false,
|
|
26
|
+
wildcardExports: new Set(), // export * from './module';
|
|
27
|
+
namespacedWildcardExports: new Map(), // export * as Utils from './module';
|
|
28
|
+
forwardedNamedExports: new Map(), // export { token as alias } from './module';
|
|
29
|
+
declaredLocalExports: new Set() // export const a = 1;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const code = await fs.readFile(filePath, 'utf8');
|
|
34
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
35
|
+
|
|
36
|
+
this.harvestExportSignatures(sourceFile, specification);
|
|
37
|
+
|
|
38
|
+
if (specification.wildcardExports.size > 0 ||
|
|
39
|
+
specification.namespacedWildcardExports.size > 0 ||
|
|
40
|
+
specification.forwardedNamedExports.size > 0) {
|
|
41
|
+
specification.isBarrelInstance = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.cachedSpecifications.set(filePath, specification);
|
|
45
|
+
return specification;
|
|
46
|
+
} catch {
|
|
47
|
+
return specification; // Error state defaults to safe boundaries layout manifest
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
harvestExportSignatures(node, spec) {
|
|
52
|
+
if (!node) return;
|
|
53
|
+
|
|
54
|
+
switch (node.kind) {
|
|
55
|
+
case ts.SyntaxKind.ExportDeclaration: {
|
|
56
|
+
const targetSpecifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
|
|
57
|
+
? node.moduleSpecifier.text
|
|
58
|
+
: null;
|
|
59
|
+
|
|
60
|
+
if (targetSpecifier) {
|
|
61
|
+
if (!node.exportClause) {
|
|
62
|
+
// Standard Wildcard Transfer: export * from './module';
|
|
63
|
+
spec.wildcardExports.add(targetSpecifier);
|
|
64
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
65
|
+
// Namespaced Wildcard Transfer: export * as Core from './module';
|
|
66
|
+
const namespaceAlias = node.exportClause.name.text;
|
|
67
|
+
spec.namespacedWildcardExports.set(namespaceAlias, targetSpecifier);
|
|
68
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
69
|
+
// Selective Re-export Forwarding: export { x, y as z } from './module';
|
|
70
|
+
node.exportClause.elements.forEach(element => {
|
|
71
|
+
const originalSymbol = element.propertyName ? element.propertyName.text : element.name.text;
|
|
72
|
+
const exposedAlias = element.name.text;
|
|
73
|
+
spec.forwardedNamedExports.set(exposedAlias, {
|
|
74
|
+
targetModule: targetSpecifier,
|
|
75
|
+
sourceSymbol: originalSymbol
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Track items declared directly within the file boundary
|
|
84
|
+
case ts.SyntaxKind.FunctionDeclaration:
|
|
85
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
86
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
87
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
88
|
+
case ts.SyntaxKind.EnumDeclaration: {
|
|
89
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
90
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
91
|
+
spec.declaredLocalExports.add(node.name.text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case ts.SyntaxKind.VariableStatement: {
|
|
98
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
99
|
+
node.declarationList.declarations.forEach(decl => {
|
|
100
|
+
if (ts.isIdentifier(decl.name)) {
|
|
101
|
+
spec.declaredLocalExports.add(decl.name.text);
|
|
102
|
+
} else if (ts.isObjectBindingPattern(decl.name)) {
|
|
103
|
+
decl.name.elements.forEach(element => {
|
|
104
|
+
if (element.name && ts.isIdentifier(element.name)) {
|
|
105
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
} else if (ts.isArrayBindingPattern(decl.name)) {
|
|
109
|
+
decl.name.elements.forEach(element => {
|
|
110
|
+
if (ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
|
|
111
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Track default exports declared directly within the file boundary:
|
|
121
|
+
// Handles both `export default function foo()` and `export default expression`.
|
|
122
|
+
// The canonical symbol name stored is 'default' so that forwardedNamedExports
|
|
123
|
+
// entries whose sourceSymbol is 'default' can resolve correctly via Rule A.
|
|
124
|
+
case ts.SyntaxKind.ExportAssignment: {
|
|
125
|
+
// ExportAssignment covers `export default <expr>` (isExportEquals === false)
|
|
126
|
+
// as well as `export = <expr>` (isExportEquals === true, CommonJS style).
|
|
127
|
+
// We register 'default' for both to ensure the barrel tracer can settle here.
|
|
128
|
+
spec.declaredLocalExports.add('default');
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
ts.forEachChild(node, child => this.harvestExportSignatures(child, spec));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Challenge #3 Resolution Logic. Unwraps nested redistribution links to find origin source nodes.
|
|
138
|
+
* @param {string} contextFilePath - Current position filename vector pointer
|
|
139
|
+
* @param {string} targetSymbolName - Targeted semantic variable signature name
|
|
140
|
+
* @param {Map} activeProjectGraph - Global active module maps directory registry
|
|
141
|
+
* @param {Set} [protectionStack] - Avoids cyclic validation traps inside self-referencing links
|
|
142
|
+
*/
|
|
143
|
+
async determineSymbolDeclarationOrigin(contextFilePath, targetSymbolName, activeProjectGraph, protectionStack = new Set()) {
|
|
144
|
+
if (protectionStack.has(contextFilePath)) {
|
|
145
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
146
|
+
}
|
|
147
|
+
protectionStack.add(contextFilePath);
|
|
148
|
+
|
|
149
|
+
const spec = await this.parseBarrelSpecification(contextFilePath);
|
|
150
|
+
if (!spec.isBarrelInstance) {
|
|
151
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Rule A: Settle boundary immediately if local declaration matches token signature name
|
|
155
|
+
if (spec.declaredLocalExports.has(targetSymbolName)) {
|
|
156
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Rule B: Evaluate explicit named re-export mappings
|
|
160
|
+
if (spec.forwardedNamedExports.has(targetSymbolName)) {
|
|
161
|
+
const routingRule = spec.forwardedNamedExports.get(targetSymbolName);
|
|
162
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, routingRule.targetModule);
|
|
163
|
+
if (fullyResolvedPath) {
|
|
164
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, routingRule.sourceSymbol, activeProjectGraph, protectionStack);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Rule C: Evaluate structural namespace alias groupings (export * as name from 'module')
|
|
169
|
+
for (const [namespaceAlias, relativeModule] of spec.namespacedWildcardExports.entries()) {
|
|
170
|
+
// If the targetSymbolName is prefixed with the namespaceAlias, then it's a member of this namespace re-export
|
|
171
|
+
if (targetSymbolName.startsWith(`${namespaceAlias}.`)) {
|
|
172
|
+
const originalSymbol = targetSymbolName.substring(namespaceAlias.length + 1);
|
|
173
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
174
|
+
if (fullyResolvedPath) {
|
|
175
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, originalSymbol, activeProjectGraph, protectionStack);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
|
|
181
|
+
//
|
|
182
|
+
// Algorithm:
|
|
183
|
+
// 1. For each `export * from './child'`, recursively resolve the symbol in the child.
|
|
184
|
+
// 2. A non-barrel child immediately returns `{ originFile: childPath }` regardless of
|
|
185
|
+
// whether it actually declares the symbol. We therefore must verify that the
|
|
186
|
+
// returned origin file actually contains the symbol in its declaredLocalExports
|
|
187
|
+
// before accepting the result.
|
|
188
|
+
// 3. If the child is itself a barrel, the recursive call already performs the full
|
|
189
|
+
// chain walk, so we only need to verify the final origin.
|
|
190
|
+
for (const relativePath of spec.wildcardExports) {
|
|
191
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
192
|
+
|
|
193
|
+
if (fullyResolvedPath) {
|
|
194
|
+
const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
|
|
195
|
+
fullyResolvedPath,
|
|
196
|
+
targetSymbolName,
|
|
197
|
+
activeProjectGraph,
|
|
198
|
+
new Set(protectionStack) // Use a copy so sibling branches don't block each other
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!continuousResolutionTrace) continue;
|
|
202
|
+
if (continuousResolutionTrace.originFile === contextFilePath) continue;
|
|
203
|
+
|
|
204
|
+
// Verify that the resolved origin actually declares the symbol.
|
|
205
|
+
// This prevents a non-barrel sibling (e.g. constants.ts) from being
|
|
206
|
+
// incorrectly returned for a symbol it does not export (e.g. formatData).
|
|
207
|
+
const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
|
|
208
|
+
if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol)) {
|
|
209
|
+
return continuousResolutionTrace;
|
|
210
|
+
}
|
|
211
|
+
// The origin spec is itself a barrel (isBarrelInstance = true) and the
|
|
212
|
+
// recursive call already resolved through it – accept the result.
|
|
213
|
+
if (originSpec.isBarrelInstance) {
|
|
214
|
+
return continuousResolutionTrace;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export class DeadCodeDetector {
|
|
2
|
+
constructor(context) {
|
|
3
|
+
this.context = context;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
detectDeadCode(graph) {
|
|
7
|
+
const deadFiles = [];
|
|
8
|
+
const deadExports = [];
|
|
9
|
+
|
|
10
|
+
// Find all entry points
|
|
11
|
+
const entryPoints = new Set();
|
|
12
|
+
for (const [filePath, node] of graph.entries()) {
|
|
13
|
+
if (node.isEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
|
|
14
|
+
entryPoints.add(filePath);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Traverse from entry points to find used files
|
|
19
|
+
const usedFiles = new Set();
|
|
20
|
+
const queue = Array.from(entryPoints);
|
|
21
|
+
|
|
22
|
+
while (queue.length > 0) {
|
|
23
|
+
const current = queue.shift();
|
|
24
|
+
if (!usedFiles.has(current)) {
|
|
25
|
+
usedFiles.add(current);
|
|
26
|
+
const node = graph.get(current);
|
|
27
|
+
if (node && node.outgoingEdges) {
|
|
28
|
+
for (const edge of node.outgoingEdges) {
|
|
29
|
+
queue.push(edge);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Identify dead files
|
|
36
|
+
for (const filePath of graph.keys()) {
|
|
37
|
+
if (!usedFiles.has(filePath)) {
|
|
38
|
+
deadFiles.push(filePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Identify dead exports in used files
|
|
43
|
+
for (const filePath of usedFiles) {
|
|
44
|
+
const node = graph.get(filePath);
|
|
45
|
+
if (!node) continue;
|
|
46
|
+
|
|
47
|
+
// If it's an entry point, we consider its exports used (unless strictly configured otherwise)
|
|
48
|
+
if (node.isEntry) continue;
|
|
49
|
+
|
|
50
|
+
for (const [exportName, exportInfo] of node.internalExports.entries()) {
|
|
51
|
+
if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
|
|
52
|
+
|
|
53
|
+
let isUsed = false;
|
|
54
|
+
// Check incoming edges if they import this specific symbol
|
|
55
|
+
for (const [otherPath, otherNode] of graph.entries()) {
|
|
56
|
+
if (otherNode.outgoingEdges.has(filePath)) {
|
|
57
|
+
if (otherNode.importedSymbols.has(`${filePath}:${exportName}`) ||
|
|
58
|
+
otherNode.importedSymbols.has(`${filePath}:*`)) {
|
|
59
|
+
isUsed = true;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!isUsed) {
|
|
66
|
+
deadExports.push({ file: filePath, symbol: exportName, line: exportInfo.start });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { deadFiles, deadExports };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { PluginRegistry } from '../plugins/PluginRegistry.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Ecosystem Entry Point Manifest & Dynamic Framework Router Heuristic Validator
|
|
7
|
+
* Intercepts implicit conventions to handle cases where direct import statements are absent.
|
|
8
|
+
* Now refactored to use a pluggable architecture.
|
|
9
|
+
*
|
|
10
|
+
* Improvements over v1:
|
|
11
|
+
* - Extended config-file detection list (Biome, Oxlint, tsup, unbuild, etc.)
|
|
12
|
+
* - Next.js App Router conventions (page.tsx, layout.tsx, loading.tsx, error.tsx, etc.)
|
|
13
|
+
* - Remix conventions (route files under app/routes/)
|
|
14
|
+
* - SvelteKit conventions (+page.svelte, +layout.svelte, etc.)
|
|
15
|
+
* - Astro page/layout conventions
|
|
16
|
+
* - Common entry-point patterns (bin/, cli.ts, server.ts, main.ts, app.ts)
|
|
17
|
+
* - Test file patterns extended to cover Vitest workspace files
|
|
18
|
+
*/
|
|
19
|
+
export class MagicDetector {
|
|
20
|
+
constructor(context) {
|
|
21
|
+
this.context = context;
|
|
22
|
+
this.registry = new PluginRegistry(context);
|
|
23
|
+
this.isInitialized = false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async ensureInitialized(baseDir) {
|
|
27
|
+
if (this.isInitialized) return;
|
|
28
|
+
await this.registry.init(baseDir || process.cwd());
|
|
29
|
+
this.isInitialized = true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Audits the project context to map active micro-framework ecosystems.
|
|
34
|
+
* @param {string} baseContextDirectory - Root file workspace context execution vector path
|
|
35
|
+
*/
|
|
36
|
+
async identifyActiveProjectEcosystems(baseContextDirectory) {
|
|
37
|
+
await this.ensureInitialized(baseContextDirectory);
|
|
38
|
+
const activePlugins = await this.registry.getActivePlugins(baseContextDirectory);
|
|
39
|
+
const activeFrameworkFlags = activePlugins.map(p => p.name);
|
|
40
|
+
|
|
41
|
+
// Universal infrastructure overrides (testing platforms and common bundlers)
|
|
42
|
+
activeFrameworkFlags.push('universal-tooling-vectors');
|
|
43
|
+
return activeFrameworkFlags;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Assesses if a file path acts as an implicit route entry point.
|
|
48
|
+
*/
|
|
49
|
+
async isImplicitlyRequiredByEcosystem(absolutePath, activeFrameworks, baseDir) {
|
|
50
|
+
await this.ensureInitialized();
|
|
51
|
+
const normalizedSystemPath = absolutePath.replace(/\\/g, '/');
|
|
52
|
+
|
|
53
|
+
const plugins = this.registry.getPlugins();
|
|
54
|
+
for (const plugin of plugins) {
|
|
55
|
+
if (activeFrameworks.includes(plugin.name)) {
|
|
56
|
+
const patterns = plugin.getRoutePatterns();
|
|
57
|
+
if (patterns.some(regex => regex.test(normalizedSystemPath))) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Apply baseline platform rules (Test suites, lint parameters, continuous integration files)
|
|
64
|
+
if (this.isCoreToolingSuiteElement(normalizedSystemPath)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
isCoreToolingSuiteElement(normalizedPath) {
|
|
72
|
+
// ── Test / spec files ──────────────────────────────────────────────────────
|
|
73
|
+
if (/\.(test|spec|e2e|cy)\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
74
|
+
if (/\.stories\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
75
|
+
|
|
76
|
+
// ── Build / bundler config files ───────────────────────────────────────────
|
|
77
|
+
const configFragments = [
|
|
78
|
+
// Test runners
|
|
79
|
+
'jest.config.', 'vitest.config.', 'vitest.workspace.',
|
|
80
|
+
'playwright.config.', 'cypress.config.',
|
|
81
|
+
// Bundlers
|
|
82
|
+
'webpack.config.', 'vite.config.', 'rollup.config.',
|
|
83
|
+
'esbuild.config.', 'parcel.config.',
|
|
84
|
+
'tsup.config.', 'unbuild.config.', 'pkgroll.config.',
|
|
85
|
+
// CSS / styling
|
|
86
|
+
'tailwind.config.', 'postcss.config.', '.postcssrc.',
|
|
87
|
+
// Linters / formatters
|
|
88
|
+
'.eslintrc.', 'eslint.config.', 'prettier.config.', '.prettierrc.',
|
|
89
|
+
'.stylelintrc.', 'stylelint.config.',
|
|
90
|
+
'biome.json', '.oxlintrc.',
|
|
91
|
+
// Babel / transpilation
|
|
92
|
+
'.babelrc.', 'babel.config.',
|
|
93
|
+
// Commit / git hooks
|
|
94
|
+
'.commitlintrc.', 'commitlint.config.',
|
|
95
|
+
'.lintstagedrc.', 'lint-staged.config.',
|
|
96
|
+
// Documentation
|
|
97
|
+
'typedoc.config.', 'typedoc.json',
|
|
98
|
+
// Monorepo tools
|
|
99
|
+
'turbo.json', 'nx.json', 'lerna.json',
|
|
100
|
+
// Misc tooling
|
|
101
|
+
'knip.config.', 'knip.json',
|
|
102
|
+
'syncpack.config.',
|
|
103
|
+
// Internal worker
|
|
104
|
+
'WorkerTaskRunner.js'
|
|
105
|
+
];
|
|
106
|
+
if (configFragments.some(f => normalizedPath.includes(f))) return true;
|
|
107
|
+
|
|
108
|
+
// ── Common application entry points ───────────────────────────────────────
|
|
109
|
+
const entryPatterns = [
|
|
110
|
+
// CLI binaries
|
|
111
|
+
'/bin/cli.js', '/bin/cli.ts', '/bin/cli.mjs',
|
|
112
|
+
'/bin/index.js', '/bin/index.ts',
|
|
113
|
+
// Server / app entry points (Reduced in v3.3.8 to avoid false positives in libraries)
|
|
114
|
+
'/src/main.ts', '/src/main.js',
|
|
115
|
+
'/src/app.ts', '/src/app.js',
|
|
116
|
+
'/src/api/HeadlessAPI.js', '/src/api/PluginSDK.js',
|
|
117
|
+
'/main.ts', '/main.js',
|
|
118
|
+
'/app.ts', '/app.js',
|
|
119
|
+
];
|
|
120
|
+
if (entryPatterns.some(p => normalizedPath.endsWith(p))) return true;
|
|
121
|
+
|
|
122
|
+
// ── Next.js App Router conventions ────────────────────────────────────────
|
|
123
|
+
// Files under app/ directory with Next.js special names
|
|
124
|
+
if (/\/app\/(page|layout|loading|error|not-found|template|default|route|middleware)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
125
|
+
// Next.js Pages Router
|
|
126
|
+
if (/\/pages\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
127
|
+
// Next.js API routes
|
|
128
|
+
if (/\/pages\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
129
|
+
// Next.js middleware
|
|
130
|
+
if (/\/middleware\.(js|ts)$/.test(normalizedPath)) return true;
|
|
131
|
+
// Next.js config
|
|
132
|
+
if (/\/next\.config\.(js|ts|mjs|cjs)$/.test(normalizedPath)) return true;
|
|
133
|
+
|
|
134
|
+
// ── Remix conventions ─────────────────────────────────────────────────────
|
|
135
|
+
if (/\/app\/routes\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
136
|
+
if (/\/app\/root\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
137
|
+
if (/\/app\/entry\.(client|server)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
138
|
+
|
|
139
|
+
// ── SvelteKit conventions ─────────────────────────────────────────────────
|
|
140
|
+
if (/\/\+page(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
141
|
+
if (/\/\+layout(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
142
|
+
if (/\/\+error\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
143
|
+
if (/\/\+server\.(js|ts)$/.test(normalizedPath)) return true;
|
|
144
|
+
if (/\/svelte\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
145
|
+
|
|
146
|
+
// ── Astro conventions ─────────────────────────────────────────────────────
|
|
147
|
+
if (/\/src\/pages\/.*\.astro$/.test(normalizedPath)) return true;
|
|
148
|
+
if (/\/src\/layouts\/.*\.astro$/.test(normalizedPath)) return true;
|
|
149
|
+
if (/\/astro\.config\.(mjs|js|ts)$/.test(normalizedPath)) return true;
|
|
150
|
+
|
|
151
|
+
// ── Nuxt conventions ──────────────────────────────────────────────────────
|
|
152
|
+
if (/\/pages\/.*\.vue$/.test(normalizedPath)) return true;
|
|
153
|
+
if (/\/layouts\/.*\.vue$/.test(normalizedPath)) return true;
|
|
154
|
+
if (/\/server\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
155
|
+
if (/\/nuxt\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
156
|
+
|
|
157
|
+
// ── React / Vite entry points ─────────────────────────────────────────────
|
|
158
|
+
if (/\/vite\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
159
|
+
|
|
160
|
+
// ── Angular conventions ───────────────────────────────────────────────────
|
|
161
|
+
if (/\/main\.(ts|js)$/.test(normalizedPath)) return true;
|
|
162
|
+
if (/\/app\.module\.(ts|js)$/.test(normalizedPath)) return true;
|
|
163
|
+
if (/\/angular\.json$/.test(normalizedPath)) return true;
|
|
164
|
+
|
|
165
|
+
// ── Expo / React Native ───────────────────────────────────────────────────
|
|
166
|
+
if (/\/app\/_layout\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
167
|
+
if (/\/app\/index\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
168
|
+
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Challenge #4 Framework Overrides. Protects interface boundaries from false positive report flags.
|
|
174
|
+
*/
|
|
175
|
+
async injectVirtualConsumerEdges(filePath, fileNode, activeFrameworks) {
|
|
176
|
+
await this.ensureInitialized();
|
|
177
|
+
if (!await this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
|
|
178
|
+
|
|
179
|
+
// Retain entry point elements within memory to keep verification safe
|
|
180
|
+
fileNode.isLibraryEntry = true;
|
|
181
|
+
|
|
182
|
+
// Apply dynamic exports coverage metrics based on active platform contracts
|
|
183
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
184
|
+
const plugins = this.registry.getPlugins();
|
|
185
|
+
|
|
186
|
+
for (const plugin of plugins) {
|
|
187
|
+
if (activeFrameworks.includes(plugin.name)) {
|
|
188
|
+
const patterns = plugin.getRoutePatterns();
|
|
189
|
+
const appliesToFramework = patterns.some(regex => regex.test(normalizedPath));
|
|
190
|
+
|
|
191
|
+
if (appliesToFramework) {
|
|
192
|
+
const contracts = plugin.getRequiredSystemContracts();
|
|
193
|
+
contracts.forEach(contractMethodToken => {
|
|
194
|
+
if (fileNode.internalExports.has(contractMethodToken)) {
|
|
195
|
+
// Emulate active local reference linkages to protect the export
|
|
196
|
+
fileNode.instantiatedIdentifiers.add(contractMethodToken);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|