pkg-scaffold 3.3.2 → 3.3.4

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.
@@ -1,124 +1,236 @@
1
- let oxcParser;
2
- try {
3
- oxcParser = await import('oxc-parser');
4
- } catch (e) {
5
- // OXC is optional; will fall back to TypeScript in ASTAnalyzer
6
- }
7
-
8
- import fs from 'fs/promises';
9
-
10
- /**
11
- * High-Performance AST Analyzer using OXC (Rust-based)
12
- * Designed to outpace Knip v6 by utilizing the fastest parser in the JS ecosystem.
13
- * Includes automatic fallback if OXC is not available in the environment.
14
- */
15
1
  export class OxcAnalyzer {
16
2
  constructor(context) {
17
3
  this.context = context;
18
- this.isAvailable = !!oxcParser;
19
- }
20
-
21
- async processFile(filePath, fileNode) {
22
- if (!this.isAvailable) {
4
+ try {
5
+ this.oxc = require("oxc-parser");
6
+ this.isAvailable = true;
7
+ } catch (e) {
8
+ this.isAvailable = false;
23
9
  if (this.context.verbose) {
24
- console.warn(`[OXC] Library not available, skipping fast-scan for: ${filePath}`);
10
+ console.warn("[OxcAnalyzer] oxc-parser not found, falling back to TypeScript compiler API.");
25
11
  }
26
- return false;
27
12
  }
13
+ }
14
+
15
+ parseFile(filePath, content, fileNode) {
16
+ if (!this.isAvailable) return false;
28
17
 
29
18
  try {
30
- const sourceText = await fs.readFile(filePath, 'utf8');
31
-
32
- const { program } = oxcParser.parseSync(sourceText, {
19
+ if (this.context.verbose) {
20
+ console.log(`[OXC] Fast-parsing: ${filePath}`);
21
+ }
22
+
23
+ const ast = this.oxc.parseSync(content, {
24
+ sourceType: "module",
33
25
  sourceFilename: filePath,
34
- sourceType: filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'script'
26
+ ecmaVersion: "latest",
35
27
  });
36
28
 
37
- this.walkOxcNode(program, fileNode);
29
+ // Initialize new properties for JSX and Decorator analysis
30
+ fileNode.jsxComponents = new Set();
31
+ fileNode.jsxProps = new Set();
32
+ fileNode.decorators = new Set();
33
+
34
+ this.walkOxcAst(ast.program, fileNode, content);
38
35
  return true;
39
- } catch (err) {
36
+ } catch (e) {
40
37
  if (this.context.verbose) {
41
- console.error(`[OXC Error] Failed parsing: ${filePath}. Reason: ${err.message}`);
38
+ console.warn(`[OXC] Failed to parse ${filePath}: ${e.message}`);
42
39
  }
43
40
  return false;
44
41
  }
45
42
  }
46
43
 
47
- walkOxcNode(node, fileNode) {
44
+ walkOxcAst(node, fileNode, content) {
48
45
  if (!node) return;
49
46
 
50
- if (node.type === 'ImportDeclaration') {
51
- const specifier = node.source.value;
52
- fileNode.explicitImports.add(specifier);
53
- node.specifiers.forEach(s => {
54
- if (s.type === 'ImportSpecifier') {
55
- fileNode.importedSymbols.add(`${specifier}:${s.imported.name}`);
56
- } else if (s.type === 'ImportDefaultSpecifier') {
47
+ switch (node.type) {
48
+ case "ImportDeclaration":
49
+ this.handleImportDeclaration(node, fileNode);
50
+ break;
51
+ case "ExportNamedDeclaration":
52
+ case "ExportDefaultDeclaration":
53
+ case "ExportAllDeclaration":
54
+ this.handleExportDeclaration(node, fileNode, content);
55
+ break;
56
+ case "CallExpression":
57
+ this.handleCallExpression(node, fileNode);
58
+ break;
59
+ case "JSXElement":
60
+ case "JSXFragment": // Consider fragments as well
61
+ this.handleJsxElement(node, fileNode);
62
+ break;
63
+ case "Decorator":
64
+ this.handleDecorator(node, fileNode);
65
+ break;
66
+ }
67
+
68
+ // Traverse children
69
+ for (const key in node) {
70
+ if (node[key] && typeof node[key] === "object") {
71
+ if (Array.isArray(node[key])) {
72
+ node[key].forEach((child) => this.walkOxcAst(child, fileNode, content));
73
+ } else {
74
+ this.walkOxcAst(node[key], fileNode, content);
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ handleImportDeclaration(node, fileNode) {
81
+ const specifier = node.source.value;
82
+ fileNode.explicitImports.add(specifier);
83
+
84
+ if (node.specifiers) {
85
+ node.specifiers.forEach((spec) => {
86
+ if (spec.type === "ImportSpecifier") {
87
+ const importedName = spec.imported.name;
88
+ const localName = spec.local.name;
89
+ fileNode.importedSymbols.add(`${specifier}:${importedName}`);
90
+ } else if (spec.type === "ImportDefaultSpecifier") {
57
91
  fileNode.importedSymbols.add(`${specifier}:default`);
58
- } else if (s.type === 'ImportNamespaceSpecifier') {
92
+ } else if (spec.type === "ImportNamespaceSpecifier") {
59
93
  fileNode.importedSymbols.add(`${specifier}:*`);
60
94
  }
61
95
  });
62
96
  }
97
+ }
98
+
99
+ handleExportDeclaration(node, fileNode, content) {
100
+ if (node.type === "ExportDefaultDeclaration") {
101
+ fileNode.internalExports.set("default", { type: "default", start: node.start, end: node.end });
102
+ return;
103
+ }
63
104
 
64
- if (node.type === 'ExportNamedDeclaration') {
65
- if (node.declaration) {
66
- this.handleOxcDeclaration(node.declaration, fileNode);
105
+ if (node.type === "ExportAllDeclaration") {
106
+ if (node.exported && node.exported.type === "ExportNamespaceSpecifier") {
107
+ // export * as name from 'module'
108
+ const name = node.exported.name;
109
+ fileNode.internalExports.set(name, { type: "re-export-namespace", source: node.source.value, originalName: "*", start: node.start, end: node.end });
110
+ } else {
111
+ // export * from 'module'
112
+ fileNode.internalExports.set("*", { type: "re-export-all", source: node.source.value });
67
113
  }
114
+ return;
115
+ }
116
+
117
+ if (node.source) {
118
+ // Re-export
119
+ const specifier = node.source.value;
68
120
  if (node.specifiers) {
69
- node.specifiers.forEach(s => {
70
- fileNode.internalExports.set(s.exported.name, { type: 'export-specifier' });
121
+ node.specifiers.forEach((spec) => {
122
+ const exportedName = spec.exported.name;
123
+ const localName = spec.local.name;
124
+ fileNode.internalExports.set(exportedName, {
125
+ type: "re-export",
126
+ source: specifier,
127
+ originalName: localName,
128
+ start: node.start,
129
+ end: node.end,
130
+ });
131
+ });
132
+ }
133
+ } else if (node.declaration) {
134
+ // Direct export
135
+ const decl = node.declaration;
136
+ if (decl.type === "VariableDeclaration") {
137
+ decl.declarations.forEach((d) => {
138
+ if (d.id.type === "Identifier") {
139
+ fileNode.internalExports.set(d.id.name, { type: "variable", start: d.start, end: d.end });
140
+ } else if (d.id.type === "ObjectPattern") {
141
+ d.id.properties.forEach((p) => {
142
+ if (p.type === "Property" && p.value.type === "Identifier") {
143
+ fileNode.internalExports.set(p.value.name, { type: "variable", start: p.start, end: p.end });
144
+ }
145
+ });
146
+ } else if (d.id.type === "ArrayPattern") {
147
+ d.id.elements.forEach((e) => {
148
+ if (e && e.type === "Identifier") {
149
+ fileNode.internalExports.set(e.name, { type: "variable", start: e.start, end: e.end });
150
+ }
151
+ });
152
+ }
71
153
  });
154
+ } else if (decl.id && decl.id.name) {
155
+ let type = "unknown";
156
+ if (decl.type === "FunctionDeclaration") type = "function";
157
+ else if (decl.type === "ClassDeclaration") type = "class";
158
+ else if (decl.type === "TSEnumDeclaration") type = "enum";
159
+ else if (decl.type === "TSInterfaceDeclaration") type = "interface";
160
+ else if (decl.type === "TSTypeAliasDeclaration") type = "type";
161
+ else if (decl.type === "TSModuleDeclaration") type = "namespace";
162
+
163
+ const exportInfo = { type, start: decl.start, end: decl.end };
164
+ fileNode.internalExports.set(decl.id.name, exportInfo);
165
+
166
+ if (decl.type === "TSEnumDeclaration") {
167
+ exportInfo.members = decl.members.map((m) => m.id.name || (m.id.type === "Identifier" ? m.id.name : ""));
168
+ } else if (decl.type === "TSInterfaceDeclaration" || decl.type === "ClassDeclaration") {
169
+ exportInfo.members = decl.body.body.filter((m) => m.key && m.key.name).map((m) => m.key.name);
170
+ }
72
171
  }
172
+ } else if (node.specifiers) {
173
+ node.specifiers.forEach((spec) => {
174
+ const exportedName = spec.exported.name;
175
+ const localName = spec.local.name;
176
+ fileNode.internalExports.set(exportedName, {
177
+ type: "export",
178
+ originalName: localName,
179
+ start: node.start,
180
+ end: node.end,
181
+ });
182
+ });
73
183
  }
184
+ }
74
185
 
75
- if (node.type === 'ExportDefaultDeclaration') {
76
- fileNode.internalExports.set('default', { type: 'default-export' });
186
+ handleCallExpression(node, fileNode) {
187
+ if (node.callee.type === "Import" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
188
+ const specifier = node.arguments[0].value;
189
+ fileNode.explicitImports.add(specifier);
190
+ fileNode.dynamicImports.add(specifier);
191
+ } else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
192
+ fileNode.explicitImports.add(node.arguments[0].value);
77
193
  }
194
+ }
195
+
196
+ handleJsxElement(node, fileNode) {
197
+ const getElementName = (nameNode) => {
198
+ if (nameNode.type === "JSXIdentifier") return nameNode.name;
199
+ if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
200
+ return "unknown";
201
+ };
78
202
 
79
- if (node.type === 'TSModuleDeclaration' && node.id.type === 'Identifier') {
80
- const namespaceName = node.id.name;
81
- if (node.body && node.body.type === 'TSModuleBlock') {
82
- node.body.body.forEach(innerNode => {
83
- if (innerNode.type === 'ExportNamedDeclaration' && innerNode.declaration) {
84
- const decl = innerNode.declaration;
85
- let memberName;
86
- if (decl.type === 'VariableDeclaration') {
87
- memberName = decl.declarations[0].id.name;
88
- } else if (decl.id) {
89
- memberName = decl.id.name;
90
- }
91
- if (memberName) {
92
- fileNode.namespaceMembers = fileNode.namespaceMembers || new Set();
93
- fileNode.namespaceMembers.add(`${namespaceName}.${memberName}`);
94
- }
203
+ if (node.openingElement) {
204
+ const tagName = getElementName(node.openingElement.name);
205
+ fileNode.jsxComponents.add(tagName);
206
+
207
+ if (node.openingElement.attributes) {
208
+ node.openingElement.attributes.forEach(attr => {
209
+ if (attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier") {
210
+ fileNode.jsxProps.add(`${tagName}:${attr.name.name}`);
95
211
  }
96
212
  });
97
213
  }
98
214
  }
99
-
100
- for (const key in node) {
101
- const child = node[key];
102
- if (child && typeof child === 'object') {
103
- if (Array.isArray(child)) {
104
- child.forEach(c => this.walkOxcNode(c, fileNode));
105
- } else {
106
- this.walkOxcNode(child, fileNode);
107
- }
108
- }
109
- }
110
215
  }
111
216
 
112
- handleOxcDeclaration(decl, fileNode) {
113
- let name;
114
- if (decl.type === 'FunctionDeclaration' || decl.type === 'ClassDeclaration' || decl.type === 'TSEnumDeclaration' || decl.type === 'TSInterfaceDeclaration' || decl.type === 'TSTypeAliasDeclaration') {
115
- name = decl.id?.name;
116
- } else if (decl.type === 'VariableDeclaration') {
117
- name = decl.declarations[0].id.name;
118
- }
119
-
120
- if (name) {
121
- fileNode.internalExports.set(name, { type: decl.type.toLowerCase() });
217
+ handleDecorator(node, fileNode) {
218
+ const getDecoratorName = (expr) => {
219
+ if (expr.type === "Identifier") return expr.name;
220
+ if (expr.type === "CallExpression" && expr.callee.type === "Identifier") return expr.callee.name;
221
+ if (expr.type === "CallExpression" && expr.callee.type === "MemberExpression" && expr.callee.property.type === "Identifier") return expr.callee.property.name;
222
+ return "unknown";
223
+ };
224
+
225
+ const decoratorName = getDecoratorName(node.expression);
226
+ fileNode.decorators.add(decoratorName);
227
+
228
+ // Optionally, extract decorator arguments
229
+ if (node.expression.type === "CallExpression") {
230
+ node.expression.arguments.forEach(arg => {
231
+ // Further analysis of arguments can be done here if needed
232
+ // e.g., if (arg.type === "StringLiteral") fileNode.decoratorArgs.add(`${decoratorName}:${arg.value}`);
233
+ });
122
234
  }
123
235
  }
124
236
  }
package/src/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { DeadCodeDetector } from "./ast/DeadCodeDetector.js";
2
+ import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
1
3
  /**
2
4
  * ============================================================================
3
5
  * 📦 pkg-scaffold v3.3.2: Unified Architectural Refactoring Orchestrator
@@ -47,6 +49,7 @@ export class RefactoringEngine {
47
49
 
48
50
  // Stage 3: Wire official AST Syntax parsers and framework processors
49
51
  this.analyzer = new ASTAnalyzer(this.context);
52
+ this.oxcAnalyzer = new OxcAnalyzer(this.context);
50
53
  this.barrelParser = new BarrelParser(this.context, this.resolver);
51
54
  this.magicDetector = new MagicDetector(this.context);
52
55
 
@@ -110,16 +113,7 @@ export class RefactoringEngine {
110
113
  }
111
114
  }
112
115
 
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
- }
116
+
123
117
 
124
118
  // Pass 3: Process source file tokens using high-performance concurrent workers
125
119
  let parallelParseCompleted = false;
@@ -139,7 +133,12 @@ export class RefactoringEngine {
139
133
  this.hydrateNodeFromCache(node, cacheManifest[filePath]);
140
134
  } else if (!parallelParseCompleted) {
141
135
  this.context.metrics.cacheMisses++;
142
- await this.analyzer.processFile(filePath, node);
136
+ const fileContent = await fs.readFile(filePath, 'utf8'); // Read file content here
137
+ if (this.oxcAnalyzer.isAvailable) {
138
+ this.oxcAnalyzer.parseFile(filePath, fileContent, node);
139
+ } else {
140
+ this.analyzer.parseFile(filePath, fileContent, node);
141
+ }
143
142
  }
144
143
 
145
144
  this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
@@ -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';
@@ -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
+ }
@@ -1,31 +1,14 @@
1
- /**
2
- * ============================================================================
3
- * Circular Dependency Detector for pkg-scaffold v3.3.0
4
- *
5
- * Copyright (C) 2026 DreamLongYT
6
- * Licensed under the Apache License, Version 2.0.
7
- * "The Original Code was made by DreamLongYT"
8
- * ============================================================================
9
- * Implements a high-performance Tarjan-based algorithm to
10
- * detect circular dependencies in the codebase graph.
11
- * Addresses Knip Issue #1734.
12
- */
13
-
14
1
  export class CircularDetector {
15
2
  constructor(context) {
16
3
  this.context = context;
17
4
  this.cycles = [];
18
5
  }
19
6
 
20
- /**
21
- * Detects cycles in the provided dependency graph using Tarjan's SCC algorithm
22
- * @param {Map} graph - The codebase dependency graph
23
- * @returns {Array} List of detected cycles
24
- */
25
7
  detectCycles(graph, context = null) {
26
8
  if (context) this.context = context;
27
9
  this.cwd = context?.cwd || this.context?.cwd || process.cwd();
28
10
  this.cycles = [];
11
+
29
12
  let index = 0;
30
13
  const stack = [];
31
14
  const indices = new Map();
@@ -63,7 +46,6 @@ export class CircularDetector {
63
46
  if (component.length > 1) {
64
47
  this.cycles.push(component.reverse());
65
48
  } else {
66
- // Check for self-loops
67
49
  const node = graph.get(v);
68
50
  if (node && node.outgoingEdges && node.outgoingEdges.has(v)) {
69
51
  this.cycles.push([v]);
@@ -81,18 +63,11 @@ export class CircularDetector {
81
63
  return this.cycles;
82
64
  }
83
65
 
84
- /**
85
- * Formats cycles for reporting with file paths
86
- */
87
66
  formatCycles() {
88
67
  return this.cycles.map(cycle => {
89
68
  const paths = cycle.map(p => {
90
- // Extract relative path for readability
91
69
  let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
92
- // Convert absolute Windows paths
93
- if (rel.includes(':\\')) {
94
- rel = rel.split(':\\')[1] || rel;
95
- }
70
+ if (rel.includes(':\\')) rel = rel.split(':\\')[1] || rel;
96
71
  return rel;
97
72
  });
98
73
  if (cycle.length === 1) return `${paths[0]} -> (self-loop)`;
@@ -100,17 +75,12 @@ export class CircularDetector {
100
75
  });
101
76
  }
102
77
 
103
- /**
104
- * Gets detailed cycle information
105
- */
106
78
  getCycleDetails() {
107
79
  return this.cycles.map((cycle, idx) => ({
108
80
  cycleId: idx + 1,
109
81
  files: cycle.map(p => {
110
82
  let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
111
- if (rel.includes(':\\')) {
112
- rel = rel.split(':\\')[1] || rel;
113
- }
83
+ if (rel.includes(':\\')) rel = rel.split(':\\')[1] || rel;
114
84
  return rel;
115
85
  }),
116
86
  length: cycle.length,
@@ -118,5 +88,4 @@ export class CircularDetector {
118
88
  }));
119
89
  }
120
90
  }
121
-
122
91
  export default CircularDetector;