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/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { DeadCodeDetector } from "./ast/DeadCodeDetector.js";
|
|
2
|
+
import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
|
|
1
3
|
/**
|
|
2
4
|
* ============================================================================
|
|
3
|
-
* 📦 pkg-scaffold v3.
|
|
5
|
+
* 📦 pkg-scaffold v3.4.0: Unified Architectural Refactoring Orchestrator
|
|
4
6
|
* ============================================================================
|
|
5
7
|
* Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
|
|
6
|
-
* supply-chain validation audits, and automated
|
|
8
|
+
* supply-chain validation audits, and automated structural healing rollbacks.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
import fs from 'fs/promises';
|
|
@@ -29,7 +31,6 @@ import { SelfHealer } from './healing/SelfHealer.js';
|
|
|
29
31
|
import { IncrementalCacheManager } from './performance/GraphCache.js';
|
|
30
32
|
import { WorkerPool } from './performance/WorkerPool.js';
|
|
31
33
|
import { SupplyChainGuard } from './performance/SupplyChainGuard.js';
|
|
32
|
-
import { SecretDetector } from './performance/SecretDetector.js';
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Primary Refactoring Engine Core Coordination Controller
|
|
@@ -47,6 +48,7 @@ export class RefactoringEngine {
|
|
|
47
48
|
|
|
48
49
|
// Stage 3: Wire official AST Syntax parsers and framework processors
|
|
49
50
|
this.analyzer = new ASTAnalyzer(this.context);
|
|
51
|
+
this.oxcAnalyzer = new OxcAnalyzer(this.context);
|
|
50
52
|
this.barrelParser = new BarrelParser(this.context, this.resolver);
|
|
51
53
|
this.magicDetector = new MagicDetector(this.context);
|
|
52
54
|
|
|
@@ -58,7 +60,6 @@ export class RefactoringEngine {
|
|
|
58
60
|
|
|
59
61
|
// Stage 5: Bind security audit utilities and performance cache rings
|
|
60
62
|
this.supplyChainGuard = new SupplyChainGuard(this.context);
|
|
61
|
-
this.secretDetector = new SecretDetector(this.context);
|
|
62
63
|
this.cacheManager = new IncrementalCacheManager(this.context);
|
|
63
64
|
this.workerPool = new WorkerPool(this.context);
|
|
64
65
|
this.gitSandbox = new GitSandbox(this.context);
|
|
@@ -84,9 +85,12 @@ export class RefactoringEngine {
|
|
|
84
85
|
await this.context.initialize();
|
|
85
86
|
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
86
87
|
|
|
88
|
+
// Always attempt workspace mesh initialization – it will auto-detect workspace
|
|
89
|
+
// configuration and flip `context.isWorkspaceEnabled` when found.
|
|
90
|
+
console.log(ansis.dim('🌐 Probing for monorepo workspace configuration...'));
|
|
91
|
+
await this.workspaceGraph.initializeWorkspaceMesh();
|
|
87
92
|
if (this.context.isWorkspaceEnabled) {
|
|
88
|
-
console.log(ansis.dim('🌐
|
|
89
|
-
await this.workspaceGraph.initializeWorkspaceMesh();
|
|
93
|
+
console.log(ansis.dim('🌐 Monorepo workspace detected – mapping package mesh layers...'));
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
// Load asset fingerprints from disk cache to maximize cold-start performance
|
|
@@ -110,17 +114,6 @@ export class RefactoringEngine {
|
|
|
110
114
|
}
|
|
111
115
|
}
|
|
112
116
|
|
|
113
|
-
// Initialize TypeScript program for AST analysis (required before processFile)
|
|
114
|
-
if (sourceCodeFilesList.length > 0) {
|
|
115
|
-
try {
|
|
116
|
-
this.analyzer.initProgram(sourceCodeFilesList);
|
|
117
|
-
} catch (e) {
|
|
118
|
-
if (this.context.verbose) {
|
|
119
|
-
console.warn('Warning: Failed to initialize TypeScript program:', e.message);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
117
|
// Pass 3: Process source file tokens using high-performance concurrent workers
|
|
125
118
|
let parallelParseCompleted = false;
|
|
126
119
|
if (sourceCodeFilesList.length > 10) {
|
|
@@ -139,13 +132,51 @@ export class RefactoringEngine {
|
|
|
139
132
|
this.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
140
133
|
} else if (!parallelParseCompleted) {
|
|
141
134
|
this.context.metrics.cacheMisses++;
|
|
142
|
-
await
|
|
135
|
+
const fileContent = await fs.readFile(filePath, 'utf8'); // Read file content here
|
|
136
|
+
if (this.oxcAnalyzer.isAvailable) {
|
|
137
|
+
this.oxcAnalyzer.parseFile(filePath, fileContent, node);
|
|
138
|
+
} else {
|
|
139
|
+
this.analyzer.parseFile(filePath, fileContent, node);
|
|
140
|
+
}
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
146
144
|
node.externalPackageUsage.forEach(pkg => this.context.usedExternalPackages.add(pkg));
|
|
147
145
|
}
|
|
148
146
|
|
|
147
|
+
// Fix: Automatically mark active ecosystem packages as used.
|
|
148
|
+
// Maps internal plugin names to their canonical npm package names.
|
|
149
|
+
const pluginToPackageMap = {
|
|
150
|
+
'typescript': 'typescript',
|
|
151
|
+
'vitest': 'vitest',
|
|
152
|
+
'eslint': 'eslint',
|
|
153
|
+
'prettier': 'prettier',
|
|
154
|
+
'tailwindcss': 'tailwindcss',
|
|
155
|
+
'postcss': 'postcss',
|
|
156
|
+
'jest': 'jest',
|
|
157
|
+
'playwright': '@playwright/test',
|
|
158
|
+
'cypress': 'cypress',
|
|
159
|
+
'storybook': 'storybook',
|
|
160
|
+
'nextjs': 'next',
|
|
161
|
+
'nuxt': 'nuxt',
|
|
162
|
+
'remix': '@remix-run/dev',
|
|
163
|
+
'sveltekit': '@sveltejs/kit',
|
|
164
|
+
'astro': 'astro'
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
activeFrameworkEcosystems.forEach(ecosystem => {
|
|
168
|
+
if (ecosystem !== 'universal-tooling-vectors') {
|
|
169
|
+
const pkgName = pluginToPackageMap[ecosystem] || ecosystem;
|
|
170
|
+
this.context.usedExternalPackages.add(pkgName);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Ensure all workspace package names are pre-marked as used so they are
|
|
175
|
+
// never reported as unused dependencies in the manifest audit.
|
|
176
|
+
if (this.context.isWorkspaceEnabled) {
|
|
177
|
+
this.workspaceGraph.markWorkspacePackagesAsUsed();
|
|
178
|
+
}
|
|
179
|
+
|
|
149
180
|
// Pass 4: Evaluate graph edges and link connections across the codebase mesh
|
|
150
181
|
console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
|
|
151
182
|
await this.linkDependencyGraph();
|
|
@@ -158,44 +189,37 @@ export class RefactoringEngine {
|
|
|
158
189
|
this.circularDetector.formatCycles().forEach(c => console.log(ansis.dim(` • ${c}`)));
|
|
159
190
|
}
|
|
160
191
|
|
|
161
|
-
// NEW: Secret Detection
|
|
162
|
-
console.log(ansis.dim('🔍 Scanning for hardcoded secrets...'));
|
|
163
|
-
const detectedSecrets = await this.secretDetector.scanCodebaseForSecrets(this.context);
|
|
164
|
-
const secretStats = this.secretDetector.getSecretStats();
|
|
165
|
-
|
|
166
192
|
// Pass 5: Compile metrics summary and print diagnostics report
|
|
167
|
-
const analysisSummary = this.context.generateSummaryReport();
|
|
168
|
-
analysisSummary.detectedSecrets = detectedSecrets;
|
|
169
|
-
analysisSummary.secretStats = secretStats;
|
|
193
|
+
const analysisSummary = await this.context.generateSummaryReport();
|
|
170
194
|
this.displayConsoleDiagnostics(analysisSummary);
|
|
171
195
|
|
|
172
|
-
// Pass 6:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
196
|
+
// Pass 6: Display Optimization Plan and Run Automated Structural Healing
|
|
197
|
+
const structuralModificationsStaged =
|
|
198
|
+
analysisSummary.structuralIssuesDetected.deadFiles.length > 0 ||
|
|
199
|
+
analysisSummary.structuralIssuesDetected.deadExports.length > 0 ||
|
|
200
|
+
analysisSummary.structuralIssuesDetected.unusedDependencies.length > 0;
|
|
201
|
+
|
|
202
|
+
if (structuralModificationsStaged) {
|
|
203
|
+
console.log(ansis.bold.yellow('\n📋 Proposed Optimization Plan:'));
|
|
204
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
205
|
+
|
|
206
|
+
if (analysisSummary.structuralIssuesDetected.deadFiles.length > 0) {
|
|
207
|
+
console.log(ansis.bold(` 🗑️ Delete ${analysisSummary.structuralIssuesDetected.deadFiles.length} orphaned files:`));
|
|
208
|
+
analysisSummary.structuralIssuesDetected.deadFiles.forEach(f => console.log(ansis.dim(` • ${f}`)));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (analysisSummary.structuralIssuesDetected.deadExports.length > 0) {
|
|
212
|
+
console.log(ansis.bold(` ✂️ Prune ${analysisSummary.structuralIssuesDetected.deadExports.length} unused named exports:`));
|
|
213
|
+
analysisSummary.structuralIssuesDetected.deadExports.forEach(e => console.log(ansis.dim(` • ${e.symbol} in ${e.file}:${e.line}`)));
|
|
214
|
+
}
|
|
192
215
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
if (analysisSummary.structuralIssuesDetected.unusedDependencies.length > 0) {
|
|
217
|
+
console.log(ansis.bold(` 📦 Remove ${analysisSummary.structuralIssuesDetected.unusedDependencies.length} unused dependencies:`));
|
|
218
|
+
analysisSummary.structuralIssuesDetected.unusedDependencies.forEach(d => console.log(ansis.dim(` • ${d.package} (${d.type} in ${d.manifest})`)));
|
|
219
|
+
}
|
|
220
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
198
221
|
|
|
222
|
+
if (this.context.allowAutoFix) {
|
|
199
223
|
let proceed = this.context.skipConfirm;
|
|
200
224
|
if (!proceed) {
|
|
201
225
|
const answer = await rl.question(ansis.bold.cyan('\n❓ Apply these structural modifications? (y/N): '));
|
|
@@ -203,6 +227,7 @@ export class RefactoringEngine {
|
|
|
203
227
|
}
|
|
204
228
|
|
|
205
229
|
if (proceed) {
|
|
230
|
+
// Execute healing lifecycle (git-state-capture -> apply -> verify -> commit/rollback)
|
|
206
231
|
await this.selfHealer.runSelfHealingLifecycle(async () => {
|
|
207
232
|
for (const relPath of analysisSummary.structuralIssuesDetected.deadFiles) {
|
|
208
233
|
const absPath = path.resolve(this.context.cwd, relPath);
|
|
@@ -262,21 +287,41 @@ export class RefactoringEngine {
|
|
|
262
287
|
|
|
263
288
|
async linkDependencyGraph() {
|
|
264
289
|
for (const [filePath, node] of this.context.graph.entries()) {
|
|
290
|
+
// Pass A: Link all explicit imports (static + dynamic + re-export sources)
|
|
265
291
|
for (const specifier of node.explicitImports) {
|
|
266
292
|
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
267
293
|
if (resolvedPath && this.context.graph.has(resolvedPath)) {
|
|
268
294
|
this.context.graph.get(resolvedPath).incomingEdges.add(filePath);
|
|
269
295
|
node.outgoingEdges.add(resolvedPath);
|
|
296
|
+
|
|
297
|
+
// Fix: Ensure all internal exports from a re-exported source are marked as used
|
|
298
|
+
// so the source file itself is never considered orphaned.
|
|
299
|
+
const targetNode = this.context.graph.get(resolvedPath);
|
|
300
|
+
const isReExport = Array.from(node.internalExports.values()).some(exp => exp.source === specifier);
|
|
301
|
+
if (isReExport) {
|
|
302
|
+
targetNode.isLibraryEntry = true; // Protect re-exported internal files
|
|
303
|
+
}
|
|
270
304
|
}
|
|
271
305
|
}
|
|
272
306
|
|
|
307
|
+
// Pass B: Link named-symbol imports through barrel/re-export chains
|
|
273
308
|
for (const specToken of node.importedSymbols) {
|
|
274
309
|
const delimiterIndex = specToken.indexOf(':');
|
|
275
310
|
if (delimiterIndex === -1) continue;
|
|
276
311
|
const specifier = specToken.slice(0, delimiterIndex);
|
|
277
312
|
const symbol = specToken.slice(delimiterIndex + 1);
|
|
278
313
|
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
279
|
-
|
|
314
|
+
|
|
315
|
+
if (!resolvedPath) continue;
|
|
316
|
+
|
|
317
|
+
if (symbol === '*') {
|
|
318
|
+
// Wildcard import / re-export-all: add a direct edge to the resolved file.
|
|
319
|
+
if (this.context.graph.has(resolvedPath)) {
|
|
320
|
+
this.context.graph.get(resolvedPath).incomingEdges.add(filePath);
|
|
321
|
+
node.outgoingEdges.add(resolvedPath);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
// Named import: trace through barrel files to the actual declaration origin.
|
|
280
325
|
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.graph);
|
|
281
326
|
if (traceResolution && this.context.graph.has(traceResolution.originFile)) {
|
|
282
327
|
this.context.graph.get(traceResolution.originFile).incomingEdges.add(filePath);
|
|
@@ -293,7 +338,6 @@ export class RefactoringEngine {
|
|
|
293
338
|
const data = JSON.parse(text);
|
|
294
339
|
const prodDeps = Object.keys(data.dependencies || {});
|
|
295
340
|
const devDeps = Object.keys(data.devDependencies || {});
|
|
296
|
-
const totalDependencies = [...prodDeps, ...devDeps];
|
|
297
341
|
|
|
298
342
|
this.context.manifestDependencies.set(packageJsonPath, {
|
|
299
343
|
dependencies: prodDeps,
|
|
@@ -301,66 +345,30 @@ export class RefactoringEngine {
|
|
|
301
345
|
peerDependencies: Object.keys(data.peerDependencies || {}),
|
|
302
346
|
optionalDependencies: Object.keys(data.optionalDependencies || {})
|
|
303
347
|
});
|
|
304
|
-
|
|
305
|
-
const supplyChainThreats = this.supplyChainGuard.detectTyposquattingAnomalies(totalDependencies);
|
|
306
|
-
for (const anomaly of supplyChainThreats) {
|
|
307
|
-
console.warn(ansis.bold.red(`🚨 Supply Chain Alert: Malicious package masking candidate discovered [${anomaly.maliciousCandidate}]. Mimics trusted library [${anomaly.targetMimicked}].`));
|
|
308
|
-
}
|
|
309
|
-
await this.supplyChainGuard.verifyIntegrityLockfileHashes(packageJsonPath);
|
|
310
|
-
} catch {}
|
|
348
|
+
} catch (e) {}
|
|
311
349
|
}
|
|
312
350
|
|
|
313
351
|
displayConsoleDiagnostics(summary) {
|
|
314
|
-
console.log(ansis.bold.cyan('\n📊
|
|
352
|
+
console.log(ansis.bold.cyan('\n📊 Codebase Optimization Summary Report'));
|
|
315
353
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
316
|
-
console.log(`⏱️ Duration
|
|
317
|
-
console.log(
|
|
318
|
-
console.log(
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (summary.structuralIssuesDetected.deadFiles.length
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
summary.structuralIssuesDetected.deadExports.forEach(e => console.log(ansis.dim(` • ${e.symbol} in ${e.file}:${e.line}`)));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (summary.structuralIssuesDetected.unusedDependencies.length > 0) {
|
|
332
|
-
console.log(ansis.bold.red(`\n📦 Unused Dependencies (${summary.structuralIssuesDetected.unusedDependencies.length}):`));
|
|
333
|
-
summary.structuralIssuesDetected.unusedDependencies.forEach(d => console.log(ansis.dim(` • ${d.package} (${d.type} in ${d.manifest})`)));
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
if (summary.structuralIssuesDetected.securityThreats.length > 0) {
|
|
337
|
-
console.log(ansis.bold.red(`
|
|
338
|
-
🛡️ Security Threats (${summary.structuralIssuesDetected.securityThreats.length}):`));
|
|
339
|
-
summary.structuralIssuesDetected.securityThreats.forEach(t => console.log(ansis.dim(` • ${t.identifier} in ${t.file}:${t.line} (Risk: ${t.riskCode})`)));
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Display detected secrets
|
|
343
|
-
if (summary.detectedSecrets && summary.detectedSecrets.length > 0) {
|
|
344
|
-
const criticalSecrets = summary.detectedSecrets.filter(s => s.severity === 'CRITICAL');
|
|
345
|
-
const highSecrets = summary.detectedSecrets.filter(s => s.severity === 'HIGH');
|
|
346
|
-
|
|
347
|
-
console.log(ansis.bold.red(`
|
|
348
|
-
🔐 Hardcoded Secrets Detected (${summary.detectedSecrets.length}):`));
|
|
349
|
-
|
|
350
|
-
if (criticalSecrets.length > 0) {
|
|
351
|
-
console.log(ansis.bold.red(` CRITICAL (${criticalSecrets.length}):`));
|
|
352
|
-
criticalSecrets.forEach(s => {
|
|
353
|
-
const relPath = s.file.split(/[\\/]/).slice(-2).join('/');
|
|
354
|
-
console.log(ansis.dim(` • ${s.type} in ${relPath}:${s.line} [${s.variable}]`));
|
|
355
|
-
});
|
|
354
|
+
console.log(`⏱️ Analysis Duration: ${summary.executionDuration}`);
|
|
355
|
+
console.log(`📂 Total Files Scanned: ${summary.totalFilesProcessed}`);
|
|
356
|
+
console.log(`💾 Cache Optimization: ${summary.graphCacheOptimization.ratio} hits`);
|
|
357
|
+
|
|
358
|
+
console.log(ansis.bold('\n🔍 Structural Integrity:'));
|
|
359
|
+
if (summary.structuralIssuesDetected.deadFiles.length === 0 &&
|
|
360
|
+
summary.structuralIssuesDetected.deadExports.length === 0 &&
|
|
361
|
+
summary.structuralIssuesDetected.unusedDependencies.length === 0) {
|
|
362
|
+
console.log(ansis.green(' ✅ No major structural debt detected.'));
|
|
363
|
+
} else {
|
|
364
|
+
if (summary.structuralIssuesDetected.deadFiles.length > 0) {
|
|
365
|
+
console.log(ansis.red(` ❌ Found ${summary.structuralIssuesDetected.deadFiles.length} orphaned/dead files.`));
|
|
356
366
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.log(ansis.dim(` • ${s.type} in ${relPath}:${s.line} [${s.variable}]`));
|
|
363
|
-
});
|
|
367
|
+
if (summary.structuralIssuesDetected.deadExports.length > 0) {
|
|
368
|
+
console.log(ansis.yellow(` ⚠️ Found ${summary.structuralIssuesDetected.deadExports.length} unused named exports.`));
|
|
369
|
+
}
|
|
370
|
+
if (summary.structuralIssuesDetected.unusedDependencies.length > 0) {
|
|
371
|
+
console.log(ansis.yellow(` 📦 Found ${summary.structuralIssuesDetected.unusedDependencies.length} unused dependencies.`));
|
|
364
372
|
}
|
|
365
373
|
}
|
|
366
374
|
|
|
@@ -377,5 +385,13 @@ export class RefactoringEngine {
|
|
|
377
385
|
if (cachedRecord.symbolSourceLocations) {
|
|
378
386
|
Object.entries(cachedRecord.symbolSourceLocations).forEach(([k, v]) => node.symbolSourceLocations.set(k, v));
|
|
379
387
|
}
|
|
388
|
+
// Restore fields that were previously missing from cache hydration
|
|
389
|
+
if (cachedRecord.externalPackageUsage) cachedRecord.externalPackageUsage.forEach(p => node.externalPackageUsage.add(p));
|
|
390
|
+
if (cachedRecord.rawStringReferences) cachedRecord.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
|
|
391
|
+
if (cachedRecord.instantiatedIdentifiers) cachedRecord.instantiatedIdentifiers.forEach(id => node.instantiatedIdentifiers.add(id));
|
|
392
|
+
if (cachedRecord.propertyAccessChains) cachedRecord.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
|
|
393
|
+
if (cachedRecord.localSuppressedRules) cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
|
|
394
|
+
if (cachedRecord.calculatedDynamicImports) node.calculatedDynamicImports = cachedRecord.calculatedDynamicImports;
|
|
395
|
+
if (cachedRecord.isLibraryEntry !== undefined) node.isLibraryEntry = cachedRecord.isLibraryEntry;
|
|
380
396
|
}
|
|
381
397
|
}
|
|
@@ -31,18 +31,26 @@ async function processThreadChunks() {
|
|
|
31
31
|
securityThreats: [],
|
|
32
32
|
localSuppressedRules: new Set(),
|
|
33
33
|
externalPackageUsage: new Set(),
|
|
34
|
-
symbolSourceLocations: new Map()
|
|
34
|
+
symbolSourceLocations: new Map(),
|
|
35
|
+
calculatedDynamicImports: [],
|
|
36
|
+
jsxComponents: new Set(),
|
|
37
|
+
jsxProps: new Set(),
|
|
38
|
+
decorators: new Set()
|
|
35
39
|
};
|
|
36
40
|
|
|
41
|
+
// Use the public getScriptKind() method added to ASTAnalyzer
|
|
42
|
+
const scriptKind = standaloneAnalyzer.getScriptKind(file);
|
|
43
|
+
|
|
37
44
|
const sourceFile = ts.createSourceFile(
|
|
38
45
|
file,
|
|
39
46
|
text,
|
|
40
47
|
ts.ScriptTarget.Latest,
|
|
41
48
|
true,
|
|
42
|
-
|
|
49
|
+
scriptKind
|
|
43
50
|
);
|
|
44
51
|
|
|
45
52
|
standaloneAnalyzer.extractTopLevelJSDocSuppreessions(sourceFile, mockNode);
|
|
53
|
+
// Use the walkNode() alias that maps to walkAST() with the correct argument order
|
|
46
54
|
standaloneAnalyzer.walkNode(sourceFile, sourceFile, mockNode);
|
|
47
55
|
|
|
48
56
|
partialGraphPayloadResults.push({
|
|
@@ -57,10 +65,14 @@ async function processThreadChunks() {
|
|
|
57
65
|
securityThreats: mockNode.securityThreats,
|
|
58
66
|
localSuppressedRules: Array.from(mockNode.localSuppressedRules),
|
|
59
67
|
externalPackageUsage: Array.from(mockNode.externalPackageUsage),
|
|
60
|
-
symbolSourceLocations: Object.fromEntries(mockNode.symbolSourceLocations)
|
|
68
|
+
symbolSourceLocations: Object.fromEntries(mockNode.symbolSourceLocations),
|
|
69
|
+
calculatedDynamicImports: mockNode.calculatedDynamicImports
|
|
61
70
|
});
|
|
62
|
-
} catch {
|
|
63
|
-
//
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Log parse errors in verbose mode so they are not silently swallowed
|
|
73
|
+
if (contextOptions.verbose) {
|
|
74
|
+
console.warn(`[Worker] Failed to parse ${file}: ${err.message}`);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
}
|
|
66
78
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadAdditionalPlugins } from "./ecosystems/PluginLoader.js";
|
|
1
2
|
import path from 'path';
|
|
2
3
|
import fs from 'fs/promises';
|
|
3
4
|
import { pathToFileURL } from 'url';
|
|
@@ -53,6 +54,15 @@ export class PluginRegistry {
|
|
|
53
54
|
// Backend Services (New in v4.0)
|
|
54
55
|
const { GraphQLPlugin, DatabasePlugin } = await import('./ecosystems/BackendServices.js');
|
|
55
56
|
|
|
57
|
+
// Extended tooling plugins (New in v4.1)
|
|
58
|
+
const {
|
|
59
|
+
TailwindPlugin, PostcssPlugin, JestPlugin, VitestPlugin,
|
|
60
|
+
PlaywrightPlugin, CypressPlugin, StorybookPlugin,
|
|
61
|
+
EslintPlugin, PrettierPlugin, HuskyPlugin, LintStagedPlugin,
|
|
62
|
+
CommitlintPlugin, BabelPlugin, RollupPlugin, WebpackPlugin,
|
|
63
|
+
GithubActionsPlugin
|
|
64
|
+
} = await import('./ecosystems/MorePlugins.js');
|
|
65
|
+
|
|
56
66
|
const builtins = [
|
|
57
67
|
new NextJsPlugin(this.context),
|
|
58
68
|
new NuxtPlugin(this.context),
|
|
@@ -65,7 +75,24 @@ export class PluginRegistry {
|
|
|
65
75
|
new SveltePlugin(this.context),
|
|
66
76
|
new AngularPlugin(this.context),
|
|
67
77
|
new GraphQLPlugin(this.context),
|
|
68
|
-
new DatabasePlugin(this.context)
|
|
78
|
+
new DatabasePlugin(this.context),
|
|
79
|
+
// Extended tooling plugins
|
|
80
|
+
new TailwindPlugin(this.context),
|
|
81
|
+
new PostcssPlugin(this.context),
|
|
82
|
+
new JestPlugin(this.context),
|
|
83
|
+
new VitestPlugin(this.context),
|
|
84
|
+
new PlaywrightPlugin(this.context),
|
|
85
|
+
new CypressPlugin(this.context),
|
|
86
|
+
new StorybookPlugin(this.context),
|
|
87
|
+
new EslintPlugin(this.context),
|
|
88
|
+
new PrettierPlugin(this.context),
|
|
89
|
+
new HuskyPlugin(this.context),
|
|
90
|
+
new LintStagedPlugin(this.context),
|
|
91
|
+
new CommitlintPlugin(this.context),
|
|
92
|
+
new BabelPlugin(this.context),
|
|
93
|
+
new RollupPlugin(this.context),
|
|
94
|
+
new WebpackPlugin(this.context),
|
|
95
|
+
new GithubActionsPlugin(this.context)
|
|
69
96
|
];
|
|
70
97
|
|
|
71
98
|
builtins.forEach(p => {
|
|
@@ -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,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
|
+
}
|