entkapp 5.5.0 → 5.6.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/README.md +1 -1
- package/bin/cli.js +2 -2
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +17 -3
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +37 -1
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Cross-Reference Dependency Matrix & Breakage Risk Auditor
|
|
6
|
+
* Traces dynamic runtime usage patterns to prevent code pruning from breaking downstream systems.
|
|
7
|
+
*/
|
|
8
|
+
export class ImpactAnalyzer {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.safetyOverlays = [/\.json$/, /\.json5$/, /\.html$/, /\.yaml$/, /\.yml$/];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Scans non-code assets and dynamic lookups to check if an unused export is needed elsewhere.
|
|
16
|
+
* @param {string} originFile - Absolute path of component housing target symbol
|
|
17
|
+
* @param {string} symbolName - Literal identifier being evaluated for deletion
|
|
18
|
+
* @param {Map} projectGraph - Full project structural graph representation
|
|
19
|
+
*/
|
|
20
|
+
async verifyRefactorSafety(originFile, symbolName, projectGraph) {
|
|
21
|
+
// Avoid dropping generic single letter tokens or framework primitives
|
|
22
|
+
if (symbolName === 'default' || symbolName.length <= 2) {
|
|
23
|
+
return { isSafeToPrune: false, blockReason: 'PROTECTED_SYSTEM_CONTRACT_KEYWORD' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Rule 1: Check across all code files for loose string-based token references
|
|
27
|
+
for (const [filePath, fileNode] of projectGraph.entries()) {
|
|
28
|
+
if (filePath === originFile) continue;
|
|
29
|
+
|
|
30
|
+
// If the symbol name is explicitly referenced in an element lookup or template slice, flag it as risky
|
|
31
|
+
if (fileNode.rawStringReferences && fileNode.rawStringReferences.has(symbolName)) {
|
|
32
|
+
return {
|
|
33
|
+
isSafeToPrune: false,
|
|
34
|
+
blockReason: `LOOSE_STRING_ACCESS_MATCH_FOUND_IN: ${path.relative(this.context.cwd, filePath)}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check member property chain lookups: customer.profile.billingAddress
|
|
39
|
+
if (fileNode.propertyAccessChains) {
|
|
40
|
+
for (const chain of fileNode.propertyAccessChains) {
|
|
41
|
+
if (chain.endsWith(`.${symbolName}`) || chain.includes(`.${symbolName}.`)) {
|
|
42
|
+
return {
|
|
43
|
+
isSafeToPrune: false,
|
|
44
|
+
blockReason: `DYNAMIC_PROPERTY_ACCESS_CHAIN_HIT: ${chain}`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Rule 2: Crawl through external static manifests (JSON metadata, HTML routing templates, workflow files)
|
|
52
|
+
const configurations = await this.gatherMetadataFiles(this.context.cwd);
|
|
53
|
+
|
|
54
|
+
for (const confPath of configurations) {
|
|
55
|
+
try {
|
|
56
|
+
const payload = await fs.readFile(confPath, 'utf8');
|
|
57
|
+
|
|
58
|
+
// Match string references inside configuration boundaries
|
|
59
|
+
if (payload.includes(`"${symbolName}"`) || payload.includes(`'${symbolName}'`) || payload.includes(`data-${symbolName}`)) {
|
|
60
|
+
return {
|
|
61
|
+
isSafeToPrune: false,
|
|
62
|
+
blockReason: `METADATA_MANIFEST_DEPENDENCY_FOUND_IN: ${path.relative(this.context.cwd, confPath)}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Read step error; skip unreadable descriptors
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { isSafeToPrune: true, blockReason: null };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async gatherMetadataFiles(dir, collected = []) {
|
|
74
|
+
try {
|
|
75
|
+
const entities = await fs.readdir(dir, { withFileTypes: true });
|
|
76
|
+
for (const ent of entities) {
|
|
77
|
+
const resolutionPath = path.join(dir, ent.name);
|
|
78
|
+
|
|
79
|
+
if (ent.isDirectory()) {
|
|
80
|
+
if (ent.name === 'node_modules' || ent.name === '.git' || ent.name === '.entkapp-cache' || ent.name === 'dist') continue;
|
|
81
|
+
await this.gatherMetadataFiles(resolutionPath, collected);
|
|
82
|
+
} else if (ent.isFile()) {
|
|
83
|
+
const actsAsMetaAsset = this.safetyOverlays.some(regex => regex.test(ent.name));
|
|
84
|
+
if (actsAsMetaAsset) {
|
|
85
|
+
collected.push(resolutionPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
return collected;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Format-Preserving Abstract Syntax Tree Source Mutation Engine
|
|
6
|
+
* Executes targeted updates using token position logic while preserving trivia (comments/JSDoc).
|
|
7
|
+
*/
|
|
8
|
+
export class SourceRewriter {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Removes a specific named export symbol declaration block from code text.
|
|
15
|
+
* @param {string} filePath - Absolute path to on-disk code component
|
|
16
|
+
* @param {string} symbolName - Target export key to prune
|
|
17
|
+
* @param {Object} exportMeta - Mapped token offset indices (start/end offsets)
|
|
18
|
+
*/
|
|
19
|
+
async stripNamedExportSignature(filePath, symbolName, exportMeta) {
|
|
20
|
+
const rawSource = await fs.readFile(filePath, 'utf8');
|
|
21
|
+
|
|
22
|
+
// Case A: Token is grouped inside a multi-export destructured statement block: export { a, b, c };
|
|
23
|
+
if (exportMeta.type === 'named') {
|
|
24
|
+
const updatedText = this.pruneFromSharedExportClause(rawSource, symbolName, exportMeta);
|
|
25
|
+
if (updatedText) return updatedText;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Case B: Isolated node removal. Calculate indices from start to end while preserving comments.
|
|
29
|
+
const startOffset = exportMeta.start;
|
|
30
|
+
const endOffset = exportMeta.end;
|
|
31
|
+
|
|
32
|
+
// Split source without dropping trailing line structural punctuation or text formatting configurations
|
|
33
|
+
const prefix = rawSource.slice(0, startOffset);
|
|
34
|
+
let suffix = rawSource.slice(endOffset);
|
|
35
|
+
|
|
36
|
+
// Clean up trailing commas or semicolons to prevent syntax errors
|
|
37
|
+
if (suffix.startsWith(';') || suffix.startsWith(',')) {
|
|
38
|
+
suffix = suffix.slice(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return prefix + suffix;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Handles selective pruning of export elements within inline groupings like `export { x, y as z }`.
|
|
46
|
+
*/
|
|
47
|
+
pruneFromSharedExportClause(sourceText, symbolName, meta) {
|
|
48
|
+
// Find the enclosing export declaration node using structural boundaries
|
|
49
|
+
const sourceFile = ts.createSourceFile(
|
|
50
|
+
'temp.ts',
|
|
51
|
+
sourceText,
|
|
52
|
+
ts.ScriptTarget.Latest,
|
|
53
|
+
true
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
let targetText = null;
|
|
57
|
+
|
|
58
|
+
const findAndPrune = (node) => {
|
|
59
|
+
if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
60
|
+
const start = node.getStart(sourceFile);
|
|
61
|
+
const end = node.getEnd();
|
|
62
|
+
|
|
63
|
+
// Check if the target node spans the exact offset coordinates of the dead symbol
|
|
64
|
+
if (meta.start >= start && meta.end <= end) {
|
|
65
|
+
const activeElements = node.exportClause.elements;
|
|
66
|
+
const keptSymbols = activeElements
|
|
67
|
+
.filter(el => el.name.text !== symbolName)
|
|
68
|
+
.map(el => el.getText(sourceFile));
|
|
69
|
+
|
|
70
|
+
if (keptSymbols.length === 0) {
|
|
71
|
+
// Drop the entire declaration block if no active exports remain inside it
|
|
72
|
+
targetText = sourceText.slice(0, start) + sourceText.slice(end);
|
|
73
|
+
} else {
|
|
74
|
+
// Rewrite the export group inline, preserving formatting and comments
|
|
75
|
+
const reconstructedClause = `export { ${keptSymbols.join(', ')} };`;
|
|
76
|
+
targetText = sourceText.slice(0, start) + reconstructedClause + sourceText.slice(end);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
ts.forEachChild(node, findAndPrune);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
findAndPrune(sourceFile);
|
|
84
|
+
return targetText;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ============================================================================
|
|
6
|
+
* Auto-Build Orchestrator v5.2.0
|
|
7
|
+
* ============================================================================
|
|
8
|
+
* Orchestrates the build process based on active frameworks and tools.
|
|
9
|
+
*/
|
|
10
|
+
export class BuildOrchestrator {
|
|
11
|
+
constructor(context) {
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.cwd = context.cwd;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async getBuildCommand(activePlugins) {
|
|
17
|
+
const pluginNames = activePlugins.map(p => p.name);
|
|
18
|
+
|
|
19
|
+
// Priority-based build command detection
|
|
20
|
+
if (pluginNames.includes('nextjs')) return 'next build';
|
|
21
|
+
if (pluginNames.includes('nuxt')) return 'nuxt build';
|
|
22
|
+
if (pluginNames.includes('remix')) return 'remix build';
|
|
23
|
+
if (pluginNames.includes('astro')) return 'astro build';
|
|
24
|
+
if (pluginNames.includes('vite')) return 'vite build';
|
|
25
|
+
if (pluginNames.includes('webpack')) return 'webpack --mode production';
|
|
26
|
+
if (pluginNames.includes('rollup')) return 'rollup -c';
|
|
27
|
+
if (pluginNames.includes('esbuild')) return 'esbuild src/index.ts --bundle --outdir=dist';
|
|
28
|
+
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async runBuild(activePlugins) {
|
|
33
|
+
const command = await this.getBuildCommand(activePlugins);
|
|
34
|
+
if (!command) {
|
|
35
|
+
return { success: false, message: 'No suitable build command found for this project setup.' };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`[BuildOrchestrator] Detected Build Command: ${command}`);
|
|
39
|
+
// In a real scenario, we would execute this via execa.
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
command: command,
|
|
43
|
+
message: `Project would be built using: ${command}`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export class CircularDetector {
|
|
4
|
+
constructor(context) {
|
|
5
|
+
this.context = context;
|
|
6
|
+
this.cycles = [];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
detectCycles(projectGraph) {
|
|
10
|
+
const stack = [];
|
|
11
|
+
const onStack = new Set();
|
|
12
|
+
const indices = new Map();
|
|
13
|
+
const lowlink = new Map();
|
|
14
|
+
const sccs = [];
|
|
15
|
+
let index = 0;
|
|
16
|
+
|
|
17
|
+
const strongConnect = (v) => {
|
|
18
|
+
indices.set(v, index);
|
|
19
|
+
lowlink.set(v, index);
|
|
20
|
+
index++;
|
|
21
|
+
stack.push(v);
|
|
22
|
+
onStack.add(v);
|
|
23
|
+
|
|
24
|
+
const node = projectGraph.get(v);
|
|
25
|
+
if (node && node.outgoingEdges) {
|
|
26
|
+
// Ensure outgoingEdges is treated as an array-like structure
|
|
27
|
+
const edges = Array.isArray(node.outgoingEdges) ? node.outgoingEdges : Array.from(node.outgoingEdges);
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < edges.length; i++) {
|
|
30
|
+
const w = edges[i];
|
|
31
|
+
if (!indices.has(w)) {
|
|
32
|
+
strongConnect(w);
|
|
33
|
+
lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
|
|
34
|
+
} else if (onStack.has(w)) {
|
|
35
|
+
lowlink.set(v, Math.min(lowlink.get(v), indices.get(w)));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (lowlink.get(v) === indices.get(v)) {
|
|
41
|
+
const scc = [];
|
|
42
|
+
let w;
|
|
43
|
+
do {
|
|
44
|
+
w = stack.pop();
|
|
45
|
+
onStack.delete(w);
|
|
46
|
+
scc.push(w);
|
|
47
|
+
} while (w !== v);
|
|
48
|
+
|
|
49
|
+
if (scc.length > 1) {
|
|
50
|
+
const cycle = scc.slice().reverse();
|
|
51
|
+
cycle.push(cycle[0]);
|
|
52
|
+
sccs.push(cycle);
|
|
53
|
+
} else if (scc.length === 1) {
|
|
54
|
+
const singleNode = projectGraph.get(scc[0]);
|
|
55
|
+
if (singleNode && singleNode.outgoingEdges) {
|
|
56
|
+
const edges = Array.isArray(singleNode.outgoingEdges) ? singleNode.outgoingEdges : Array.from(singleNode.outgoingEdges);
|
|
57
|
+
let hasSelfLoop = false;
|
|
58
|
+
for (let i = 0; i < edges.length; i++) {
|
|
59
|
+
if (edges[i] === scc[0]) {
|
|
60
|
+
hasSelfLoop = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (hasSelfLoop) {
|
|
65
|
+
sccs.push([scc[0], scc[0]]);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const keys = Array.from(projectGraph.keys());
|
|
73
|
+
for (let i = 0; i < keys.length; i++) {
|
|
74
|
+
if (!indices.has(keys[i])) {
|
|
75
|
+
strongConnect(keys[i]);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.cycles = sccs;
|
|
80
|
+
return sccs;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
formatCycles() {
|
|
84
|
+
return this.cycles.map(cycle => {
|
|
85
|
+
return cycle.map(p => {
|
|
86
|
+
const relativePath = path.relative(this.context.cwd, p);
|
|
87
|
+
return relativePath.replace(/\\/g, '/');
|
|
88
|
+
}).join(' -> ');
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ============================================================================
|
|
6
|
+
* Auto-Config Engine v5.2.0
|
|
7
|
+
* ============================================================================
|
|
8
|
+
* Generates and optimizes configuration files based on project structure.
|
|
9
|
+
*/
|
|
10
|
+
export class ConfigGenerator {
|
|
11
|
+
constructor(context) {
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.cwd = context.cwd;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates missing configurations for active plugins.
|
|
18
|
+
*/
|
|
19
|
+
async generateMissingConfigs(activePlugins) {
|
|
20
|
+
const generated = [];
|
|
21
|
+
const files = await fs.readdir(this.cwd);
|
|
22
|
+
|
|
23
|
+
for (const plugin of activePlugins) {
|
|
24
|
+
const configFiles = plugin.getConfigFiles();
|
|
25
|
+
const exists = configFiles.some(f => files.includes(f));
|
|
26
|
+
|
|
27
|
+
if (!exists && this.templates[plugin.name]) {
|
|
28
|
+
const template = this.templates[plugin.name]();
|
|
29
|
+
const fileName = configFiles[0]; // Use the first recommended filename
|
|
30
|
+
const filePath = path.join(this.cwd, fileName);
|
|
31
|
+
|
|
32
|
+
// In a real scenario, we would write the file here.
|
|
33
|
+
// For the SDK, we return the plan.
|
|
34
|
+
generated.push({
|
|
35
|
+
plugin: plugin.name,
|
|
36
|
+
file: fileName,
|
|
37
|
+
content: template
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return generated;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get templates() {
|
|
45
|
+
return {
|
|
46
|
+
'tailwind': () => `/** @type {import('tailwindcss').Config} */
|
|
47
|
+
module.exports = {
|
|
48
|
+
content: [
|
|
49
|
+
"./index.html",
|
|
50
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
51
|
+
],
|
|
52
|
+
theme: {
|
|
53
|
+
extend: {},
|
|
54
|
+
},
|
|
55
|
+
plugins: [],
|
|
56
|
+
}`,
|
|
57
|
+
'prettier': () => `{
|
|
58
|
+
"semi": true,
|
|
59
|
+
"singleQuote": true,
|
|
60
|
+
"tabWidth": 2,
|
|
61
|
+
"trailingComma": "es5"
|
|
62
|
+
}`,
|
|
63
|
+
'eslint': () => `{
|
|
64
|
+
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
|
65
|
+
"parser": "@typescript-eslint/parser",
|
|
66
|
+
"plugins": ["@typescript-eslint"],
|
|
67
|
+
"root": true
|
|
68
|
+
}`,
|
|
69
|
+
'typescript': () => `{
|
|
70
|
+
"compilerOptions": {
|
|
71
|
+
"target": "ESNext",
|
|
72
|
+
"module": "ESNext",
|
|
73
|
+
"moduleResolution": "node",
|
|
74
|
+
"strict": true,
|
|
75
|
+
"esModuleInterop": true,
|
|
76
|
+
"skipLibCheck": true,
|
|
77
|
+
"forceConsistentCasingInFileNames": true
|
|
78
|
+
},
|
|
79
|
+
"include": ["src/**/*"]
|
|
80
|
+
}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ConfigLoader
|
|
6
|
+
* Loads and merges entkapp configuration from multiple sources:
|
|
7
|
+
* - entkapp/config.json (project config)
|
|
8
|
+
* - entkapp.config.js / entkapp.config.mjs (JS config)
|
|
9
|
+
* - package.json "entkapp" key
|
|
10
|
+
* - CLI flags (passed as overrides)
|
|
11
|
+
*/
|
|
12
|
+
export class ConfigLoader {
|
|
13
|
+
constructor(cwd) {
|
|
14
|
+
this.cwd = cwd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async loadConfig(overrides = {}) {
|
|
18
|
+
let config = this._defaultConfig();
|
|
19
|
+
|
|
20
|
+
// 1. Try entkapp/config.json
|
|
21
|
+
const jsonConfigPath = path.join(this.cwd, 'entkapp', 'config.json');
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fs.readFile(jsonConfigPath, 'utf8');
|
|
24
|
+
// Strip comments from JSON (JSONC support)
|
|
25
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
+
const parsed = JSON.parse(stripped);
|
|
27
|
+
config = this._merge(config, parsed);
|
|
28
|
+
} catch (e) {}
|
|
29
|
+
|
|
30
|
+
// 2. Try entkapp.config.js / entkapp.config.mjs
|
|
31
|
+
for (const configFile of ['entkapp.config.mjs', 'entkapp.config.mjs', 'entkapp.config.cjs']) {
|
|
32
|
+
let jsConfigPath = path.join(this.cwd, configFile);
|
|
33
|
+
try {
|
|
34
|
+
// FIX: Windows compatibility for dynamic imports
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
jsConfigPath = 'file://' + jsConfigPath.replace(/\\/g, '/');
|
|
37
|
+
}
|
|
38
|
+
const mod = await import(jsConfigPath);
|
|
39
|
+
const jsConfig = mod.default || mod;
|
|
40
|
+
if (typeof jsConfig === 'object') {
|
|
41
|
+
config = this._merge(config, jsConfig);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Try package.json "entkapp" key
|
|
48
|
+
const pkgPath = path.join(this.cwd, 'package.json');
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
51
|
+
if (pkg.entkapp && typeof pkg.entkapp === 'object') {
|
|
52
|
+
config = this._merge(config, pkg.entkapp);
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
|
|
56
|
+
// 4. Apply CLI overrides
|
|
57
|
+
config = this._merge(config, overrides);
|
|
58
|
+
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_defaultConfig() {
|
|
63
|
+
return {
|
|
64
|
+
interface: 'CLI',
|
|
65
|
+
useBuiltinPlugins: true,
|
|
66
|
+
useCustomPlugins: true,
|
|
67
|
+
options: {
|
|
68
|
+
verbose: false,
|
|
69
|
+
fastMode: true,
|
|
70
|
+
selfHealing: true
|
|
71
|
+
},
|
|
72
|
+
enabledPlugins: [],
|
|
73
|
+
ignoreDependencies: ['entkapp', '@types/*'],
|
|
74
|
+
exclude: ['node_modules', '.git', 'dist', 'build', 'coverage'],
|
|
75
|
+
entryPoints: [],
|
|
76
|
+
workspace: false
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_merge(base, override) {
|
|
81
|
+
if (!override || typeof override !== 'object') return base;
|
|
82
|
+
const result = { ...base };
|
|
83
|
+
for (const key of Object.keys(override)) {
|
|
84
|
+
if (key === 'options' && typeof override[key] === 'object') {
|
|
85
|
+
result.options = { ...(base.options || {}), ...override[key] };
|
|
86
|
+
} else if (Array.isArray(override[key])) {
|
|
87
|
+
result[key] = override[key];
|
|
88
|
+
} else {
|
|
89
|
+
result[key] = override[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
export class DependencyResolver {
|
|
5
|
+
constructor(context, pathMapper, workspaceGraph) {
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.pathMapper = pathMapper;
|
|
8
|
+
this.workspaceGraph = workspaceGraph;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
normalizePath(p) {
|
|
12
|
+
if (!p) return p;
|
|
13
|
+
const original = p;
|
|
14
|
+
// Handle Windows drive letters and backslashes
|
|
15
|
+
let normalized = p.replace(/\\/g, '/');
|
|
16
|
+
if (/^[a-z]:\//i.test(normalized)) {
|
|
17
|
+
normalized = normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
18
|
+
}
|
|
19
|
+
if (this.context.verbose && original !== normalized) {
|
|
20
|
+
// console.log(`[Path-DEBUG] Normalized: ${original} -> ${normalized}`);
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolveModulePath(sourceFile, specifier) {
|
|
26
|
+
const cleanSource = this.normalizePath(sourceFile);
|
|
27
|
+
|
|
28
|
+
// Check if it's a workspace package reference
|
|
29
|
+
if (this.context.isWorkspaceEnabled && this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
30
|
+
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
31
|
+
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
32
|
+
// Return the first entry point for the workspace package
|
|
33
|
+
return this.normalizePath(match.entryPoints[0]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// UPGRADE: Use PathMapper for sophisticated resolution (TS-to-JS, aliases, etc.)
|
|
38
|
+
if (this.pathMapper) {
|
|
39
|
+
// Allow pathMapper to resolve aliases directly from specifier
|
|
40
|
+
const aliasResolved = this.pathMapper.resolvePath(specifier);
|
|
41
|
+
if (aliasResolved && aliasResolved !== specifier && existsSync(aliasResolved)) {
|
|
42
|
+
return this.normalizePath(aliasResolved);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const dir = path.dirname(cleanSource);
|
|
46
|
+
const target = path.resolve(dir, specifier);
|
|
47
|
+
const resolved = this.pathMapper.resolvePath(target);
|
|
48
|
+
if (resolved && existsSync(resolved)) {
|
|
49
|
+
return this.normalizePath(resolved);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (specifier.startsWith('.')) {
|
|
54
|
+
const dir = path.dirname(cleanSource);
|
|
55
|
+
const target = path.resolve(dir, specifier);
|
|
56
|
+
const normalizedTarget = this.normalizePath(target);
|
|
57
|
+
|
|
58
|
+
const extensions = ['', '.mjs', '.ts', '.tsx', '.jsx', '/index.mjs', '/index.ts', '/index.tsx'];
|
|
59
|
+
for (const ext of extensions) {
|
|
60
|
+
const p = normalizedTarget + ext;
|
|
61
|
+
if (existsSync(p)) return this.normalizePath(p);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* Dependency Auto-Fixer v5.1.0
|
|
8
|
+
* ============================================================================
|
|
9
|
+
* Automatically installs missing dependencies using the detected package manager.
|
|
10
|
+
*/
|
|
11
|
+
export class DependencyFixer {
|
|
12
|
+
constructor(context) {
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.cwd = context.cwd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async detectPackageManager() {
|
|
18
|
+
const files = await fs.readdir(this.cwd);
|
|
19
|
+
if (files.includes('pnpm-lock.yaml')) return 'pnpm';
|
|
20
|
+
if (files.includes('yarn.lock')) return 'yarn';
|
|
21
|
+
if (files.includes('bun.lockb')) return 'bun';
|
|
22
|
+
return 'npm';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async fix(diagnostics) {
|
|
26
|
+
const pkgManager = await this.detectPackageManager();
|
|
27
|
+
const toInstall = {
|
|
28
|
+
dependencies: new Set(),
|
|
29
|
+
devDependencies: new Set()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (const d of diagnostics) {
|
|
33
|
+
if (d.package && d.severity === 'error') {
|
|
34
|
+
if (d.expectedIn === 'devDependencies') {
|
|
35
|
+
toInstall.devDependencies.add(d.package);
|
|
36
|
+
} else {
|
|
37
|
+
toInstall.dependencies.add(d.package);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const results = [];
|
|
43
|
+
|
|
44
|
+
if (toInstall.dependencies.size > 0) {
|
|
45
|
+
const deps = Array.from(toInstall.dependencies);
|
|
46
|
+
results.push(await this._runInstall(pkgManager, deps, false));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (toInstall.devDependencies.size > 0) {
|
|
50
|
+
const devDeps = Array.from(toInstall.devDependencies);
|
|
51
|
+
results.push(await this._runInstall(pkgManager, devDeps, true));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _runInstall(pm, packages, isDev) {
|
|
58
|
+
const args = [];
|
|
59
|
+
if (pm === 'npm') {
|
|
60
|
+
args.push('install', isDev ? '--save-dev' : '--save');
|
|
61
|
+
} else if (pm === 'yarn') {
|
|
62
|
+
args.push('add', isDev ? '--dev' : '');
|
|
63
|
+
} else if (pm === 'pnpm') {
|
|
64
|
+
args.push('add', isDev ? '--save-dev' : '');
|
|
65
|
+
} else if (pm === 'bun') {
|
|
66
|
+
args.push('add', isDev ? '--dev' : '');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const finalArgs = [...args.filter(Boolean), ...packages];
|
|
70
|
+
console.log(`[DependencyFixer] Running: ${pm} ${finalArgs.join(' ')}`);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// In a real scenario, we would use execa here.
|
|
74
|
+
// For the SDK, we return the command that should be run.
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
command: `${pm} ${finalArgs.join(' ')}`,
|
|
78
|
+
packages
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: e.message,
|
|
84
|
+
packages
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|