pkg-scaffold 3.2.0 → 3.3.2

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.
@@ -0,0 +1,157 @@
1
+ /**
2
+ * ============================================================================
3
+ * Modern Frameworks Plugins for pkg-scaffold v4.0.0
4
+ * ============================================================================
5
+ * Built-in support for React, Vue, Svelte, and Angular.
6
+ */
7
+
8
+ import { BasePlugin } from '../BasePlugin.js';
9
+
10
+ /**
11
+ * React Ecosystem Plugin
12
+ */
13
+ export class ReactPlugin extends BasePlugin {
14
+ get name() {
15
+ return 'react';
16
+ }
17
+
18
+ getConfigFiles() {
19
+ return ['package.json'];
20
+ }
21
+
22
+ async isActive(baseDir) {
23
+ try {
24
+ const pkgJson = JSON.parse(await require('fs').promises.readFile(require('path').join(baseDir, 'package.json'), 'utf8'));
25
+ return !!(pkgJson.dependencies?.react || pkgJson.devDependencies?.react);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ getRoutePatterns() {
32
+ return [/\.(tsx?|jsx?)$/];
33
+ }
34
+
35
+ getRequiredSystemContracts() {
36
+ return ['default', 'Component', 'PureComponent', 'Fragment', 'useEffect', 'useState', 'useContext', 'useReducer', 'useCallback', 'useMemo', 'useRef', 'useImperativeHandle', 'useLayoutEffect', 'useDebugValue'];
37
+ }
38
+
39
+ async analyze(node, filePath) {
40
+ if (node.explicitImports.has('react')) {
41
+ node.isReactComponent = true;
42
+ }
43
+
44
+ // Detect JSX
45
+ if (node.rawCode && (node.rawCode.includes('</') || node.rawCode.includes('/>'))) {
46
+ node.hasJSX = true;
47
+ }
48
+
49
+ // Detect Hooks
50
+ const hookMatches = node.rawCode?.match(/use[A-Z]\w+/g) || [];
51
+ if (hookMatches.length > 0) {
52
+ node.reactHooks = new Set(hookMatches);
53
+ }
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Vue Ecosystem Plugin
59
+ */
60
+ export class VuePlugin extends BasePlugin {
61
+ get name() {
62
+ return 'vue';
63
+ }
64
+
65
+ getConfigFiles() {
66
+ return ['package.json', 'vue.config.js', 'vite.config.ts', 'vite.config.js'];
67
+ }
68
+
69
+ async isActive(baseDir) {
70
+ try {
71
+ const pkgJson = JSON.parse(await require('fs').promises.readFile(require('path').join(baseDir, 'package.json'), 'utf8'));
72
+ return !!(pkgJson.dependencies?.vue || pkgJson.devDependencies?.vue);
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ getRoutePatterns() {
79
+ return [/\.vue$/, /\.(tsx?|jsx?)$/];
80
+ }
81
+
82
+ async analyze(node, filePath) {
83
+ if (filePath.endsWith('.vue')) {
84
+ node.isVueSFC = true;
85
+ // Extract template/script/style sections
86
+ const templateMatch = node.rawCode?.match(/<template>([\s\S]*)<\/template>/);
87
+ if (templateMatch) node.vueTemplate = templateMatch[1];
88
+
89
+ const scriptMatch = node.rawCode?.match(/<script(?: setup)?(?: lang=['"]\w+['"])?>([\s\S]*)<\/script>/);
90
+ if (scriptMatch) node.vueScript = scriptMatch[1];
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Svelte Ecosystem Plugin
97
+ */
98
+ export class SveltePlugin extends BasePlugin {
99
+ get name() {
100
+ return 'svelte';
101
+ }
102
+
103
+ getConfigFiles() {
104
+ return ['package.json', 'svelte.config.js'];
105
+ }
106
+
107
+ async isActive(baseDir) {
108
+ try {
109
+ const pkgJson = JSON.parse(await require('fs').promises.readFile(require('path').join(baseDir, 'package.json'), 'utf8'));
110
+ return !!(pkgJson.dependencies?.svelte || pkgJson.devDependencies?.svelte);
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+
116
+ getRoutePatterns() {
117
+ return [/\.svelte$/, /\.(tsx?|jsx?)$/];
118
+ }
119
+
120
+ async analyze(node, filePath) {
121
+ if (filePath.endsWith('.svelte')) {
122
+ node.isSvelteComponent = true;
123
+ }
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Angular Ecosystem Plugin
129
+ */
130
+ export class AngularPlugin extends BasePlugin {
131
+ get name() {
132
+ return 'angular';
133
+ }
134
+
135
+ getConfigFiles() {
136
+ return ['package.json', 'angular.json'];
137
+ }
138
+
139
+ async isActive(baseDir) {
140
+ try {
141
+ const pkgJson = JSON.parse(await require('fs').promises.readFile(require('path').join(baseDir, 'package.json'), 'utf8'));
142
+ return !!(pkgJson.dependencies?.['@angular/core'] || pkgJson.devDependencies?.['@angular/core']);
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ getRoutePatterns() {
149
+ return [/\.ts$/];
150
+ }
151
+
152
+ async analyze(node, filePath) {
153
+ if (node.rawCode?.includes('@Component') || node.rawCode?.includes('@Injectable') || node.rawCode?.includes('@NgModule')) {
154
+ node.isAngularEntity = true;
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,122 @@
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
+ export class CircularDetector {
15
+ constructor(context) {
16
+ this.context = context;
17
+ this.cycles = [];
18
+ }
19
+
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
+ detectCycles(graph, context = null) {
26
+ if (context) this.context = context;
27
+ this.cwd = context?.cwd || this.context?.cwd || process.cwd();
28
+ this.cycles = [];
29
+ let index = 0;
30
+ const stack = [];
31
+ const indices = new Map();
32
+ const lowlink = new Map();
33
+ const onStack = new Set();
34
+
35
+ const strongconnect = (v) => {
36
+ indices.set(v, index);
37
+ lowlink.set(v, index);
38
+ index++;
39
+ stack.push(v);
40
+ onStack.add(v);
41
+
42
+ const node = graph.get(v);
43
+ if (node && node.outgoingEdges) {
44
+ for (const w of node.outgoingEdges) {
45
+ if (!indices.has(w)) {
46
+ strongconnect(w);
47
+ lowlink.set(v, Math.min(lowlink.get(v), lowlink.get(w)));
48
+ } else if (onStack.has(w)) {
49
+ lowlink.set(v, Math.min(lowlink.get(v), indices.get(w)));
50
+ }
51
+ }
52
+ }
53
+
54
+ if (lowlink.get(v) === indices.get(v)) {
55
+ const component = [];
56
+ let w;
57
+ do {
58
+ w = stack.pop();
59
+ onStack.delete(w);
60
+ component.push(w);
61
+ } while (w !== v);
62
+
63
+ if (component.length > 1) {
64
+ this.cycles.push(component.reverse());
65
+ } else {
66
+ // Check for self-loops
67
+ const node = graph.get(v);
68
+ if (node && node.outgoingEdges && node.outgoingEdges.has(v)) {
69
+ this.cycles.push([v]);
70
+ }
71
+ }
72
+ }
73
+ };
74
+
75
+ for (const v of graph.keys()) {
76
+ if (!indices.has(v)) {
77
+ strongconnect(v);
78
+ }
79
+ }
80
+
81
+ return this.cycles;
82
+ }
83
+
84
+ /**
85
+ * Formats cycles for reporting with file paths
86
+ */
87
+ formatCycles() {
88
+ return this.cycles.map(cycle => {
89
+ const paths = cycle.map(p => {
90
+ // Extract relative path for readability
91
+ let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
92
+ // Convert absolute Windows paths
93
+ if (rel.includes(':\\')) {
94
+ rel = rel.split(':\\')[1] || rel;
95
+ }
96
+ return rel;
97
+ });
98
+ if (cycle.length === 1) return `${paths[0]} -> (self-loop)`;
99
+ return paths.join(' -> ') + ' -> ' + paths[0];
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Gets detailed cycle information
105
+ */
106
+ getCycleDetails() {
107
+ return this.cycles.map((cycle, idx) => ({
108
+ cycleId: idx + 1,
109
+ files: cycle.map(p => {
110
+ let rel = p.replace(this.context.cwd, '').replace(/^\//, '');
111
+ if (rel.includes(':\\')) {
112
+ rel = rel.split(':\\')[1] || rel;
113
+ }
114
+ return rel;
115
+ }),
116
+ length: cycle.length,
117
+ isSelfLoop: cycle.length === 1
118
+ }));
119
+ }
120
+ }
121
+
122
+ export default CircularDetector;
@@ -25,15 +25,25 @@ export class PathMapper {
25
25
  const rawText = await fs.readFile(configPath, 'utf8');
26
26
 
27
27
  // Strip inline single-line and block comments before parsing
28
- const jsonCleanText = rawText.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
28
+ // Improved regex to handle more edge cases in tsconfig comments
29
+ const jsonCleanText = rawText
30
+ .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1')
31
+ .replace(/,(\s*[\]}])/g, '$1'); // Remove trailing commas
32
+
29
33
  const tsconfig = JSON.parse(jsonCleanText);
30
34
 
31
35
  if (!tsconfig.compilerOptions) return;
32
36
 
33
37
  const opts = tsconfig.compilerOptions;
38
+
39
+ // v6 Path Resolution Fix (Knip Issue #1794)
40
+ // Ensure baseUrl is correctly resolved relative to the tsconfig file location
41
+ const configDir = path.dirname(configPath);
34
42
  if (opts.baseUrl) {
35
43
  this.baseUrl = opts.baseUrl;
36
- this.absoluteBaseUrl = path.resolve(this.context.cwd, this.baseUrl);
44
+ this.absoluteBaseUrl = path.resolve(configDir, this.baseUrl);
45
+ } else {
46
+ this.absoluteBaseUrl = configDir;
37
47
  }
38
48
 
39
49
  if (opts.paths) {
@@ -21,6 +21,16 @@ export class WorkspaceGraph {
21
21
  const pnpmWorkspacePath = path.join(this.context.cwd, 'pnpm-workspace.yaml');
22
22
 
23
23
  let workspaceGlobs = [];
24
+ this.hoistedDependencies = new Set();
25
+
26
+ // Load hoisted dependencies from root package.json (Knip Issue #1792 fix)
27
+ try {
28
+ const rootPkg = JSON.parse(await fs.readFile(rootPackageJsonPath, 'utf8'));
29
+ const deps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
30
+ Object.keys(deps).forEach(d => this.hoistedDependencies.add(d));
31
+ } catch (e) {
32
+ // No root package.json or unreadable
33
+ }
24
34
 
25
35
  // Protocol A: Check for pnpm workspace configurations
26
36
  try {