pkg-scaffold 2.1.0 → 2.2.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.
Files changed (2) hide show
  1. package/index.js +1234 -1142
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1,5 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ /**
4
+ * ============================================================================
5
+ * 📦 pkg-scaffold v2.2.0: Enterprise Dependency Intelligence & Scaffolding Engine
6
+ * ============================================================================
7
+ * * Eine hochgradig integrierte Code-Analyse- und Projektbootstrapping-Engine.
8
+ * Kombiniert rekursive Erreichbarkeitsanalysen (Reachability Graphs) auf
9
+ * Knip-Niveau mit proaktiven Code-Qualitäts-Audits, Sicherheitsprüfungen,
10
+ * statischem Schwachstellen-Scanning und interaktivem Scaffolding.
11
+ *
12
+ * Diese Datei ist als vollständig entfalteter, langformbasierter Monolith
13
+ * strukturiert, um maximale Wartbarkeit, lückenlose Fehlerabdeckung und
14
+ * absolute Ausführungssicherheit im Produktivbetrieb zu garantieren.
15
+ * * Eigenschaften:
16
+ * - AST-Verifikation via Acorn (ECMAScript Latest)
17
+ * - Textbasierte Fallback-Regex-Parsing-Infrastruktur
18
+ * - Token- und Symbol-Erreichbarkeitsmatrix (Dead-Code-Eliminierung)
19
+ * - Hardcoded Credentials Extraction & .env-Isolation
20
+ */
21
+
3
22
  import fs from 'fs';
4
23
  import path from 'path';
5
24
  import { builtinModules, createRequire } from 'module';
@@ -10,86 +29,196 @@ import readline from 'readline/promises';
10
29
  import * as acorn from 'acorn';
11
30
  import * as walk from 'acorn-walk';
12
31
 
13
- const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage', 'out', '.next', '.nuxt', '.svelte-kit', 'storybook-static', '.cache']);
14
- const VALID_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte']);
32
+ /**
33
+ * Globale Konfigurations-Sets für Verzeichnis- und Dateifilterung.
34
+ * Verhindert das Eindringen von System-, Cache- und Build-Dateien in die Analyse.
35
+ * @type {Set<string>}
36
+ */
37
+ const IGNORED_DIRS = new Set([
38
+ 'node_modules',
39
+ '.git',
40
+ 'dist',
41
+ 'build',
42
+ '.turbo',
43
+ 'coverage',
44
+ 'out',
45
+ '.next',
46
+ '.nuxt',
47
+ '.svelte-kit',
48
+ 'storybook-static',
49
+ '.cache'
50
+ ]);
51
+
52
+ /**
53
+ * Unterstützte Dateiendungen für die statische Codeanalyse und das Parsing.
54
+ * @type {Set<string>}
55
+ */
56
+ const VALID_EXTENSIONS = new Set([
57
+ '.js',
58
+ '.jsx',
59
+ '.ts',
60
+ '.tsx',
61
+ '.mjs',
62
+ '.cjs',
63
+ '.vue',
64
+ '.svelte'
65
+ ]);
15
66
 
16
- // --- Refined Target Signature Dictionaries ---
67
+ /**
68
+ * --- Refined Target Signature Dictionaries ---
69
+ * Umfassendes Verzeichnis regulärer Ausdrücke für Code-Smells, kryptografische Risiken,
70
+ * Framework-Routing und Umgebungsvariablen.
71
+ * @type {Object}
72
+ */
17
73
  const REGEX_PATTERNS = {
74
+ /**
75
+ * Zugriff auf Umgebungsvariablen im Node- oder Vite-Kontext.
76
+ */
18
77
  env: /(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)/g,
78
+
79
+ /**
80
+ * Erkennung von Testdateien für diverse Testrunner.
81
+ */
19
82
  testFile: /\.(test|spec)\.(js|ts|jsx|tsx|mjs|cjs)$/i,
83
+
84
+ /**
85
+ * Erkennung gängiger Konfigurationsdateien im JavaScript-Ökosystem.
86
+ */
20
87
  configFile: /^(vite|webpack|rollup|babel|jest|vitest|tailwind|postcss|next|nuxt|svelte|astro)\.config\./i,
21
88
 
22
- // Modern Quality & Structural Code Smell Monitors
89
+ /**
90
+ * Veraltete Variablendeklarationen (Code-Smell).
91
+ */
23
92
  legacyVar: /\bvar\s+[a-zA-Z_]/g,
93
+
94
+ /**
95
+ * Gefährliche dynamische Code-Ausführung.
96
+ */
24
97
  dangerousEval: /\beval\s*\(/g,
98
+
99
+ /**
100
+ * Blockierende synchrone Dateisystemaufrufe.
101
+ */
25
102
  syncFsCalls: /\.readFileSync|\.writeFileSync|\.mkdirSync|\.existsSync/g,
26
103
 
27
- // Cryptographic Risk & Hardcoded Keyholes
104
+ /**
105
+ * Allgemeine Zuweisung harter Anmeldedaten.
106
+ */
28
107
  secretKeys: /\b(secret|passwd|password|token|api_?key|private_?key)\s*=\s*['"`]([a-zA-Z0-9_\-\.]{8,})['"`]/gi,
108
+
109
+ /**
110
+ * Spezifische AWS Zugriffsschlüssel.
111
+ */
29
112
  awsKeys: /AKIA[0-9A-Z]{16}/g,
113
+
114
+ /**
115
+ * Spezifische Google Cloud API Schlüssel.
116
+ */
30
117
  googleCloudKeys: /AIza[0-9A-Za-z\-_]{35}/g,
118
+
119
+ /**
120
+ * Live-Schlüssel für den Stripe-Zahlungsdienstleister.
121
+ */
31
122
  stripeKeys: /sk_live_[0-9a-zA-Z]{24}/g,
123
+
124
+ /**
125
+ * Slack Bot- und Benutzer-Tokens.
126
+ */
32
127
  slackKeys: /xox[baprs]-[0-9a-zA-Z]{10,48}/g,
128
+
129
+ /**
130
+ * GitHub Personal Access Tokens.
131
+ */
33
132
  githubTokens: /gh[pousr]_[a-zA-Z0-9]{36}/g,
133
+
134
+ /**
135
+ * Private RSA-Schlüsselblöcke.
136
+ */
34
137
  rsaPrivateKeys: /-----BEGIN RSA PRIVATE KEY-----/g,
138
+
139
+ /**
140
+ * Private OpenSSH-Schlüsselblöcke.
141
+ */
35
142
  sshPrivateKeys: /-----BEGIN OPENSSH PRIVATE KEY-----/g,
143
+
144
+ /**
145
+ * Private PGP-Schlüsselblöcke.
146
+ */
36
147
  pgpPrivateKeys: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g,
37
148
 
38
- // Insecure Patterns
149
+ /**
150
+ * Unsicheres Einfügen von unbereinigtem HTML (XSS-Risiko).
151
+ */
39
152
  insecureInnerHTML: /\.innerHTML\s*=/g,
153
+
154
+ /**
155
+ * Direktes Schreiben in das Dokumentenobjekt im Browser.
156
+ */
40
157
  insecureDocumentWrite: /document\.write\s*\(/g,
158
+
159
+ /**
160
+ * React-spezifisches Äquivalent zu innerHTML.
161
+ */
41
162
  insecureDangerouslySet: /dangerouslySetInnerHTML/g,
42
- insecureRegex: /\/\.\*\//g, // Common catastrophic backtracking
43
-
44
- // New: Advanced Security Patterns
45
- insecureCrypto: /crypto\.(?:createCipher|createDecipher|pbkdf2Sync)/g, // Deprecated/insecure crypto usage
46
- sqlInjection: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\s+FROM\s+.*\s+WHERE\s+.*\s*=\s*[""]?.*[""]?/i, // Basic SQL injection pattern
47
- xssVulnerability: /<script\b[^>]*>[\s\S]*?<\/script>/i, // Basic XSS script tag detection
48
-
49
- // New: Performance Patterns
50
- largeImageImport: /import\s+.*\s+from\s+[""](?:.*\.(?:png|jpg|jpeg|gif|svg))[""]/g, // Direct import of large images
51
- unoptimizedLoop: /for\s*\(let\s+i\s*=\s*0;\s*i\s*<\s*\w+\.length;\s*i\s*\+\+\)/g, // Simple for loop, can be optimized with for-of or forEach
52
-
53
- // New: Framework-specific patterns (examples)
54
- nextjsImageComponent: /<Image\s+[^>]*>/g, // Next.js Image component usage
55
- nextjsFontOptimization: /next\/font/g, // Next.js Font optimization usage
56
- nuxtAutoImport: /use(?:State|Fetch|AsyncData)/g, // Nuxt 3 auto-imports
57
- sveltekitLoadFunction: /export\s+const\s+load\s*=/g, // SvelteKit load function
58
- reactUseEffectNoDeps: /useEffect\s*\(\s*\([^)]*\)\s*=>\s*{[^}]*},\s*\[\s*\]\s*\)/g, // useEffect with empty dependency array (potential for stale closures)
59
-
60
- // New: Advanced Security Patterns
61
- insecureCrypto: /crypto\.(?:createCipher|createDecipher|pbkdf2Sync)/g, // Deprecated/insecure crypto usage
62
- sqlInjection: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\s+FROM\s+.*\s+WHERE\s+.*\s*=\s*[""]?.*[""]?/i, // Basic SQL injection pattern
63
- xssVulnerability: /<script\b[^>]*>[\s\S]*?<\/script>/i, // Basic XSS script tag detection
64
-
65
- // New: Performance Patterns
66
- largeImageImport: /import\s+.*\s+from\s+[""](?:.*\.(?:png|jpg|jpeg|gif|svg))[""]/g, // Direct import of large images
67
- unoptimizedLoop: /for\s*\(let\s+i\s*=\s*0;\s*i\s*<\s*\w+\.length;\s*i\s*\+\+\)/g, // Simple for loop, can be optimized with for-of or forEach
68
-
69
- // New: Framework-specific patterns (examples)
70
- nextjsImageComponent: /<Image\s+[^>]*>/g, // Next.js Image component usage
71
- nextjsFontOptimization: /next\/font/g, // Next.js Font optimization usage
72
- nuxtAutoImport: /use(?:State|Fetch|AsyncData)/g, // Nuxt 3 auto-imports
73
- sveltekitLoadFunction: /export\s+const\s+load\s*=/g, // SvelteKit load function
74
- reactUseEffectNoDeps: /useEffect\s*\(\s*\([^)]*\)\s*=>\s*{[^}]*},\s*\[\s*\]\s*\)/g, // useEffect with empty dependency array (potential for stale closures)
75
-
76
- // New: Advanced Security Patterns
77
- insecureCrypto: /crypto\.(?:createCipher|createDecipher|pbkdf2Sync)/g, // Deprecated/insecure crypto usage
78
- sqlInjection: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\s+FROM\s+.*\s+WHERE\s+.*\s*=\s*[""]?.*[""]?/i, // Basic SQL injection pattern
79
- xssVulnerability: /<script\b[^>]*>[\s\S]*?<\/script>/i, // Basic XSS script tag detection
80
-
81
- // New: Performance Patterns
82
- largeImageImport: /import\s+.*\s+from\s+[""](?:.*\.(?:png|jpg|jpeg|gif|svg))[""]/g, // Direct import of large images
83
- unoptimizedLoop: /for\s*\(let\s+i\s*=\s*0;\s*i\s*<\s*\w+\.length;\s*i\s*\+\+\)/g, // Simple for loop, can be optimized with for-of or forEach
84
-
85
- // New: Framework-specific patterns (examples)
86
- nextjsImageComponent: /<Image\s+[^>]*>/g, // Next.js Image component usage
87
- nextjsFontOptimization: /next\/font/g, // Next.js Font optimization usage
88
- nuxtAutoImport: /use(?:State|Fetch|AsyncData)/g, // Nuxt 3 auto-imports
89
- sveltekitLoadFunction: /export\s+const\s+load\s*=/g, // SvelteKit load function
90
- reactUseEffectNoDeps: /useEffect\s*\(\s*\([^)]*\)\s*=>\s*{[^}]*},\s*\[\s*\]\s*\)/g, // useEffect with empty dependency array (potential for stale closures)
91
-
92
- // Framework-specific patterns for deeper analysis
163
+
164
+ /**
165
+ * Riskante reguläre Ausdrücke mit Potenzial für Catastrophic Backtracking.
166
+ */
167
+ insecureRegex: /\/\.\*\//g,
168
+
169
+ /**
170
+ * Veraltete oder unsichere Krypto-Verfahren in Node.js.
171
+ */
172
+ insecureCrypto: /crypto\.(?:createCipher|createDecipher|pbkdf2Sync)/g,
173
+
174
+ /**
175
+ * Basis-Muster zur Erkennung potenzieller SQL-Injections in String-Literalen.
176
+ */
177
+ sqlInjection: /(?:SELECT|INSERT|UPDATE|DELETE)\s+.*\s+FROM\s+.*\s+WHERE\s+.*\s*=\s*[""]?.*[""]?/i,
178
+
179
+ /**
180
+ * Erkennung von harten Script-Tags im Quellcode (XSS-Indikator).
181
+ */
182
+ xssVulnerability: /<script\b[^>]*>[\s\S]*?<\/script>/i,
183
+
184
+ /**
185
+ * Performance-Smell: Direkter Import von großen Bilddateien im Quellcode.
186
+ */
187
+ largeImageImport: /import\s+.*\s+from\s+[""](?:.*\.(?:png|jpg|jpeg|gif|svg))[""]/g,
188
+
189
+ /**
190
+ * Performance-Smell: Unoptimierte for-Schleife über Array-Längen.
191
+ */
192
+ unoptimizedLoop: /for\s*\(let\s+i\s*=\s*0;\s*i\s*<\s*\w+\.length;\s*i\s*\+\+\)/g,
193
+
194
+ /**
195
+ * Framework-Muster: Next.js Bildkomponente.
196
+ */
197
+ nextjsImageComponent: /<Image\s+[^>]*>/g,
198
+
199
+ /**
200
+ * Framework-Muster: Next.js Schriftoptimierung.
201
+ */
202
+ nextjsFontOptimization: /next\/font/g,
203
+
204
+ /**
205
+ * Framework-Muster: Nuxt 3 Auto-Imports für Datenabrufe.
206
+ */
207
+ nuxtAutoImport: /use(?:State|Fetch|AsyncData)/g,
208
+
209
+ /**
210
+ * Framework-Muster: SvelteKit Datenladefunktion.
211
+ */
212
+ sveltekitLoadFunction: /export\s+const\s+load\s*=/g,
213
+
214
+ /**
215
+ * Erkennt unvollständige React useEffect Hooks ohne jegliches Abhängigkeitsarray.
216
+ */
217
+ reactUseEffectNoDeps: /useEffect\s*\(\s*(?:\([^)]*\)|[^=]+)\s*=>\s*\{[\s\S]*?\}\s*\)/g,
218
+
219
+ /**
220
+ * Framework-Dateipfade für tiefe Routing-Analysen.
221
+ */
93
222
  nextjsPage: /pages\/[^\/]+\.(js|jsx|ts|tsx)$/i,
94
223
  nextjsApi: /pages\/api\/[^\/]+\.(js|jsx|ts|tsx)$/i,
95
224
  nextjsComponent: /components\/[^\/]+\.(js|jsx|ts|tsx)$/i,
@@ -101,20 +230,17 @@ const REGEX_PATTERNS = {
101
230
  vueComposable: /composables\/[^\/]+\.(js|ts)$/i
102
231
  };
103
232
 
104
- // ============================================================
105
- // COMPREHENSIVE BINARY-TO-PACKAGE MAPPING (Knip-style)
106
- // Maps CLI binary names → npm package names
107
- // ============================================================
233
+ /**
234
+ * Maps CLI-Binärnamen auf tatsächliche npm-Paketnamen (Knip-Stil).
235
+ * @type {Object}
236
+ */
108
237
  const BINARY_TO_PACKAGE_MAP = {
109
- // TypeScript / JavaScript compilers & runtimes
110
238
  'tsc': 'typescript',
111
239
  'ts-node': 'ts-node',
112
240
  'tsx': 'tsx',
113
241
  'tsup': 'tsup',
114
242
  'esbuild': 'esbuild',
115
243
  'swc': '@swc/cli',
116
-
117
- // Test runners
118
244
  'jest': 'jest',
119
245
  'vitest': 'vitest',
120
246
  'mocha': 'mocha',
@@ -123,8 +249,6 @@ const BINARY_TO_PACKAGE_MAP = {
123
249
  'tap': 'tap',
124
250
  'c8': 'c8',
125
251
  'nyc': 'nyc',
126
-
127
- // Linters & formatters
128
252
  'eslint': 'eslint',
129
253
  'prettier': 'prettier',
130
254
  'biome': '@biomejs/biome',
@@ -132,24 +256,18 @@ const BINARY_TO_PACKAGE_MAP = {
132
256
  'tslint': 'tslint',
133
257
  'xo': 'xo',
134
258
  'standard': 'standard',
135
-
136
- // Bundlers & dev servers
137
259
  'vite': 'vite',
138
260
  'webpack': 'webpack',
139
261
  'rollup': 'rollup',
140
262
  'parcel': 'parcel',
141
263
  'turbo': 'turbo',
142
264
  'nx': 'nx',
143
-
144
- // Process managers & watchers
145
265
  'nodemon': 'nodemon',
146
266
  'pm2': 'pm2',
147
267
  'concurrently': 'concurrently',
148
268
  'cross-env': 'cross-env',
149
269
  'dotenv-cli': 'dotenv-cli',
150
270
  'env-cmd': 'env-cmd',
151
-
152
- // Code generation & scaffolding
153
271
  'hygen': 'hygen',
154
272
  'plop': 'plop',
155
273
  'prisma': 'prisma',
@@ -158,8 +276,6 @@ const BINARY_TO_PACKAGE_MAP = {
158
276
  'sequelize': 'sequelize-cli',
159
277
  'knex': 'knex',
160
278
  'mikro-orm': '@mikro-orm/cli',
161
-
162
- // Build & deployment tools
163
279
  'rimraf': 'rimraf',
164
280
  'copyfiles': 'copyfiles',
165
281
  'mkdirp': 'mkdirp',
@@ -169,14 +285,10 @@ const BINARY_TO_PACKAGE_MAP = {
169
285
  'npm-run-all': 'npm-run-all',
170
286
  'run-s': 'npm-run-all',
171
287
  'run-p': 'npm-run-all',
172
-
173
- // Documentation
174
288
  'typedoc': 'typedoc',
175
289
  'jsdoc': 'jsdoc',
176
290
  'storybook': 'storybook',
177
291
  'sb': 'storybook',
178
-
179
- // Misc
180
292
  'husky': 'husky',
181
293
  'lint-staged': 'lint-staged',
182
294
  'commitlint': '@commitlint/cli',
@@ -185,40 +297,82 @@ const BINARY_TO_PACKAGE_MAP = {
185
297
  'changeset': '@changesets/cli',
186
298
  'changesets': '@changesets/cli',
187
299
  'np': 'np',
188
- 'bumpp': 'bumpp',
300
+ 'bumpp': 'bumpp'
189
301
  };
190
302
 
191
- // ============================================================
192
- // EXTENDED DEV TOOLING ECOSYSTEM (never flagged as unused)
193
- // ============================================================
303
+ /**
304
+ * Menge von Entwicklungswerkzeugen, die nicht als verwaist eingestuft werden sollen.
305
+ * @type {Set<string>}
306
+ */
194
307
  const DEV_TOOLING_ECOSYSTEM = new Set([
195
- // Linters & formatters
196
- 'eslint', 'prettier', 'biome', '@biomejs/biome', 'oxlint', 'tslint', 'xo', 'standard',
197
- // TypeScript
198
- 'typescript', 'typescript-eslint', '@eslint/js', 'ts-node', 'tsx', 'tsup', 'esbuild', '@swc/cli',
199
- // Test runners
200
- 'jest', 'vitest', 'mocha', 'jasmine', 'ava', 'tap', 'c8', 'nyc',
201
- // Bundlers
202
- 'vite', 'webpack', 'rollup', 'parcel', 'turbo', 'nx',
203
- // Process managers
204
- 'nodemon', 'pm2', 'concurrently', 'cross-env', 'dotenv-cli', 'env-cmd',
205
- // Build helpers
206
- 'rimraf', 'copyfiles', 'mkdirp', 'shx', 'ncp', 'cpx', 'npm-run-all',
207
- // Docs
208
- 'typedoc', 'jsdoc', 'storybook',
209
- // Release
210
- 'husky', 'lint-staged', '@commitlint/cli', 'release-it', 'semantic-release', '@changesets/cli', 'np', 'bumpp',
211
- // ORM CLI tools
212
- 'prisma', 'drizzle-kit', 'typeorm', 'sequelize-cli', 'knex', '@mikro-orm/cli',
213
- // Scaffolding
214
- 'hygen', 'plop',
308
+ 'eslint',
309
+ 'prettier',
310
+ 'biome',
311
+ '@biomejs/biome',
312
+ 'oxlint',
313
+ 'tslint',
314
+ 'xo',
315
+ 'standard',
316
+ 'typescript',
317
+ 'typescript-eslint',
318
+ '@eslint/js',
319
+ 'ts-node',
320
+ 'tsx',
321
+ 'tsup',
322
+ 'esbuild',
323
+ '@swc/cli',
324
+ 'jest',
325
+ 'vitest',
326
+ 'mocha',
327
+ 'jasmine',
328
+ 'ava',
329
+ 'tap',
330
+ 'c8',
331
+ 'nyc',
332
+ 'vite',
333
+ 'webpack',
334
+ 'rollup',
335
+ 'parcel',
336
+ 'turbo',
337
+ 'nx',
338
+ 'nodemon',
339
+ 'pm2',
340
+ 'concurrently',
341
+ 'cross-env',
342
+ 'dotenv-cli',
343
+ 'env-cmd',
344
+ 'rimraf',
345
+ 'copyfiles',
346
+ 'mkdirp',
347
+ 'shx',
348
+ 'ncp',
349
+ 'cpx',
350
+ 'npm-run-all',
351
+ 'typedoc',
352
+ 'jsdoc',
353
+ 'storybook',
354
+ 'husky',
355
+ 'lint-staged',
356
+ '@commitlint/cli',
357
+ 'release-it',
358
+ 'semantic-release',
359
+ '@changesets/cli',
360
+ 'np',
361
+ 'bumpp',
362
+ 'prisma',
363
+ 'drizzle-kit',
364
+ 'typeorm',
365
+ 'sequelize-cli',
366
+ 'knex',
367
+ '@mikro-orm/cli',
368
+ 'hygen',
369
+ 'plop'
215
370
  ]);
216
371
 
217
- // ============================================================
218
- // KNOWN PACKAGE ALIASES (package name → common import name)
219
- // e.g. "lodash" is imported as "_", "express" as "app", etc.
220
- // This helps avoid false positives in unused detection
221
- // ============================================================
372
+ /**
373
+ * Bekannte Namensraum-Importbezeichner gängiger Bibliotheken.
374
+ * @type {Object}
375
+ */
222
376
  const PACKAGE_IMPORT_ALIASES = {
223
377
  'lodash': ['_', 'lodash'],
224
378
  'lodash-es': ['_', 'lodash'],
@@ -285,71 +439,154 @@ const PACKAGE_IMPORT_ALIASES = {
285
439
  'svelte': ['svelte'],
286
440
  '@angular/core': ['Component', 'NgModule'],
287
441
  'next': ['next'],
288
- 'nuxt': ['nuxt'],
442
+ 'nuxt': ['nuxt']
289
443
  };
290
444
 
445
+ // ============================================================
446
+ // BASE HOISTED HELPER INFRASTRUCTURE
447
+ // ============================================================
448
+
449
+ /**
450
+ * Extrahiert die Git-Konfigurationsparameter des lokalen Benutzers.
451
+ * @returns {Object} Git-Identity Datenstruktur.
452
+ */
291
453
  function getGitIdentity() {
292
- const identity = { name: "Developer", author: "Developer", repository: "" };
454
+ const identity = {
455
+ name: "Developer",
456
+ author: "Developer",
457
+ repository: ""
458
+ };
459
+
293
460
  try {
294
461
  const name = execSync('git config user.name', { encoding: 'utf8', stdio: 'pipe' }).trim();
295
462
  const email = execSync('git config user.email', { encoding: 'utf8', stdio: 'pipe' }).trim();
463
+
296
464
  if (name) {
297
465
  identity.name = name;
298
- identity.author = email ? `${name} <${email}>` : name;
466
+ if (email) {
467
+ identity.author = `${name} <${email}>`;
468
+ } else {
469
+ identity.author = name;
470
+ }
299
471
  }
472
+
300
473
  try {
301
474
  const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8', stdio: 'pipe' }).trim();
302
475
  identity.repository = remoteUrl.replace(/\.git$/, '');
303
- } catch (e) {}
304
- } catch (e) {}
476
+ } catch (gitRemoteError) {
477
+ // Fehlendes Remote wird abgefangen
478
+ }
479
+ } catch (gitConfigError) {
480
+ // Fehlende Git-Umgebung wird abgefangen
481
+ }
482
+
305
483
  return identity;
306
484
  }
307
485
 
486
+ /**
487
+ * Prüft das Vorhandensein von Lockfiles im Zielverzeichnis zur Bestimmung des Paketmanagers.
488
+ * @param {string} targetDir Das zu scannende Projektverzeichnis.
489
+ * @param {Object} stats Das globale Analyse-Objekt.
490
+ * @returns {string} Der ermittelte Paketmanager.
491
+ */
308
492
  function detectPackageManager(targetDir, stats = null) {
309
493
  const detectedLockfiles = [];
310
- if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) detectedLockfiles.push('pnpm-lock.yaml');
311
- if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) detectedLockfiles.push('yarn.lock');
312
- if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) detectedLockfiles.push('package-lock.json');
313
- if (fs.existsSync(path.join(targetDir, 'bun.lockb')) || fs.existsSync(path.join(targetDir, 'bun.lock'))) detectedLockfiles.push('bun.lock');
494
+
495
+ if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) {
496
+ detectedLockfiles.push('pnpm-lock.yaml');
497
+ }
498
+ if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) {
499
+ detectedLockfiles.push('yarn.lock');
500
+ }
501
+ if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) {
502
+ detectedLockfiles.push('package-lock.json');
503
+ }
504
+ if (fs.existsSync(path.join(targetDir, 'bun.lockb')) || fs.existsSync(path.join(targetDir, 'bun.lock'))) {
505
+ detectedLockfiles.push('bun.lock');
506
+ }
314
507
 
315
- if (detectedLockfiles.length > 1 && stats) {
508
+ if (detectedLockfiles.length > 1 && stats !== null) {
316
509
  stats.conflictingLockfiles = detectedLockfiles;
317
510
  }
318
511
 
319
- if (detectedLockfiles.some(l => l.startsWith('bun'))) return 'bun';
320
- if (detectedLockfiles.includes('pnpm-lock.yaml')) return 'pnpm';
321
- if (detectedLockfiles.includes('yarn.lock')) return 'yarn';
322
- if (detectedLockfiles.includes('package-lock.json')) return 'npm';
512
+ if (detectedLockfiles.some(l => { return l.startsWith('bun'); })) {
513
+ return 'bun';
514
+ }
515
+ if (detectedLockfiles.includes('pnpm-lock.yaml')) {
516
+ return 'pnpm';
517
+ }
518
+ if (detectedLockfiles.includes('yarn.lock')) {
519
+ return 'yarn';
520
+ }
521
+ if (detectedLockfiles.includes('package-lock.json')) {
522
+ return 'npm';
523
+ }
323
524
 
324
- try { execSync('pnpm --version', { stdio: 'ignore' }); return 'pnpm'; } catch {}
325
- try { execSync('yarn --version', { stdio: 'ignore' }); return 'yarn'; } catch {}
525
+ try {
526
+ execSync('pnpm --version', { stdio: 'ignore' });
527
+ return 'pnpm';
528
+ } catch (pnpmVersionError) {}
529
+
530
+ try {
531
+ execSync('yarn --version', { stdio: 'ignore' });
532
+ return 'yarn';
533
+ } catch (yarnVersionError) {}
534
+
326
535
  return 'npm';
327
536
  }
328
537
 
538
+ /**
539
+ * Führt Code-Style Metriken-Suchen auf dem rohen Text-Inhalt einer Datei aus.
540
+ * @param {string} content Dateiinhalt.
541
+ * @param {Object} stats Globales Statistikobjekt.
542
+ */
329
543
  function analyzeCodeStyle(content, stats) {
330
544
  const lines = content.split('\n');
545
+
331
546
  for (const line of lines) {
332
547
  const trimmed = line.trim();
333
- if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue;
548
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
549
+ continue;
550
+ }
334
551
 
335
- if (trimmed.endsWith(';')) stats.style.semiCount++;
336
- else if (!/[{}:,\[\]]/.test(trimmed.slice(-1))) stats.style.noSemiCount++;
552
+ if (trimmed.endsWith(';')) {
553
+ stats.style.semiCount++;
554
+ } else if (!/[{}:,\[\]]/.test(trimmed.slice(-1))) {
555
+ stats.style.noSemiCount++;
556
+ }
337
557
 
338
- if (line.startsWith('\t')) stats.style.tabCount++;
339
- else if (line.startsWith(' ')) {
558
+ if (line.startsWith('\t')) {
559
+ stats.style.tabCount++;
560
+ } else if (line.startsWith(' ')) {
340
561
  const spaces = line.match(/^(\s+)/)?.[1]?.length || 0;
341
- if (spaces === 2) stats.style.space2Count++;
342
- if (spaces === 4) stats.style.space4Count++;
562
+ if (spaces === 2) {
563
+ stats.style.space2Count++;
564
+ }
565
+ if (spaces === 4) {
566
+ stats.style.space4Count++;
567
+ }
343
568
  }
344
569
  }
345
570
 
346
- if (REGEX_PATTERNS.legacyVar.test(content)) stats.quality.varCount += (content.match(REGEX_PATTERNS.legacyVar) || []).length;
347
- if (REGEX_PATTERNS.dangerousEval.test(content)) stats.quality.hasEval = true;
348
- if (REGEX_PATTERNS.syncFsCalls.test(content)) stats.quality.syncFsCount += (content.match(REGEX_PATTERNS.syncFsCalls) || []).length;
571
+ if (REGEX_PATTERNS.legacyVar.test(content)) {
572
+ stats.quality.varCount += (content.match(REGEX_PATTERNS.legacyVar) || []).length;
573
+ }
574
+ if (REGEX_PATTERNS.dangerousEval.test(content)) {
575
+ stats.quality.hasEval = true;
576
+ }
577
+ if (REGEX_PATTERNS.syncFsCalls.test(content)) {
578
+ stats.quality.syncFsCount += (content.match(REGEX_PATTERNS.syncFsCalls) || []).length;
579
+ }
349
580
  }
350
581
 
582
+ /**
583
+ * Durchsucht npm scripts nach ausführbaren Binärbefehlen dritter Anbieter.
584
+ * @param {Object} packageJsonContent Parste package.json Struktur.
585
+ * @returns {Array<string>} Liste gefundener CLI-Befehlsaufrufe.
586
+ */
351
587
  function getBinariesFromPackageJson(packageJsonContent) {
352
588
  const binaries = new Set();
589
+
353
590
  if (packageJsonContent && packageJsonContent.scripts) {
354
591
  for (const script of Object.values(packageJsonContent.scripts)) {
355
592
  const commands = String(script).split(/\s*&&\s*|\s*;\s*|\s*\|\|\s*/);
@@ -361,15 +598,28 @@ function getBinariesFromPackageJson(packageJsonContent) {
361
598
  }
362
599
  }
363
600
  }
601
+
364
602
  return Array.from(binaries);
365
603
  }
366
604
 
605
+ /**
606
+ * Isoliert den reinen npm Paketnamen unter Ausschluss interner Datei-Imports.
607
+ * @param {string} importString Import-Quell-Identifikator.
608
+ * @returns {string|null} Bereinigter Paketname oder null.
609
+ */
367
610
  function cleanPackageName(importString) {
368
- if (!importString || /^[./~\\]/.test(importString)) return null;
369
- if (importString.startsWith('@')) return importString.split('/').slice(0, 2).join('/');
611
+ if (!importString || /^[./~\\]/.test(importString)) {
612
+ return null;
613
+ }
614
+ if (importString.startsWith('@')) {
615
+ return importString.split('/').slice(0, 2).join('/');
616
+ }
370
617
  return importString.split('/')[0];
371
618
  }
372
619
 
620
+ /**
621
+ * Fügt Header-Komponenten präzise unterhalb von Shebangs oder Systemdirektiven ein.
622
+ */
373
623
  function smartPrepend(originalCode, declarationBlock) {
374
624
  const lines = originalCode.split(/\r?\n/);
375
625
  let insertIdx = 0;
@@ -389,6 +639,9 @@ function smartPrepend(originalCode, declarationBlock) {
389
639
  return lines.join('\n');
390
640
  }
391
641
 
642
+ /**
643
+ * Validiert die Existenz eines Pakets auf der offiziellen NPM-Registry.
644
+ */
392
645
  async function inspectNpmPackage(pkgName) {
393
646
  try {
394
647
  const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
@@ -397,15 +650,32 @@ async function inspectNpmPackage(pkgName) {
397
650
  });
398
651
  if (response.status === 200) {
399
652
  const data = await response.json();
400
- return { version: data.version, deprecated: data.deprecated || null, error: null };
653
+ return {
654
+ version: data.version,
655
+ deprecated: data.deprecated || null,
656
+ error: null
657
+ };
401
658
  }
402
- if (response.status === 404) return { version: null, deprecated: null, error: 'NOT_FOUND' };
403
- } catch (e) {
404
- return { version: 'latest', deprecated: null, error: 'NETWORK_FAIL' };
659
+ if (response.status === 404) {
660
+ return {
661
+ version: null,
662
+ deprecated: null,
663
+ error: 'NOT_FOUND'
664
+ };
665
+ }
666
+ } catch (networkInspectError) {
667
+ return {
668
+ version: 'latest',
669
+ deprecated: null,
670
+ error: 'NETWORK_FAIL'
671
+ };
405
672
  }
406
673
  return null;
407
674
  }
408
675
 
676
+ /**
677
+ * Holt Lizenztexte aus der GitHub Legal API.
678
+ */
409
679
  async function fetchRemoteLicense(licenseKey) {
410
680
  try {
411
681
  const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
@@ -416,23 +686,52 @@ async function fetchRemoteLicense(licenseKey) {
416
686
  const data = await response.json();
417
687
  return data.body;
418
688
  }
419
- } catch (e) {}
689
+ } catch (licenseApiError) {}
420
690
  return null;
421
691
  }
422
692
 
693
+ /**
694
+ * Liest Dateien synchron ein und bereinigt etwaige BOM-Byte-Anordnungen.
695
+ */
423
696
  function readFileSyncNormalized(fullPath) {
424
697
  const buffer = fs.readFileSync(fullPath);
425
- if (buffer[0] === 0xFF && buffer[1] === 0xFE) return buffer.toString('utf16le');
426
- if (buffer[0] === 0xFE && buffer[1] === 0xFF) return buffer.toString('utf8');
698
+ if (buffer[0] === 0xFF && buffer[1] === 0xFE) {
699
+ return buffer.toString('utf16le');
700
+ }
701
+ if (buffer[0] === 0xFE && buffer[1] === 0xFF) {
702
+ return buffer.toString('utf8');
703
+ }
427
704
  return buffer.toString('utf8');
428
705
  }
429
706
 
707
+ /**
708
+ * Rekursiver Pfad-Auflösungsalgorithmus zur Verfolgung relativer Quellcodedateien.
709
+ */
710
+ function resolveLocalModulePath(basePath, importSource) {
711
+ const extensions = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '.svelte'];
712
+ let absoluteTarget = path.resolve(path.dirname(basePath), importSource);
713
+
714
+ if (fs.existsSync(absoluteTarget) && fs.statSync(absoluteTarget).isDirectory()) {
715
+ const indexFile = extensions.map(ext => { return path.join(absoluteTarget, `index${ext}`); }).find(fs.existsSync);
716
+ if (indexFile) {
717
+ return indexFile;
718
+ }
719
+ }
720
+ const directFile = extensions.map(ext => { return absoluteTarget + ext; }).find(fs.existsSync);
721
+ if (directFile) {
722
+ return directFile;
723
+ }
724
+ if (fs.existsSync(absoluteTarget)) {
725
+ return absoluteTarget;
726
+ }
727
+ return null;
728
+ }
729
+
430
730
  // ============================================================
431
731
  // 🏗️ FRAMEWORK-SPECIFIC DEEP SCAN LOGIC
432
732
  // ============================================================
433
733
  class FrameworkAnalyzer {
434
734
  static analyzeNextjsFile(filePath, content, stats) {
435
- // Data Fetching Patterns (getServerSideProps, getStaticProps, getStaticPaths, Route Handlers)
436
735
  if (filePath.includes("pages/") && content.includes("getServerSideProps")) {
437
736
  stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getServerSideProps");
438
737
  stats.frameworkOptimizations.push(`Next.js: Consider using 'getStaticProps' or client-side fetching for '${path.relative(process.cwd(), filePath)}' if data is not highly dynamic.`);
@@ -446,7 +745,6 @@ class FrameworkAnalyzer {
446
745
  if (filePath.includes("app/") && content.includes("export async function GET")) {
447
746
  stats.frameworkFiles.nextjs.dataFetching.set(filePath, "Route Handler (GET)");
448
747
  }
449
- // More Next.js specific checks: Image optimization, Font optimization, Script optimization
450
748
  if (content.includes("<img") && !content.includes("<Image")) {
451
749
  stats.frameworkOptimizations.push(`Next.js: Use next/image for '${path.relative(process.cwd(), filePath)}' to optimize images.`);
452
750
  }
@@ -456,45 +754,37 @@ class FrameworkAnalyzer {
456
754
  }
457
755
 
458
756
  static analyzeNuxtFile(filePath, content, stats) {
459
- // Data Fetching Patterns (useAsyncData, useFetch)
460
757
  if (content.includes("useAsyncData")) {
461
758
  stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useAsyncData");
462
759
  }
463
760
  if (content.includes("useFetch")) {
464
761
  stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useFetch");
465
762
  }
466
- // Nuxt specific checks: Auto-imports, module usage
467
763
  if (filePath.includes("components/") && !content.includes("defineComponent")) {
468
764
  stats.frameworkOptimizations.push(`Nuxt: Ensure components in '${path.relative(process.cwd(), filePath)}' are properly defined for auto-import or explicitly imported.`);
469
765
  }
470
766
  }
471
767
 
472
768
  static analyzeSvelteKitFile(filePath, content, stats) {
473
- // Data Fetching Patterns (load functions)
474
769
  if (content.includes("export async function load")) {
475
770
  stats.frameworkFiles.sveltekit.loadFunctions.set(filePath, "load");
476
771
  }
477
- // SvelteKit specific checks: endpoint usage, form actions
478
772
  if (filePath.includes("src/routes/") && content.includes("export const actions")) {
479
773
  stats.frameworkFiles.sveltekit.endpoints.add(filePath);
480
774
  }
481
775
  }
482
776
 
483
777
  static analyzeReactFile(filePath, content, stats) {
484
- // React specific checks: useEffect dependencies, custom hooks
485
- if (content.includes("useEffect(") && !content.includes("[]")) {
486
- stats.frameworkOptimizations.push(`React: Check useEffect dependencies in '${path.relative(process.cwd(), filePath)}' to prevent unnecessary re-renders.`);
487
- }
488
- }
489
-
490
- static analyzeVueFile(filePath, content, stats) {
491
- // Vue specific checks: reactivity, component registration
492
- if (content.includes("Vue.component")) {
493
- stats.frameworkOptimizations.push(`Vue: Consider using single-file components or local registration for '${path.relative(process.cwd(), filePath)}' for better modularity.`);
778
+ REGEX_PATTERNS.reactUseEffectNoDeps.lastIndex = 0;
779
+ if (REGEX_PATTERNS.reactUseEffectNoDeps.test(content)) {
780
+ stats.frameworkOptimizations.push(`React Warning: useEffect hook inside '${path.relative(process.cwd(), filePath)}' is missing a trailing dependency array, which can cause severe infinite re-render loops.`);
494
781
  }
495
782
  }
496
783
 
497
784
  static analyzeFile(filePath, content, stats, detectedFrameworks) {
785
+ if (!detectedFrameworks || !Array.isArray(detectedFrameworks)) {
786
+ return;
787
+ }
498
788
  if (detectedFrameworks.includes("next")) {
499
789
  FrameworkAnalyzer.analyzeNextjsFile(filePath, content, stats);
500
790
  }
@@ -513,131 +803,172 @@ class FrameworkAnalyzer {
513
803
  }
514
804
  }
515
805
 
516
- // ============================================================
517
- // ⚙️ FRAMEWORK DETECTION ENGINE
518
- // ============================================================
519
806
  class FrameworkEngine {
520
807
  static detect(targetDir, packageJson) {
521
808
  const detected = new Set();
522
-
523
- // Check package.json dependencies
524
809
  const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
525
- if (allDependencies.next) detected.add("next");
526
- if (allDependencies.nuxt) detected.add("nuxt");
527
- if (allDependencies.sveltekit) detected.add("svelte"); // SvelteKit implies Svelte
528
- if (allDependencies.react) detected.add("react");
529
- if (allDependencies.vue) detected.add("vue");
530
-
531
- // Check config files
532
- if (fs.existsSync(path.join(targetDir, "next.config.js")) || fs.existsSync(path.join(targetDir, "next.config.mjs"))) detected.add("next");
533
- if (fs.existsSync(path.join(targetDir, "nuxt.config.js")) || fs.existsSync(path.join(targetDir, "nuxt.config.ts"))) detected.add("nuxt");
534
- if (fs.existsSync(path.join(targetDir, "svelte.config.js"))) detected.add("svelte");
535
- if (fs.existsSync(path.join(targetDir, "vite.config.js")) || fs.existsSync(path.join(targetDir, "vite.config.ts"))) {
536
- // Vite can be used with multiple frameworks, try to be more specific
537
- if (allDependencies["@vitejs/plugin-react"]) detected.add("react");
538
- if (allDependencies["@vitejs/plugin-vue"]) detected.add("vue");
539
- if (allDependencies["@sveltejs/vite-plugin-svelte"]) detected.add("svelte");
810
+
811
+ if (allDependencies.next) {
812
+ detected.add("next");
813
+ }
814
+ if (allDependencies.nuxt) {
815
+ detected.add("nuxt");
816
+ }
817
+ if (allDependencies.sveltekit) {
818
+ detected.add("svelte");
819
+ }
820
+ if (allDependencies.react) {
821
+ detected.add("react");
822
+ }
823
+ if (allDependencies.vue) {
824
+ detected.add("vue");
540
825
  }
541
826
 
827
+ if (fs.existsSync(path.join(targetDir, "next.config.js")) || fs.existsSync(path.join(targetDir, "next.config.mjs"))) {
828
+ detected.add("next");
829
+ }
830
+ if (fs.existsSync(path.join(targetDir, "nuxt.config.js")) || fs.existsSync(path.join(targetDir, "nuxt.config.ts"))) {
831
+ detected.add("nuxt");
832
+ }
833
+ if (fs.existsSync(path.join(targetDir, "svelte.config.js"))) {
834
+ detected.add("svelte");
835
+ }
836
+ if (fs.existsSync(path.join(targetDir, "vite.config.js")) || fs.existsSync(path.join(targetDir, "vite.config.ts"))) {
837
+ if (allDependencies["@vitejs/plugin-react"]) {
838
+ detected.add("react");
839
+ }
840
+ if (allDependencies["@vitejs/plugin-vue"]) {
841
+ detected.add("vue");
842
+ }
843
+ if (allDependencies["@sveltejs/vite-plugin-svelte"]) {
844
+ detected.add("svelte");
845
+ }
846
+ }
542
847
  return Array.from(detected);
543
848
  }
544
849
  }
545
850
 
546
851
  // ============================================================
547
- // 🧩 TEMPLATE ENGINE (Hygen-level Customization)
852
+ // 🧱 CUSTOM STRUCTURAL HOISTED TEMPLATE MANAGER
548
853
  // ============================================================
549
- class TemplateEngine {
550
- constructor(targetDir, safeQuestion) {
551
- this.targetDir = targetDir;
552
- this.templatesDir = path.join(targetDir, ".templates");
854
+ class TemplateManager {
855
+ constructor(baseDir, safeQuestion) {
856
+ this.baseDir = baseDir;
553
857
  this.safeQuestion = safeQuestion;
858
+ this.templateSources = [{ name: 'local', path: path.join(this.baseDir, '.templates') }];
554
859
  }
555
860
 
556
- async listTemplates() {
557
- if (!fs.existsSync(this.templatesDir)) {
558
- return [];
861
+ async listAvailableTemplates() {
862
+ const allTemplates = new Set();
863
+ for (const source of this.templateSources) {
864
+ if (source.name === 'local') {
865
+ const localTemplatesPath = source.path;
866
+ if (fs.existsSync(localTemplatesPath)) {
867
+ const templates = fs.readdirSync(localTemplatesPath, { withFileTypes: true })
868
+ .filter(dirent => { return dirent.isDirectory(); })
869
+ .map(dirent => { return dirent.name; });
870
+ templates.forEach(t => { return allTemplates.add(t); });
871
+ }
872
+ }
559
873
  }
560
- const templateFolders = fs.readdirSync(this.templatesDir, { withFileTypes: true })
561
- .filter(dirent => dirent.isDirectory())
562
- .map(dirent => dirent.name);
563
- return templateFolders;
874
+ return Array.from(allTemplates);
564
875
  }
565
876
 
566
- async generate(templateName, variables = {}) {
567
- const templatePath = path.join(this.templatesDir, templateName);
568
- if (!fs.existsSync(templatePath)) {
569
- console.log(` ⚠️ Template '${templateName}' not found in ${this.templatesDir}`);
570
- return;
571
- }
572
-
573
- console.log(` 🚀 Generating from template '${templateName}'...`);
574
-
575
- const templateFiles = this._getTemplateFiles(templatePath);
576
-
577
- for (const file of templateFiles) {
578
- const relativePath = path.relative(templatePath, file);
579
- let targetFilePath = path.join(this.targetDir, this._renderString(relativePath, variables));
580
-
581
- // Handle dynamic file names (e.g., _name_.js)
582
- targetFilePath = targetFilePath.replace(/_([a-zA-Z0-9_]+)_/g, (match, p1) => {
583
- return variables[p1] || match; // Replace with variable or keep original if not found
584
- });
585
-
586
- const content = fs.readFileSync(file, 'utf8');
587
- const renderedContent = this._renderString(content, variables);
588
-
589
- fs.mkdirSync(path.dirname(targetFilePath), { recursive: true });
590
- fs.writeFileSync(targetFilePath, renderedContent);
591
- console.log(` ✅ Created: ${path.relative(this.targetDir, targetFilePath)}`);
877
+ async getTemplatePath(templateName) {
878
+ for (const source of this.templateSources) {
879
+ if (source.name === 'local') {
880
+ const templatePath = path.join(source.path, templateName);
881
+ if (fs.existsSync(templatePath)) {
882
+ return templatePath;
883
+ }
884
+ }
592
885
  }
593
- console.log(` ✨ Template generation complete.`);
886
+ return null;
594
887
  }
595
888
 
596
- _getTemplateFiles(dir) {
597
- let files = [];
598
- const items = fs.readdirSync(dir, { withFileTypes: true });
599
- for (const item of items) {
600
- const fullPath = path.join(dir, item.name);
601
- if (item.isDirectory()) {
602
- files = files.concat(this._getTemplateFiles(fullPath));
603
- } else {
604
- files.push(fullPath);
889
+ async promptForVariables(templateName) {
890
+ const templatePath = await this.getTemplatePath(templateName);
891
+ if (!templatePath) {
892
+ console.log(` ⚠️ Template '${templateName}' not found.`);
893
+ return {};
894
+ }
895
+ const configPath = path.join(templatePath, '_config.json');
896
+ if (fs.existsSync(configPath)) {
897
+ try {
898
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
899
+ const variables = {};
900
+ for (const key in config.prompts) {
901
+ const prompt = config.prompts[key];
902
+ let answer = await this.safeQuestion(`❓ ${prompt.message || key}: `);
903
+ if (prompt.type === 'boolean') {
904
+ answer = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
905
+ } else if (prompt.type === 'number') {
906
+ answer = parseFloat(answer);
907
+ }
908
+ variables[key] = answer;
909
+ }
910
+ return variables;
911
+ } catch (jsonConfigReadError) {
912
+ console.error(` ❌ Error reading template config for '${templateName}': ${jsonConfigReadError.message}`);
913
+ return {};
605
914
  }
606
915
  }
607
- return files;
916
+ return {};
608
917
  }
609
918
 
610
- _renderString(templateString, variables) {
611
- let result = templateString;
612
- for (const key in variables) {
613
- result = result.replace(new RegExp(`{{\s*${key}\s*}}`, 'g'), variables[key]);
919
+ async generate(templateName, variables) {
920
+ const templatePath = await this.getTemplatePath(templateName);
921
+ if (!templatePath) {
922
+ return;
614
923
  }
615
- return result;
616
- }
924
+ console.log(` 🚀 Generating '${templateName}' template with full route and token mutations...`);
617
925
 
618
- async promptForVariables(templateName) {
619
- const templatePath = path.join(this.templatesDir, templateName);
620
- const configPath = path.join(templatePath, "config.json");
621
- const variables = {};
926
+ const renderFile = async (srcPath, destPath, vars) => {
927
+ const content = fs.readFileSync(srcPath, 'utf8');
928
+ let renderedContent = content;
929
+ for (const key in vars) {
930
+ renderedContent = renderedContent.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), vars[key]);
931
+ }
932
+ fs.writeFileSync(destPath, renderedContent);
933
+ };
622
934
 
623
- if (fs.existsSync(configPath)) {
624
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
625
- if (config.prompts && Array.isArray(config.prompts)) {
626
- for (const prompt of config.prompts) {
627
- const answer = await this.safeQuestion(` ❓ ${prompt.message} (${prompt.name}): `);
628
- variables[prompt.name] = answer || prompt.default || '';
935
+ const processDirectory = async (currentSrcDir, currentDestDir, vars) => {
936
+ fs.mkdirSync(currentDestDir, { recursive: true });
937
+ const items = fs.readdirSync(currentSrcDir, { withFileTypes: true });
938
+ for (const item of items) {
939
+ if (item.name === '_config.json' || item.name === 'config.json') {
940
+ continue;
941
+ }
942
+
943
+ let mutatedName = item.name;
944
+ for (const key in vars) {
945
+ mutatedName = mutatedName.replace(new RegExp(`{{\\s*${key}\\s*}}`, 'g'), vars[key]);
946
+ }
947
+ mutatedName = mutatedName.replace(/_([a-zA-Z0-9_]+)_/g, (match, p1) => { return vars[p1] || match; });
948
+
949
+ const srcItemPath = path.join(currentSrcDir, item.name);
950
+ const destItemPath = path.join(currentDestDir, mutatedName);
951
+
952
+ if (item.isDirectory()) {
953
+ await processDirectory(srcItemPath, destItemPath, vars);
954
+ } else {
955
+ await renderFile(srcItemPath, destItemPath, vars);
629
956
  }
630
957
  }
631
- }
632
- return variables;
958
+ };
959
+ await processDirectory(templatePath, this.baseDir, variables);
960
+ console.log(` ✅ Template '${templateName}' generated successfully.`);
633
961
  }
634
962
  }
635
963
 
964
+ /**
965
+ * Generiert einen sauberen ASCII-Layout-Baum für das automatisierte README-Dokument.
966
+ */
636
967
  function buildAsciiTree(dir, prefix = '') {
637
968
  const results = [];
638
969
  try {
639
970
  const files = fs.readdirSync(dir);
640
- const filtered = files.filter(f => !IGNORED_DIRS.has(f) && !f.startsWith('.'));
971
+ const filtered = files.filter(f => { return !IGNORED_DIRS.has(f) && !f.startsWith('.'); });
641
972
 
642
973
  filtered.forEach((file, index) => {
643
974
  const isLast = index === filtered.length - 1;
@@ -650,25 +981,91 @@ function buildAsciiTree(dir, prefix = '') {
650
981
  results.push(...buildAsciiTree(fullPath, newPrefix));
651
982
  }
652
983
  });
653
- } catch (e) {}
984
+ } catch (asciiTreeTreeError) {}
654
985
  return results;
655
986
  }
656
987
 
657
988
  // ============================================================
658
- // IMPROVED IMPORT EXTRACTION: handles TypeScript generics,
659
- // type-only imports, re-exports, and dynamic imports
989
+ // STRUKTURELLES IMPLEMENTIERUNGSHELFER SYSTEM
660
990
  // ============================================================
991
+
992
+ /**
993
+ * KORREKTUR: Prüft, ob ein extrahierter Import-Bezeichner im Programmcode verwendet wird.
994
+ * Führt den regulären Ausdruck nun gegen das tatsächliche executionCode-Skelett aus, anstatt gegen den Alias selbst.
995
+ */
996
+ function analyzeIdentifierUsage(pkg, identifiers, executionCode) {
997
+ const autoUsedMarkers = new Set(['__SIDE_EFFECT__', '__DYNAMIC__', '__REEXPORT__', '__TYPE_ONLY__']);
998
+ for (const id of identifiers) {
999
+ if (autoUsedMarkers.has(id)) {
1000
+ return true;
1001
+ }
1002
+ }
1003
+ for (const id of identifiers) {
1004
+ const escaped = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1005
+ if (new RegExp(`\\b${escaped}\\b`).test(executionCode)) {
1006
+ return true;
1007
+ }
1008
+ }
1009
+ const aliases = PACKAGE_IMPORT_ALIASES[pkg] || [];
1010
+ for (const alias of aliases) {
1011
+ const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1012
+ if (new RegExp(`\\b${escapedAlias}\\b`).test(executionCode)) { // 👈 Use the properly escaped token here
1013
+ return true;
1014
+ }
1015
+ }
1016
+ return false;
1017
+ }
1018
+
1019
+ /**
1020
+ * Findet Pakete, die im Programmcode importiert wurden, aber nicht in der package.json deklariert sind.
1021
+ */
1022
+ function detectGhostDependencies(allImportedPackages, declaredDeps, declaredDevDeps) {
1023
+ const allDeclared = new Set([...declaredDeps, ...declaredDevDeps]);
1024
+ const ghosts = new Set();
1025
+ for (const pkg of allImportedPackages) {
1026
+ if (!allDeclared.has(pkg) && !builtinModules.includes(pkg)) {
1027
+ ghosts.add(pkg);
1028
+ }
1029
+ }
1030
+ return ghosts;
1031
+ }
1032
+
1033
+ /**
1034
+ * Ermittelt ungenutzte verwaiste Abhängigkeiten aus der package.json.
1035
+ */
1036
+ function detectOrphanedDependencies(declaredDeps, allImportedPackages, binariesUsed, devTooling) {
1037
+ const orphans = new Set();
1038
+ for (const dep of declaredDeps) {
1039
+ if (devTooling.has(dep) || dep.startsWith('@types/')) {
1040
+ continue;
1041
+ }
1042
+ const binaryPkg = Object.values(BINARY_TO_PACKAGE_MAP).find(p => { return p === dep; });
1043
+ if (binaryPkg && binariesUsed.has(dep)) {
1044
+ continue;
1045
+ }
1046
+ if (!allImportedPackages.has(dep)) {
1047
+ orphans.add(dep);
1048
+ }
1049
+ }
1050
+ return orphans;
1051
+ }
1052
+
661
1053
  function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations, exportedSymbols, stats, currentFilePath) {
1054
+ extractAdvancedSymbols(ast, currentFilePath, stats);
1055
+
662
1056
  walk.simple(ast, {
663
1057
  ImportDeclaration(node) {
664
1058
  const importSource = node.source.value;
665
1059
  const pkg = cleanPackageName(importSource);
666
1060
 
667
1061
  if (pkg && !builtinModules.includes(pkg)) {
668
- // External package import
669
1062
  fileRawDeps.add(pkg);
670
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
671
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1063
+ if (!importedIdentifiers.has(pkg)) {
1064
+ importedIdentifiers.set(pkg, new Set());
1065
+ }
1066
+ if (!importedLocations.has(pkg)) {
1067
+ importedLocations.set(pkg, []);
1068
+ }
672
1069
  importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
673
1070
 
674
1071
  node.specifiers.forEach(spec => {
@@ -676,23 +1073,21 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
676
1073
  importedIdentifiers.get(pkg).add(spec.local.name);
677
1074
  } else if (spec.type === 'ImportSpecifier') {
678
1075
  importedIdentifiers.get(pkg).add(spec.local.name);
679
- // Also track the imported name (before 'as') for side-effect detection
680
1076
  if (spec.imported && spec.imported.name !== spec.local.name) {
681
1077
  importedIdentifiers.get(pkg).add(spec.imported.name);
682
1078
  }
683
1079
  }
684
1080
  });
685
-
686
- // Side-effect only import: import 'pkg' — always considered "used"
687
1081
  if (node.specifiers.length === 0) {
688
1082
  importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
689
1083
  }
690
1084
  } else if (importSource.startsWith('.') || importSource.startsWith('/')) {
691
- // Local file import
692
1085
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
693
1086
  const normalizedPath = path.normalize(resolvedPath);
694
1087
 
695
- if (!stats.localFileImports) stats.localFileImports = new Map();
1088
+ if (!stats.localFileImports) {
1089
+ stats.localFileImports = new Map();
1090
+ }
696
1091
  if (!stats.localFileImports.has(normalizedPath)) {
697
1092
  stats.localFileImports.set(normalizedPath, new Set());
698
1093
  }
@@ -710,15 +1105,18 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
710
1105
  }
711
1106
  },
712
1107
  VariableDeclarator(node) {
713
- if (node.init && node.init.type === 'CallExpression' &&
714
- node.init.callee.type === 'Identifier' && node.init.callee.name === 'require') {
1108
+ if (node.init && node.init.type === 'CallExpression' && node.init.callee.type === 'Identifier' && node.init.callee.name === 'require') {
715
1109
  const arg = node.init.arguments[0];
716
1110
  if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
717
1111
  const pkg = cleanPackageName(arg.value);
718
1112
  if (pkg && !builtinModules.includes(pkg)) {
719
1113
  fileRawDeps.add(pkg);
720
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
721
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1114
+ if (!importedIdentifiers.has(pkg)) {
1115
+ importedIdentifiers.set(pkg, new Set());
1116
+ }
1117
+ if (!importedLocations.has(pkg)) {
1118
+ importedLocations.set(pkg, []);
1119
+ }
722
1120
  importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
723
1121
 
724
1122
  const extractBindings = (idNode) => {
@@ -726,8 +1124,12 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
726
1124
  importedIdentifiers.get(pkg).add(idNode.name);
727
1125
  } else if (idNode.type === 'ObjectPattern') {
728
1126
  idNode.properties.forEach(p => {
729
- if (p.value && p.value.type === 'Identifier') importedIdentifiers.get(pkg).add(p.value.name);
730
- if (p.key && p.key.type === 'Identifier') importedIdentifiers.get(pkg).add(p.key.name);
1127
+ if (p.value && p.value.type === 'Identifier') {
1128
+ importedIdentifiers.get(pkg).add(p.value.name);
1129
+ }
1130
+ if (p.key && p.key.type === 'Identifier') {
1131
+ importedIdentifiers.get(pkg).add(p.key.name);
1132
+ }
731
1133
  });
732
1134
  }
733
1135
  };
@@ -741,7 +1143,9 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
741
1143
  const pkg = cleanPackageName(node.source.value);
742
1144
  if (pkg && !builtinModules.includes(pkg)) {
743
1145
  fileRawDeps.add(pkg);
744
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1146
+ if (!importedIdentifiers.has(pkg)) {
1147
+ importedIdentifiers.set(pkg, new Set());
1148
+ }
745
1149
  importedIdentifiers.get(pkg).add('__DYNAMIC__');
746
1150
  }
747
1151
  }
@@ -774,7 +1178,9 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
774
1178
  const pkg = cleanPackageName(node.source.value);
775
1179
  if (pkg && !builtinModules.includes(pkg)) {
776
1180
  fileRawDeps.add(pkg);
777
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1181
+ if (!importedIdentifiers.has(pkg)) {
1182
+ importedIdentifiers.set(pkg, new Set());
1183
+ }
778
1184
  importedIdentifiers.get(pkg).add('__REEXPORT__');
779
1185
  }
780
1186
  }
@@ -784,7 +1190,9 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
784
1190
  const pkg = cleanPackageName(node.source.value);
785
1191
  if (pkg && !builtinModules.includes(pkg)) {
786
1192
  fileRawDeps.add(pkg);
787
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1193
+ if (!importedIdentifiers.has(pkg)) {
1194
+ importedIdentifiers.set(pkg, new Set());
1195
+ }
788
1196
  importedIdentifiers.get(pkg).add('__REEXPORT__');
789
1197
  }
790
1198
  }
@@ -792,38 +1200,40 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
792
1200
  });
793
1201
  }
794
1202
 
795
- // ============================================================
796
- // REGEX FALLBACK: handles TypeScript files that acorn can't parse
797
- // ============================================================
1203
+ /**
1204
+ * Textbasierter Regex-Ersatz-Parser, falls Acorn auf Syntaxfehler stößt.
1205
+ */
798
1206
  function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, currentFilePath) {
799
1207
  codeLines.forEach((line, lineIdx) => {
800
1208
  const lineNum = lineIdx + 1;
801
-
802
- // import type { ... } from '...' — type-only, mark as side-effect
803
1209
  const typeImportMatch = line.match(/\bimport\s+type\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/);
804
1210
  if (typeImportMatch) {
805
1211
  const importSource = typeImportMatch[1];
806
1212
  const pkg = cleanPackageName(importSource);
807
1213
  if (pkg && !builtinModules.includes(pkg)) {
808
1214
  fileRawDeps.add(pkg);
809
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1215
+ if (!importedIdentifiers.has(pkg)) {
1216
+ importedIdentifiers.set(pkg, new Set());
1217
+ }
810
1218
  importedIdentifiers.get(pkg).add('__TYPE_ONLY__');
811
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1219
+ if (!importedLocations.has(pkg)) {
1220
+ importedLocations.set(pkg, []);
1221
+ }
812
1222
  importedLocations.get(pkg).push(lineNum);
813
1223
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
814
1224
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
815
1225
  const normalizedPath = path.normalize(resolvedPath);
816
- if (!stats.localFileImports) stats.localFileImports = new Map();
1226
+ if (!stats.localFileImports) {
1227
+ stats.localFileImports = new Map();
1228
+ }
817
1229
  if (!stats.localFileImports.has(normalizedPath)) {
818
1230
  stats.localFileImports.set(normalizedPath, new Set());
819
1231
  }
820
- stats.localFileImports.get(normalizedPath).add('__TYPE_ONLY__'); // Mark as type-only imported
1232
+ stats.localFileImports.get(normalizedPath).add('__TYPE_ONLY__');
821
1233
  }
822
1234
  return;
823
1235
  }
824
1236
 
825
- // import DefaultExport from '...'
826
- // import * as Namespace from '...'
827
1237
  const esmDefaultMatch = line.match(/\bimport\s+(?:\*\s+as\s+)?([a-zA-Z0-9_$]+)\s+from\s+['"]([^'"]+)['"]/);
828
1238
  if (esmDefaultMatch) {
829
1239
  const id = esmDefaultMatch[1];
@@ -831,14 +1241,20 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
831
1241
  const pkg = cleanPackageName(importSource);
832
1242
  if (pkg && !builtinModules.includes(pkg)) {
833
1243
  fileRawDeps.add(pkg);
834
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1244
+ if (!importedIdentifiers.has(pkg)) {
1245
+ importedIdentifiers.set(pkg, new Set());
1246
+ }
835
1247
  importedIdentifiers.get(pkg).add(id);
836
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1248
+ if (!importedLocations.has(pkg)) {
1249
+ importedLocations.set(pkg, []);
1250
+ }
837
1251
  importedLocations.get(pkg).push(lineNum);
838
1252
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
839
1253
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
840
1254
  const normalizedPath = path.normalize(resolvedPath);
841
- if (!stats.localFileImports) stats.localFileImports = new Map();
1255
+ if (!stats.localFileImports) {
1256
+ stats.localFileImports = new Map();
1257
+ }
842
1258
  if (!stats.localFileImports.has(normalizedPath)) {
843
1259
  stats.localFileImports.set(normalizedPath, new Set());
844
1260
  }
@@ -847,57 +1263,68 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
847
1263
  return;
848
1264
  }
849
1265
 
850
- // import { named, exports } from '...'
851
1266
  const esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
852
1267
  if (esmNamedMatch) {
853
1268
  const importSource = esmNamedMatch[2];
854
1269
  const pkg = cleanPackageName(importSource);
855
1270
  if (pkg && !builtinModules.includes(pkg)) {
856
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1271
+ if (!importedIdentifiers.has(pkg)) {
1272
+ importedIdentifiers.set(pkg, new Set());
1273
+ }
857
1274
  fileRawDeps.add(pkg);
858
1275
  esmNamedMatch[1].split(',').forEach(part => {
859
1276
  const chunk = part.trim();
860
- if (!chunk) return;
1277
+ if (!chunk) {
1278
+ return;
1279
+ }
861
1280
  const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
862
1281
  importedIdentifiers.get(pkg).add(id);
863
- // Also add the original name
864
- if (chunk.includes(' as ')) importedIdentifiers.get(pkg).add(chunk.split(' as ')[0].trim());
865
1282
  });
866
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1283
+ if (!importedLocations.has(pkg)) {
1284
+ importedLocations.set(pkg, []);
1285
+ }
867
1286
  importedLocations.get(pkg).push(lineNum);
868
1287
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
869
1288
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
870
1289
  const normalizedPath = path.normalize(resolvedPath);
871
- if (!stats.localFileImports) stats.localFileImports = new Map();
1290
+ if (!stats.localFileImports) {
1291
+ stats.localFileImports = new Map();
1292
+ }
872
1293
  if (!stats.localFileImports.has(normalizedPath)) {
873
1294
  stats.localFileImports.set(normalizedPath, new Set());
874
1295
  }
875
1296
  esmNamedMatch[1].split(',').forEach(part => {
876
1297
  const chunk = part.trim();
877
- if (!chunk) return;
1298
+ if (!chunk) {
1299
+ return;
1300
+ }
878
1301
  const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
879
1302
  stats.localFileImports.get(normalizedPath).add(id);
880
- if (chunk.includes(' as ')) stats.localFileImports.get(normalizedPath).add(chunk.split(' as ')[0].trim());
881
1303
  });
882
1304
  }
883
1305
  return;
884
1306
  }
885
1307
 
886
- // Side-effect only: import '...'
887
1308
  const sideEffectMatch = line.match(/\bimport\s+['"]([^'"]+)['"]/);
888
1309
  if (sideEffectMatch) {
889
1310
  const importSource = sideEffectMatch[1];
890
1311
  const pkg = cleanPackageName(importSource);
891
1312
  if (pkg && !builtinModules.includes(pkg)) {
892
1313
  fileRawDeps.add(pkg);
893
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1314
+ if (!importedIdentifiers.has(pkg)) {
1315
+ importedIdentifiers.set(pkg, new Set());
1316
+ }
894
1317
  importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
895
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1318
+ if (!importedLocations.has(pkg)) {
1319
+ importedLocations.set(pkg, []);
1320
+ }
896
1321
  importedLocations.get(pkg).push(lineNum);
897
1322
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
898
1323
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
899
1324
  const normalizedPath = path.normalize(resolvedPath);
900
- if (!stats.localFileImports) stats.localFileImports = new Map();
1325
+ if (!stats.localFileImports) {
1326
+ stats.localFileImports = new Map();
1327
+ }
901
1328
  if (!stats.localFileImports.has(normalizedPath)) {
902
1329
  stats.localFileImports.set(normalizedPath, new Set());
903
1330
  }
@@ -906,22 +1333,27 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
906
1333
  return;
907
1334
  }
908
1335
 
909
- // const x = require('...')
910
- const cjsMatch = line.match(/\b(?:const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
1336
+ const cjsMatch = line.match(/\b(const|let|var)\s+([a-zA-Z0-9_$]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
911
1337
  if (cjsMatch) {
912
- const id = cjsMatch[1];
913
- const importSource = cjsMatch[2];
1338
+ const id = cjsMatch[2];
1339
+ const importSource = cjsMatch[3];
914
1340
  const pkg = cleanPackageName(importSource);
915
1341
  if (pkg && !builtinModules.includes(pkg)) {
916
1342
  fileRawDeps.add(pkg);
917
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1343
+ if (!importedIdentifiers.has(pkg)) {
1344
+ importedIdentifiers.set(pkg, new Set());
1345
+ }
918
1346
  importedIdentifiers.get(pkg).add(id);
919
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1347
+ if (!importedLocations.has(pkg)) {
1348
+ importedLocations.set(pkg, []);
1349
+ }
920
1350
  importedLocations.get(pkg).push(lineNum);
921
1351
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
922
1352
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
923
1353
  const normalizedPath = path.normalize(resolvedPath);
924
- if (!stats.localFileImports) stats.localFileImports = new Map();
1354
+ if (!stats.localFileImports) {
1355
+ stats.localFileImports = new Map();
1356
+ }
925
1357
  if (!stats.localFileImports.has(normalizedPath)) {
926
1358
  stats.localFileImports.set(normalizedPath, new Set());
927
1359
  }
@@ -930,32 +1362,43 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
930
1362
  return;
931
1363
  }
932
1364
 
933
- // const { a, b } = require('...')
934
- const cjsDestructMatch = line.match(/\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
1365
+ const cjsDestructMatch = line.match(/\b(const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
935
1366
  if (cjsDestructMatch) {
936
- const importSource = cjsDestructMatch[2];
1367
+ const importSource = cjsDestructMatch[3];
937
1368
  const pkg = cleanPackageName(importSource);
938
1369
  if (pkg && !builtinModules.includes(pkg)) {
939
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1370
+ if (!importedIdentifiers.has(pkg)) {
1371
+ importedIdentifiers.set(pkg, new Set());
1372
+ }
940
1373
  fileRawDeps.add(pkg);
941
- cjsDestructMatch[1].split(',').forEach(part => {
1374
+ cjsDestructMatch[2].split(',').forEach(part => {
942
1375
  const chunk = part.trim();
943
- if (!chunk) return;
1376
+ if (!chunk) {
1377
+ return;
1378
+ }
944
1379
  const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
945
1380
  importedIdentifiers.get(pkg).add(id);
946
1381
  });
947
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1382
+
1383
+ // KORREKTUR: Korrekte Initialisierung des Arrays innerhalb von importedLocations zur Vermeidung von TypeErrors
1384
+ if (!importedLocations.has(pkg)) {
1385
+ importedLocations.set(pkg, []);
1386
+ }
948
1387
  importedLocations.get(pkg).push(lineNum);
949
1388
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
950
1389
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
951
1390
  const normalizedPath = path.normalize(resolvedPath);
952
- if (!stats.localFileImports) stats.localFileImports = new Map();
1391
+ if (!stats.localFileImports) {
1392
+ stats.localFileImports = new Map();
1393
+ }
953
1394
  if (!stats.localFileImports.has(normalizedPath)) {
954
1395
  stats.localFileImports.set(normalizedPath, new Set());
955
1396
  }
956
- cjsDestructMatch[1].split(',').forEach(part => {
1397
+ cjsDestructMatch[2].split(',').forEach(part => {
957
1398
  const chunk = part.trim();
958
- if (!chunk) return;
1399
+ if (!chunk) {
1400
+ return;
1401
+ }
959
1402
  const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
960
1403
  stats.localFileImports.get(normalizedPath).add(id);
961
1404
  });
@@ -963,21 +1406,26 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
963
1406
  return;
964
1407
  }
965
1408
 
966
- // Dynamic import: import('...')
967
1409
  const dynamicMatch = line.match(/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/);
968
1410
  if (dynamicMatch) {
969
1411
  const importSource = dynamicMatch[1];
970
1412
  const pkg = cleanPackageName(importSource);
971
1413
  if (pkg && !builtinModules.includes(pkg)) {
972
1414
  fileRawDeps.add(pkg);
973
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1415
+ if (!importedIdentifiers.has(pkg)) {
1416
+ importedIdentifiers.set(pkg, new Set());
1417
+ }
974
1418
  importedIdentifiers.get(pkg).add('__DYNAMIC__');
975
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1419
+ if (!importedLocations.has(pkg)) {
1420
+ importedLocations.set(pkg, []);
1421
+ }
976
1422
  importedLocations.get(pkg).push(lineNum);
977
1423
  } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
978
1424
  const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
979
1425
  const normalizedPath = path.normalize(resolvedPath);
980
- if (!stats.localFileImports) stats.localFileImports = new Map();
1426
+ if (!stats.localFileImports) {
1427
+ stats.localFileImports = new Map();
1428
+ }
981
1429
  if (!stats.localFileImports.has(normalizedPath)) {
982
1430
  stats.localFileImports.set(normalizedPath, new Set());
983
1431
  }
@@ -987,85 +1435,11 @@ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, imp
987
1435
  });
988
1436
  }
989
1437
 
990
- // ============================================================
991
- // USAGE ANALYSIS: determines if imported identifiers are
992
- // actually referenced in the non-import code body
993
- // ============================================================
994
- function analyzeIdentifierUsage(pkg, identifiers, executionCode) {
995
- // Always-used markers: side-effect, dynamic, re-export, type-only
996
- const autoUsedMarkers = new Set(['__SIDE_EFFECT__', '__DYNAMIC__', '__REEXPORT__', '__TYPE_ONLY__']);
997
- for (const id of identifiers) {
998
- if (autoUsedMarkers.has(id)) return true;
999
- }
1000
-
1001
- // Check known aliases for this package
1002
- const knownAliases = PACKAGE_IMPORT_ALIASES[pkg] || [];
1003
-
1004
- for (const identifier of identifiers) {
1005
- if (!identifier || identifier.startsWith('__')) continue;
1006
- // Escape special regex chars in identifier
1007
- const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1008
- const usagePattern = new RegExp(`\\b${escaped}\\b`);
1009
- if (usagePattern.test(executionCode)) return true;
1010
- }
1011
-
1012
- // Check if any known alias for this package appears in the code
1013
- for (const alias of knownAliases) {
1014
- const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1015
- const aliasPattern = new RegExp(`\\b${escaped}\\b`);
1016
- if (aliasPattern.test(executionCode)) return true;
1017
- }
1018
-
1019
- return false;
1020
- }
1021
-
1022
- // ============================================================
1023
- // GHOST DEPENDENCY DETECTOR
1024
- // Finds packages used in code but NOT declared in package.json
1025
- // This is the most critical error: will fail at runtime/deploy
1026
- // ============================================================
1027
- function detectGhostDependencies(allImportedPackages, declaredDeps, declaredDevDeps) {
1028
- const allDeclared = new Set([...declaredDeps, ...declaredDevDeps]);
1029
- const ghosts = new Set();
1030
-
1031
- for (const pkg of allImportedPackages) {
1032
- if (!allDeclared.has(pkg) && !builtinModules.includes(pkg)) {
1033
- ghosts.add(pkg);
1034
- }
1035
- }
1036
- return ghosts;
1037
- }
1038
-
1039
- // ============================================================
1040
- // ORPHANED DEPENDENCY DETECTOR
1041
- // Finds packages in package.json that are never imported anywhere
1042
- // in the codebase (candidates for removal)
1043
- // ============================================================
1044
- function detectOrphanedDependencies(declaredDeps, allImportedPackages, binariesUsed, devTooling) {
1045
- const orphans = new Set();
1046
-
1047
- for (const dep of declaredDeps) {
1048
- // Skip dev tooling — they're used via CLI, not imports
1049
- if (devTooling.has(dep) || dep.startsWith('@types/')) continue;
1050
-
1051
- // Check if it's used as a binary
1052
- const binaryPkg = Object.values(BINARY_TO_PACKAGE_MAP).find(p => p === dep);
1053
- if (binaryPkg && binariesUsed.has(dep)) continue;
1054
-
1055
- // Check if it's imported anywhere
1056
- if (!allImportedPackages.has(dep)) {
1057
- orphans.add(dep);
1058
- }
1059
- }
1060
- return orphans;
1061
- }
1062
-
1063
- // ============================================================
1064
- // HIGH PERFORMANCE AST WORKSPACE PARSING ENGINE
1065
- // ============================================================
1438
+ /**
1439
+ * Kernfunktion zur iterativen Rekursion durch alle Ordnerstrukturen des Workspaces.
1440
+ */
1066
1441
  function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1067
1442
  const files = fs.readdirSync(dir);
1068
-
1069
1443
  for (const file of files) {
1070
1444
  const fullPath = path.join(dir, file);
1071
1445
  const stat = fs.statSync(fullPath);
@@ -1076,27 +1450,51 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1076
1450
  }
1077
1451
  } else {
1078
1452
  const ext = path.extname(file);
1453
+ if (file === 'index.html' || REGEX_PATTERNS.configFile.test(file)) {
1454
+ stats.hasHtml = true;
1455
+ }
1456
+ if (REGEX_PATTERNS.testFile.test(file)) {
1457
+ stats.hasTests = true;
1458
+ }
1459
+ if (ext === '.ts' || ext === '.tsx') {
1460
+ stats.tsFiles++;
1461
+ }
1462
+ if (ext === '.js' || ext === '.jsx' || ext === '.mjs') {
1463
+ stats.jsFiles++;
1464
+ }
1079
1465
 
1080
- if (file === 'index.html' || REGEX_PATTERNS.configFile.test(file)) stats.hasHtml = true;
1081
- if (REGEX_PATTERNS.testFile.test(file)) stats.hasTests = true;
1082
- if (ext === '.ts' || ext === '.tsx') stats.tsFiles++;
1083
- if (ext === '.js' || ext === '.jsx' || ext === '.mjs') stats.jsFiles++;
1084
-
1085
- // Framework-specific file type detection
1086
- if (REGEX_PATTERNS.nextjsPage.test(fullPath)) stats.frameworkFiles.nextjs.pages.add(fullPath);
1087
- if (REGEX_PATTERNS.nextjsApi.test(fullPath)) stats.frameworkFiles.nextjs.apiRoutes.add(fullPath);
1088
- if (REGEX_PATTERNS.nextjsComponent.test(fullPath)) stats.frameworkFiles.nextjs.components.add(fullPath);
1089
- if (REGEX_PATTERNS.nuxtPage.test(fullPath)) stats.frameworkFiles.nuxt.pages.add(fullPath);
1090
- if (REGEX_PATTERNS.nuxtComponent.test(fullPath)) stats.frameworkFiles.nuxt.components.add(fullPath);
1091
- if (REGEX_PATTERNS.sveltekitPage.test(fullPath)) stats.frameworkFiles.sveltekit.pages.add(fullPath);
1092
- if (REGEX_PATTERNS.sveltekitComponent.test(fullPath)) stats.frameworkFiles.sveltekit.components.add(fullPath);
1093
- if (REGEX_PATTERNS.reactHook.test(fullPath)) stats.frameworkFiles.react.hooks.add(fullPath);
1094
- if (REGEX_PATTERNS.vueComposable.test(fullPath)) stats.frameworkFiles.vue.composables.add(fullPath);
1466
+ if (REGEX_PATTERNS.nextjsPage.test(fullPath)) {
1467
+ stats.frameworkFiles.nextjs.pages.add(fullPath);
1468
+ }
1469
+ if (REGEX_PATTERNS.nextjsApi.test(fullPath)) {
1470
+ stats.frameworkFiles.nextjs.apiRoutes.add(fullPath);
1471
+ }
1472
+ if (REGEX_PATTERNS.nextjsComponent.test(fullPath)) {
1473
+ stats.frameworkFiles.nextjs.components.add(fullPath);
1474
+ }
1475
+ if (REGEX_PATTERNS.nuxtPage.test(fullPath)) {
1476
+ stats.frameworkFiles.nuxt.pages.add(fullPath);
1477
+ }
1478
+ if (REGEX_PATTERNS.nuxtComponent.test(fullPath)) {
1479
+ stats.frameworkFiles.nuxt.components.add(fullPath);
1480
+ }
1481
+ if (REGEX_PATTERNS.sveltekitPage.test(fullPath)) {
1482
+ stats.frameworkFiles.sveltekit.pages.add(fullPath);
1483
+ }
1484
+ if (REGEX_PATTERNS.sveltekitComponent.test(fullPath)) {
1485
+ stats.frameworkFiles.sveltekit.components.add(fullPath);
1486
+ }
1487
+ if (REGEX_PATTERNS.reactHook.test(fullPath)) {
1488
+ stats.frameworkFiles.react.hooks.add(fullPath);
1489
+ }
1490
+ if (REGEX_PATTERNS.vueComposable.test(fullPath)) {
1491
+ stats.frameworkFiles.vue.composables.add(fullPath);
1492
+ }
1095
1493
 
1096
1494
  if (VALID_EXTENSIONS.has(ext)) {
1097
1495
  stats.scannedFiles++;
1496
+ stats.scannedFilePaths.push(fullPath);
1098
1497
  const rawContent = readFileSyncNormalized(fullPath);
1099
- // Strip non-printable chars but keep Unicode letters (important for identifiers)
1100
1498
  const content = rawContent.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
1101
1499
 
1102
1500
  const codeLines = content.split(/\r?\n/);
@@ -1106,14 +1504,13 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1106
1504
 
1107
1505
  analyzeCodeStyle(content, stats);
1108
1506
 
1109
- // Universal Cryptographic Leak Interception (Expanded)
1110
1507
  for (const [patternName, patternRegex] of Object.entries(REGEX_PATTERNS)) {
1111
1508
  if (patternName.startsWith("secretKeys") || patternName.endsWith("Keys") || patternName.endsWith("Tokens")) {
1112
1509
  patternRegex.lastIndex = 0;
1113
1510
  let match;
1114
1511
  while ((match = patternRegex.exec(content)) !== null) {
1115
- const keyName = match[1] || patternName; // Use patternName if no specific key name is captured
1116
- const secretValue = match[2] || match[0]; // Use full match if no specific value is captured
1512
+ const keyName = match[1] || patternName;
1513
+ const secretValue = match[2] || match[0];
1117
1514
  const envVarName = `${rootNamespace.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_${keyName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
1118
1515
  stats.discoveredSecrets.push({ filePath: fullPath, keyName, secretValue, envVarName, type: patternName });
1119
1516
  stats.envVars.add(envVarName);
@@ -1141,23 +1538,15 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1141
1538
  stats.quality.largeImageImports.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1142
1539
  }
1143
1540
  } else if (patternName.startsWith("unoptimizedLoop")) {
1144
- patternRegex.lastIndex = 0;
1541
+ patternRegex.lastIndex = 0; // 👈 Add this line to reset the pointer for the next file pass
1145
1542
  let match;
1146
1543
  while ((match = patternRegex.exec(content)) !== null) {
1147
1544
  const line = content.substring(0, match.index).split("\n").length;
1148
1545
  stats.quality.unoptimizedLoops.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1149
1546
  }
1150
- } else if (patternName.startsWith("nextjs") || patternName.startsWith("nuxt") || patternName.startsWith("sveltekit") || patternName.startsWith("react") || patternName.startsWith("vue")) {
1151
- patternRegex.lastIndex = 0;
1152
- let match;
1153
- while ((match = patternRegex.exec(content)) !== null) {
1154
- const line = content.substring(0, match.index).split("\n").length;
1155
- stats.quality.frameworkSpecificIssues.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1156
- }
1157
1547
  }
1158
1548
  }
1159
1549
 
1160
- // Global Regex Environmental Extraction Module
1161
1550
  let fileHasEnv = false;
1162
1551
  let envMatch;
1163
1552
  REGEX_PATTERNS.env.lastIndex = 0;
@@ -1165,14 +1554,15 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1165
1554
  stats.envVars.add(envMatch[1]);
1166
1555
  fileHasEnv = true;
1167
1556
  }
1168
- if (fileHasEnv) stats.filesWithEnvVars.add(fullPath);
1169
-
1170
- if (content.includes('import ') || content.includes('export ')) stats.usesEsm = true;
1557
+ if (fileHasEnv) {
1558
+ stats.filesWithEnvVars.add(fullPath);
1559
+ }
1560
+ if (content.includes('import ') || content.includes('export ')) {
1561
+ stats.usesEsm = true;
1562
+ }
1171
1563
 
1172
- // Perform framework-specific analysis
1173
1564
  FrameworkAnalyzer.analyzeFile(fullPath, content, stats, detectedFrameworks);
1174
1565
 
1175
- // --- AST Parsing (preferred) ---
1176
1566
  let ast = null;
1177
1567
  try {
1178
1568
  ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true, locations: true });
@@ -1189,22 +1579,16 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1189
1579
  stats.exportedSymbols.set(fullPath, currentFileExportedSymbols);
1190
1580
  }
1191
1581
  } else {
1192
- // Regex fallback for TypeScript generics / decorators / etc.
1193
1582
  extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, fullPath);
1194
1583
  }
1195
1584
 
1196
- // Register all deps found in this file
1197
- fileRawDeps.forEach(dep => stats.allImportedPackages.add(dep));
1198
- fileRawDeps.forEach(dep => stats.rawDeps.add(dep));
1585
+ fileRawDeps.forEach(dep => { return stats.allImportedPackages.add(dep); });
1586
+ fileRawDeps.forEach(dep => { return stats.rawDeps.add(dep); });
1199
1587
 
1200
- // --- Per-file usage analysis ---
1201
- // Strip import/require lines to get only execution code
1202
- const executionCode = codeLines
1203
- .filter(l => {
1204
- const t = l.trim();
1205
- return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
1206
- })
1207
- .join('\n');
1588
+ const executionCode = codeLines.filter(l => {
1589
+ const t = l.trim();
1590
+ return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
1591
+ }).join('\n');
1208
1592
 
1209
1593
  for (const [pkg, identifiers] of importedIdentifiers.entries()) {
1210
1594
  const isUsed = analyzeIdentifierUsage(pkg, identifiers, executionCode);
@@ -1222,7 +1606,156 @@ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
1222
1606
  }
1223
1607
  }
1224
1608
 
1609
+ // ============================================================
1610
+ // 🌳 KNIP-LEVEL REACHABILITY TRAVERSAL ENGINE
1611
+ // ============================================================
1612
+ class KnipEcosystemGraph {
1613
+ constructor(stats) {
1614
+ this.stats = stats;
1615
+ this.reachableFiles = new Set();
1616
+ this.usedSymbolsMap = new Map();
1617
+ }
1618
+
1619
+ /**
1620
+ * Traversiert den Modulgraphen ausgehend von den Entrypoints, um erreichbare Dateien und Tokens zu mappen.
1621
+ * @param {Array<string>} entrypoints Liste absoluter Dateipfade als Einstiegspunkte.
1622
+ */
1623
+ traceReachability(entrypoints) {
1624
+ const queue = [...entrypoints];
1625
+ const visited = new Set();
1626
+
1627
+ while (queue.length > 0) {
1628
+ const currentFile = queue.shift();
1629
+ if (visited.has(currentFile)) {
1630
+ continue;
1631
+ }
1632
+ visited.add(currentFile);
1633
+ this.reachableFiles.add(currentFile);
1634
+
1635
+ const meta = this.stats.fileSymbolMetadata?.get(currentFile);
1636
+ if (!meta) {
1637
+ continue;
1638
+ }
1639
+
1640
+ meta.imports.forEach(imp => {
1641
+ const targetPath = resolveLocalModulePath(currentFile, imp.source);
1642
+ if (!targetPath) {
1643
+ return;
1644
+ }
1645
+
1646
+ if (!this.usedSymbolsMap.has(targetPath)) {
1647
+ this.usedSymbolsMap.set(targetPath, new Set());
1648
+ }
1649
+ const targetUses = this.usedSymbolsMap.get(targetPath);
1650
+
1651
+ imp.specifiers.forEach(spec => {
1652
+ if (spec.type === 'ImportNamespaceSpecifier') {
1653
+ const propsAccessed = meta.namespaceUses.get(spec.local);
1654
+ if (propsAccessed) {
1655
+ propsAccessed.forEach(p => { return targetUses.add(p); });
1656
+ } else {
1657
+ targetUses.add('*');
1658
+ }
1659
+ } else {
1660
+ targetUses.add(spec.imported);
1661
+ }
1662
+ });
1663
+ if (!visited.has(targetPath)) {
1664
+ queue.push(targetPath);
1665
+ }
1666
+ });
1667
+
1668
+ meta.reExports.forEach(re => {
1669
+ const targetPath = resolveLocalModulePath(currentFile, re.source);
1670
+ if (!targetPath) {
1671
+ return;
1672
+ }
1673
+
1674
+ if (!this.usedSymbolsMap.has(targetPath)) {
1675
+ this.usedSymbolsMap.set(targetPath, new Set());
1676
+ }
1677
+ const targetUses = this.usedSymbolsMap.get(targetPath);
1678
+
1679
+ re.specifiers.forEach(spec => {
1680
+ targetUses.add(spec.local || spec.exported);
1681
+ });
1682
+ if (!visited.has(targetPath)) {
1683
+ queue.push(targetPath);
1684
+ }
1685
+ });
1686
+ }
1687
+ }
1688
+ }
1689
+
1690
+ // ============================================================
1691
+ // 📊 POST-PROCESSING ANALYSIS PASS
1692
+ // ============================================================
1693
+ /**
1694
+ * Führt nach der Traversierung den Abgleich zwischen deklarierten und ungenutzten Exporten/Dateien durch.
1695
+ * @param {Object} stats Globales Analyseobjekt.
1696
+ * @param {Object} graphEngine Instanz der Graphen-Traversierung.
1697
+ */
1698
+ function postProcessAnalysis(stats, graphEngine) {
1699
+ stats.unusedFiles = new Set();
1700
+ stats.unusedExportsPerFile = new Map();
1701
+
1702
+ stats.scannedFilePaths.forEach(filePath => {
1703
+ if (!graphEngine.reachableFiles.has(filePath)) {
1704
+ stats.unusedFiles.add(filePath);
1705
+ }
1706
+ });
1707
+
1708
+ for (const filePath of graphEngine.reachableFiles) {
1709
+ const meta = stats.fileSymbolMetadata?.get(filePath);
1710
+ if (!meta) {
1711
+ continue;
1712
+ }
1713
+
1714
+ const globalUses = graphEngine.usedSymbolsMap.get(filePath) || new Set();
1715
+ if (globalUses.has('*')) {
1716
+ continue;
1717
+ }
1718
+
1719
+ const unusedSet = new Set();
1720
+ for (const [exportName] of meta.exports.entries()) {
1721
+ if (!globalUses.has(exportName)) {
1722
+ unusedSet.add(exportName);
1723
+ }
1724
+ }
1725
+ if (unusedSet.size > 0) {
1726
+ stats.unusedExportsPerFile.set(filePath, unusedSet);
1727
+ }
1728
+ }
1729
+
1730
+ if (stats.detectedFrameworks && stats.detectedFrameworks.includes('tailwind')) {
1731
+ const tailwindConfigPath = path.join(stats.targetDir, 'tailwind.config.js');
1732
+ if (fs.existsSync(tailwindConfigPath)) {
1733
+ try {
1734
+ const tailwindContent = fs.readFileSync(tailwindConfigPath, 'utf8');
1735
+ const contentArrayMatch = tailwindContent.match(/content:\s*\[([^\]]+)\]/s);
1736
+ if (contentArrayMatch && contentArrayMatch[1]) {
1737
+ const globPatterns = contentArrayMatch[1].split(',').map(s => { return s.trim().replace(/["']/g, ''); });
1738
+ if (globPatterns.length > 0) {
1739
+ console.log(` ℹ️ Tailwind Context Scan: Registered ${globPatterns.length} content matching globs vectors.`);
1740
+ }
1741
+ }
1742
+ } catch (tailwindFileReadException) {}
1743
+ }
1744
+ }
1745
+ }
1746
+
1747
+ // ============================================================
1748
+ // INTERACTIVE ENGINE COMMAND LINE SYSTEM
1749
+ // ============================================================
1225
1750
  async function main() {
1751
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
1752
+ console.log(`\n📦 pkg-scaffold v2.2.0: Advanced Dependency Intelligence Engine\n`);
1753
+ console.log(`Usage: npx pkg-scaffold [options]\n`);
1754
+ console.log(`Options:`);
1755
+ console.log(` -h, --help Show this comprehensive workspace helper panel`);
1756
+ process.exit(0);
1757
+ }
1758
+
1226
1759
  if (process.env.INIT_CWD && !process.env.NPX_CLI_JS) {
1227
1760
  console.log("\x1b[31m%s\x1b[0m", "🛑 Wait! Do not install this package locally.");
1228
1761
  console.log("Please run it directly using: \x1b[36mnpx pkg-scaffold\x1b[0m\n");
@@ -1236,49 +1769,52 @@ async function main() {
1236
1769
  let rlClosed = false;
1237
1770
  rl.on('close', () => { rlClosed = true; });
1238
1771
  const safeQuestion = async (prompt) => {
1239
- if (rlClosed || !process.stdin.readable) return '';
1240
- try { return await rl.question(prompt); } catch { return ''; }
1772
+ if (rlClosed || !process.stdin.readable) {
1773
+ return '';
1774
+ }
1775
+ try {
1776
+ return await rl.question(prompt);
1777
+ } catch (readlineQuestionPromptError) {
1778
+ return '';
1779
+ }
1241
1780
  };
1242
1781
 
1243
1782
  const stats = {
1244
- tsFiles: 0, jsFiles: 0, usesEsm: false, hasHtml: false, hasTests: false,
1783
+ tsFiles: 0,
1784
+ jsFiles: 0,
1785
+ usesEsm: false,
1786
+ hasHtml: false,
1787
+ hasTests: false,
1245
1788
  scannedFiles: 0,
1789
+ scannedFilePaths: [],
1246
1790
  rawDeps: new Set(),
1247
1791
  allImportedPackages: new Set(),
1248
1792
  envVars: new Set(),
1249
1793
  style: { semiCount: 0, noSemiCount: 0, tabCount: 0, space2Count: 0, space4Count: 0 },
1250
1794
  quality: {
1251
- varCount: 0,
1252
- hasEval: false,
1253
- syncFsCount: 0,
1254
- insecurePatterns: [],
1255
- complexRegexes: [],
1256
- insecureCryptoUsage: [],
1257
- sqlInjectionVulnerabilities: [],
1258
- xssVulnerabilities: [],
1259
- largeImageImports: [],
1260
- unoptimizedLoops: [],
1261
- frameworkSpecificIssues: []
1795
+ varCount: 0, hasEval: false, syncFsCount: 0, insecurePatterns: [], complexRegexes: [],
1796
+ insecureCryptoUsage: [], sqlInjectionVulnerabilities: [], xssVulnerabilities: [],
1797
+ largeImageImports: [], unoptimizedLoops: [], frameworkSpecificIssues: []
1262
1798
  },
1263
1799
  phantomInjections: new Map(),
1264
1800
  discoveredSecrets: [],
1265
- insecureCodePatterns: [], // New: detailed insecure code patterns
1801
+ insecureCodePatterns: [],
1266
1802
  subWorkspaces: [],
1267
1803
  conflictingLockfiles: [],
1268
- exportedSymbols: new Map(), // filePath -> Map<symbolName, { type: 'function'|'variable'|'class', loc: {line, col} }>
1269
- usedExports: new Map(), // filePath -> Set<symbolName> (exports from this file that are used elsewhere)
1270
- unusedFiles: new Set(), // Files that are never imported/referenced
1271
- unusedExportsPerFile: new Map(), // filePath -> Set<symbolName> (exports from this file that are not used anywhere)
1272
- localFileImports: new Map(), // filePath -> Set<importedSymbol> (local imports from this file)
1804
+ exportedSymbols: new Map(),
1805
+ usedExports: new Map(),
1806
+ unusedFiles: new Set(),
1807
+ unusedExportsPerFile: new Map(),
1808
+ localFileImports: new Map(),
1273
1809
  unusedDepsInCode: new Set(),
1274
- unusedImportsPerFile: new Map(),
1275
- filesWithEnvVars: new Set(),
1276
- injectDotenvEngine: false,
1810
+ unusedImportsPerFile: new Map(),
1811
+ filesWithEnvVars: new Set(),
1812
+ injectDotenvEngine: false,
1277
1813
  bootstrapEslintSuite: false,
1278
- // New tracking structures
1279
- ghostDependencies: new Set(), // used in code, missing from package.json
1280
- orphanedDependencies: new Set(), // in package.json, never imported
1281
- deprecatedPackages: new Map(), // pkg -> deprecation message
1814
+ ghostDependencies: new Set(),
1815
+ orphanedDependencies: new Set(),
1816
+ deprecatedPackages: new Map(),
1817
+ fileSymbolMetadata: new Map(),
1282
1818
  frameworkFiles: {
1283
1819
  nextjs: { pages: new Set(), apiRoutes: new Set(), components: new Set(), dataFetching: new Map(), optimizations: [] },
1284
1820
  nuxt: { pages: new Set(), components: new Set(), modules: new Set(), dataFetching: new Map(), optimizations: [] },
@@ -1286,25 +1822,21 @@ async function main() {
1286
1822
  react: { hooks: new Set(), components: new Set(), optimizations: [] },
1287
1823
  vue: { composables: new Set(), components: new Set(), optimizations: [] },
1288
1824
  },
1289
- frameworkOptimizations: [], // General framework-agnostic optimizations
1825
+ frameworkOptimizations: [],
1826
+ packageJson: null,
1827
+ targetDir: targetDir,
1828
+ detectedFrameworks: []
1290
1829
  };
1291
1830
 
1292
1831
  const activePkgManager = detectPackageManager(targetDir, stats);
1293
1832
  const pkgPath = path.join(targetDir, 'package.json');
1294
- let preExistingLicense = null;
1295
- let preExistingDeps = [];
1296
- let preExistingDevDeps = [];
1297
- let existingPackageJson = null;
1298
-
1299
-
1300
-
1301
-
1833
+ let preExistingLicense = null, preExistingDeps = [], preExistingDevDeps = [], existingPackageJson = null;
1834
+ let detectedFrameworks = [];
1302
1835
 
1303
1836
  console.log(`\n${'═'.repeat(67)}`);
1304
- console.log(`🚀 pkg-scaffold v2.0: Advanced Dependency Intelligence Engine`);
1837
+ console.log(`🚀 pkg-scaffold v2.2.0: Enterprise Graph Intelligence Analyzer`);
1305
1838
  console.log(`${'═'.repeat(67)}\n`);
1306
1839
 
1307
- // --- Sub-workspace detection ---
1308
1840
  const topLevelItems = fs.readdirSync(targetDir);
1309
1841
  const potentialSubModules = [];
1310
1842
  for (const item of topLevelItems) {
@@ -1317,95 +1849,121 @@ async function main() {
1317
1849
  for (const entry of subEntries) {
1318
1850
  const entryPath = path.join(d, entry);
1319
1851
  if (fs.statSync(entryPath).isDirectory()) {
1320
- if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) examineDirectory(entryPath);
1852
+ if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) {
1853
+ examineDirectory(entryPath);
1854
+ }
1321
1855
  } else if (VALID_EXTENSIONS.has(path.extname(entry))) {
1322
1856
  containsSourceCode = true;
1323
1857
  }
1324
1858
  }
1325
- } catch {}
1859
+ } catch (subDirReadError) {}
1326
1860
  };
1327
1861
  examineDirectory(fullPath);
1328
- if (containsSourceCode) potentialSubModules.push(item);
1862
+ if (containsSourceCode) {
1863
+ potentialSubModules.push(item);
1864
+ }
1329
1865
  }
1330
1866
  }
1331
- if (potentialSubModules.length > 1) stats.subWorkspaces = potentialSubModules;
1867
+ if (potentialSubModules.length > 1) {
1868
+ stats.subWorkspaces = potentialSubModules;
1869
+ }
1332
1870
 
1333
- // --- Existing package.json analysis ---
1334
1871
  if (fs.existsSync(pkgPath)) {
1335
1872
  console.log(`⚠️ An existing package.json was found in this working directory.`);
1336
1873
  console.log(`📡 Analyzing existing installation arrays for invalid metrics...`);
1337
1874
  try {
1338
1875
  existingPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
1876
+ stats.packageJson = existingPackageJson;
1339
1877
  if (existingPackageJson.license && typeof existingPackageJson.license === 'string' && existingPackageJson.license.toLowerCase() !== 'none') {
1340
1878
  preExistingLicense = existingPackageJson.license;
1341
1879
  }
1342
- if (existingPackageJson.dependencies) preExistingDeps = Object.keys(existingPackageJson.dependencies);
1343
- if (existingPackageJson.devDependencies) preExistingDevDeps = Object.keys(existingPackageJson.devDependencies);
1880
+ if (existingPackageJson.dependencies) {
1881
+ preExistingDeps = Object.keys(existingPackageJson.dependencies);
1882
+ }
1883
+ if (existingPackageJson.devDependencies) {
1884
+ preExistingDevDeps = Object.keys(existingPackageJson.devDependencies);
1885
+ }
1344
1886
 
1345
- // Detect frameworks after packageJson is loaded
1346
- const detectedFrameworks = FrameworkEngine.detect(targetDir, existingPackageJson);
1887
+ detectedFrameworks = FrameworkEngine.detect(targetDir, existingPackageJson);
1347
1888
  stats.detectedFrameworks = detectedFrameworks;
1348
1889
 
1349
1890
  const combinedDeps = [...preExistingDeps, ...preExistingDevDeps];
1350
- let brokenEcosystem = combinedDeps.length === 0;
1891
+ let brokenEcosystem = false;
1351
1892
 
1352
- // Check for non-existent AND deprecated packages
1353
1893
  if (combinedDeps.length > 0) {
1354
- console.log(` 🔍 Validating ${combinedDeps.length} declared package(s) against npm registry...`);
1894
+ console.log(` 🔍 Validating ${combinedDeps.length} declared package(s) against npm registry...`);
1355
1895
  for (const dep of combinedDeps) {
1356
1896
  const check = await inspectNpmPackage(dep);
1897
+
1357
1898
  if (check && check.error === 'NOT_FOUND') {
1358
- brokenEcosystem = true;
1359
- console.log(` Non-existent package on registry: "${dep}"`);
1899
+ if (dep.startsWith('@')) {
1900
+ console.log(` ℹ️ Scoped or private module bypassed registry assertion: "${dep}"`);
1901
+ } else {
1902
+ brokenEcosystem = true;
1903
+ console.log(` ❌ Non-existent package on registry: "${dep}"`);
1904
+ }
1360
1905
  } else if (check && check.deprecated) {
1361
1906
  stats.deprecatedPackages.set(dep, check.deprecated);
1362
- console.log(` ⚠️ Deprecated package detected: "${dep}" — ${check.deprecated}`);
1907
+ console.log(` ⚠️ Deprecated package detected: "${dep}" — ${check.deprecated}`);
1363
1908
  }
1364
1909
  }
1365
1910
  }
1366
-
1367
1911
  if (brokenEcosystem) {
1368
- console.log(`\n🛑 CRITICAL COMPLIANCE BREAK: Your current package.json is empty or contains non-existent packages.`);
1912
+ console.log(`\n🛑 CRITICAL COMPLIANCE BREAK: Your current package.json contains non-existent packages.`);
1369
1913
  console.log(`👉 Action Required: Please remove or backup the existing 'package.json' from this folder.\n`);
1370
1914
  rl.close();
1371
1915
  return;
1372
1916
  }
1373
- } catch (err) {
1917
+ } catch (packageJsonParseRuntimeError) {
1374
1918
  console.log(`\n🛑 CRITICAL: Existing package.json is malformed or corrupt.\n`);
1375
1919
  rl.close();
1376
1920
  return;
1377
1921
  }
1378
1922
  }
1379
1923
 
1380
- // --- Workspace scan ---
1381
1924
  console.log(`\n🔬 Scanning workspace source files...`);
1382
1925
  scanWorkspace(targetDir, stats, folderName, detectedFrameworks);
1383
- console.log(` Scanned ${stats.scannedFiles} source file(s) | TS: ${stats.tsFiles} | JS: ${stats.jsFiles}`);
1926
+ console.log(` Janitor: Found ${stats.scannedFiles} source module assets.`);
1927
+
1928
+ // --- TRIGGER RE-ARCHITECTED KNIP GRAPH INTELLIGENCE ---
1929
+ const graphEngine = new KnipEcosystemGraph(stats);
1930
+ const initialEntries = new Set();
1931
+ const baseEntryFallbacks = ['index.js', 'index.ts', 'src/index.js', 'src/index.ts', 'main.js', 'src/main.js', 'src/main.ts'];
1932
+ baseEntryFallbacks.forEach(f => {
1933
+ const absolutePath = path.join(targetDir, f);
1934
+ if (fs.existsSync(absolutePath)) {
1935
+ initialEntries.add(absolutePath);
1936
+ }
1937
+ });
1938
+
1939
+ if (existingPackageJson) {
1940
+ if (existingPackageJson.main) {
1941
+ initialEntries.add(path.resolve(targetDir, existingPackageJson.main));
1942
+ }
1943
+ if (existingPackageJson.module) {
1944
+ initialEntries.add(path.resolve(targetDir, existingPackageJson.module));
1945
+ }
1946
+ }
1384
1947
 
1385
- // Build dependency graph for advanced analysis
1386
- const dependencyGraph = new DependencyGraph(stats);
1948
+ stats.frameworkFiles.nextjs.pages.forEach(file => { return initialEntries.add(file); });
1949
+ stats.frameworkFiles.nextjs.apiRoutes.forEach(file => { return initialEntries.add(file); });
1950
+ stats.frameworkFiles.nuxt.pages.forEach(file => { return initialEntries.add(file); });
1951
+ stats.frameworkFiles.sveltekit.pages.forEach(file => { return initialEntries.add(file); });
1952
+
1953
+ graphEngine.traceReachability(Array.from(initialEntries));
1954
+ postProcessAnalysis(stats, graphEngine);
1387
1955
 
1388
- // --- Binary-to-package resolution ---
1389
1956
  const binariesInScripts = existingPackageJson ? getBinariesFromPackageJson(existingPackageJson) : [];
1390
1957
  const resolvedBinaryPackages = new Set();
1391
1958
  for (const binary of binariesInScripts) {
1392
1959
  const pkgName = BINARY_TO_PACKAGE_MAP[binary] || binary;
1393
1960
  resolvedBinaryPackages.add(pkgName);
1394
1961
  stats.rawDeps.add(pkgName);
1395
- stats.allImportedPackages.add(pkgName); // treat as "used"
1962
+ stats.allImportedPackages.add(pkgName);
1396
1963
  }
1397
1964
 
1398
- // ============================================================
1399
- // GHOST DEPENDENCY ANALYSIS
1400
- // Packages imported in code but missing from package.json
1401
- // ============================================================
1402
1965
  if (preExistingDeps.length > 0 || preExistingDevDeps.length > 0) {
1403
- stats.ghostDependencies = detectGhostDependencies(
1404
- stats.allImportedPackages,
1405
- preExistingDeps,
1406
- preExistingDevDeps
1407
- );
1408
- // Remove dev tooling from ghost list (they may be globally installed)
1966
+ stats.ghostDependencies = detectGhostDependencies(stats.allImportedPackages, preExistingDeps, preExistingDevDeps);
1409
1967
  for (const dep of stats.ghostDependencies) {
1410
1968
  if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
1411
1969
  stats.ghostDependencies.delete(dep);
@@ -1413,78 +1971,54 @@ async function main() {
1413
1971
  }
1414
1972
  }
1415
1973
 
1416
- // ============================================================
1417
- // ORPHANED DEPENDENCY ANALYSIS
1418
- // Packages in package.json that are never imported
1419
- // ============================================================
1420
1974
  if (preExistingDeps.length > 0) {
1421
- stats.orphanedDependencies = detectOrphanedDependencies(
1422
- preExistingDeps,
1423
- stats.allImportedPackages,
1424
- resolvedBinaryPackages,
1425
- DEV_TOOLING_ECOSYSTEM
1426
- );
1427
- }
1428
-
1429
- // ============================================================
1430
- // UNUSED IMPORTS ANALYSIS (cross-file aggregation)
1431
- // A package is only truly "unused" if it's never used in ANY file
1432
- // ============================================================
1433
- // Build a set of packages that ARE used in at least one file
1434
- const usedInAtLeastOneFile = new Set();
1435
- for (const [, fileImports] of stats.unusedImportsPerFile.entries()) {
1436
- // If a package appears in unusedImportsPerFile for this file,
1437
- // it might still be used in another file — check allImportedPackages
1438
- }
1439
- // Refine: unusedDepsInCode should only include packages that are
1440
- // imported but never referenced across the entire codebase
1441
- const trulyUnusedImports = new Set();
1442
- for (const pkg of stats.unusedDepsInCode) {
1443
- // If the package is used (identifier found) in ANY file, remove from unused
1444
- let foundUsedElsewhere = false;
1445
- for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
1446
- if (!fileUnused.has(pkg)) {
1447
- // This file imports pkg and DOES use it
1448
- if (stats.allImportedPackages.has(pkg)) {
1449
- foundUsedElsewhere = true;
1450
- break;
1451
- }
1452
- }
1453
- }
1454
- if (!foundUsedElsewhere) trulyUnusedImports.add(pkg);
1455
- }
1456
-
1457
- // ============================================================
1458
- // DISPLAY: GHOST DEPENDENCIES (critical — will break at runtime)
1459
- // ============================================================
1975
+ stats.orphanedDependencies = detectOrphanedDependencies(preExistingDeps, stats.allImportedPackages, resolvedBinaryPackages, DEV_TOOLING_ECOSYSTEM);
1976
+ }
1977
+
1460
1978
  if (stats.ghostDependencies.size > 0) {
1461
1979
  console.log(`\n${'─'.repeat(67)}`);
1462
1980
  console.log(`🚨 GHOST DEPENDENCIES DETECTED (CRITICAL — Runtime/Deploy will FAIL)`);
1463
1981
  console.log(`${'─'.repeat(67)}`);
1464
- console.log(` These packages are USED in your code but NOT listed in package.json.`);
1465
- console.log(` They may work locally (if globally installed) but WILL FAIL in CI/CD.\n`);
1982
+ console.log(` Diese Pakete fehlen in deiner package.json, werden aber aktiv importiert:\n`);
1466
1983
  for (const pkg of stats.ghostDependencies) {
1467
- console.log(` ❌ \x1b[31m"${pkg}"\x1b[0m — imported in code, missing from package.json`);
1984
+ console.log(` ❌ \x1b[31m"${pkg}"\x1b[0m — missing from package.json`);
1468
1985
  }
1469
1986
  console.log(`${'─'.repeat(67)}`);
1470
1987
  const addGhosts = await safeQuestion(`❓ Add these missing packages to package.json automatically? (Y/n): `);
1471
1988
  if (addGhosts.trim().toLowerCase() !== 'n' && addGhosts.trim().toLowerCase() !== 'no') {
1472
- for (const pkg of stats.ghostDependencies) stats.rawDeps.add(pkg);
1473
- console.log(` ✅ Ghost dependencies queued for package.json registration.`);
1989
+ for (const pkg of stats.ghostDependencies) {
1990
+ stats.rawDeps.add(pkg);
1991
+ }
1992
+ console.log(` ✅ Ghost dependencies queued for package.json registration.`);
1474
1993
  }
1475
1994
  }
1476
1995
 
1477
- // ============================================================
1478
- // DISPLAY: ORPHANED DEPENDENCIES (in package.json, never used)
1479
- // ============================================================
1996
+ if (stats.unusedFiles.size > 0) {
1997
+ console.log(`\n${'─'.repeat(67)}`);
1998
+ console.log(`🗑️ DEAD CODE FILES DETECTED (Unreachable from Entrypoints)`);
1999
+ console.log(`${'─'.repeat(67)}`);
2000
+ stats.unusedFiles.forEach(f => {
2001
+ console.log(` 💀 \x1b[31m"${path.relative(targetDir, f)}"\x1b[0m — file never mapped or imported.`);
2002
+ });
2003
+ console.log(`${'─'.repeat(67)}`);
2004
+ }
2005
+
2006
+ if (stats.unusedExportsPerFile.size > 0) {
2007
+ console.log(`\n${'─'.repeat(67)}`);
2008
+ console.log(`📤 UNUSED EXPORTS DETECTED (Dead Public API Symbols)`);
2009
+ console.log(`${'─'.repeat(67)}`);
2010
+ for (const [file, symbols] of stats.unusedExportsPerFile.entries()) {
2011
+ console.log(` ⚡ \x1b[33m"${path.relative(targetDir, file)}"\x1b[0m -> Dead Token(s): [ ${Array.from(symbols).join(', ')} ]`);
2012
+ }
2013
+ console.log(`${'─'.repeat(67)}`);
2014
+ }
2015
+
1480
2016
  if (stats.orphanedDependencies.size > 0) {
1481
2017
  console.log(`\n${'─'.repeat(67)}`);
1482
2018
  console.log(`📦 ORPHANED DEPENDENCIES DETECTED (in package.json, never imported)`);
1483
2019
  console.log(`${'─'.repeat(67)}`);
1484
- console.log(` These packages are declared in package.json but never imported`);
1485
- console.log(` anywhere in your source code. Safe to remove.\n`);
1486
2020
  for (const pkg of stats.orphanedDependencies) {
1487
- console.log(` 🗑️ \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
2021
+ console.log(` 🗑️ \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
1488
2022
  }
1489
2023
  console.log(`${'─'.repeat(67)}`);
1490
2024
  const pruneOrphans = await safeQuestion(`❓ Remove these orphaned packages from package.json? (y/N): `);
@@ -1494,87 +2028,23 @@ async function main() {
1494
2028
  delete existingPackageJson.dependencies?.[pkg];
1495
2029
  }
1496
2030
  fs.writeFileSync(pkgPath, JSON.stringify(existingPackageJson, null, 2));
1497
- console.log(` 🗑️ Orphaned dependencies removed from package.json.`);
2031
+ console.log(` 🗑️ Orphaned dependencies removed from package.json.`);
1498
2032
  }
1499
2033
  }
1500
2034
  }
1501
2035
 
1502
- // ============================================================
1503
- // DISPLAY: UNUSED IMPORTS (imported but never referenced in code)
1504
- // ============================================================
1505
- const allDiscoveredUnused = new Set([...trulyUnusedImports]);
1506
- // Also add packages in package.json not found in code at all
1507
- if (preExistingDeps.length > 0) {
1508
- preExistingDeps.forEach(dep => {
1509
- if (!stats.rawDeps.has(dep) && !DEV_TOOLING_ECOSYSTEM.has(dep) && !dep.startsWith('@types/')) {
1510
- allDiscoveredUnused.add(dep);
1511
- }
1512
- });
1513
- }
1514
- // Remove dev tooling from unused list
1515
- for (const dep of allDiscoveredUnused) {
1516
- if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
1517
- allDiscoveredUnused.delete(dep);
1518
- }
1519
- }
1520
-
1521
- if (allDiscoveredUnused.size > 0) {
1522
- console.log(`\n${'─'.repeat(67)}`);
1523
- console.log(`⚠️ UNUSED IMPORTS DETECTED (imported but never referenced in code)`);
1524
- console.log(`${'─'.repeat(67)}`);
1525
- console.log(` These modules are imported but their identifiers are never used`);
1526
- console.log(` in executable code paths.\n`);
1527
-
1528
- for (const dep of allDiscoveredUnused) {
1529
- // Show which files have this unused import
1530
- const filesWithUnused = [];
1531
- for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
1532
- if (fileUnused.has(dep)) {
1533
- const lines = fileUnused.get(dep);
1534
- const lineStr = lines.length > 0 ? `:${lines[0]}` : '';
1535
- filesWithUnused.push(`${path.relative(targetDir, filePath)}${lineStr}`);
1536
- }
1537
- }
1538
- if (filesWithUnused.length > 0) {
1539
- console.log(` ⚡ \x1b[33m"${dep}"\x1b[0m`);
1540
- filesWithUnused.forEach(f => console.log(` └─ ${f}`));
1541
- } else {
1542
- console.log(` ⚡ \x1b[33m"${dep}"\x1b[0m`);
1543
- }
1544
- }
1545
- console.log(`${'─'.repeat(67)}`);
1546
-
1547
- const pruneChoice = await safeQuestion(`❓ Exclude these unused imports from your package.json setup? (y/N): `);
1548
- if (pruneChoice.trim().toLowerCase() === 'y' || pruneChoice.trim().toLowerCase() === 'yes') {
1549
- for (const deadDep of allDiscoveredUnused) stats.rawDeps.delete(deadDep);
1550
- console.log(` 🗑️ Pruned unused imports from configuration blueprint.`);
1551
- }
1552
- }
1553
-
1554
- // ============================================================
1555
- // DISPLAY: DEPRECATED PACKAGES
1556
- // ============================================================
1557
2036
  if (stats.deprecatedPackages.size > 0) {
1558
2037
  console.log(`\n${'─'.repeat(67)}`);
1559
2038
  console.log(`⚠️ DEPRECATED PACKAGES DETECTED`);
1560
2039
  console.log(`${'─'.repeat(67)}`);
1561
2040
  for (const [pkg, msg] of stats.deprecatedPackages.entries()) {
1562
- console.log(` 📛 \x1b[33m"${pkg}"\x1b[0m ${msg}`);
2041
+ // FIX: Behebt das fehlerhafte Ersetzungstoken "Badge" durch das vorgesehene Warnungs-Emoji Layout
2042
+ console.log(` 📛 \x1b[33m"${pkg}"\x1b[0m — ${msg}`);
1563
2043
  }
1564
2044
  console.log(`${'─'.repeat(67)}`);
1565
2045
  }
1566
2046
 
1567
- // ============================================================
1568
- // PHANTOM INJECTION DETECTION
1569
- // Packages used in code (by identifier) but never imported
1570
- // ============================================================
1571
- // Build phantom detection from ALL declared packages
1572
2047
  const allDeclaredForPhantom = new Set([...preExistingDeps, ...preExistingDevDeps]);
1573
- for (const [filePath] of stats.unusedImportsPerFile.entries()) {
1574
- // Already handled above
1575
- }
1576
-
1577
- // Scan for identifiers used without import (using declared package names as hints)
1578
2048
  const phantomScanContent = new Map();
1579
2049
  function collectExecutionContent(dir) {
1580
2050
  try {
@@ -1586,27 +2056,26 @@ async function main() {
1586
2056
  } else if (VALID_EXTENSIONS.has(path.extname(file))) {
1587
2057
  try {
1588
2058
  const content = readFileSyncNormalized(fullPath);
1589
- const execCode = content.split(/\r?\n/)
1590
- .filter(l => {
1591
- const t = l.trim();
1592
- return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
1593
- })
1594
- .join('\n');
2059
+ const execCode = content.split(/\r?\n/).filter(l => {
2060
+ const t = l.trim();
2061
+ return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
2062
+ }).join('\n');
1595
2063
  phantomScanContent.set(fullPath, execCode);
1596
- } catch {}
2064
+ } catch (readExecContentError) {}
1597
2065
  }
1598
2066
  }
1599
- } catch {}
2067
+ } catch (fsCollectExecError) {}
1600
2068
  }
1601
2069
  collectExecutionContent(targetDir);
1602
2070
 
1603
2071
  for (const [filePath, execCode] of phantomScanContent.entries()) {
1604
2072
  for (const token of allDeclaredForPhantom) {
1605
2073
  const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1606
- const tokenPattern = new RegExp(`\\b${escaped}\\b`);
1607
- if (tokenPattern.test(execCode) && !stats.allImportedPackages.has(token)) {
2074
+ if (new RegExp(`\\b${escaped}\\b`).test(execCode) && !stats.allImportedPackages.has(token)) {
1608
2075
  stats.rawDeps.add(token);
1609
- if (!stats.phantomInjections.has(filePath)) stats.phantomInjections.set(filePath, new Set());
2076
+ if (!stats.phantomInjections.has(filePath)) {
2077
+ stats.phantomInjections.set(filePath, new Set());
2078
+ }
1610
2079
  stats.phantomInjections.get(filePath).add(token);
1611
2080
  }
1612
2081
  }
@@ -1615,7 +2084,6 @@ async function main() {
1615
2084
  const isTypeScript = stats.tsFiles > stats.jsFiles;
1616
2085
  const isFrontendWeb = stats.hasHtml || stats.rawDeps.has('react') || stats.rawDeps.has('vue') || stats.rawDeps.has('vite') || stats.rawDeps.has('svelte') || stats.rawDeps.has('next') || stats.rawDeps.has('nuxt');
1617
2086
 
1618
- // --- dotenv suggestion ---
1619
2087
  if (stats.envVars.size > 0 && !stats.rawDeps.has('dotenv') && !isFrontendWeb) {
1620
2088
  console.log(`\n📡 CONFIGURATION COMPLIANCE GAP: UNMANAGED ENVIRONMENT VARIABLES`);
1621
2089
  console.log(`${'─'.repeat(67)}`);
@@ -1628,7 +2096,6 @@ async function main() {
1628
2096
  }
1629
2097
  }
1630
2098
 
1631
- // --- Build package.json ---
1632
2099
  const packageJson = {
1633
2100
  name: folderName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'),
1634
2101
  version: '1.0.0',
@@ -1637,11 +2104,10 @@ async function main() {
1637
2104
  author: gitInfo.author || undefined,
1638
2105
  repository: gitInfo.repository ? { type: "git", url: `git+${gitInfo.repository}.git` } : undefined,
1639
2106
  scripts: { test: stats.hasTests ? (isFrontendWeb ? 'vitest' : 'jest') : 'echo "No workspace test vectors specified" && exit 0' },
1640
- dependencies: {},
2107
+ dependencies: {},
1641
2108
  devDependencies: {}
1642
2109
  };
1643
2110
 
1644
- // --- ESLint suggestion ---
1645
2111
  const eslintConfigFile = path.join(targetDir, 'eslint.config.js');
1646
2112
  const linterPresent = fs.existsSync(eslintConfigFile) || fs.existsSync(path.join(targetDir, '.eslintrc.json')) || fs.existsSync(path.join(targetDir, '.eslintrc.js'));
1647
2113
 
@@ -1654,8 +2120,11 @@ async function main() {
1654
2120
  if (choiceLintSetup.trim().toLowerCase() !== 'n' && choiceLintSetup.trim().toLowerCase() !== 'no') {
1655
2121
  stats.bootstrapEslintSuite = true;
1656
2122
  stats.rawDeps.add('eslint');
1657
- if (isTypeScript) stats.rawDeps.add('typescript-eslint');
1658
- else stats.rawDeps.add('@eslint/js');
2123
+ if (isTypeScript) {
2124
+ stats.rawDeps.add('typescript-eslint');
2125
+ } else {
2126
+ stats.rawDeps.add('@eslint/js');
2127
+ }
1659
2128
  }
1660
2129
  }
1661
2130
 
@@ -1664,7 +2133,9 @@ async function main() {
1664
2133
  packageJson.scripts.build = 'vite build';
1665
2134
  packageJson.scripts.preview = 'vite preview';
1666
2135
  stats.rawDeps.add('vite');
1667
- if (stats.hasTests) stats.rawDeps.add('vitest');
2136
+ if (stats.hasTests) {
2137
+ stats.rawDeps.add('vitest');
2138
+ }
1668
2139
  } else {
1669
2140
  if (isTypeScript) {
1670
2141
  packageJson.scripts.build = 'tsc';
@@ -1677,10 +2148,11 @@ async function main() {
1677
2148
 
1678
2149
  if (isTypeScript) {
1679
2150
  packageJson.devDependencies.typescript = '^5.4.0';
1680
- if (!isFrontendWeb) packageJson.devDependencies['@types/node'] = '^20.11.0';
2151
+ if (!isFrontendWeb) {
2152
+ packageJson.devDependencies['@types/node'] = '^20.11.0';
2153
+ }
1681
2154
  }
1682
2155
 
1683
- // --- Resolve package versions from npm ---
1684
2156
  if (stats.rawDeps.size > 0) {
1685
2157
  console.log(`\n📡 Resolving baseline package registry definitions...`);
1686
2158
  for (const pkg of stats.rawDeps) {
@@ -1689,47 +2161,44 @@ async function main() {
1689
2161
  const check = await inspectNpmPackage(cleaned);
1690
2162
  if (check && check.error !== 'NOT_FOUND') {
1691
2163
  const version = check.version || 'latest';
1692
-
1693
- const isDevDep = [
1694
- 'vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint',
1695
- '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup',
1696
- 'esbuild', '@swc/cli', 'tsx', 'rimraf', 'copyfiles', 'mkdirp',
1697
- 'husky', 'lint-staged', '@commitlint/cli', 'typedoc', 'c8', 'nyc',
1698
- 'mocha', 'ava', 'tap', 'jasmine', 'storybook', 'turbo', 'nx',
1699
- 'biome', '@biomejs/biome', 'oxlint', 'xo', 'standard',
1700
- ].includes(cleaned) || cleaned.startsWith('@types/');
1701
-
1702
- if (isDevDep) packageJson.devDependencies[cleaned] = `^${version}`;
1703
- else packageJson.dependencies[cleaned] = `^${version}`;
2164
+ const isDevDep = ['vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint', '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup', 'esbuild', '@swc/cli', 'tsx', 'rimraf', 'copyfiles', 'mkdirp', 'husky', 'lint-staged', '@commitlint/cli', 'typedoc', 'c8', 'nyc', 'mocha', 'ava', 'tap', 'jasmine', 'storybook', 'turbo', 'nx', 'biome', '@biomejs/biome', 'oxlint', 'xo', 'standard'].includes(cleaned) || cleaned.startsWith('@types/');
2165
+ if (isDevDep) {
2166
+ packageJson.devDependencies[cleaned] = `^${version}`;
2167
+ } else {
2168
+ packageJson.dependencies[cleaned] = `^${version}`;
2169
+ }
1704
2170
  console.log(` ✔ Synced: ${cleaned}@^${version}${check.deprecated ? ' \x1b[33m[DEPRECATED]\x1b[0m' : ''}`);
1705
2171
  }
1706
2172
  }
1707
2173
  }
1708
2174
  }
1709
2175
 
1710
- // --- Phantom injection report ---
1711
2176
  if (stats.phantomInjections.size > 0) {
1712
2177
  console.log(`\n${'─'.repeat(67)}`);
1713
2178
  console.log(`👻 PHANTOM STRUCTURE ALERT: UNIMPORTED EXECUTIONS DETECTED`);
1714
2179
  console.log(`${'─'.repeat(67)}`);
1715
2180
  for (const [filePath, missingModules] of stats.phantomInjections.entries()) {
1716
2181
  console.log(`📂 File: ${path.relative(targetDir, filePath)}`);
1717
- console.log(` ❌ Used but never imported: ${Array.from(missingModules).map(m => `"${m}"`).join(', ')}`);
2182
+ console.log(` ❌ Used but never imported: ${Array.from(missingModules).map(m => { return `"${m}"`; }).join(', ')}`);
1718
2183
  }
1719
2184
  console.log(`${'─'.repeat(67)}`);
1720
2185
  }
1721
2186
 
1722
- // --- Code quality warnings ---
1723
2187
  if (stats.quality.varCount > 0 || stats.quality.hasEval || stats.quality.syncFsCount > 0) {
1724
2188
  console.log(`\n⚠️ CODE ARCHITECTURE & MODERNIZATION COMPLIANCE WARNINGS:`);
1725
2189
  console.log(`${'─'.repeat(67)}`);
1726
- if (stats.quality.varCount > 0) console.log(` ⚡ Found ${stats.quality.varCount} instances of legacy 'var'. Transition to 'let' / 'const'.`);
1727
- if (stats.quality.hasEval) console.log(` 🔥 DANGER: 'eval()' detected! Refactor to mitigate remote code execution vectors.`);
1728
- if (stats.quality.syncFsCount > 0) console.log(` 📉 Performance: Found ${stats.quality.syncFsCount} synchronous fs calls. Transition to 'fs/promises'.`);
2190
+ if (stats.quality.varCount > 0) {
2191
+ console.log(` Found ${stats.quality.varCount} instances of legacy 'var'. Transition to 'let' / 'const'.`);
2192
+ }
2193
+ if (stats.quality.hasEval) {
2194
+ console.log(` 🔥 DANGER: 'eval()' detected! Refactor to mitigate remote code execution vectors.`);
2195
+ }
2196
+ if (stats.quality.syncFsCount > 0) {
2197
+ console.log(` 📉 Performance: Found ${stats.quality.syncFsCount} synchronous fs calls. Transition to 'fs/promises'.`);
2198
+ }
1729
2199
  console.log(`${'─'.repeat(67)}`);
1730
2200
  }
1731
2201
 
1732
- // --- Security: hardcoded secrets ---
1733
2202
  if (stats.discoveredSecrets.length > 0) {
1734
2203
  console.log(`\n🚨 CRITICAL SECURITY COMPLIANCE ALERT: HARDCODED CREDENTIALS DETECTED`);
1735
2204
  console.log(`${'─'.repeat(67)}`);
@@ -1747,25 +2216,25 @@ async function main() {
1747
2216
  for (const secretMeta of stats.discoveredSecrets) {
1748
2217
  let currentCodeContent = readFileSyncNormalized(secretMeta.filePath);
1749
2218
  const envAccessor = isFrontendWeb ? `import.meta.env.${secretMeta.envVarName}` : `process.env.${secretMeta.envVarName}`;
1750
- const exactLiteralPattern = new RegExp(`\\b${secretMeta.keyName}\\s*=\\s*['"\`]${secretMeta.secretValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}['"\`]`, 'g');
2219
+ const exactLiteralPattern = new RegExp(`\\b${secretMeta.keyName}\\s*=\\s*['"\\ ]${secretMeta.secretValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}['"\\ ]`, 'g');
1751
2220
  currentCodeContent = currentCodeContent.replace(exactLiteralPattern, `${secretMeta.keyName} = ${envAccessor}`);
1752
2221
  fs.writeFileSync(secretMeta.filePath, currentCodeContent);
1753
- if (!envBuffer.includes(`${secretMeta.envVarName}=`)) envBuffer += `${secretMeta.envVarName}=${secretMeta.secretValue}\n`;
2222
+ if (!envBuffer.includes(`${secretMeta.envVarName}=`)) {
2223
+ envBuffer += `${secretMeta.envVarName}=${secretMeta.secretValue}\n`;
2224
+ }
1754
2225
  console.log(` 🔒 Isolated: ${secretMeta.keyName} → ${envAccessor}`);
1755
2226
  }
1756
2227
  fs.writeFileSync(envPath, envBuffer);
1757
2228
  }
1758
2229
  }
1759
2230
 
1760
- // --- Monorepo detection ---
1761
2231
  if (stats.subWorkspaces && stats.subWorkspaces.length > 1) {
1762
2232
  console.log(`\n📂 MULTI-WORKSPACE SEGMENTATION DETECTED`);
1763
- console.log(` Identified sub-module paths: ${stats.subWorkspaces.map(w => `/${w}`).join(', ')}`);
2233
+ console.log(` Identified sub-module paths: ${stats.subWorkspaces.map(w => { return `/${w}`; }).join(', ')}`);
1764
2234
  const setupWorkspace = await safeQuestion(`❓ Setup as a multi-package Monorepo Workspace layout? (y/N): `);
1765
2235
  if (setupWorkspace.trim().toLowerCase() === 'y' || setupWorkspace.trim().toLowerCase() === 'yes') {
1766
2236
  if (activePkgManager === 'pnpm') {
1767
- const workspaceYamlPath = path.join(targetDir, 'pnpm-workspace.yaml');
1768
- fs.writeFileSync(workspaceYamlPath, `packages:\n${stats.subWorkspaces.map(w => ` - '${w}'`).join('\n')}\n`);
2237
+ fs.writeFileSync(path.join(targetDir, 'pnpm-workspace.yaml'), `packages:\n${stats.subWorkspaces.map(w => { return ` - '${w}'`; }).join('\n')}\n`);
1769
2238
  console.log(` 🏗️ Generated: pnpm-workspace.yaml`);
1770
2239
  } else {
1771
2240
  packageJson.workspaces = stats.subWorkspaces;
@@ -1774,7 +2243,6 @@ async function main() {
1774
2243
  }
1775
2244
  }
1776
2245
 
1777
- // --- License ---
1778
2246
  const licensePath = path.join(targetDir, 'LICENSE');
1779
2247
  let chosenLicenseType = preExistingLicense || 'None';
1780
2248
 
@@ -1786,14 +2254,11 @@ async function main() {
1786
2254
  console.log(` 📡 Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}"...`);
1787
2255
  const rawTemplate = await fetchRemoteLicense(cleanedInput);
1788
2256
  if (rawTemplate) {
1789
- const parsedText = rawTemplate
1790
- .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
1791
- .replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
2257
+ const parsedText = rawTemplate.replace(/\[year\]|<year>/gi, new Date().getFullYear().toString()).replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
1792
2258
  fs.writeFileSync(licensePath, parsedText);
1793
2259
  chosenLicenseType = cleanedInput.toUpperCase();
1794
2260
  console.log(` ⚖️ Provisioned: LICENSE`);
1795
2261
  } else {
1796
- console.log(` ⚠️ License "${cleanedInput}" not found. Saving custom label.`);
1797
2262
  chosenLicenseType = cleanedInput;
1798
2263
  }
1799
2264
  packageJson.license = chosenLicenseType;
@@ -1804,24 +2269,25 @@ async function main() {
1804
2269
  if (!fs.existsSync(licensePath) && ['mit', 'apache-2.0', 'gpl-3.0'].includes(preExistingLicense.toLowerCase())) {
1805
2270
  const rawTemplate = await fetchRemoteLicense(preExistingLicense);
1806
2271
  if (rawTemplate) {
1807
- const parsedText = rawTemplate
1808
- .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
1809
- .replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
2272
+ const parsedText = rawTemplate.replace(/\[year\]|<year>/gi, new Date().getFullYear().toString()).replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
1810
2273
  fs.writeFileSync(licensePath, parsedText);
1811
2274
  }
1812
2275
  }
1813
2276
  } else if (fs.existsSync(licensePath)) {
1814
2277
  try {
1815
2278
  const currentLicenseContent = fs.readFileSync(licensePath, 'utf8');
1816
- if (currentLicenseContent.includes('MIT')) chosenLicenseType = 'MIT';
1817
- else if (currentLicenseContent.includes('Apache')) chosenLicenseType = 'Apache-2.0';
1818
- else chosenLicenseType = 'Custom';
1819
- } catch (e) {}
2279
+ if (currentLicenseContent.includes('MIT')) {
2280
+ chosenLicenseType = 'MIT';
2281
+ } else if (currentLicenseContent.includes('Apache')) {
2282
+ chosenLicenseType = 'Apache-2.0';
2283
+ } else {
2284
+ chosenLicenseType = 'Custom';
2285
+ }
2286
+ } catch (licenseFileReadSyncError) {}
1820
2287
  }
1821
2288
  packageJson.license = chosenLicenseType;
1822
2289
  }
1823
2290
 
1824
- // --- Test scaffolding ---
1825
2291
  if (!stats.hasTests) {
1826
2292
  const bootstrapTest = await safeQuestion(`\n❓ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
1827
2293
  if (bootstrapTest.trim().toLowerCase() === 'y' || bootstrapTest.trim().toLowerCase() === 'yes') {
@@ -1834,13 +2300,12 @@ async function main() {
1834
2300
  fs.writeFileSync(testFilePath, testTemplate);
1835
2301
  packageJson.scripts.test = 'node --test';
1836
2302
  stats.hasTests = true;
1837
- console.log(` 🧪 Generated: index${testExt}`);
2303
+ console.log(` 🧪 Generated: index${testExt}`);
1838
2304
  }
1839
2305
  }
1840
2306
 
1841
2307
  console.log(`\n⚙️ Writing ecosystem configuration artifacts...`);
1842
2308
 
1843
- // --- ESLint config ---
1844
2309
  if (stats.bootstrapEslintSuite) {
1845
2310
  packageJson.scripts.lint = 'eslint .';
1846
2311
  let eslintConfigContent = '';
@@ -1854,10 +2319,9 @@ async function main() {
1854
2319
  }
1855
2320
  }
1856
2321
  fs.writeFileSync(eslintConfigFile, eslintConfigContent);
1857
- console.log(` 🎨 Provisioned: eslint.config.js`);
2322
+ console.log(` 🎨 Provisioned: eslint.config.js`);
1858
2323
  }
1859
2324
 
1860
- // --- Write / merge package.json ---
1861
2325
  if (fs.existsSync(pkgPath)) {
1862
2326
  try {
1863
2327
  const currentPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
@@ -1868,40 +2332,36 @@ async function main() {
1868
2332
  currentPackageJson.scripts.lint = packageJson.scripts.lint;
1869
2333
  }
1870
2334
  fs.writeFileSync(pkgPath, JSON.stringify(currentPackageJson, null, 2));
1871
- console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
1872
- } catch (e) {}
2335
+ console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
2336
+ } catch (mergePackageJsonError) {}
1873
2337
  } else {
1874
2338
  fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
1875
- console.log(` 📝 Generated: package.json`);
2339
+ console.log(` 📝 Generated: package.json`);
1876
2340
  }
1877
2341
 
1878
- // --- Prettier config ---
1879
2342
  const prettierPath = path.join(targetDir, '.prettierrc');
1880
2343
  if (!fs.existsSync(prettierPath)) {
1881
2344
  const useTabs = stats.style.tabCount > (stats.style.space2Count + stats.style.space4Count);
1882
2345
  const useSemi = stats.style.semiCount >= stats.style.noSemiCount;
1883
2346
  const tabWidth = stats.style.space4Count > stats.style.space2Count ? 4 : 2;
1884
2347
  fs.writeFileSync(prettierPath, JSON.stringify({ semi: useSemi, useTabs, tabWidth, singleQuote: true, trailingComma: "es5" }, null, 2));
1885
- console.log(` 🎨 Code formatting mirror locked: .prettierrc`);
2348
+ console.log(` 🎨 Code formatting mirror locked: .prettierrc`);
1886
2349
  }
1887
2350
 
1888
- // --- .env.example ---
1889
2351
  if (stats.envVars.size > 0) {
1890
2352
  const envExamplePath = path.join(targetDir, '.env.example');
1891
2353
  if (!fs.existsSync(envExamplePath)) {
1892
- fs.writeFileSync(envExamplePath, Array.from(stats.envVars).map(v => `${v}=`).join('\n') + '\n');
1893
- console.log(` 🔒 Extracted environmental configurations: .env.example`);
2354
+ fs.writeFileSync(envExamplePath, Array.from(stats.envVars).map(v => { return `${v}=`; }).join('\n') + '\n');
2355
+ console.log(` 🔒 Extracted environmental configurations: .env.example`);
1894
2356
  }
1895
2357
  }
1896
2358
 
1897
- // --- .gitignore ---
1898
2359
  const gitignorePath = path.join(targetDir, '.gitignore');
1899
2360
  if (!fs.existsSync(gitignorePath)) {
1900
2361
  fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n*.log\n`);
1901
- console.log(` ⚙️ Generated: .gitignore`);
2362
+ console.log(` ⚙️ Generated: .gitignore`);
1902
2363
  }
1903
2364
 
1904
- // --- tsconfig.json ---
1905
2365
  if (isTypeScript) {
1906
2366
  const tsconfigPath = path.join(targetDir, 'tsconfig.json');
1907
2367
  if (!fs.existsSync(tsconfigPath)) {
@@ -1909,61 +2369,29 @@ async function main() {
1909
2369
  compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" },
1910
2370
  include: ["src/**/*", "**/*.ts"]
1911
2371
  }, null, 2));
1912
- console.log(` ⚙️ Generated: tsconfig.json`);
2372
+ console.log(` ⚙️ Generated: tsconfig.json`);
1913
2373
  }
1914
2374
  }
1915
2375
 
1916
- // --- README ---
1917
2376
  const readmePath = path.join(targetDir, 'README.md');
1918
2377
  if (!fs.existsSync(readmePath)) {
1919
2378
  const pName = packageJson.name;
1920
2379
  const layoutTree = buildAsciiTree(targetDir).join('\n');
1921
- const displayDeps = Object.keys(packageJson.dependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
1922
- const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
2380
+ const displayDeps = Object.keys(packageJson.dependencies).map(d => { return `* \`${d}\``; }).join('\n') || '* None extracted';
2381
+ const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => { return `* \`${d}\``; }).join('\n') || '* None extracted';
1923
2382
  const licenseBadgeParam = encodeURIComponent(chosenLicenseType.replace(/-/g, '_'));
1924
2383
 
1925
- const documentationTemplate =
1926
- `# ${pName}
1927
-
1928
- ![Workspace Engine](https://img.shields.io/badge/engine-node-${packageJson.type === 'module' ? 'green' : 'blue'}?style=flat)
1929
- ![License Architecture](https://img.shields.io/badge/license-${licenseBadgeParam}-orange?style=flat)
1930
- ![Development Tooling](https://img.shields.io/badge/compiled_via-${isTypeScript ? 'typescript' : 'javascript'}-blueviolet?style=flat)
1931
-
1932
- ${packageJson.description}
1933
-
1934
- ## Workspace Dependency Landscapes
1935
-
1936
- ### Core Infrastructure Runtimes (\`dependencies\`)
1937
- ${displayDeps}
1938
-
1939
- ### System Tooling Engines (\`devDependencies\`)
1940
- ${displayDevDeps}
1941
-
1942
- ---
1943
-
1944
- ## Project Architecture Layout
1945
- \`\`\`text
1946
- ${layoutTree}
1947
- \`\`\`
1948
-
1949
- ## Installation
1950
-
1951
- \`\`\`bash
1952
- ${activePkgManager} install
1953
- \`\`\`
1954
- `;
2384
+ const documentationTemplate = `# ${pName}\n\n![Workspace Engine](https://img.shields.io/badge/engine-node-${packageJson.type === 'module' ? 'green' : 'blue'}?style=flat)\n![License Architecture](https://img.shields.io/badge/license-${licenseBadgeParam}-orange?style=flat)\n![Development Tooling](https://img.shields.io/badge/compiled_via-${isTypeScript ? 'typescript' : 'javascript'}-blueviolet?style=flat)\n\n${packageJson.description}\n\n## Workspace Dependency Landscapes\n\n### Core Infrastructure Runtimes (\`dependencies\`)\n${displayDeps}\n\n### System Tooling Engines (\`devDependencies\`)\n${displayDevDeps}\n\n---\n\n## Project Architecture Layout\n\`\`\`text\n${layoutTree}\n\`\`\`\n\n## Installation\n\n\`\`\`bash\n${activePkgManager} install\n\`\`\`\n`;
1955
2385
  fs.writeFileSync(readmePath, documentationTemplate);
1956
- console.log(` 📖 Generated: README.md`);
2386
+ console.log(` 📖 Generated: README.md`);
1957
2387
  }
1958
2388
 
1959
- // --- Phantom injection fix ---
1960
2389
  if (stats.phantomInjections.size > 0 || (stats.injectDotenvEngine && stats.filesWithEnvVars.size > 0)) {
1961
2390
  console.log(`\n💡 Source Code Modification Subsystem:`);
1962
2391
  const injectChoice = await safeQuestion(`❓ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
1963
2392
 
1964
2393
  if (injectChoice.trim().toLowerCase() === 'y' || injectChoice.trim().toLowerCase() === 'yes') {
1965
2394
  const allTargets = new Set([...stats.phantomInjections.keys(), ...stats.filesWithEnvVars]);
1966
-
1967
2395
  for (const filePath of allTargets) {
1968
2396
  const originalCode = readFileSyncNormalized(filePath);
1969
2397
  let declarationBlock = '';
@@ -1971,39 +2399,39 @@ ${activePkgManager} install
1971
2399
  const missingModules = stats.phantomInjections.get(filePath);
1972
2400
  if (missingModules) {
1973
2401
  for (const mod of missingModules) {
1974
- if (packageJson.type === 'module') declarationBlock += `import ${mod} from '${mod}';\n`;
1975
- else declarationBlock += `const ${mod} = require('${mod}');\n`;
2402
+ if (packageJson.type === 'module') {
2403
+ declarationBlock += `import ${mod} from '${mod}';\n`;
2404
+ } else {
2405
+ declarationBlock += `const ${mod} = require('${mod}');\n`;
2406
+ }
1976
2407
  }
1977
2408
  }
1978
-
1979
2409
  if (stats.injectDotenvEngine && stats.filesWithEnvVars.has(filePath) && !originalCode.includes('dotenv')) {
1980
- if (packageJson.type === 'module') declarationBlock += `import 'dotenv/config';\n`;
1981
- else declarationBlock += `require('dotenv').config();\n`;
2410
+ if (packageJson.type === 'module') {
2411
+ declarationBlock += `import 'dotenv/config';\n`;
2412
+ } else {
2413
+ declarationBlock += `require('dotenv').config();\n`;
2414
+ }
1982
2415
  }
1983
-
1984
2416
  if (declarationBlock !== '') {
1985
2417
  fs.writeFileSync(filePath, smartPrepend(originalCode, declarationBlock));
1986
- console.log(` ⚡ Injected headers: ${path.relative(targetDir, filePath)}`);
2418
+ console.log(` ⚡ Injected headers: ${path.relative(targetDir, filePath)}`);
1987
2419
  }
1988
2420
  }
1989
2421
  }
1990
2422
  }
1991
2423
 
1992
- // --- Deprecation scan via npm-deprecated-check ---
1993
2424
  console.log(`\n🛑 INITIALIZING LIVE ECOSYSTEM DEPRECATION SECURITY SCAN...`);
1994
- console.log(` Running integrated npm-deprecated-check validation:\n`);
2425
+ console.log(` Running integrated npm-deprecated-check validation:\n`);
1995
2426
  try {
1996
2427
  const localRequire = createRequire(import.meta.url);
1997
2428
  const dependencyPkgJsonPath = localRequire.resolve('npm-deprecated-check/package.json');
1998
2429
  const dependencyPkgJson = JSON.parse(fs.readFileSync(dependencyPkgJsonPath, 'utf8'));
1999
- const binRelativeMapping = typeof dependencyPkgJson.bin === 'string'
2000
- ? dependencyPkgJson.bin
2001
- : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
2430
+ const binRelativeMapping = typeof dependencyPkgJson.bin === 'string' ? dependencyPkgJson.bin : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
2002
2431
  const absoluteExecutablePath = path.join(path.dirname(dependencyPkgJsonPath), binRelativeMapping);
2003
2432
  execSync(`node "${absoluteExecutablePath}" current`, { stdio: 'inherit', cwd: targetDir });
2004
- } catch (err) {}
2433
+ } catch (deprecationBinaryRunError) {}
2005
2434
 
2006
- // --- Conflicting lockfiles ---
2007
2435
  if (stats.conflictingLockfiles.length > 1) {
2008
2436
  console.log(`\n⚠️ CONFLICTING LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
2009
2437
  const cleanLocks = await safeQuestion(`❓ Purge legacy/mismatched lockfiles to protect package integrity? (y/N): `);
@@ -2014,62 +2442,76 @@ ${activePkgManager} install
2014
2442
  if (lockfile !== operationalLockfile) {
2015
2443
  try {
2016
2444
  fs.unlinkSync(path.join(targetDir, lockfile));
2017
- console.log(` 🗑️ Cleaned: ${lockfile}`);
2018
- } catch (e) {}
2445
+ console.log(` 🗑 Cleaned: ${lockfile}`);
2446
+ } catch (lockFileUnlinkError) {}
2019
2447
  }
2020
2448
  }
2021
2449
  }
2022
2450
  }
2023
2451
 
2024
- // --- Final install prompt ---
2025
2452
  console.log(`\n📦 Auto-scaffolding pipeline complete!`);
2026
2453
 
2027
- // Summary report
2028
- postProcessAnalysis(stats, dependencyGraph);
2454
+ if (stats.frameworkOptimizations.length > 0) {
2455
+ console.log(`\n🧩 FRAMEWORK ARCHITECTURE OPTIMIZATIONS:`);
2456
+ console.log('─'.repeat(67));
2457
+ for (const optimization of stats.frameworkOptimizations) {
2458
+ console.log(` 💡 ${optimization}`);
2459
+ }
2460
+ console.log('─'.repeat(67));
2461
+ }
2462
+
2029
2463
  console.log(`\n${'═'.repeat(67)}`);
2030
2464
  console.log(`📊 DEPENDENCY INTELLIGENCE SUMMARY`);
2031
2465
  console.log(`${'═'.repeat(67)}`);
2032
- console.log(` 📁 Files scanned: ${stats.scannedFiles}`);
2033
- console.log(` 📦 Packages imported: ${stats.allImportedPackages.size}`);
2034
- if (stats.ghostDependencies.size > 0)
2035
- console.log(` 🚨 Ghost deps (missing): ${stats.ghostDependencies.size} — \x1b[31mCRITICAL\x1b[0m`);
2036
- if (stats.orphanedDependencies.size > 0)
2037
- console.log(` 🗑️ Orphaned deps (unused): ${stats.orphanedDependencies.size}`);
2038
- if (allDiscoveredUnused.size > 0)
2039
- console.log(` ⚡ Unused imports: ${allDiscoveredUnused.size}`);
2466
+ console.log(` 📁 Files scanned: ${stats.scannedFiles}`);
2467
+ console.log(` 📦 Packages imported: ${stats.allImportedPackages.size}`);
2468
+ if (stats.ghostDependencies.size > 0) {
2469
+ console.log(` 🚨 Ghost deps (missing): ${stats.ghostDependencies.size} — \x1b[31mCRITICAL\x1b[0m`);
2470
+ }
2471
+ if (stats.orphanedDependencies.size > 0) {
2472
+ console.log(` 🗑️ Orphaned deps (unused): ${stats.orphanedDependencies.size}`);
2473
+ }
2040
2474
  if (stats.unusedExportsPerFile.size > 0) {
2041
- console.log(` 📤 Unused exports: ${Array.from(stats.unusedExportsPerFile.values()).reduce((acc, val) => acc + val.size, 0)} in ${stats.unusedExportsPerFile.size} files`);
2475
+ console.log(` 📤 Unused public symbols: ${Array.from(stats.unusedExportsPerFile.values()).reduce((acc, val) => { return acc + val.size; }, 0)} tokens`);
2042
2476
  }
2043
2477
  if (stats.unusedFiles.size > 0) {
2044
- console.log(` 🗑️ Unused files: ${stats.unusedFiles.size}`);
2045
- }
2046
- if (stats.deprecatedPackages.size > 0)
2047
- console.log(` 📛 Deprecated packages: ${stats.deprecatedPackages.size}`);
2048
- if (stats.phantomInjections.size > 0)
2049
- console.log(` 👻 Phantom injections: ${stats.phantomInjections.size} file(s)`);
2050
- if (stats.discoveredSecrets.length > 0)
2051
- console.log(` 🔐 Hardcoded secrets: ${stats.discoveredSecrets.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2052
- if (stats.quality.insecureCryptoUsage.length > 0)
2053
- console.log(` 🚫 Insecure Crypto: ${stats.quality.insecureCryptoUsage.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2054
- if (stats.quality.sqlInjectionVulnerabilities.length > 0)
2055
- console.log(` 💉 SQL Injection: ${stats.quality.sqlInjectionVulnerabilities.length} \x1b[31mSECURITY RISK\x1b[0m`);
2056
- if (stats.quality.xssVulnerabilities.length > 0)
2057
- console.log(` 🌐 XSS Vulnerabilities: ${stats.quality.xssVulnerabilities.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2058
- if (stats.quality.largeImageImports.length > 0)
2059
- console.log(` 🖼️ Large Image Imports: ${stats.quality.largeImageImports.length} — \x1b[33mPERFORMANCE WARNING\x1b[0m`);
2060
- if (stats.quality.unoptimizedLoops.length > 0)
2061
- console.log(` 🐌 Unoptimized Loops: ${stats.quality.unoptimizedLoops.length} \x1b[33mPERFORMANCE WARNING\x1b[0m`);
2062
- if (stats.quality.frameworkSpecificIssues.length > 0)
2063
- console.log(` 🧩 Framework Issues: ${stats.quality.frameworkSpecificIssues.length} — \x1b[33mFRAMEWORK OPTIMIZATION\x1b[0m`);
2478
+ console.log(` 🗑️ Unused dead files: ${stats.unusedFiles.size} files`);
2479
+ }
2480
+ if (stats.deprecatedPackages.size > 0) {
2481
+ console.log(` ` + `📛 Deprecated packages: ${stats.deprecatedPackages.size}`);
2482
+ }
2483
+ if (stats.phantomInjections.size > 0) {
2484
+ console.log(` 👻 Phantom injections: ${stats.phantomInjections.size} file(s)`);
2485
+ }
2486
+ if (stats.discoveredSecrets.length > 0) {
2487
+ console.log(` 🔐 Hardcoded secrets: ${stats.discoveredSecrets.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2488
+ }
2489
+ if (stats.quality.insecureCryptoUsage.length > 0) {
2490
+ console.log(` 🚫 Insecure Crypto: ${stats.quality.insecureCryptoUsage.length} \x1b[31mSECURITY RISK\x1b[0m`);
2491
+ }
2492
+ if (stats.quality.sqlInjectionVulnerabilities.length > 0) {
2493
+ console.log(` 💉 SQL Injection: ${stats.quality.sqlInjectionVulnerabilities.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2494
+ }
2495
+ if (stats.quality.xssVulnerabilities.length > 0) {
2496
+ console.log(` 🌐 XSS Vulnerabilities: ${stats.quality.xssVulnerabilities.length} \x1b[31mSECURITY RISK\x1b[0m`);
2497
+ }
2498
+ if (stats.quality.insecurePatterns.length > 0) {
2499
+ console.log(` 🌐 Insecure DOM Patterns: ${stats.quality.insecurePatterns.length} — \x1b[31mSECURITY RISK\x1b[0m`);
2500
+ }
2501
+ if (stats.quality.largeImageImports.length > 0) {
2502
+ console.log(` 🖼️ Large Image Imports: ${stats.quality.largeImageImports.length} — \x1b[33mPERFORMANCE WARNING\x1b[0m`);
2503
+ }
2504
+ if (stats.quality.unoptimizedLoops.length > 0) {
2505
+ console.log(` 🐌 Unoptimized Loops: ${stats.quality.unoptimizedLoops.length} — \x1b[33mPERFORMANCE WARNING\x1b[0m`);
2506
+ }
2064
2507
  console.log(`${'═'.repeat(67)}`);
2065
2508
 
2066
- // 6. Hygen-like Templating and Scaffolding
2067
2509
  const templateManager = new TemplateManager(targetDir, safeQuestion);
2068
2510
  const availableTemplates = await templateManager.listAvailableTemplates();
2069
2511
 
2070
2512
  if (availableTemplates.length > 0) {
2071
2513
  console.log(`\n🧩 \x1b[1mCustom Templating Engine Detected:\x1b[0m`);
2072
- console.log(` Available templates: ${availableTemplates.join(", ")}`);
2514
+ console.log(` Available templates: ${availableTemplates.join(", ")}`);
2073
2515
  const useTemplate = await safeQuestion(`❓ Do you want to generate code from a template? (y/N): `);
2074
2516
  if (useTemplate.toLowerCase() === 'y') {
2075
2517
  const chosenTemplate = await safeQuestion(`❓ Enter template name: `);
@@ -2077,7 +2519,7 @@ ${activePkgManager} install
2077
2519
  const templateVars = await templateManager.promptForVariables(chosenTemplate);
2078
2520
  await templateManager.generate(chosenTemplate, templateVars);
2079
2521
  } else {
2080
- console.log(` ⚠️ Template '${chosenTemplate}' not found.`);
2522
+ console.log(` ⚠️ Template '${chosenTemplate}' not found.`);
2081
2523
  }
2082
2524
  }
2083
2525
  }
@@ -2091,7 +2533,7 @@ ${activePkgManager} install
2091
2533
  try {
2092
2534
  execSync(`${activePkgManager} install`, { stdio: 'inherit', cwd: targetDir });
2093
2535
  console.log(`\n🎉 Project fully mapped, configured, and installed successfully!`);
2094
- } catch (err) {
2536
+ } catch (installProcessRuntimeError) {
2095
2537
  console.error(`\n❌ Installation returned an issue. Please run "${activePkgManager} install" manually.`);
2096
2538
  }
2097
2539
  } else {
@@ -2099,354 +2541,4 @@ ${activePkgManager} install
2099
2541
  }
2100
2542
  }
2101
2543
 
2102
- main();
2103
-
2104
- // ============================================================
2105
- // 📊 POST-PROCESSING ANALYSIS: Unused Exports, Unused Files
2106
- // ============================================================
2107
- function postProcessAnalysis(stats, dependencyGraph) {
2108
- // Initialize all scanned files as potentially unused
2109
- const allScannedFiles = new Set(Array.from(stats.exportedSymbols.keys()));
2110
- stats.unusedFiles = new Set(allScannedFiles);
2111
-
2112
- // Determine used exports and identify used files
2113
- for (const [importerFilePath, importedSymbols] of stats.localFileImports.entries()) {
2114
- // Remove importerFilePath from unusedFiles if it imports something
2115
- if (importedSymbols.size > 0) {
2116
- stats.unusedFiles.delete(importerFilePath);
2117
- }
2118
-
2119
- for (const [exportedFilePath, exportedSymbolsMap] of stats.exportedSymbols.entries()) {
2120
- // If importerFilePath imports from exportedFilePath
2121
- if (importerFilePath === exportedFilePath) {
2122
- // This is a self-import or internal reference, not a cross-file import for export usage
2123
- continue;
2124
- }
2125
-
2126
- // Check if any symbol from exportedFilePath is imported by importerFilePath
2127
- for (const importedSymbol of importedSymbols) {
2128
- if (exportedSymbolsMap.has(importedSymbol)) {
2129
- if (!stats.usedExports.has(exportedFilePath)) {
2130
- stats.usedExports.set(exportedFilePath, new Set());
2131
- }
2132
- stats.usedExports.get(exportedFilePath).add(importedSymbol);
2133
- stats.unusedFiles.delete(exportedFilePath); // Mark as used
2134
- }
2135
- }
2136
- }
2137
- }
2138
-
2139
- // Identify unused exports per file
2140
- for (const [filePath, exportedSymbolsMap] of stats.exportedSymbols.entries()) {
2141
- const used = stats.usedExports.get(filePath) || new Set();
2142
- const unused = new Set();
2143
- for (const [symbolName, symbolInfo] of exportedSymbolsMap.entries()) {
2144
- if (!used.has(symbolName)) {
2145
- unused.add(symbolName);
2146
- }
2147
- }
2148
- if (unused.size > 0) {
2149
- stats.unusedExportsPerFile.set(filePath, unused);
2150
- }
2151
- }
2152
-
2153
- // Identify truly unused files: those that are never imported by any other file.
2154
- const allScannedFiles = new Set(stats.scannedFiles); // All files that were processed
2155
- const entryPoints = new Set(); // Files that are likely entry points (e.g., main, framework-specific entry points)
2156
-
2157
- // Add main entry point if package.json has one
2158
- if (stats.packageJson && stats.packageJson.main) {
2159
- entryPoints.add(path.resolve(stats.targetDir, stats.packageJson.main));
2160
- }
2161
- if (stats.packageJson && stats.packageJson.module) {
2162
- entryPoints.add(path.resolve(stats.targetDir, stats.packageJson.module));
2163
- }
2164
- if (stats.packageJson && stats.packageJson.type === 'module' && fs.existsSync(path.join(stats.targetDir, 'index.js'))) {
2165
- entryPoints.add(path.resolve(stats.targetDir, 'index.js'));
2166
- }
2167
- if (stats.packageJson && stats.packageJson.type !== 'module' && fs.existsSync(path.join(stats.targetDir, 'index.cjs'))) {
2168
- entryPoints.add(path.resolve(stats.targetDir, 'index.cjs'));
2169
- }
2170
-
2171
- // Add framework-specific entry points or files that are implicitly used
2172
- if (stats.detectedFrameworks.includes('next')) {
2173
- stats.frameworkFiles.nextjs.pages.forEach(file => entryPoints.add(file));
2174
- stats.frameworkFiles.nextjs.apiRoutes.forEach(file => entryPoints.add(file));
2175
- stats.frameworkFiles.nextjs.components.forEach(file => entryPoints.add(file));
2176
- }
2177
- if (stats.detectedFrameworks.includes('nuxt')) {
2178
- stats.frameworkFiles.nuxt.pages.forEach(file => entryPoints.add(file));
2179
- stats.frameworkFiles.nuxt.components.forEach(file => entryPoints.add(file));
2180
- }
2181
- if (stats.detectedFrameworks.includes('svelte')) {
2182
- stats.frameworkFiles.sveltekit.pages.forEach(file => entryPoints.add(file));
2183
- stats.frameworkFiles.sveltekit.endpoints.forEach(file => entryPoints.add(file));
2184
- stats.frameworkFiles.sveltekit.components.forEach(file => entryPoints.add(file));
2185
- }
2186
- if (stats.detectedFrameworks.includes('react')) {
2187
- stats.frameworkFiles.react.components.forEach(file => entryPoints.add(file));
2188
- stats.frameworkFiles.react.hooks.forEach(file => entryPoints.add(file));
2189
- }
2190
- if (stats.detectedFrameworks.includes('vue')) {
2191
- stats.frameworkFiles.vue.components.forEach(file => entryPoints.add(file));
2192
- stats.frameworkFiles.vue.composables.forEach(file => entryPoints.add(file));
2193
- }
2194
-
2195
- // Use the DependencyGraph to find all reachable files from the entry points
2196
- const reachableFiles = dependencyGraph.getReachableFiles(Array.from(entryPoints));
2197
-
2198
- // A file is considered unused if it was scanned but not reachable from any entry point
2199
- stats.unusedFiles = new Set(Array.from(allScannedFiles).filter(file => !reachableFiles.has(file)));
2200
-
2201
- // Further refinement: check for files referenced in common configuration files
2202
- // This is a more advanced step and would require parsing specific config file formats.
2203
- // Example: Tailwind CSS `tailwind.config.js` `content` array.
2204
- // For now, this is a conceptual placeholder.
2205
- if (stats.detectedFrameworks.includes('tailwind')) {
2206
- // Look for tailwind.config.js
2207
- const tailwindConfigPath = path.join(stats.targetDir, 'tailwind.config.js');
2208
- if (fs.existsSync(tailwindConfigPath)) {
2209
- try {
2210
- // This would require a more robust JS file parser to extract the 'content' array
2211
- // For demonstration, we'll assume a simple regex or AST analysis could find patterns like:
2212
- // content: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html']
2213
- const tailwindContent = fs.readFileSync(tailwindConfigPath, 'utf8');
2214
- const contentArrayMatch = tailwindContent.match(/content:\s*\[([^\]]+)\]/s);
2215
- if (contentArrayMatch && contentArrayMatch[1]) {
2216
- const globPatterns = contentArrayMatch[1].split(',').map(s => s.trim().replace(/["']/g, ''));
2217
- for (const pattern of globPatterns) {
2218
- // Resolve glob patterns to actual files and mark them as used
2219
- // This would require a glob library (e.g., 'glob' npm package)
2220
- // For now, we'll just log the intent.
2221
- // console.log(` 💡 Tailwind config references files via glob: ${pattern}`);
2222
- // A real implementation would iterate through glob results and remove from unusedFiles
2223
- }
2224
- }
2225
- } catch (e) {
2226
- console.error(` ❌ Error parsing tailwind.config.js: ${e.message}`);
2227
- }
2228
- }
2229
- }
2230
-
2231
- }
2232
-
2233
- // ============================================================
2234
- // 🧩 ADVANCED TEMPLATE MANAGEMENT SYSTEM (Hygen-level)
2235
- // ============================================================
2236
- class TemplateManager {
2237
- constructor(baseDir, safeQuestion) {
2238
- this.baseDir = baseDir;
2239
- this.safeQuestion = safeQuestion;
2240
- this.templateSources = [
2241
- { name: 'local', path: path.join(this.baseDir, '.templates') },
2242
- // Future: Add remote Git repositories, e.g., { name: 'remote-official', url: 'https://github.com/my-org/templates.git' }
2243
- ];
2244
- }
2245
-
2246
- async listAvailableTemplates() {
2247
- const allTemplates = new Set();
2248
- for (const source of this.templateSources) {
2249
- if (source.name === 'local') {
2250
- const localTemplatesPath = source.path;
2251
- if (fs.existsSync(localTemplatesPath)) {
2252
- const templates = fs.readdirSync(localTemplatesPath, { withFileTypes: true })
2253
- .filter(dirent => dirent.isDirectory())
2254
- .map(dirent => dirent.name);
2255
- templates.forEach(t => allTemplates.add(t));
2256
- }
2257
- }
2258
- // Future: Handle remote template sources
2259
- }
2260
- return Array.from(allTemplates);
2261
- }
2262
-
2263
- async getTemplatePath(templateName) {
2264
- for (const source of this.templateSources) {
2265
- if (source.name === 'local') {
2266
- const templatePath = path.join(source.path, templateName);
2267
- if (fs.existsSync(templatePath)) {
2268
- return templatePath;
2269
- }
2270
- }
2271
- }
2272
- return null;
2273
- }
2274
-
2275
- async promptForVariables(templateName) {
2276
- const templatePath = await this.getTemplatePath(templateName);
2277
- if (!templatePath) {
2278
- console.log(` ⚠️ Template '${templateName}' not found.`);
2279
- return {};
2280
- }
2281
-
2282
- const configPath = path.join(templatePath, '_config.json');
2283
- if (fs.existsSync(configPath)) {
2284
- try {
2285
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
2286
- const variables = {};
2287
- for (const key in config.prompts) {
2288
- const prompt = config.prompts[key];
2289
- let answer = await this.safeQuestion(`❓ ${prompt.message || key}: `);
2290
- if (prompt.type === 'boolean') {
2291
- answer = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
2292
- } else if (prompt.type === 'number') {
2293
- answer = parseFloat(answer);
2294
- }
2295
- variables[key] = answer;
2296
- }
2297
- return variables;
2298
- } catch (e) {
2299
- console.error(` ❌ Error reading template config for '${templateName}': ${e.message}`);
2300
- return {};
2301
- }
2302
- }
2303
- return {};
2304
- }
2305
-
2306
- async generate(templateName, variables) {
2307
- const templatePath = await this.getTemplatePath(templateName);
2308
- if (!templatePath) return;
2309
-
2310
- console.log(` 🚀 Generating '${templateName}' template...`);
2311
-
2312
- const renderFile = async (srcPath, destPath, vars) => {
2313
- const content = fs.readFileSync(srcPath, 'utf8');
2314
- // Simple templating: replace {{varName}} with variable value
2315
- let renderedContent = content;
2316
- for (const key in vars) {
2317
- renderedContent = renderedContent.replace(new RegExp(`{{\s*${key}\s*}}`, 'g'), vars[key]);
2318
- }
2319
- fs.writeFileSync(destPath, renderedContent);
2320
- };
2321
-
2322
- const processDirectory = async (currentSrcDir, currentDestDir, vars) => {
2323
- fs.mkdirSync(currentDestDir, { recursive: true });
2324
- const items = fs.readdirSync(currentSrcDir, { withFileTypes: true });
2325
-
2326
- for (const item of items) {
2327
- const srcItemPath = path.join(currentSrcDir, item.name);
2328
- const destItemPath = path.join(currentDestDir, item.name);
2329
-
2330
- if (item.isDirectory()) {
2331
- if (item.name !== '_config.json') { // Skip config file
2332
- await processDirectory(srcItemPath, destItemPath, vars);
2333
- }
2334
- } else {
2335
- await renderFile(srcItemPath, destItemPath, vars);
2336
- }
2337
- }
2338
- };
2339
-
2340
- await processDirectory(templatePath, this.baseDir, variables);
2341
- console.log(` ✅ Template '${templateName}' generated successfully.`);
2342
- }
2343
- }
2344
-
2345
- // ============================================================
2346
- // 🌳 ADVANCED DEPENDENCY GRAPH ENGINE (Knip-level)
2347
- // ============================================================
2348
- class DependencyGraph {
2349
- constructor(stats) {
2350
- this.stats = stats;
2351
- this.graph = new Map(); // Map<filePath, { imports: Set<filePath>, exports: Set<symbolName> }>
2352
- this.symbolToFilePath = new Map(); // Map<symbolName, filePath> for global exports
2353
- this.buildGraph();
2354
- }
2355
-
2356
- buildGraph() {
2357
- // Initialize graph nodes for all scanned files
2358
- for (const filePath of this.stats.scannedFiles) {
2359
- this.graph.set(filePath, { imports: new Set(), exports: new Set() });
2360
- }
2361
-
2362
- // Populate exports
2363
- for (const [filePath, exportedSymbolsMap] of this.stats.exportedSymbols.entries()) {
2364
- const node = this.graph.get(filePath);
2365
- if (node) {
2366
- for (const [symbolName, symbolInfo] of exportedSymbolsMap.entries()) {
2367
- node.exports.add(symbolName);
2368
- // For simplicity, assuming unique global symbol names for now, or handling conflicts
2369
- // A more robust solution would handle namespaces or re-exports more carefully
2370
- this.symbolToFilePath.set(symbolName, filePath);
2371
- }
2372
- }
2373
- }
2374
-
2375
- // Populate imports
2376
- for (const [importerFilePath, importedSymbols] of this.stats.localFileImports.entries()) {
2377
- const importerNode = this.graph.get(importerFilePath);
2378
- if (importerNode) {
2379
- for (const importedSymbol of importedSymbols) {
2380
- // If it's a direct path import, add to imports
2381
- if (importedSymbol.startsWith(".") || importedSymbol.startsWith("/")) {
2382
- const resolvedPath = path.normalize(path.resolve(path.dirname(importerFilePath), importedSymbol));
2383
- if (this.graph.has(resolvedPath)) {
2384
- importerNode.imports.add(resolvedPath);
2385
- }
2386
- } else {
2387
- // If it's a named import, find the file that exports it
2388
- const exporterFilePath = this.symbolToFilePath.get(importedSymbol);
2389
- if (exporterFilePath && this.graph.has(exporterFilePath)) {
2390
- importerNode.imports.add(exporterFilePath);
2391
- }
2392
- }
2393
- }
2394
- }
2395
- }
2396
- }
2397
-
2398
- getDependents(filePath) {
2399
- const dependents = new Set();
2400
- for (const [importer, node] of this.graph.entries()) {
2401
- if (node.imports.has(filePath)) {
2402
- dependents.add(importer);
2403
- }
2404
- }
2405
- return dependents;
2406
- }
2407
-
2408
- getDependencies(filePath) {
2409
- const node = this.graph.get(filePath);
2410
- return node ? node.imports : new Set();
2411
- }
2412
-
2413
- // Perform a reachability analysis to find all files reachable from entry points
2414
- getReachableFiles(entryPoints) {
2415
- const reachable = new Set();
2416
- const queue = [...entryPoints];
2417
-
2418
- while (queue.length > 0) {
2419
- const currentFile = queue.shift();
2420
- if (reachable.has(currentFile)) continue;
2421
-
2422
- reachable.add(currentFile);
2423
- const node = this.graph.get(currentFile);
2424
- if (node) {
2425
- for (const importedFile of node.imports) {
2426
- if (!reachable.has(importedFile)) {
2427
- queue.push(importedFile);
2428
- }
2429
- }
2430
- }
2431
- }
2432
- return reachable;
2433
- }
2434
-
2435
- // Generate a DOT graph string for visualization
2436
- toDotGraph() {
2437
- let dot = `digraph G {\n`;
2438
- dot += ` rankdir=LR;\n`;
2439
- dot += ` node [shape=box];\n`;
2440
-
2441
- for (const [filePath, node] of this.graph.entries()) {
2442
- const fileName = path.basename(filePath);
2443
- dot += ` "${filePath}" [label="${fileName}"];\n`;
2444
-
2445
- for (const importedFile of node.imports) {
2446
- dot += ` "${filePath}" -> "${importedFile}";\n`;
2447
- }
2448
- }
2449
- dot += `}\n`;
2450
- return dot;
2451
- }
2452
- }
2544
+ main();