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,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Backend Services Plugins for entkapp v4.1.0
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Built-in support for GraphQL, REST APIs, and Databases.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* GraphQL Ecosystem Plugin
|
|
14
|
+
*/
|
|
15
|
+
export class GraphQLPlugin extends BasePlugin {
|
|
16
|
+
get name() {
|
|
17
|
+
return 'graphql';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getConfigFiles() {
|
|
21
|
+
return ['package.json', 'graphql.config.js', '.graphqlconfig'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async isActive(baseDir) {
|
|
25
|
+
try {
|
|
26
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
27
|
+
return !!(pkgJson.dependencies?.graphql || pkgJson.devDependencies?.graphql || pkgJson.dependencies?.['@apollo/client']);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async analyze(node, filePath) {
|
|
34
|
+
// Detect GraphQL tagged templates
|
|
35
|
+
const gqlPattern = /gql\s*`([\s\S]*?)`/g;
|
|
36
|
+
const matches = node.rawCode?.match(gqlPattern) || [];
|
|
37
|
+
if (matches.length > 0) {
|
|
38
|
+
node.graphqlQueries = matches;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Database Ecosystem Plugin (Prisma, Drizzle, TypeORM)
|
|
45
|
+
*/
|
|
46
|
+
export class DatabasePlugin extends BasePlugin {
|
|
47
|
+
get name() {
|
|
48
|
+
return 'database';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
getConfigFiles() {
|
|
52
|
+
return ['package.json', 'prisma/schema.prisma', 'drizzle.config.ts', 'ormconfig.json'];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async analyze(node, filePath) {
|
|
56
|
+
// Detect DB usage
|
|
57
|
+
if (node.explicitImports.has('@prisma/client') || node.explicitImports.has('drizzle-orm')) {
|
|
58
|
+
node.usesDatabase = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
4
|
+
|
|
5
|
+
export class NuxtPlugin extends BasePlugin {
|
|
6
|
+
get name() { return 'nuxt'; }
|
|
7
|
+
getConfigFiles() { return ['nuxt.config.js', 'nuxt.config.ts']; }
|
|
8
|
+
getRoutePatterns() {
|
|
9
|
+
return [/\/pages\//, /\/server\/(api|routes|middleware)\//, /\/components\/[a-zA-Z0-9_\-\/]+\.vue$/];
|
|
10
|
+
}
|
|
11
|
+
getRequiredSystemContracts() { return ['default']; }
|
|
12
|
+
async isActive(baseDir) {
|
|
13
|
+
for (const file of this.getConfigFiles()) {
|
|
14
|
+
try { await fs.access(path.join(baseDir, file)); return true; } catch {}
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class RemixPlugin extends BasePlugin {
|
|
21
|
+
get name() { return 'remix'; }
|
|
22
|
+
getConfigFiles() { return ['remix.config.js', 'vite.config.js', 'vite.config.ts']; }
|
|
23
|
+
getRoutePatterns() { return [/\/app\/routes\//, /\/app\/root\.(tsx|jsx)$/]; }
|
|
24
|
+
getRequiredSystemContracts() { return ['default', 'loader', 'action', 'meta', 'links']; }
|
|
25
|
+
async isActive(baseDir) {
|
|
26
|
+
for (const file of this.getConfigFiles()) {
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(path.join(baseDir, file), 'utf8');
|
|
29
|
+
if (content.includes('@remix-run/') || content.includes('remix')) return true;
|
|
30
|
+
} catch {}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SvelteKitPlugin extends BasePlugin {
|
|
37
|
+
get name() { return 'sveltekit'; }
|
|
38
|
+
getConfigFiles() { return ['svelte.config.js', 'vite.config.ts']; }
|
|
39
|
+
getRoutePatterns() {
|
|
40
|
+
return [/\+page\.(svelte|ts|js)$/, /\+page\.server\.(ts|js)$/, /\+layout\.(svelte|ts|js)$/, /\+server\.(ts|js)$/];
|
|
41
|
+
}
|
|
42
|
+
getRequiredSystemContracts() { return ['load', 'actions', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']; }
|
|
43
|
+
async isActive(baseDir) {
|
|
44
|
+
try {
|
|
45
|
+
await fs.access(path.join(baseDir, 'svelte.config.js'));
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class AstroPlugin extends BasePlugin {
|
|
54
|
+
get name() { return 'astro'; }
|
|
55
|
+
getConfigFiles() { return ['astro.config.mjs', 'astro.config.cjs', 'astro.config.ts']; }
|
|
56
|
+
getRoutePatterns() { return [/\/src\/pages\/.*\.astro$/, /\/src\/pages\/.*\.(ts|js)$/]; }
|
|
57
|
+
getRequiredSystemContracts() { return ['default', 'getStaticPaths']; }
|
|
58
|
+
async isActive(baseDir) {
|
|
59
|
+
for (const file of this.getConfigFiles()) {
|
|
60
|
+
try { await fs.access(path.join(baseDir, file)); return true; } catch {}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Modern Frameworks Plugins for entkapp v4.0.0
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Built-in support for React, Vue, Svelte, and Angular.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* React Ecosystem Plugin
|
|
14
|
+
*/
|
|
15
|
+
export class ReactPlugin extends BasePlugin {
|
|
16
|
+
get name() {
|
|
17
|
+
return 'react';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getConfigFiles() {
|
|
21
|
+
return ['package.json'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async isActive(baseDir) {
|
|
25
|
+
try {
|
|
26
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
27
|
+
return !!(pkgJson.dependencies?.react || pkgJson.devDependencies?.react);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getRoutePatterns() {
|
|
34
|
+
return [/\.(tsx?|jsx?)$/];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getRequiredSystemContracts() {
|
|
38
|
+
return ['default', 'Component', 'PureComponent', 'Fragment', 'useEffect', 'useState', 'useContext', 'useReducer', 'useCallback', 'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect', 'useDebugValue'];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async analyze(node, filePath) {
|
|
42
|
+
if (node.explicitImports.has('react')) {
|
|
43
|
+
node.isReactComponent = true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Detect JSX
|
|
47
|
+
if (node.rawCode && (node.rawCode.includes('</') || node.rawCode.includes('/>'))) {
|
|
48
|
+
node.hasJSX = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Detect Hooks
|
|
52
|
+
const hookMatches = node.rawCode?.match(/use[A-Z]\w+/g) || [];
|
|
53
|
+
if (hookMatches.length > 0) {
|
|
54
|
+
node.reactHooks = new Set(hookMatches);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Vue Ecosystem Plugin
|
|
61
|
+
*/
|
|
62
|
+
export class VuePlugin extends BasePlugin {
|
|
63
|
+
get name() {
|
|
64
|
+
return 'vue';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
getConfigFiles() {
|
|
68
|
+
return ['package.json', 'vue.config.js', 'vite.config.ts', 'vite.config.js'];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async isActive(baseDir) {
|
|
72
|
+
try {
|
|
73
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
74
|
+
return !!(pkgJson.dependencies?.vue || pkgJson.devDependencies?.vue);
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getRoutePatterns() {
|
|
81
|
+
return [/\.vue$/, /\.(tsx?|jsx?)$/];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async analyze(node, filePath) {
|
|
85
|
+
if (filePath.endsWith('.vue')) {
|
|
86
|
+
node.isVueSFC = true;
|
|
87
|
+
// Extract template/script/style sections
|
|
88
|
+
const templateMatch = node.rawCode?.match(/<template>([\s\S]*)<\/template>/);
|
|
89
|
+
if (templateMatch) node.vueTemplate = templateMatch[1];
|
|
90
|
+
|
|
91
|
+
const scriptMatch = node.rawCode?.match(/<script(?: setup)?(?: lang=['"]\w+['"])?>([\s\S]*)<\/script>/);
|
|
92
|
+
if (scriptMatch) node.vueScript = scriptMatch[1];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Svelte Ecosystem Plugin
|
|
99
|
+
*/
|
|
100
|
+
export class SveltePlugin extends BasePlugin {
|
|
101
|
+
get name() {
|
|
102
|
+
return 'svelte';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getConfigFiles() {
|
|
106
|
+
return ['package.json', 'svelte.config.js'];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async isActive(baseDir) {
|
|
110
|
+
try {
|
|
111
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
112
|
+
return !!(pkgJson.dependencies?.svelte || pkgJson.devDependencies?.svelte);
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getRoutePatterns() {
|
|
119
|
+
return [/\.svelte$/, /\.(tsx?|jsx?)$/];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async analyze(node, filePath) {
|
|
123
|
+
if (filePath.endsWith('.svelte')) {
|
|
124
|
+
node.isSvelteComponent = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Angular Ecosystem Plugin
|
|
131
|
+
*/
|
|
132
|
+
export class AngularPlugin extends BasePlugin {
|
|
133
|
+
get name() {
|
|
134
|
+
return 'angular';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getConfigFiles() {
|
|
138
|
+
return ['package.json', 'angular.json'];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async isActive(baseDir) {
|
|
142
|
+
try {
|
|
143
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
144
|
+
return !!(pkgJson.dependencies?.['@angular/core'] || pkgJson.devDependencies?.['@angular/core']);
|
|
145
|
+
} catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
getRoutePatterns() {
|
|
151
|
+
return [/\.ts$/];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async analyze(node, filePath) {
|
|
155
|
+
if (node.rawCode?.includes('@Component') || node.rawCode?.includes('@Injectable') || node.rawCode?.includes('@NgModule')) {
|
|
156
|
+
node.isAngularEntity = true;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
4
|
+
|
|
5
|
+
export class TailwindPlugin extends BasePlugin {
|
|
6
|
+
get name() { return 'tailwind'; }
|
|
7
|
+
getConfigFiles() { return ['tailwind.config.js', 'tailwind.config.ts', 'tailwind.config.cjs', 'tailwind.config.mjs']; }
|
|
8
|
+
async isActive(baseDir) {
|
|
9
|
+
try {
|
|
10
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
11
|
+
return !!(pkgJson.dependencies?.tailwindcss || pkgJson.devDependencies?.tailwindcss);
|
|
12
|
+
} catch { return false; }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class PostcssPlugin extends BasePlugin {
|
|
17
|
+
get name() { return 'postcss'; }
|
|
18
|
+
getConfigFiles() { return ['postcss.config.js', 'postcss.config.cjs', 'postcss.config.mjs']; }
|
|
19
|
+
async isActive(baseDir) {
|
|
20
|
+
try {
|
|
21
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
22
|
+
return !!(pkgJson.dependencies?.postcss || pkgJson.devDependencies?.postcss);
|
|
23
|
+
} catch { return false; }
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class JestPlugin extends BasePlugin {
|
|
28
|
+
get name() { return 'jest'; }
|
|
29
|
+
getConfigFiles() { return ['jest.config.js', 'jest.config.ts', 'jest.config.mjs', 'jest.config.cjs', 'package.json']; }
|
|
30
|
+
getRoutePatterns() { return [/\.(test|spec)\.[jt]sx?$/]; }
|
|
31
|
+
async isActive(baseDir) {
|
|
32
|
+
try {
|
|
33
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
34
|
+
return !!(pkgJson.dependencies?.jest || pkgJson.devDependencies?.jest);
|
|
35
|
+
} catch { return false; }
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class VitestPlugin extends BasePlugin {
|
|
40
|
+
get name() { return 'vitest'; }
|
|
41
|
+
getConfigFiles() { return ['vitest.config.ts', 'vitest.config.js', 'vitest.config.mjs', 'vitest.workspace.ts']; }
|
|
42
|
+
getRoutePatterns() { return [/\.(test|spec)\.[jt]sx?$/]; }
|
|
43
|
+
async isActive(baseDir) {
|
|
44
|
+
try {
|
|
45
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
46
|
+
return !!(pkgJson.dependencies?.vitest || pkgJson.devDependencies?.vitest);
|
|
47
|
+
} catch { return false; }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class PlaywrightPlugin extends BasePlugin {
|
|
52
|
+
get name() { return 'playwright'; }
|
|
53
|
+
getConfigFiles() { return ['playwright.config.ts', 'playwright.config.js']; }
|
|
54
|
+
getRoutePatterns() { return [/.*\.spec\.[jt]s$/]; }
|
|
55
|
+
async isActive(baseDir) {
|
|
56
|
+
try {
|
|
57
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
58
|
+
return !!(pkgJson.dependencies?.['@playwright/test'] || pkgJson.devDependencies?.['@playwright/test']);
|
|
59
|
+
} catch { return false; }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class CypressPlugin extends BasePlugin {
|
|
64
|
+
get name() { return 'cypress'; }
|
|
65
|
+
getConfigFiles() { return ['cypress.config.ts', 'cypress.config.js']; }
|
|
66
|
+
getRoutePatterns() { return [/cypress\/e2e\/.*\.cy\.[jt]s$/]; }
|
|
67
|
+
async isActive(baseDir) {
|
|
68
|
+
try {
|
|
69
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
70
|
+
return !!(pkgJson.dependencies?.cypress || pkgJson.devDependencies?.cypress);
|
|
71
|
+
} catch { return false; }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class StorybookPlugin extends BasePlugin {
|
|
76
|
+
get name() { return 'storybook'; }
|
|
77
|
+
getConfigFiles() { return ['.storybook/main.js', '.storybook/main.ts', '.storybook/preview.js', '.storybook/preview.ts']; }
|
|
78
|
+
getRoutePatterns() { return [/\.stories\.[jt]sx?$/]; }
|
|
79
|
+
async isActive(baseDir) {
|
|
80
|
+
try {
|
|
81
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
82
|
+
return !!(pkgJson.dependencies?.storybook || pkgJson.devDependencies?.storybook || pkgJson.devDependencies?.['@storybook/react']);
|
|
83
|
+
} catch { return false; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class EslintPlugin extends BasePlugin {
|
|
88
|
+
get name() { return 'eslint'; }
|
|
89
|
+
getConfigFiles() { return ['.eslintrc', '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.json', 'eslint.config.js', 'eslint.config.mjs']; }
|
|
90
|
+
async isActive(baseDir) {
|
|
91
|
+
try {
|
|
92
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
93
|
+
return !!(pkgJson.dependencies?.eslint || pkgJson.devDependencies?.eslint);
|
|
94
|
+
} catch { return false; }
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export class PrettierPlugin extends BasePlugin {
|
|
99
|
+
get name() { return 'prettier'; }
|
|
100
|
+
getConfigFiles() { return ['.prettierrc', '.prettierrc.js', '.prettierrc.cjs', '.prettierrc.json', 'prettier.config.js']; }
|
|
101
|
+
async isActive(baseDir) {
|
|
102
|
+
try {
|
|
103
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
104
|
+
return !!(pkgJson.dependencies?.prettier || pkgJson.devDependencies?.prettier);
|
|
105
|
+
} catch { return false; }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class HuskyPlugin extends BasePlugin {
|
|
110
|
+
get name() { return 'husky'; }
|
|
111
|
+
getConfigFiles() { return ['.husky/pre-commit', '.husky/pre-push']; }
|
|
112
|
+
async isActive(baseDir) {
|
|
113
|
+
try {
|
|
114
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
115
|
+
return !!(pkgJson.dependencies?.husky || pkgJson.devDependencies?.husky);
|
|
116
|
+
} catch { return false; }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export class LintStagedPlugin extends BasePlugin {
|
|
121
|
+
get name() { return 'lint-staged'; }
|
|
122
|
+
getConfigFiles() { return ['.lintstagedrc', '.lintstagedrc.js', '.lintstagedrc.json', 'lint-staged.config.js', 'package.json']; }
|
|
123
|
+
async isActive(baseDir) {
|
|
124
|
+
try {
|
|
125
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
126
|
+
return !!(pkgJson.dependencies?.['lint-staged'] || pkgJson.devDependencies?.['lint-staged']);
|
|
127
|
+
} catch { return false; }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class CommitlintPlugin extends BasePlugin {
|
|
132
|
+
get name() { return 'commitlint'; }
|
|
133
|
+
getConfigFiles() { return ['.commitlintrc', '.commitlintrc.js', '.commitlintrc.json', 'commitlint.config.js', 'package.json']; }
|
|
134
|
+
async isActive(baseDir) {
|
|
135
|
+
try {
|
|
136
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
137
|
+
return !!(pkgJson.dependencies?.['@commitlint/cli'] || pkgJson.devDependencies?.['@commitlint/cli']);
|
|
138
|
+
} catch { return false; }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class BabelPlugin extends BasePlugin {
|
|
143
|
+
get name() { return 'babel'; }
|
|
144
|
+
getConfigFiles() { return ['.babelrc', '.babelrc.js', '.babelrc.json', 'babel.config.js', 'babel.config.json', 'package.json']; }
|
|
145
|
+
async isActive(baseDir) {
|
|
146
|
+
try {
|
|
147
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
148
|
+
return !!(pkgJson.dependencies?.['@babel/core'] || pkgJson.devDependencies?.['@babel/core']);
|
|
149
|
+
} catch { return false; }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export class RollupPlugin extends BasePlugin {
|
|
154
|
+
get name() { return 'rollup'; }
|
|
155
|
+
getConfigFiles() { return ['rollup.config.js', 'rollup.config.mjs', 'rollup.config.ts']; }
|
|
156
|
+
async isActive(baseDir) {
|
|
157
|
+
try {
|
|
158
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
159
|
+
return !!(pkgJson.dependencies?.rollup || pkgJson.devDependencies?.rollup);
|
|
160
|
+
} catch { return false; }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class WebpackPlugin extends BasePlugin {
|
|
165
|
+
get name() { return 'webpack'; }
|
|
166
|
+
getConfigFiles() { return ['webpack.config.js', 'webpack.config.ts', 'webpack.config.mjs', 'webpack.config.cjs']; }
|
|
167
|
+
async isActive(baseDir) {
|
|
168
|
+
try {
|
|
169
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
170
|
+
return !!(pkgJson.dependencies?.webpack || pkgJson.devDependencies?.webpack);
|
|
171
|
+
} catch { return false; }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export class GithubActionsPlugin extends BasePlugin {
|
|
176
|
+
get name() { return 'github-actions'; }
|
|
177
|
+
getConfigFiles() { return ['.github/workflows/*.yml', '.github/workflows/*.yaml']; }
|
|
178
|
+
async isActive(baseDir) {
|
|
179
|
+
try {
|
|
180
|
+
await fs.access(path.join(baseDir, '.github/workflows'));
|
|
181
|
+
return true;
|
|
182
|
+
} catch { return false; }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
4
|
+
|
|
5
|
+
export class NextJsPlugin extends BasePlugin {
|
|
6
|
+
get name() { return 'nextjs'; }
|
|
7
|
+
|
|
8
|
+
getConfigFiles() {
|
|
9
|
+
return ['next.config.js', 'next.config.mjs', 'next.config.ts'];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getRoutePatterns() {
|
|
13
|
+
return [
|
|
14
|
+
/\/pages\/api\//,
|
|
15
|
+
/\/pages\/[a-zA-Z0-9_\-\[\]]+/i,
|
|
16
|
+
/\/app\/([\w\-\[\]]+\/)+(page|route|layout|loading|error|not-found)\.(ts|tsx|js|jsx)$/
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
getRequiredSystemContracts() {
|
|
21
|
+
return ['default', 'getServerSideProps', 'getStaticProps', 'getStaticPaths', 'generateMetadata', 'middleware'];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async isActive(baseDir) {
|
|
25
|
+
for (const file of this.getConfigFiles()) {
|
|
26
|
+
try {
|
|
27
|
+
await fs.access(path.join(baseDir, file));
|
|
28
|
+
return true;
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TailwindPlugin, PostcssPlugin, JestPlugin, VitestPlugin, PlaywrightPlugin, CypressPlugin, StorybookPlugin, EslintPlugin, PrettierPlugin, HuskyPlugin, LintStagedPlugin, CommitlintPlugin, BabelPlugin, RollupPlugin, WebpackPlugin, GithubActionsPlugin } from './MorePlugins.js';
|
|
2
|
+
|
|
3
|
+
export function loadAdditionalPlugins(registry) {
|
|
4
|
+
registry.register(new TailwindPlugin(registry.context));
|
|
5
|
+
registry.register(new PostcssPlugin(registry.context));
|
|
6
|
+
registry.register(new JestPlugin(registry.context));
|
|
7
|
+
registry.register(new VitestPlugin(registry.context));
|
|
8
|
+
registry.register(new PlaywrightPlugin(registry.context));
|
|
9
|
+
registry.register(new CypressPlugin(registry.context));
|
|
10
|
+
registry.register(new StorybookPlugin(registry.context));
|
|
11
|
+
registry.register(new EslintPlugin(registry.context));
|
|
12
|
+
registry.register(new PrettierPlugin(registry.context));
|
|
13
|
+
registry.register(new HuskyPlugin(registry.context));
|
|
14
|
+
registry.register(new LintStagedPlugin(registry.context));
|
|
15
|
+
registry.register(new CommitlintPlugin(registry.context));
|
|
16
|
+
registry.register(new BabelPlugin(registry.context));
|
|
17
|
+
registry.register(new RollupPlugin(registry.context));
|
|
18
|
+
registry.register(new WebpackPlugin(registry.context));
|
|
19
|
+
registry.register(new GithubActionsPlugin(registry.context));
|
|
20
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { BasePlugin } from '../BasePlugin.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TypeScript Plugin for entkapp.
|
|
7
|
+
* Handles tsconfig.json detection and TypeScript-specific entry points.
|
|
8
|
+
*/
|
|
9
|
+
export class TypeScriptPlugin extends BasePlugin {
|
|
10
|
+
get name() {
|
|
11
|
+
return 'typescript';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
getConfigFiles() {
|
|
15
|
+
return ['tsconfig.json', 'tsconfig.base.json', 'tsconfig.eslint.json'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getRoutePatterns() {
|
|
19
|
+
// Common TypeScript entry points and declaration files
|
|
20
|
+
return [
|
|
21
|
+
/src\/index\.ts$/,
|
|
22
|
+
/src\/main\.ts$/,
|
|
23
|
+
/src\/lib\.ts$/,
|
|
24
|
+
/.*\.d\.ts$/
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getRequiredSystemContracts() {
|
|
29
|
+
// TypeScript specific implicit exports or requirements
|
|
30
|
+
return ['default'];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Custom Getter for v3.2.0: Get the compiler version from the project.
|
|
35
|
+
*/
|
|
36
|
+
async getCompilerVersion() {
|
|
37
|
+
try {
|
|
38
|
+
const packageJsonPath = path.join(this.context.cwd, 'package.json');
|
|
39
|
+
const content = await fs.readFile(packageJsonPath, 'utf8');
|
|
40
|
+
const pkg = JSON.parse(content);
|
|
41
|
+
return pkg.devDependencies?.typescript || pkg.dependencies?.typescript || 'unknown';
|
|
42
|
+
} catch {
|
|
43
|
+
return 'not installed';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async isActive(baseDir) {
|
|
48
|
+
for (const file of this.getConfigFiles()) {
|
|
49
|
+
try {
|
|
50
|
+
await fs.access(path.join(baseDir, file));
|
|
51
|
+
return true;
|
|
52
|
+
} catch {}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -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
|
+
}
|