pkg-scaffold 3.3.3 β 3.3.5
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 +62 -22
- package/bin/cli.js +7 -7
- package/package.json +5 -4
- package/pnpm-workspace.yaml +2 -0
- package/src/EngineContext.js +47 -19
- package/src/ast/ASTAnalyzer.js +287 -132
- package/src/ast/BarrelParser.js +51 -19
- package/src/ast/DeadCodeDetector.js +73 -0
- package/src/ast/MagicDetector.js +111 -11
- package/src/ast/OxcAnalyzer.js +250 -79
- package/src/healing/GitSandbox.js +44 -122
- package/src/healing/SelfHealer.js +29 -130
- package/src/index.js +124 -108
- package/src/performance/WorkerTaskRunner.js +17 -5
- package/src/plugins/PluginRegistry.js +28 -1
- package/src/plugins/ecosystems/MorePlugins.js +184 -0
- package/src/plugins/ecosystems/PluginLoader.js +20 -0
- package/src/resolution/CircularDetector.js +3 -34
- package/src/resolution/ConfigLoader.js +34 -6
- package/src/resolution/DependencyProfiler.js +261 -9
- package/src/resolution/WorkSpaceGraph.js +148 -35
- package/src/performance/SecretDetector.js +0 -378
package/README.md
CHANGED
|
@@ -1,41 +1,81 @@
|
|
|
1
|
-
#
|
|
1
|
+
# π¦ pkg-scaffold
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
**The Ultimate Enterprise Codebase Janitor
|
|
5
|
+
> **The Ultimate Enterprise Codebase Janitor.** Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.
|
|
6
6
|
|
|
7
|
-
  
|
|
8
8
|
|
|
9
|
-
`pkg-scaffold` is
|
|
9
|
+
`pkg-scaffold` is a next-generation tool designed to declutter your JavaScript and TypeScript projects. It finds unused files, unused dependencies, dead code, circular dependencies, and more. It is built to be a direct and superior competitor to `knip.dev`.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## π Why pkg-scaffold over Knip?
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
* **β‘ Blazing Fast:** Powered by `oxc-parser` (Rust-based) for lightning-fast AST traversal. Fallback to TypeScript compiler API when needed.
|
|
14
|
+
* **π Massive Plugin Ecosystem:** Over 20+ built-in plugins (Next.js, Nuxt, SvelteKit, Tailwind, Jest, Vitest, Playwright, GitHub Actions, Webpack, Babel, Rollup, ESLint, Prettier, Husky, and many more).
|
|
15
|
+
* **π True Dead Code Detection:** Advanced graph-based reachability analysis to find truly dead files and unused exports, even deep within your codebase.
|
|
16
|
+
* **π Circular Dependency Detection:** High-performance Tarjan-based algorithm to detect and report circular dependencies.
|
|
17
|
+
* **π‘οΈ Supply Chain Guard:** Detects typosquatting and verifies integrity lockfile hashes.
|
|
18
|
+
* **π οΈ Automated Structural Healing:** Not just reporting, but automatically fixing structural issues (removing dead files, pruning unused dependencies) with git-based rollback protection.
|
|
19
|
+
* **βοΈ Flexible Configuration:** Supports `pkg-scaffold.json`, `pkg-scaffold.ts`, `scaffold.config.js`, and more.
|
|
17
20
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
### Installation
|
|
21
|
+
## π¦ Installation
|
|
21
22
|
|
|
22
23
|
```bash
|
|
23
|
-
npm install -
|
|
24
|
+
npm install -D pkg-scaffold
|
|
25
|
+
# or
|
|
26
|
+
pnpm add -D pkg-scaffold
|
|
27
|
+
# or
|
|
28
|
+
pnpm add -D pkg-scaffold
|
|
24
29
|
```
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
## π Usage
|
|
32
|
+
|
|
33
|
+
Run the CLI at the root of your project:
|
|
27
34
|
|
|
28
35
|
```bash
|
|
29
|
-
pkg-scaffold
|
|
30
|
-
pkg-scaffold --fix --test-command 'npm test'
|
|
36
|
+
npx pkg-scaffold --run
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
|
|
39
|
+
### CLI Options
|
|
40
|
+
|
|
41
|
+
* `-c, --cwd <path>`: Specify the execution context root directory.
|
|
42
|
+
* `--fix`: Enable atomic code updates, structural file pruning, and active type sanitization.
|
|
43
|
+
* `--no-fix`: Disable direct file manipulation (dry-run reporting mode).
|
|
44
|
+
* `--tsconfig <filename>`: Specify path to custom layout configurations.
|
|
45
|
+
* `--verbose`: Toggle expanded trace telemetry for debug operational diagnostics.
|
|
46
|
+
* `-r, --run`: Execute the primary operational pipeline loop.
|
|
47
|
+
* `-y, --yes`: Skip confirmation prompts.
|
|
48
|
+
|
|
49
|
+
## βοΈ Configuration
|
|
50
|
+
|
|
51
|
+
Create a `pkg-scaffold.json` (or `.js`, `.ts`) in your project root:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"entryPoints": ["src/index.ts"],
|
|
56
|
+
"exclude": ["node_modules/**", "dist/**", "**/*.test.ts"],
|
|
57
|
+
"rules": {
|
|
58
|
+
"no-unused-exports": "error",
|
|
59
|
+
"no-dead-code": "error"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## π Supported Plugins
|
|
65
|
+
|
|
66
|
+
`pkg-scaffold` automatically detects your ecosystem and enables the relevant plugins:
|
|
67
|
+
|
|
68
|
+
* **Frameworks:** Next.js, Nuxt, Remix, SvelteKit, Astro, Vue, Angular
|
|
69
|
+
* **Testing:** Jest, Vitest, Playwright, Cypress
|
|
70
|
+
* **Build Tools:** Webpack, Rollup, Babel, PostCSS, TailwindCSS
|
|
71
|
+
* **Linters/Formatters:** ESLint, Prettier, Commitlint, Lint-Staged, Husky
|
|
72
|
+
* **CI/CD:** GitHub Actions
|
|
73
|
+
* **And more!**
|
|
74
|
+
|
|
75
|
+
## π€ Contributing
|
|
34
76
|
|
|
35
|
-
|
|
36
|
-
- [guide](https://dreamlongyt.github.io/pkg-scaffold/guide)
|
|
37
|
-
- [references](https://dreamlongyt.github.io/pkg-scaffold/reference)
|
|
77
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
38
78
|
|
|
39
|
-
## License
|
|
79
|
+
## π License
|
|
40
80
|
|
|
41
|
-
|
|
81
|
+
Apache-2.0 License.
|
package/bin/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ async function bootstrap() {
|
|
|
28
28
|
program
|
|
29
29
|
.name('pkg-scaffold')
|
|
30
30
|
.description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
|
|
31
|
-
.version(packageJsonContent.version || '3.3.
|
|
31
|
+
.version(packageJsonContent.version || '3.3.5');
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
@@ -105,9 +105,9 @@ async function bootstrap() {
|
|
|
105
105
|
// Load local config if available
|
|
106
106
|
let localConfig = {};
|
|
107
107
|
try {
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
localConfig =
|
|
108
|
+
const { ConfigLoader } = await import('../src/resolution/ConfigLoader.js');
|
|
109
|
+
const loader = new ConfigLoader(targetCwd);
|
|
110
|
+
localConfig = await loader.loadConfig(targetCwd);
|
|
111
111
|
} catch (e) {}
|
|
112
112
|
|
|
113
113
|
// Merge options with local config
|
|
@@ -130,9 +130,9 @@ async function bootstrap() {
|
|
|
130
130
|
}, timeoutMs);
|
|
131
131
|
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
132
132
|
|
|
133
|
-
console.log(ansis.bold.green(`\nπ¦ pkg-scaffold v${packageJsonContent.version || '3.3.
|
|
133
|
+
console.log(ansis.bold.green(`\nπ¦ pkg-scaffold v${packageJsonContent.version || '3.3.5'} Engine Activation`));
|
|
134
134
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
135
|
-
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(
|
|
135
|
+
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
136
136
|
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
|
137
137
|
console.log(`${ansis.bold('Validation Sandbox :')} ${ansis.magenta(options.testCommand)}`);
|
|
138
138
|
console.log(ansis.dim('------------------------------------------------------------\n'));
|
|
@@ -140,7 +140,7 @@ async function bootstrap() {
|
|
|
140
140
|
const { RefactoringEngine } = await import('../src/index.js');
|
|
141
141
|
|
|
142
142
|
const engine = new RefactoringEngine({
|
|
143
|
-
cwd:
|
|
143
|
+
cwd: targetCwd,
|
|
144
144
|
autoFix: options.fix,
|
|
145
145
|
tsconfig: options.tsconfig,
|
|
146
146
|
testCommand: options.testCommand,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkg-scaffold",
|
|
3
|
-
"version": "3.3.
|
|
4
|
-
"description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and
|
|
3
|
+
"version": "3.3.5",
|
|
4
|
+
"description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "echo \"Error: no test specified\" && exit 0",
|
|
14
14
|
"test:stability": "npm run test",
|
|
15
15
|
"docs:dev": "vitepress dev docs",
|
|
16
|
-
"docs:build": "vitepress build docs && cp docs/sitemap.xml docs/.vitepress/dist/ && cp docs/robots.txt docs/.vitepress/dist",
|
|
16
|
+
"docs:build": "vitepress build docs && cp docs/sitemap.xml docs/.vitepress/dist/ && cp docs/robots.txt docs/.vitepress/dist && cp logo.png docs/.vitepress/dist",
|
|
17
17
|
"docs:preview": "vitepress preview docs"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
"package.json",
|
|
49
49
|
"packages",
|
|
50
50
|
"scan",
|
|
51
|
-
|
|
51
|
+
|
|
52
|
+
"structural-healing",
|
|
52
53
|
"setup",
|
|
53
54
|
"type",
|
|
54
55
|
"types",
|
package/src/EngineContext.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* π¦ pkg-scaffold v3.3.
|
|
3
|
+
* π¦ pkg-scaffold v3.3.5: Enterprise In-Memory Codebase State Manifest
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Implements a high-density, centralized graph database context for tracking
|
|
6
6
|
* software engineering debt, dependencies, types, and vulnerabilities.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
|
+
import { DependencyProfiler } from './resolution/DependencyProfiler.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* High-Fidelity Graph Element Node representing a single file asset boundary.
|
|
@@ -25,6 +26,9 @@ export class GraphNode {
|
|
|
25
26
|
this.explicitImports = new Set();
|
|
26
27
|
this.dynamicImports = new Set();
|
|
27
28
|
this.importedSymbols = new Set(); // Format: 'specifier:symbol' or 'specifier:*'
|
|
29
|
+
this.jsxComponents = new Set();
|
|
30
|
+
this.jsxProps = new Set();
|
|
31
|
+
this.decorators = new Set();
|
|
28
32
|
|
|
29
33
|
// Internal API Exposed Interfaces (Symbol Name -> ExportMetadata)
|
|
30
34
|
this.internalExports = new Map();
|
|
@@ -39,8 +43,7 @@ export class GraphNode {
|
|
|
39
43
|
this.incomingEdges = new Set(); // Set of absolute filePaths depending on this component
|
|
40
44
|
this.outgoingEdges = new Set(); // Set of absolute internal filePaths this component calls
|
|
41
45
|
|
|
42
|
-
//
|
|
43
|
-
this.securityThreats = [];
|
|
46
|
+
// Structural Syntax Boundaries
|
|
44
47
|
this.calculatedDynamicImports = [];
|
|
45
48
|
this.localSuppressedRules = new Set();
|
|
46
49
|
this.externalPackageUsage = new Set(); // Tracked third-party package names
|
|
@@ -87,6 +90,17 @@ export class GraphNode {
|
|
|
87
90
|
|
|
88
91
|
// Safe fallback lookup inside string reference caches (e.g., obj['databaseUrl'])
|
|
89
92
|
if (parentNode.rawStringReferences.has(symbolName)) return true;
|
|
93
|
+
|
|
94
|
+
// Check for JSX component usage
|
|
95
|
+
if (parentNode.jsxComponents.has(symbolName)) return true;
|
|
96
|
+
|
|
97
|
+
// Check for JSX prop usage (e.g., <MyComponent myProp={symbolName} />)
|
|
98
|
+
for (const jsxProp of parentNode.jsxProps) {
|
|
99
|
+
if (jsxProp.endsWith(`:${symbolName}`)) return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check for decorator usage
|
|
103
|
+
if (parentNode.decorators.has(symbolName)) return true;
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
return false;
|
|
@@ -146,6 +160,9 @@ export class EngineContext {
|
|
|
146
160
|
};
|
|
147
161
|
this.usedExternalPackages = new Set(); // Global set of used npm packages
|
|
148
162
|
this.manifestDependencies = new Map(); // Package.json path -> { dependencies, devDependencies, peerDependencies, optionalDependencies }
|
|
163
|
+
|
|
164
|
+
// DependencyProfiler instance for implicit invocation tracing
|
|
165
|
+
this._depProfiler = new DependencyProfiler(this);
|
|
149
166
|
}
|
|
150
167
|
|
|
151
168
|
/**
|
|
@@ -223,7 +240,7 @@ export class EngineContext {
|
|
|
223
240
|
* Processes the entire active dependency map to compile structural issue indices.
|
|
224
241
|
* Evaluates orphaned components, dead exports, and supply-chain threats.
|
|
225
242
|
*/
|
|
226
|
-
generateSummaryReport() {
|
|
243
|
+
async generateSummaryReport() {
|
|
227
244
|
this.metrics.endTime = Date.now();
|
|
228
245
|
const durationSeconds = ((this.metrics.endTime - this.metrics.startTime) / 1000).toFixed(2);
|
|
229
246
|
|
|
@@ -240,7 +257,6 @@ export class EngineContext {
|
|
|
240
257
|
structuralIssuesDetected: {
|
|
241
258
|
deadFiles: [],
|
|
242
259
|
deadExports: [],
|
|
243
|
-
securityThreats: [],
|
|
244
260
|
unusedDependencies: []
|
|
245
261
|
},
|
|
246
262
|
modificationsExecuted: {
|
|
@@ -252,6 +268,11 @@ export class EngineContext {
|
|
|
252
268
|
for (const [filePath, node] of this.graph.entries()) {
|
|
253
269
|
// Skip package control files from standard structural dead-code checks
|
|
254
270
|
if (filePath.endsWith('package.json')) continue;
|
|
271
|
+
|
|
272
|
+
// Fix: Always track external package usage from all files, even if they are orphaned,
|
|
273
|
+
// to prevent false-positive unused dependency reports.
|
|
274
|
+
node.externalPackageUsage.forEach(pkg => this.usedExternalPackages.add(pkg));
|
|
275
|
+
|
|
255
276
|
if (this.isPathIgnored(filePath)) continue;
|
|
256
277
|
|
|
257
278
|
const relativePath = path.relative(this.cwd, filePath);
|
|
@@ -285,28 +306,34 @@ export class EngineContext {
|
|
|
285
306
|
}
|
|
286
307
|
}
|
|
287
308
|
|
|
288
|
-
// Category C: Security Vulnerabilities
|
|
289
|
-
if (node.securityThreats && node.securityThreats.length > 0) {
|
|
290
|
-
node.securityThreats.forEach(threat => {
|
|
291
|
-
summary.structuralIssuesDetected.securityThreats.push({
|
|
292
|
-
file: relativePath,
|
|
293
|
-
identifier: threat.variableKey,
|
|
294
|
-
riskCode: threat.riskCode || 'HIGH_RISK_SECRET_LEAK',
|
|
295
|
-
entropy: threat.entropyValue,
|
|
296
|
-
line: threat.line || 1
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
309
|
}
|
|
301
310
|
|
|
302
311
|
// Category D: Unused Dependencies Audit (Enhanced Classification)
|
|
303
312
|
for (const [manifestPath, manifestData] of this.manifestDependencies.entries()) {
|
|
304
313
|
const relativeManifest = path.relative(this.cwd, manifestPath);
|
|
305
|
-
|
|
314
|
+
const packageRoot = path.dirname(manifestPath);
|
|
315
|
+
|
|
316
|
+
// Collect packages that are implicitly used via scripts / config files
|
|
317
|
+
const implicitlyUsed = await this._depProfiler.traceImplicitInvocations(packageRoot);
|
|
318
|
+
|
|
319
|
+
// Resolve peer dependencies of all used packages so they are not flagged
|
|
320
|
+
const allUsedForPeerResolution = new Set([...this.usedExternalPackages, ...implicitlyUsed]);
|
|
321
|
+
const peerDepsOfUsed = await this._depProfiler.resolvePeerDependencies(allUsedForPeerResolution, packageRoot);
|
|
322
|
+
|
|
306
323
|
const checkDeps = (deps, type) => {
|
|
307
324
|
if (!deps) return;
|
|
308
325
|
for (const dep of deps) {
|
|
309
|
-
|
|
326
|
+
// Skip peer and optional dependencies β they are never "unused" in the
|
|
327
|
+
// traditional sense because they are not required to be imported directly.
|
|
328
|
+
if (this._depProfiler.shouldExcludeFromUnusedCheck(dep, type)) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const isUsedInCode = this.usedExternalPackages.has(dep);
|
|
333
|
+
const isUsedImplicitly = implicitlyUsed.has(dep);
|
|
334
|
+
const isRequiredAsPeer = peerDepsOfUsed.has(dep);
|
|
335
|
+
|
|
336
|
+
if (!isUsedInCode && !isUsedImplicitly && !isRequiredAsPeer) {
|
|
310
337
|
summary.structuralIssuesDetected.unusedDependencies.push({
|
|
311
338
|
manifest: relativeManifest,
|
|
312
339
|
package: dep,
|
|
@@ -319,6 +346,7 @@ export class EngineContext {
|
|
|
319
346
|
|
|
320
347
|
checkDeps(manifestData.dependencies, 'dependency');
|
|
321
348
|
checkDeps(manifestData.devDependencies, 'devDependency');
|
|
349
|
+
// peerDependencies and optionalDependencies are excluded via shouldExcludeFromUnusedCheck
|
|
322
350
|
checkDeps(manifestData.peerDependencies, 'peerDependency');
|
|
323
351
|
checkDeps(manifestData.optionalDependencies, 'optionalDependency');
|
|
324
352
|
}
|