pkg-scaffold 2.0.1 → 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 +1556 -577
  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,38 +29,218 @@ 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
28
- secretKeys: /\b(secret|passwd|password|token|api_?key|private_?key)\s*=\s*['"`]([a-zA-Z0-9_\-\.]{8,})['"`]/gi
104
+ /**
105
+ * Allgemeine Zuweisung harter Anmeldedaten.
106
+ */
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
+ */
112
+ awsKeys: /AKIA[0-9A-Z]{16}/g,
113
+
114
+ /**
115
+ * Spezifische Google Cloud API Schlüssel.
116
+ */
117
+ googleCloudKeys: /AIza[0-9A-Za-z\-_]{35}/g,
118
+
119
+ /**
120
+ * Live-Schlüssel für den Stripe-Zahlungsdienstleister.
121
+ */
122
+ stripeKeys: /sk_live_[0-9a-zA-Z]{24}/g,
123
+
124
+ /**
125
+ * Slack Bot- und Benutzer-Tokens.
126
+ */
127
+ slackKeys: /xox[baprs]-[0-9a-zA-Z]{10,48}/g,
128
+
129
+ /**
130
+ * GitHub Personal Access Tokens.
131
+ */
132
+ githubTokens: /gh[pousr]_[a-zA-Z0-9]{36}/g,
133
+
134
+ /**
135
+ * Private RSA-Schlüsselblöcke.
136
+ */
137
+ rsaPrivateKeys: /-----BEGIN RSA PRIVATE KEY-----/g,
138
+
139
+ /**
140
+ * Private OpenSSH-Schlüsselblöcke.
141
+ */
142
+ sshPrivateKeys: /-----BEGIN OPENSSH PRIVATE KEY-----/g,
143
+
144
+ /**
145
+ * Private PGP-Schlüsselblöcke.
146
+ */
147
+ pgpPrivateKeys: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g,
148
+
149
+ /**
150
+ * Unsicheres Einfügen von unbereinigtem HTML (XSS-Risiko).
151
+ */
152
+ insecureInnerHTML: /\.innerHTML\s*=/g,
153
+
154
+ /**
155
+ * Direktes Schreiben in das Dokumentenobjekt im Browser.
156
+ */
157
+ insecureDocumentWrite: /document\.write\s*\(/g,
158
+
159
+ /**
160
+ * React-spezifisches Äquivalent zu innerHTML.
161
+ */
162
+ insecureDangerouslySet: /dangerouslySetInnerHTML/g,
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
+ */
222
+ nextjsPage: /pages\/[^\/]+\.(js|jsx|ts|tsx)$/i,
223
+ nextjsApi: /pages\/api\/[^\/]+\.(js|jsx|ts|tsx)$/i,
224
+ nextjsComponent: /components\/[^\/]+\.(js|jsx|ts|tsx)$/i,
225
+ nuxtPage: /pages\/[^\/]+\.(vue|js|ts)$/i,
226
+ nuxtComponent: /components\/[^\/]+\.(vue|js|ts)$/i,
227
+ sveltekitPage: /src\/routes\/[^\/]+\/\+page\.(svelte|js|ts)$/i,
228
+ sveltekitComponent: /src\/lib\/[^\/]+\.(svelte|js|ts)$/i,
229
+ reactHook: /hooks\/[^\/]+\.(js|jsx|ts|tsx)$/i,
230
+ vueComposable: /composables\/[^\/]+\.(js|ts)$/i
29
231
  };
30
232
 
31
- // ============================================================
32
- // COMPREHENSIVE BINARY-TO-PACKAGE MAPPING (Knip-style)
33
- // Maps CLI binary names → npm package names
34
- // ============================================================
233
+ /**
234
+ * Maps CLI-Binärnamen auf tatsächliche npm-Paketnamen (Knip-Stil).
235
+ * @type {Object}
236
+ */
35
237
  const BINARY_TO_PACKAGE_MAP = {
36
- // TypeScript / JavaScript compilers & runtimes
37
238
  'tsc': 'typescript',
38
239
  'ts-node': 'ts-node',
39
240
  'tsx': 'tsx',
40
241
  'tsup': 'tsup',
41
242
  'esbuild': 'esbuild',
42
243
  'swc': '@swc/cli',
43
-
44
- // Test runners
45
244
  'jest': 'jest',
46
245
  'vitest': 'vitest',
47
246
  'mocha': 'mocha',
@@ -50,8 +249,6 @@ const BINARY_TO_PACKAGE_MAP = {
50
249
  'tap': 'tap',
51
250
  'c8': 'c8',
52
251
  'nyc': 'nyc',
53
-
54
- // Linters & formatters
55
252
  'eslint': 'eslint',
56
253
  'prettier': 'prettier',
57
254
  'biome': '@biomejs/biome',
@@ -59,24 +256,18 @@ const BINARY_TO_PACKAGE_MAP = {
59
256
  'tslint': 'tslint',
60
257
  'xo': 'xo',
61
258
  'standard': 'standard',
62
-
63
- // Bundlers & dev servers
64
259
  'vite': 'vite',
65
260
  'webpack': 'webpack',
66
261
  'rollup': 'rollup',
67
262
  'parcel': 'parcel',
68
263
  'turbo': 'turbo',
69
264
  'nx': 'nx',
70
-
71
- // Process managers & watchers
72
265
  'nodemon': 'nodemon',
73
266
  'pm2': 'pm2',
74
267
  'concurrently': 'concurrently',
75
268
  'cross-env': 'cross-env',
76
269
  'dotenv-cli': 'dotenv-cli',
77
270
  'env-cmd': 'env-cmd',
78
-
79
- // Code generation & scaffolding
80
271
  'hygen': 'hygen',
81
272
  'plop': 'plop',
82
273
  'prisma': 'prisma',
@@ -85,8 +276,6 @@ const BINARY_TO_PACKAGE_MAP = {
85
276
  'sequelize': 'sequelize-cli',
86
277
  'knex': 'knex',
87
278
  'mikro-orm': '@mikro-orm/cli',
88
-
89
- // Build & deployment tools
90
279
  'rimraf': 'rimraf',
91
280
  'copyfiles': 'copyfiles',
92
281
  'mkdirp': 'mkdirp',
@@ -96,14 +285,10 @@ const BINARY_TO_PACKAGE_MAP = {
96
285
  'npm-run-all': 'npm-run-all',
97
286
  'run-s': 'npm-run-all',
98
287
  'run-p': 'npm-run-all',
99
-
100
- // Documentation
101
288
  'typedoc': 'typedoc',
102
289
  'jsdoc': 'jsdoc',
103
290
  'storybook': 'storybook',
104
291
  'sb': 'storybook',
105
-
106
- // Misc
107
292
  'husky': 'husky',
108
293
  'lint-staged': 'lint-staged',
109
294
  'commitlint': '@commitlint/cli',
@@ -112,40 +297,82 @@ const BINARY_TO_PACKAGE_MAP = {
112
297
  'changeset': '@changesets/cli',
113
298
  'changesets': '@changesets/cli',
114
299
  'np': 'np',
115
- 'bumpp': 'bumpp',
300
+ 'bumpp': 'bumpp'
116
301
  };
117
302
 
118
- // ============================================================
119
- // EXTENDED DEV TOOLING ECOSYSTEM (never flagged as unused)
120
- // ============================================================
303
+ /**
304
+ * Menge von Entwicklungswerkzeugen, die nicht als verwaist eingestuft werden sollen.
305
+ * @type {Set<string>}
306
+ */
121
307
  const DEV_TOOLING_ECOSYSTEM = new Set([
122
- // Linters & formatters
123
- 'eslint', 'prettier', 'biome', '@biomejs/biome', 'oxlint', 'tslint', 'xo', 'standard',
124
- // TypeScript
125
- 'typescript', 'typescript-eslint', '@eslint/js', 'ts-node', 'tsx', 'tsup', 'esbuild', '@swc/cli',
126
- // Test runners
127
- 'jest', 'vitest', 'mocha', 'jasmine', 'ava', 'tap', 'c8', 'nyc',
128
- // Bundlers
129
- 'vite', 'webpack', 'rollup', 'parcel', 'turbo', 'nx',
130
- // Process managers
131
- 'nodemon', 'pm2', 'concurrently', 'cross-env', 'dotenv-cli', 'env-cmd',
132
- // Build helpers
133
- 'rimraf', 'copyfiles', 'mkdirp', 'shx', 'ncp', 'cpx', 'npm-run-all',
134
- // Docs
135
- 'typedoc', 'jsdoc', 'storybook',
136
- // Release
137
- 'husky', 'lint-staged', '@commitlint/cli', 'release-it', 'semantic-release', '@changesets/cli', 'np', 'bumpp',
138
- // ORM CLI tools
139
- 'prisma', 'drizzle-kit', 'typeorm', 'sequelize-cli', 'knex', '@mikro-orm/cli',
140
- // Scaffolding
141
- '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'
142
370
  ]);
143
371
 
144
- // ============================================================
145
- // KNOWN PACKAGE ALIASES (package name → common import name)
146
- // e.g. "lodash" is imported as "_", "express" as "app", etc.
147
- // This helps avoid false positives in unused detection
148
- // ============================================================
372
+ /**
373
+ * Bekannte Namensraum-Importbezeichner gängiger Bibliotheken.
374
+ * @type {Object}
375
+ */
149
376
  const PACKAGE_IMPORT_ALIASES = {
150
377
  'lodash': ['_', 'lodash'],
151
378
  'lodash-es': ['_', 'lodash'],
@@ -212,71 +439,154 @@ const PACKAGE_IMPORT_ALIASES = {
212
439
  'svelte': ['svelte'],
213
440
  '@angular/core': ['Component', 'NgModule'],
214
441
  'next': ['next'],
215
- 'nuxt': ['nuxt'],
442
+ 'nuxt': ['nuxt']
216
443
  };
217
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
+ */
218
453
  function getGitIdentity() {
219
- const identity = { name: "Developer", author: "Developer", repository: "" };
454
+ const identity = {
455
+ name: "Developer",
456
+ author: "Developer",
457
+ repository: ""
458
+ };
459
+
220
460
  try {
221
461
  const name = execSync('git config user.name', { encoding: 'utf8', stdio: 'pipe' }).trim();
222
462
  const email = execSync('git config user.email', { encoding: 'utf8', stdio: 'pipe' }).trim();
463
+
223
464
  if (name) {
224
465
  identity.name = name;
225
- identity.author = email ? `${name} <${email}>` : name;
466
+ if (email) {
467
+ identity.author = `${name} <${email}>`;
468
+ } else {
469
+ identity.author = name;
470
+ }
226
471
  }
472
+
227
473
  try {
228
474
  const remoteUrl = execSync('git remote get-url origin', { encoding: 'utf8', stdio: 'pipe' }).trim();
229
475
  identity.repository = remoteUrl.replace(/\.git$/, '');
230
- } catch (e) {}
231
- } catch (e) {}
476
+ } catch (gitRemoteError) {
477
+ // Fehlendes Remote wird abgefangen
478
+ }
479
+ } catch (gitConfigError) {
480
+ // Fehlende Git-Umgebung wird abgefangen
481
+ }
482
+
232
483
  return identity;
233
484
  }
234
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
+ */
235
492
  function detectPackageManager(targetDir, stats = null) {
236
493
  const detectedLockfiles = [];
237
- if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) detectedLockfiles.push('pnpm-lock.yaml');
238
- if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) detectedLockfiles.push('yarn.lock');
239
- if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) detectedLockfiles.push('package-lock.json');
240
- 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
+ }
241
507
 
242
- if (detectedLockfiles.length > 1 && stats) {
508
+ if (detectedLockfiles.length > 1 && stats !== null) {
243
509
  stats.conflictingLockfiles = detectedLockfiles;
244
510
  }
245
511
 
246
- if (detectedLockfiles.some(l => l.startsWith('bun'))) return 'bun';
247
- if (detectedLockfiles.includes('pnpm-lock.yaml')) return 'pnpm';
248
- if (detectedLockfiles.includes('yarn.lock')) return 'yarn';
249
- 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
+ }
250
524
 
251
- try { execSync('pnpm --version', { stdio: 'ignore' }); return 'pnpm'; } catch {}
252
- 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
+
253
535
  return 'npm';
254
536
  }
255
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
+ */
256
543
  function analyzeCodeStyle(content, stats) {
257
544
  const lines = content.split('\n');
545
+
258
546
  for (const line of lines) {
259
547
  const trimmed = line.trim();
260
- if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) continue;
548
+ if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
549
+ continue;
550
+ }
261
551
 
262
- if (trimmed.endsWith(';')) stats.style.semiCount++;
263
- 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
+ }
264
557
 
265
- if (line.startsWith('\t')) stats.style.tabCount++;
266
- else if (line.startsWith(' ')) {
558
+ if (line.startsWith('\t')) {
559
+ stats.style.tabCount++;
560
+ } else if (line.startsWith(' ')) {
267
561
  const spaces = line.match(/^(\s+)/)?.[1]?.length || 0;
268
- if (spaces === 2) stats.style.space2Count++;
269
- if (spaces === 4) stats.style.space4Count++;
562
+ if (spaces === 2) {
563
+ stats.style.space2Count++;
564
+ }
565
+ if (spaces === 4) {
566
+ stats.style.space4Count++;
567
+ }
270
568
  }
271
569
  }
272
570
 
273
- if (REGEX_PATTERNS.legacyVar.test(content)) stats.quality.varCount += (content.match(REGEX_PATTERNS.legacyVar) || []).length;
274
- if (REGEX_PATTERNS.dangerousEval.test(content)) stats.quality.hasEval = true;
275
- 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
+ }
276
580
  }
277
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
+ */
278
587
  function getBinariesFromPackageJson(packageJsonContent) {
279
588
  const binaries = new Set();
589
+
280
590
  if (packageJsonContent && packageJsonContent.scripts) {
281
591
  for (const script of Object.values(packageJsonContent.scripts)) {
282
592
  const commands = String(script).split(/\s*&&\s*|\s*;\s*|\s*\|\|\s*/);
@@ -288,15 +598,28 @@ function getBinariesFromPackageJson(packageJsonContent) {
288
598
  }
289
599
  }
290
600
  }
601
+
291
602
  return Array.from(binaries);
292
603
  }
293
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
+ */
294
610
  function cleanPackageName(importString) {
295
- if (!importString || /^[./~\\]/.test(importString)) return null;
296
- 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
+ }
297
617
  return importString.split('/')[0];
298
618
  }
299
619
 
620
+ /**
621
+ * Fügt Header-Komponenten präzise unterhalb von Shebangs oder Systemdirektiven ein.
622
+ */
300
623
  function smartPrepend(originalCode, declarationBlock) {
301
624
  const lines = originalCode.split(/\r?\n/);
302
625
  let insertIdx = 0;
@@ -316,6 +639,9 @@ function smartPrepend(originalCode, declarationBlock) {
316
639
  return lines.join('\n');
317
640
  }
318
641
 
642
+ /**
643
+ * Validiert die Existenz eines Pakets auf der offiziellen NPM-Registry.
644
+ */
319
645
  async function inspectNpmPackage(pkgName) {
320
646
  try {
321
647
  const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
@@ -324,15 +650,32 @@ async function inspectNpmPackage(pkgName) {
324
650
  });
325
651
  if (response.status === 200) {
326
652
  const data = await response.json();
327
- 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
+ };
658
+ }
659
+ if (response.status === 404) {
660
+ return {
661
+ version: null,
662
+ deprecated: null,
663
+ error: 'NOT_FOUND'
664
+ };
328
665
  }
329
- if (response.status === 404) return { version: null, deprecated: null, error: 'NOT_FOUND' };
330
- } catch (e) {
331
- return { version: 'latest', deprecated: null, error: 'NETWORK_FAIL' };
666
+ } catch (networkInspectError) {
667
+ return {
668
+ version: 'latest',
669
+ deprecated: null,
670
+ error: 'NETWORK_FAIL'
671
+ };
332
672
  }
333
673
  return null;
334
674
  }
335
675
 
676
+ /**
677
+ * Holt Lizenztexte aus der GitHub Legal API.
678
+ */
336
679
  async function fetchRemoteLicense(licenseKey) {
337
680
  try {
338
681
  const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
@@ -343,22 +686,289 @@ async function fetchRemoteLicense(licenseKey) {
343
686
  const data = await response.json();
344
687
  return data.body;
345
688
  }
346
- } catch (e) {}
689
+ } catch (licenseApiError) {}
347
690
  return null;
348
691
  }
349
692
 
693
+ /**
694
+ * Liest Dateien synchron ein und bereinigt etwaige BOM-Byte-Anordnungen.
695
+ */
350
696
  function readFileSyncNormalized(fullPath) {
351
697
  const buffer = fs.readFileSync(fullPath);
352
- if (buffer[0] === 0xFF && buffer[1] === 0xFE) return buffer.toString('utf16le');
353
- 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
+ }
354
704
  return buffer.toString('utf8');
355
705
  }
356
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
+
730
+ // ============================================================
731
+ // 🏗️ FRAMEWORK-SPECIFIC DEEP SCAN LOGIC
732
+ // ============================================================
733
+ class FrameworkAnalyzer {
734
+ static analyzeNextjsFile(filePath, content, stats) {
735
+ if (filePath.includes("pages/") && content.includes("getServerSideProps")) {
736
+ stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getServerSideProps");
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.`);
738
+ }
739
+ if (filePath.includes("pages/") && content.includes("getStaticProps")) {
740
+ stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getStaticProps");
741
+ }
742
+ if (filePath.includes("pages/") && content.includes("getStaticPaths")) {
743
+ stats.frameworkFiles.nextjs.dataFetching.set(filePath, "getStaticPaths");
744
+ }
745
+ if (filePath.includes("app/") && content.includes("export async function GET")) {
746
+ stats.frameworkFiles.nextjs.dataFetching.set(filePath, "Route Handler (GET)");
747
+ }
748
+ if (content.includes("<img") && !content.includes("<Image")) {
749
+ stats.frameworkOptimizations.push(`Next.js: Use next/image for '${path.relative(process.cwd(), filePath)}' to optimize images.`);
750
+ }
751
+ if (content.includes("<link") && content.includes("googlefonts") && !content.includes("next/font")) {
752
+ stats.frameworkOptimizations.push(`Next.js: Use next/font for '${path.relative(process.cwd(), filePath)}' to optimize fonts.`);
753
+ }
754
+ }
755
+
756
+ static analyzeNuxtFile(filePath, content, stats) {
757
+ if (content.includes("useAsyncData")) {
758
+ stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useAsyncData");
759
+ }
760
+ if (content.includes("useFetch")) {
761
+ stats.frameworkFiles.nuxt.dataFetching.set(filePath, "useFetch");
762
+ }
763
+ if (filePath.includes("components/") && !content.includes("defineComponent")) {
764
+ stats.frameworkOptimizations.push(`Nuxt: Ensure components in '${path.relative(process.cwd(), filePath)}' are properly defined for auto-import or explicitly imported.`);
765
+ }
766
+ }
767
+
768
+ static analyzeSvelteKitFile(filePath, content, stats) {
769
+ if (content.includes("export async function load")) {
770
+ stats.frameworkFiles.sveltekit.loadFunctions.set(filePath, "load");
771
+ }
772
+ if (filePath.includes("src/routes/") && content.includes("export const actions")) {
773
+ stats.frameworkFiles.sveltekit.endpoints.add(filePath);
774
+ }
775
+ }
776
+
777
+ static analyzeReactFile(filePath, content, stats) {
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.`);
781
+ }
782
+ }
783
+
784
+ static analyzeFile(filePath, content, stats, detectedFrameworks) {
785
+ if (!detectedFrameworks || !Array.isArray(detectedFrameworks)) {
786
+ return;
787
+ }
788
+ if (detectedFrameworks.includes("next")) {
789
+ FrameworkAnalyzer.analyzeNextjsFile(filePath, content, stats);
790
+ }
791
+ if (detectedFrameworks.includes("nuxt")) {
792
+ FrameworkAnalyzer.analyzeNuxtFile(filePath, content, stats);
793
+ }
794
+ if (detectedFrameworks.includes("svelte")) {
795
+ FrameworkAnalyzer.analyzeSvelteKitFile(filePath, content, stats);
796
+ }
797
+ if (detectedFrameworks.includes("react")) {
798
+ FrameworkAnalyzer.analyzeReactFile(filePath, content, stats);
799
+ }
800
+ if (detectedFrameworks.includes("vue")) {
801
+ FrameworkAnalyzer.analyzeVueFile(filePath, content, stats);
802
+ }
803
+ }
804
+ }
805
+
806
+ class FrameworkEngine {
807
+ static detect(targetDir, packageJson) {
808
+ const detected = new Set();
809
+ const allDependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
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");
825
+ }
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
+ }
847
+ return Array.from(detected);
848
+ }
849
+ }
850
+
851
+ // ============================================================
852
+ // 🧱 CUSTOM STRUCTURAL HOISTED TEMPLATE MANAGER
853
+ // ============================================================
854
+ class TemplateManager {
855
+ constructor(baseDir, safeQuestion) {
856
+ this.baseDir = baseDir;
857
+ this.safeQuestion = safeQuestion;
858
+ this.templateSources = [{ name: 'local', path: path.join(this.baseDir, '.templates') }];
859
+ }
860
+
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
+ }
873
+ }
874
+ return Array.from(allTemplates);
875
+ }
876
+
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
+ }
885
+ }
886
+ return null;
887
+ }
888
+
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 {};
914
+ }
915
+ }
916
+ return {};
917
+ }
918
+
919
+ async generate(templateName, variables) {
920
+ const templatePath = await this.getTemplatePath(templateName);
921
+ if (!templatePath) {
922
+ return;
923
+ }
924
+ console.log(` 🚀 Generating '${templateName}' template with full route and token mutations...`);
925
+
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
+ };
934
+
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);
956
+ }
957
+ }
958
+ };
959
+ await processDirectory(templatePath, this.baseDir, variables);
960
+ console.log(` ✅ Template '${templateName}' generated successfully.`);
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Generiert einen sauberen ASCII-Layout-Baum für das automatisierte README-Dokument.
966
+ */
357
967
  function buildAsciiTree(dir, prefix = '') {
358
968
  const results = [];
359
969
  try {
360
970
  const files = fs.readdirSync(dir);
361
- const filtered = files.filter(f => !IGNORED_DIRS.has(f) && !f.startsWith('.'));
971
+ const filtered = files.filter(f => { return !IGNORED_DIRS.has(f) && !f.startsWith('.'); });
362
972
 
363
973
  filtered.forEach((file, index) => {
364
974
  const isLast = index === filtered.length - 1;
@@ -371,22 +981,91 @@ function buildAsciiTree(dir, prefix = '') {
371
981
  results.push(...buildAsciiTree(fullPath, newPrefix));
372
982
  }
373
983
  });
374
- } catch (e) {}
984
+ } catch (asciiTreeTreeError) {}
375
985
  return results;
376
986
  }
377
987
 
378
988
  // ============================================================
379
- // IMPROVED IMPORT EXTRACTION: handles TypeScript generics,
380
- // type-only imports, re-exports, and dynamic imports
989
+ // STRUKTURELLES IMPLEMENTIERUNGSHELFER SYSTEM
381
990
  // ============================================================
382
- function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations) {
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
+
1053
+ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations, exportedSymbols, stats, currentFilePath) {
1054
+ extractAdvancedSymbols(ast, currentFilePath, stats);
1055
+
383
1056
  walk.simple(ast, {
384
1057
  ImportDeclaration(node) {
385
- const pkg = cleanPackageName(node.source.value);
1058
+ const importSource = node.source.value;
1059
+ const pkg = cleanPackageName(importSource);
1060
+
386
1061
  if (pkg && !builtinModules.includes(pkg)) {
387
1062
  fileRawDeps.add(pkg);
388
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
389
- 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
+ }
390
1069
  importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
391
1070
 
392
1071
  node.specifiers.forEach(spec => {
@@ -394,29 +1073,50 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
394
1073
  importedIdentifiers.get(pkg).add(spec.local.name);
395
1074
  } else if (spec.type === 'ImportSpecifier') {
396
1075
  importedIdentifiers.get(pkg).add(spec.local.name);
397
- // Also track the imported name (before 'as') for side-effect detection
398
1076
  if (spec.imported && spec.imported.name !== spec.local.name) {
399
1077
  importedIdentifiers.get(pkg).add(spec.imported.name);
400
1078
  }
401
1079
  }
402
1080
  });
403
-
404
- // Side-effect only import: import 'pkg' — always considered "used"
405
1081
  if (node.specifiers.length === 0) {
406
1082
  importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
407
1083
  }
1084
+ } else if (importSource.startsWith('.') || importSource.startsWith('/')) {
1085
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1086
+ const normalizedPath = path.normalize(resolvedPath);
1087
+
1088
+ if (!stats.localFileImports) {
1089
+ stats.localFileImports = new Map();
1090
+ }
1091
+ if (!stats.localFileImports.has(normalizedPath)) {
1092
+ stats.localFileImports.set(normalizedPath, new Set());
1093
+ }
1094
+
1095
+ node.specifiers.forEach(spec => {
1096
+ if (spec.type === 'ImportDefaultSpecifier' || spec.type === 'ImportNamespaceSpecifier') {
1097
+ stats.localFileImports.get(normalizedPath).add(spec.local.name);
1098
+ } else if (spec.type === 'ImportSpecifier') {
1099
+ stats.localFileImports.get(normalizedPath).add(spec.local.name);
1100
+ if (spec.imported && spec.imported.name !== spec.local.name) {
1101
+ stats.localFileImports.get(normalizedPath).add(spec.imported.name);
1102
+ }
1103
+ }
1104
+ });
408
1105
  }
409
1106
  },
410
1107
  VariableDeclarator(node) {
411
- if (node.init && node.init.type === 'CallExpression' &&
412
- 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') {
413
1109
  const arg = node.init.arguments[0];
414
1110
  if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
415
1111
  const pkg = cleanPackageName(arg.value);
416
1112
  if (pkg && !builtinModules.includes(pkg)) {
417
1113
  fileRawDeps.add(pkg);
418
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
419
- 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
+ }
420
1120
  importedLocations.get(pkg).push(node.loc?.start?.line ?? 0);
421
1121
 
422
1122
  const extractBindings = (idNode) => {
@@ -424,8 +1124,12 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
424
1124
  importedIdentifiers.get(pkg).add(idNode.name);
425
1125
  } else if (idNode.type === 'ObjectPattern') {
426
1126
  idNode.properties.forEach(p => {
427
- if (p.value && p.value.type === 'Identifier') importedIdentifiers.get(pkg).add(p.value.name);
428
- 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
+ }
429
1133
  });
430
1134
  }
431
1135
  };
@@ -439,17 +1143,44 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
439
1143
  const pkg = cleanPackageName(node.source.value);
440
1144
  if (pkg && !builtinModules.includes(pkg)) {
441
1145
  fileRawDeps.add(pkg);
442
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1146
+ if (!importedIdentifiers.has(pkg)) {
1147
+ importedIdentifiers.set(pkg, new Set());
1148
+ }
443
1149
  importedIdentifiers.get(pkg).add('__DYNAMIC__');
444
1150
  }
445
1151
  }
446
1152
  },
447
1153
  ExportNamedDeclaration(node) {
1154
+ if (node.declaration) {
1155
+ if (node.declaration.type === 'VariableDeclaration') {
1156
+ node.declaration.declarations.forEach(decl => {
1157
+ if (decl.id.type === 'Identifier') {
1158
+ exportedSymbols.set(decl.id.name, { type: 'variable', loc: decl.id.loc.start });
1159
+ }
1160
+ });
1161
+ } else if (node.declaration.type === 'FunctionDeclaration') {
1162
+ if (node.declaration.id) {
1163
+ exportedSymbols.set(node.declaration.id.name, { type: 'function', loc: node.declaration.id.loc.start });
1164
+ }
1165
+ } else if (node.declaration.type === 'ClassDeclaration') {
1166
+ if (node.declaration.id) {
1167
+ exportedSymbols.set(node.declaration.id.name, { type: 'class', loc: node.declaration.id.loc.start });
1168
+ }
1169
+ }
1170
+ } else if (node.specifiers) {
1171
+ node.specifiers.forEach(spec => {
1172
+ if (spec.exported.type === 'Identifier') {
1173
+ exportedSymbols.set(spec.exported.name, { type: 'namedExport', loc: spec.exported.loc.start });
1174
+ }
1175
+ });
1176
+ }
448
1177
  if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
449
1178
  const pkg = cleanPackageName(node.source.value);
450
1179
  if (pkg && !builtinModules.includes(pkg)) {
451
1180
  fileRawDeps.add(pkg);
452
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1181
+ if (!importedIdentifiers.has(pkg)) {
1182
+ importedIdentifiers.set(pkg, new Set());
1183
+ }
453
1184
  importedIdentifiers.get(pkg).add('__REEXPORT__');
454
1185
  }
455
1186
  }
@@ -459,7 +1190,9 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
459
1190
  const pkg = cleanPackageName(node.source.value);
460
1191
  if (pkg && !builtinModules.includes(pkg)) {
461
1192
  fileRawDeps.add(pkg);
462
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1193
+ if (!importedIdentifiers.has(pkg)) {
1194
+ importedIdentifiers.set(pkg, new Set());
1195
+ }
463
1196
  importedIdentifiers.get(pkg).add('__REEXPORT__');
464
1197
  }
465
1198
  }
@@ -467,226 +1200,301 @@ function extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLo
467
1200
  });
468
1201
  }
469
1202
 
470
- // ============================================================
471
- // REGEX FALLBACK: handles TypeScript files that acorn can't parse
472
- // ============================================================
473
- function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations) {
1203
+ /**
1204
+ * Textbasierter Regex-Ersatz-Parser, falls Acorn auf Syntaxfehler stößt.
1205
+ */
1206
+ function extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, currentFilePath) {
474
1207
  codeLines.forEach((line, lineIdx) => {
475
1208
  const lineNum = lineIdx + 1;
476
-
477
- // import type { ... } from '...' — type-only, mark as side-effect
478
1209
  const typeImportMatch = line.match(/\bimport\s+type\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/);
479
1210
  if (typeImportMatch) {
480
- const pkg = cleanPackageName(typeImportMatch[1]);
1211
+ const importSource = typeImportMatch[1];
1212
+ const pkg = cleanPackageName(importSource);
481
1213
  if (pkg && !builtinModules.includes(pkg)) {
482
1214
  fileRawDeps.add(pkg);
483
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1215
+ if (!importedIdentifiers.has(pkg)) {
1216
+ importedIdentifiers.set(pkg, new Set());
1217
+ }
484
1218
  importedIdentifiers.get(pkg).add('__TYPE_ONLY__');
485
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1219
+ if (!importedLocations.has(pkg)) {
1220
+ importedLocations.set(pkg, []);
1221
+ }
486
1222
  importedLocations.get(pkg).push(lineNum);
1223
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1224
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1225
+ const normalizedPath = path.normalize(resolvedPath);
1226
+ if (!stats.localFileImports) {
1227
+ stats.localFileImports = new Map();
1228
+ }
1229
+ if (!stats.localFileImports.has(normalizedPath)) {
1230
+ stats.localFileImports.set(normalizedPath, new Set());
1231
+ }
1232
+ stats.localFileImports.get(normalizedPath).add('__TYPE_ONLY__');
487
1233
  }
488
1234
  return;
489
1235
  }
490
1236
 
491
- // import DefaultExport from '...'
492
- // import * as Namespace from '...'
493
1237
  const esmDefaultMatch = line.match(/\bimport\s+(?:\*\s+as\s+)?([a-zA-Z0-9_$]+)\s+from\s+['"]([^'"]+)['"]/);
494
1238
  if (esmDefaultMatch) {
495
1239
  const id = esmDefaultMatch[1];
496
- const pkg = cleanPackageName(esmDefaultMatch[2]);
1240
+ const importSource = esmDefaultMatch[2];
1241
+ const pkg = cleanPackageName(importSource);
497
1242
  if (pkg && !builtinModules.includes(pkg)) {
498
1243
  fileRawDeps.add(pkg);
499
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1244
+ if (!importedIdentifiers.has(pkg)) {
1245
+ importedIdentifiers.set(pkg, new Set());
1246
+ }
500
1247
  importedIdentifiers.get(pkg).add(id);
501
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1248
+ if (!importedLocations.has(pkg)) {
1249
+ importedLocations.set(pkg, []);
1250
+ }
502
1251
  importedLocations.get(pkg).push(lineNum);
1252
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1253
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1254
+ const normalizedPath = path.normalize(resolvedPath);
1255
+ if (!stats.localFileImports) {
1256
+ stats.localFileImports = new Map();
1257
+ }
1258
+ if (!stats.localFileImports.has(normalizedPath)) {
1259
+ stats.localFileImports.set(normalizedPath, new Set());
1260
+ }
1261
+ stats.localFileImports.get(normalizedPath).add(id);
503
1262
  }
504
1263
  return;
505
1264
  }
506
1265
 
507
- // import { named, exports } from '...'
508
1266
  const esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
509
1267
  if (esmNamedMatch) {
510
- const pkg = cleanPackageName(esmNamedMatch[2]);
1268
+ const importSource = esmNamedMatch[2];
1269
+ const pkg = cleanPackageName(importSource);
511
1270
  if (pkg && !builtinModules.includes(pkg)) {
512
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1271
+ if (!importedIdentifiers.has(pkg)) {
1272
+ importedIdentifiers.set(pkg, new Set());
1273
+ }
513
1274
  fileRawDeps.add(pkg);
514
1275
  esmNamedMatch[1].split(',').forEach(part => {
515
1276
  const chunk = part.trim();
516
- if (!chunk) return;
1277
+ if (!chunk) {
1278
+ return;
1279
+ }
517
1280
  const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
518
1281
  importedIdentifiers.get(pkg).add(id);
519
- // Also add the original name
520
- if (chunk.includes(' as ')) importedIdentifiers.get(pkg).add(chunk.split(' as ')[0].trim());
521
1282
  });
522
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1283
+ if (!importedLocations.has(pkg)) {
1284
+ importedLocations.set(pkg, []);
1285
+ }
523
1286
  importedLocations.get(pkg).push(lineNum);
1287
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1288
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1289
+ const normalizedPath = path.normalize(resolvedPath);
1290
+ if (!stats.localFileImports) {
1291
+ stats.localFileImports = new Map();
1292
+ }
1293
+ if (!stats.localFileImports.has(normalizedPath)) {
1294
+ stats.localFileImports.set(normalizedPath, new Set());
1295
+ }
1296
+ esmNamedMatch[1].split(',').forEach(part => {
1297
+ const chunk = part.trim();
1298
+ if (!chunk) {
1299
+ return;
1300
+ }
1301
+ const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
1302
+ stats.localFileImports.get(normalizedPath).add(id);
1303
+ });
524
1304
  }
525
1305
  return;
526
1306
  }
527
1307
 
528
- // Side-effect only: import '...'
529
1308
  const sideEffectMatch = line.match(/\bimport\s+['"]([^'"]+)['"]/);
530
1309
  if (sideEffectMatch) {
531
- const pkg = cleanPackageName(sideEffectMatch[1]);
1310
+ const importSource = sideEffectMatch[1];
1311
+ const pkg = cleanPackageName(importSource);
532
1312
  if (pkg && !builtinModules.includes(pkg)) {
533
1313
  fileRawDeps.add(pkg);
534
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1314
+ if (!importedIdentifiers.has(pkg)) {
1315
+ importedIdentifiers.set(pkg, new Set());
1316
+ }
535
1317
  importedIdentifiers.get(pkg).add('__SIDE_EFFECT__');
536
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1318
+ if (!importedLocations.has(pkg)) {
1319
+ importedLocations.set(pkg, []);
1320
+ }
537
1321
  importedLocations.get(pkg).push(lineNum);
1322
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1323
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1324
+ const normalizedPath = path.normalize(resolvedPath);
1325
+ if (!stats.localFileImports) {
1326
+ stats.localFileImports = new Map();
1327
+ }
1328
+ if (!stats.localFileImports.has(normalizedPath)) {
1329
+ stats.localFileImports.set(normalizedPath, new Set());
1330
+ }
1331
+ stats.localFileImports.get(normalizedPath).add('__SIDE_EFFECT__');
538
1332
  }
539
1333
  return;
540
1334
  }
541
1335
 
542
- // const x = require('...')
543
- 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*\)/);
544
1337
  if (cjsMatch) {
545
- const id = cjsMatch[1];
546
- const pkg = cleanPackageName(cjsMatch[2]);
1338
+ const id = cjsMatch[2];
1339
+ const importSource = cjsMatch[3];
1340
+ const pkg = cleanPackageName(importSource);
547
1341
  if (pkg && !builtinModules.includes(pkg)) {
548
1342
  fileRawDeps.add(pkg);
549
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1343
+ if (!importedIdentifiers.has(pkg)) {
1344
+ importedIdentifiers.set(pkg, new Set());
1345
+ }
550
1346
  importedIdentifiers.get(pkg).add(id);
551
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1347
+ if (!importedLocations.has(pkg)) {
1348
+ importedLocations.set(pkg, []);
1349
+ }
552
1350
  importedLocations.get(pkg).push(lineNum);
1351
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1352
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1353
+ const normalizedPath = path.normalize(resolvedPath);
1354
+ if (!stats.localFileImports) {
1355
+ stats.localFileImports = new Map();
1356
+ }
1357
+ if (!stats.localFileImports.has(normalizedPath)) {
1358
+ stats.localFileImports.set(normalizedPath, new Set());
1359
+ }
1360
+ stats.localFileImports.get(normalizedPath).add(id);
553
1361
  }
554
1362
  return;
555
1363
  }
556
1364
 
557
- // const { a, b } = require('...')
558
- 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*\)/);
559
1366
  if (cjsDestructMatch) {
560
- const pkg = cleanPackageName(cjsDestructMatch[2]);
1367
+ const importSource = cjsDestructMatch[3];
1368
+ const pkg = cleanPackageName(importSource);
561
1369
  if (pkg && !builtinModules.includes(pkg)) {
562
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1370
+ if (!importedIdentifiers.has(pkg)) {
1371
+ importedIdentifiers.set(pkg, new Set());
1372
+ }
563
1373
  fileRawDeps.add(pkg);
564
- cjsDestructMatch[1].split(',').forEach(part => {
1374
+ cjsDestructMatch[2].split(',').forEach(part => {
565
1375
  const chunk = part.trim();
566
- if (!chunk) return;
1376
+ if (!chunk) {
1377
+ return;
1378
+ }
567
1379
  const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
568
1380
  importedIdentifiers.get(pkg).add(id);
569
1381
  });
570
- 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
+ }
571
1387
  importedLocations.get(pkg).push(lineNum);
1388
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1389
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1390
+ const normalizedPath = path.normalize(resolvedPath);
1391
+ if (!stats.localFileImports) {
1392
+ stats.localFileImports = new Map();
1393
+ }
1394
+ if (!stats.localFileImports.has(normalizedPath)) {
1395
+ stats.localFileImports.set(normalizedPath, new Set());
1396
+ }
1397
+ cjsDestructMatch[2].split(',').forEach(part => {
1398
+ const chunk = part.trim();
1399
+ if (!chunk) {
1400
+ return;
1401
+ }
1402
+ const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
1403
+ stats.localFileImports.get(normalizedPath).add(id);
1404
+ });
572
1405
  }
573
1406
  return;
574
1407
  }
575
1408
 
576
- // Dynamic import: import('...')
577
1409
  const dynamicMatch = line.match(/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/);
578
1410
  if (dynamicMatch) {
579
- const pkg = cleanPackageName(dynamicMatch[1]);
1411
+ const importSource = dynamicMatch[1];
1412
+ const pkg = cleanPackageName(importSource);
580
1413
  if (pkg && !builtinModules.includes(pkg)) {
581
1414
  fileRawDeps.add(pkg);
582
- if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
1415
+ if (!importedIdentifiers.has(pkg)) {
1416
+ importedIdentifiers.set(pkg, new Set());
1417
+ }
583
1418
  importedIdentifiers.get(pkg).add('__DYNAMIC__');
584
- if (!importedLocations.has(pkg)) importedLocations.set(pkg, []);
1419
+ if (!importedLocations.has(pkg)) {
1420
+ importedLocations.set(pkg, []);
1421
+ }
585
1422
  importedLocations.get(pkg).push(lineNum);
1423
+ } else if (importSource.startsWith(".") || importSource.startsWith("/")) {
1424
+ const resolvedPath = path.resolve(path.dirname(currentFilePath), importSource);
1425
+ const normalizedPath = path.normalize(resolvedPath);
1426
+ if (!stats.localFileImports) {
1427
+ stats.localFileImports = new Map();
1428
+ }
1429
+ if (!stats.localFileImports.has(normalizedPath)) {
1430
+ stats.localFileImports.set(normalizedPath, new Set());
1431
+ }
1432
+ stats.localFileImports.get(normalizedPath).add('__DYNAMIC__');
586
1433
  }
587
1434
  }
588
1435
  });
589
1436
  }
590
1437
 
591
- // ============================================================
592
- // USAGE ANALYSIS: determines if imported identifiers are
593
- // actually referenced in the non-import code body
594
- // ============================================================
595
- function analyzeIdentifierUsage(pkg, identifiers, executionCode) {
596
- // Always-used markers: side-effect, dynamic, re-export, type-only
597
- const autoUsedMarkers = new Set(['__SIDE_EFFECT__', '__DYNAMIC__', '__REEXPORT__', '__TYPE_ONLY__']);
598
- for (const id of identifiers) {
599
- if (autoUsedMarkers.has(id)) return true;
600
- }
601
-
602
- // Check known aliases for this package
603
- const knownAliases = PACKAGE_IMPORT_ALIASES[pkg] || [];
604
-
605
- for (const identifier of identifiers) {
606
- if (!identifier || identifier.startsWith('__')) continue;
607
- // Escape special regex chars in identifier
608
- const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
609
- const usagePattern = new RegExp(`\\b${escaped}\\b`);
610
- if (usagePattern.test(executionCode)) return true;
611
- }
612
-
613
- // Check if any known alias for this package appears in the code
614
- for (const alias of knownAliases) {
615
- const escaped = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
616
- const aliasPattern = new RegExp(`\\b${escaped}\\b`);
617
- if (aliasPattern.test(executionCode)) return true;
618
- }
619
-
620
- return false;
621
- }
622
-
623
- // ============================================================
624
- // GHOST DEPENDENCY DETECTOR
625
- // Finds packages used in code but NOT declared in package.json
626
- // This is the most critical error: will fail at runtime/deploy
627
- // ============================================================
628
- function detectGhostDependencies(allImportedPackages, declaredDeps, declaredDevDeps) {
629
- const allDeclared = new Set([...declaredDeps, ...declaredDevDeps]);
630
- const ghosts = new Set();
631
-
632
- for (const pkg of allImportedPackages) {
633
- if (!allDeclared.has(pkg) && !builtinModules.includes(pkg)) {
634
- ghosts.add(pkg);
635
- }
636
- }
637
- return ghosts;
638
- }
639
-
640
- // ============================================================
641
- // ORPHANED DEPENDENCY DETECTOR
642
- // Finds packages in package.json that are never imported anywhere
643
- // in the codebase (candidates for removal)
644
- // ============================================================
645
- function detectOrphanedDependencies(declaredDeps, allImportedPackages, binariesUsed, devTooling) {
646
- const orphans = new Set();
647
-
648
- for (const dep of declaredDeps) {
649
- // Skip dev tooling — they're used via CLI, not imports
650
- if (devTooling.has(dep) || dep.startsWith('@types/')) continue;
651
-
652
- // Check if it's used as a binary
653
- const binaryPkg = Object.values(BINARY_TO_PACKAGE_MAP).find(p => p === dep);
654
- if (binaryPkg && binariesUsed.has(dep)) continue;
655
-
656
- // Check if it's imported anywhere
657
- if (!allImportedPackages.has(dep)) {
658
- orphans.add(dep);
659
- }
660
- }
661
- return orphans;
662
- }
663
-
664
- // ============================================================
665
- // HIGH PERFORMANCE AST WORKSPACE PARSING ENGINE
666
- // ============================================================
667
- function scanWorkspace(dir, stats, rootNamespace) {
1438
+ /**
1439
+ * Kernfunktion zur iterativen Rekursion durch alle Ordnerstrukturen des Workspaces.
1440
+ */
1441
+ function scanWorkspace(dir, stats, rootNamespace, detectedFrameworks) {
668
1442
  const files = fs.readdirSync(dir);
669
-
670
1443
  for (const file of files) {
671
1444
  const fullPath = path.join(dir, file);
672
1445
  const stat = fs.statSync(fullPath);
673
1446
 
674
1447
  if (stat.isDirectory()) {
675
1448
  if (!IGNORED_DIRS.has(file) && !file.startsWith('.')) {
676
- scanWorkspace(fullPath, stats, rootNamespace);
1449
+ scanWorkspace(fullPath, stats, rootNamespace, detectedFrameworks);
677
1450
  }
678
1451
  } else {
679
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
+ }
680
1465
 
681
- if (file === 'index.html' || REGEX_PATTERNS.configFile.test(file)) stats.hasHtml = true;
682
- if (REGEX_PATTERNS.testFile.test(file)) stats.hasTests = true;
683
- if (ext === '.ts' || ext === '.tsx') stats.tsFiles++;
684
- if (ext === '.js' || ext === '.jsx' || ext === '.mjs') stats.jsFiles++;
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
+ }
685
1493
 
686
1494
  if (VALID_EXTENSIONS.has(ext)) {
687
1495
  stats.scannedFiles++;
1496
+ stats.scannedFilePaths.push(fullPath);
688
1497
  const rawContent = readFileSyncNormalized(fullPath);
689
- // Strip non-printable chars but keep Unicode letters (important for identifiers)
690
1498
  const content = rawContent.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
691
1499
 
692
1500
  const codeLines = content.split(/\r?\n/);
@@ -696,18 +1504,49 @@ function scanWorkspace(dir, stats, rootNamespace) {
696
1504
 
697
1505
  analyzeCodeStyle(content, stats);
698
1506
 
699
- // Universal Cryptographic Leak Interception
700
- REGEX_PATTERNS.secretKeys.lastIndex = 0;
701
- let secretMatch;
702
- while ((secretMatch = REGEX_PATTERNS.secretKeys.exec(content)) !== null) {
703
- const keyName = secretMatch[1];
704
- const secretValue = secretMatch[2];
705
- const envVarName = `${rootNamespace.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_${keyName.toUpperCase()}`;
706
- stats.discoveredSecrets.push({ filePath: fullPath, keyName, secretValue, envVarName });
707
- stats.envVars.add(envVarName);
1507
+ for (const [patternName, patternRegex] of Object.entries(REGEX_PATTERNS)) {
1508
+ if (patternName.startsWith("secretKeys") || patternName.endsWith("Keys") || patternName.endsWith("Tokens")) {
1509
+ patternRegex.lastIndex = 0;
1510
+ let match;
1511
+ while ((match = patternRegex.exec(content)) !== null) {
1512
+ const keyName = match[1] || patternName;
1513
+ const secretValue = match[2] || match[0];
1514
+ const envVarName = `${rootNamespace.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_${keyName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
1515
+ stats.discoveredSecrets.push({ filePath: fullPath, keyName, secretValue, envVarName, type: patternName });
1516
+ stats.envVars.add(envVarName);
1517
+ }
1518
+ } else if (patternName.startsWith("insecure")) {
1519
+ patternRegex.lastIndex = 0;
1520
+ let match;
1521
+ while ((match = patternRegex.exec(content)) !== null) {
1522
+ const line = content.substring(0, match.index).split("\n").length;
1523
+ if (patternName === "insecureCrypto") {
1524
+ stats.quality.insecureCryptoUsage.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1525
+ } else if (patternName === "sqlInjection") {
1526
+ stats.quality.sqlInjectionVulnerabilities.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1527
+ } else if (patternName === "xssVulnerability") {
1528
+ stats.quality.xssVulnerabilities.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1529
+ } else {
1530
+ stats.quality.insecurePatterns.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1531
+ }
1532
+ }
1533
+ } else if (patternName.startsWith("largeImageImport")) {
1534
+ patternRegex.lastIndex = 0;
1535
+ let match;
1536
+ while ((match = patternRegex.exec(content)) !== null) {
1537
+ const line = content.substring(0, match.index).split("\n").length;
1538
+ stats.quality.largeImageImports.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1539
+ }
1540
+ } else if (patternName.startsWith("unoptimizedLoop")) {
1541
+ patternRegex.lastIndex = 0; // 👈 Add this line to reset the pointer for the next file pass
1542
+ let match;
1543
+ while ((match = patternRegex.exec(content)) !== null) {
1544
+ const line = content.substring(0, match.index).split("\n").length;
1545
+ stats.quality.unoptimizedLoops.push({ filePath: fullPath, type: patternName, line, code: match[0] });
1546
+ }
1547
+ }
708
1548
  }
709
1549
 
710
- // Global Regex Environmental Extraction Module
711
1550
  let fileHasEnv = false;
712
1551
  let envMatch;
713
1552
  REGEX_PATTERNS.env.lastIndex = 0;
@@ -715,11 +1554,15 @@ function scanWorkspace(dir, stats, rootNamespace) {
715
1554
  stats.envVars.add(envMatch[1]);
716
1555
  fileHasEnv = true;
717
1556
  }
718
- if (fileHasEnv) stats.filesWithEnvVars.add(fullPath);
1557
+ if (fileHasEnv) {
1558
+ stats.filesWithEnvVars.add(fullPath);
1559
+ }
1560
+ if (content.includes('import ') || content.includes('export ')) {
1561
+ stats.usesEsm = true;
1562
+ }
719
1563
 
720
- if (content.includes('import ') || content.includes('export ')) stats.usesEsm = true;
1564
+ FrameworkAnalyzer.analyzeFile(fullPath, content, stats, detectedFrameworks);
721
1565
 
722
- // --- AST Parsing (preferred) ---
723
1566
  let ast = null;
724
1567
  try {
725
1568
  ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true, locations: true });
@@ -730,24 +1573,22 @@ function scanWorkspace(dir, stats, rootNamespace) {
730
1573
  }
731
1574
 
732
1575
  if (ast) {
733
- extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations);
1576
+ const currentFileExportedSymbols = new Map();
1577
+ extractImportsFromAST(ast, fileRawDeps, importedIdentifiers, importedLocations, currentFileExportedSymbols, stats, fullPath);
1578
+ if (currentFileExportedSymbols.size > 0) {
1579
+ stats.exportedSymbols.set(fullPath, currentFileExportedSymbols);
1580
+ }
734
1581
  } else {
735
- // Regex fallback for TypeScript generics / decorators / etc.
736
- extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations);
1582
+ extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, fullPath);
737
1583
  }
738
1584
 
739
- // Register all deps found in this file
740
- fileRawDeps.forEach(dep => stats.allImportedPackages.add(dep));
741
- 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); });
742
1587
 
743
- // --- Per-file usage analysis ---
744
- // Strip import/require lines to get only execution code
745
- const executionCode = codeLines
746
- .filter(l => {
747
- const t = l.trim();
748
- return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
749
- })
750
- .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');
751
1592
 
752
1593
  for (const [pkg, identifiers] of importedIdentifiers.entries()) {
753
1594
  const isUsed = analyzeIdentifierUsage(pkg, identifiers, executionCode);
@@ -765,7 +1606,161 @@ function scanWorkspace(dir, stats, rootNamespace) {
765
1606
  }
766
1607
  }
767
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
+ // ============================================================
768
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
+
1759
+ if (process.env.INIT_CWD && !process.env.NPX_CLI_JS) {
1760
+ console.log("\x1b[31m%s\x1b[0m", "🛑 Wait! Do not install this package locally.");
1761
+ console.log("Please run it directly using: \x1b[36mnpx pkg-scaffold\x1b[0m\n");
1762
+ process.exit(1);
1763
+ }
769
1764
  const targetDir = process.cwd();
770
1765
  const folderName = path.basename(targetDir);
771
1766
  const gitInfo = getGitIdentity();
@@ -774,45 +1769,74 @@ async function main() {
774
1769
  let rlClosed = false;
775
1770
  rl.on('close', () => { rlClosed = true; });
776
1771
  const safeQuestion = async (prompt) => {
777
- if (rlClosed || !process.stdin.readable) return '';
778
- try { return await safeQuestion(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
+ }
779
1780
  };
780
1781
 
781
1782
  const stats = {
782
- 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,
783
1788
  scannedFiles: 0,
1789
+ scannedFilePaths: [],
784
1790
  rawDeps: new Set(),
785
1791
  allImportedPackages: new Set(),
786
1792
  envVars: new Set(),
787
1793
  style: { semiCount: 0, noSemiCount: 0, tabCount: 0, space2Count: 0, space4Count: 0 },
788
- quality: { varCount: 0, hasEval: false, syncFsCount: 0 },
1794
+ quality: {
1795
+ varCount: 0, hasEval: false, syncFsCount: 0, insecurePatterns: [], complexRegexes: [],
1796
+ insecureCryptoUsage: [], sqlInjectionVulnerabilities: [], xssVulnerabilities: [],
1797
+ largeImageImports: [], unoptimizedLoops: [], frameworkSpecificIssues: []
1798
+ },
789
1799
  phantomInjections: new Map(),
790
1800
  discoveredSecrets: [],
1801
+ insecureCodePatterns: [],
791
1802
  subWorkspaces: [],
792
1803
  conflictingLockfiles: [],
1804
+ exportedSymbols: new Map(),
1805
+ usedExports: new Map(),
1806
+ unusedFiles: new Set(),
1807
+ unusedExportsPerFile: new Map(),
1808
+ localFileImports: new Map(),
793
1809
  unusedDepsInCode: new Set(),
794
- unusedImportsPerFile: new Map(),
795
- filesWithEnvVars: new Set(),
796
- injectDotenvEngine: false,
1810
+ unusedImportsPerFile: new Map(),
1811
+ filesWithEnvVars: new Set(),
1812
+ injectDotenvEngine: false,
797
1813
  bootstrapEslintSuite: false,
798
- // New tracking structures
799
- ghostDependencies: new Set(), // used in code, missing from package.json
800
- orphanedDependencies: new Set(), // in package.json, never imported
801
- deprecatedPackages: new Map(), // pkg -> deprecation message
1814
+ ghostDependencies: new Set(),
1815
+ orphanedDependencies: new Set(),
1816
+ deprecatedPackages: new Map(),
1817
+ fileSymbolMetadata: new Map(),
1818
+ frameworkFiles: {
1819
+ nextjs: { pages: new Set(), apiRoutes: new Set(), components: new Set(), dataFetching: new Map(), optimizations: [] },
1820
+ nuxt: { pages: new Set(), components: new Set(), modules: new Set(), dataFetching: new Map(), optimizations: [] },
1821
+ sveltekit: { pages: new Set(), components: new Set(), endpoints: new Set(), loadFunctions: new Map(), optimizations: [] },
1822
+ react: { hooks: new Set(), components: new Set(), optimizations: [] },
1823
+ vue: { composables: new Set(), components: new Set(), optimizations: [] },
1824
+ },
1825
+ frameworkOptimizations: [],
1826
+ packageJson: null,
1827
+ targetDir: targetDir,
1828
+ detectedFrameworks: []
802
1829
  };
803
1830
 
804
1831
  const activePkgManager = detectPackageManager(targetDir, stats);
805
1832
  const pkgPath = path.join(targetDir, 'package.json');
806
- let preExistingLicense = null;
807
- let preExistingDeps = [];
808
- let preExistingDevDeps = [];
809
- let existingPackageJson = null;
1833
+ let preExistingLicense = null, preExistingDeps = [], preExistingDevDeps = [], existingPackageJson = null;
1834
+ let detectedFrameworks = [];
810
1835
 
811
1836
  console.log(`\n${'═'.repeat(67)}`);
812
- console.log(`🚀 pkg-scaffold v2.0: Advanced Dependency Intelligence Engine`);
1837
+ console.log(`🚀 pkg-scaffold v2.2.0: Enterprise Graph Intelligence Analyzer`);
813
1838
  console.log(`${'═'.repeat(67)}\n`);
814
1839
 
815
- // --- Sub-workspace detection ---
816
1840
  const topLevelItems = fs.readdirSync(targetDir);
817
1841
  const potentialSubModules = [];
818
1842
  for (const item of topLevelItems) {
@@ -825,88 +1849,121 @@ async function main() {
825
1849
  for (const entry of subEntries) {
826
1850
  const entryPath = path.join(d, entry);
827
1851
  if (fs.statSync(entryPath).isDirectory()) {
828
- if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) examineDirectory(entryPath);
1852
+ if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) {
1853
+ examineDirectory(entryPath);
1854
+ }
829
1855
  } else if (VALID_EXTENSIONS.has(path.extname(entry))) {
830
1856
  containsSourceCode = true;
831
1857
  }
832
1858
  }
833
- } catch {}
1859
+ } catch (subDirReadError) {}
834
1860
  };
835
1861
  examineDirectory(fullPath);
836
- if (containsSourceCode) potentialSubModules.push(item);
1862
+ if (containsSourceCode) {
1863
+ potentialSubModules.push(item);
1864
+ }
837
1865
  }
838
1866
  }
839
- if (potentialSubModules.length > 1) stats.subWorkspaces = potentialSubModules;
1867
+ if (potentialSubModules.length > 1) {
1868
+ stats.subWorkspaces = potentialSubModules;
1869
+ }
840
1870
 
841
- // --- Existing package.json analysis ---
842
1871
  if (fs.existsSync(pkgPath)) {
843
1872
  console.log(`⚠️ An existing package.json was found in this working directory.`);
844
1873
  console.log(`📡 Analyzing existing installation arrays for invalid metrics...`);
845
1874
  try {
846
1875
  existingPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
1876
+ stats.packageJson = existingPackageJson;
847
1877
  if (existingPackageJson.license && typeof existingPackageJson.license === 'string' && existingPackageJson.license.toLowerCase() !== 'none') {
848
1878
  preExistingLicense = existingPackageJson.license;
849
1879
  }
850
- if (existingPackageJson.dependencies) preExistingDeps = Object.keys(existingPackageJson.dependencies);
851
- 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
+ }
1886
+
1887
+ detectedFrameworks = FrameworkEngine.detect(targetDir, existingPackageJson);
1888
+ stats.detectedFrameworks = detectedFrameworks;
852
1889
 
853
1890
  const combinedDeps = [...preExistingDeps, ...preExistingDevDeps];
854
- let brokenEcosystem = combinedDeps.length === 0;
1891
+ let brokenEcosystem = false;
855
1892
 
856
- // Check for non-existent AND deprecated packages
857
1893
  if (combinedDeps.length > 0) {
858
- console.log(` 🔍 Validating ${combinedDeps.length} declared package(s) against npm registry...`);
1894
+ console.log(` 🔍 Validating ${combinedDeps.length} declared package(s) against npm registry...`);
859
1895
  for (const dep of combinedDeps) {
860
1896
  const check = await inspectNpmPackage(dep);
1897
+
861
1898
  if (check && check.error === 'NOT_FOUND') {
862
- brokenEcosystem = true;
863
- 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
+ }
864
1905
  } else if (check && check.deprecated) {
865
1906
  stats.deprecatedPackages.set(dep, check.deprecated);
866
- console.log(` ⚠️ Deprecated package detected: "${dep}" — ${check.deprecated}`);
1907
+ console.log(` ⚠️ Deprecated package detected: "${dep}" — ${check.deprecated}`);
867
1908
  }
868
1909
  }
869
1910
  }
870
-
871
1911
  if (brokenEcosystem) {
872
- 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.`);
873
1913
  console.log(`👉 Action Required: Please remove or backup the existing 'package.json' from this folder.\n`);
874
1914
  rl.close();
875
1915
  return;
876
1916
  }
877
- } catch (err) {
1917
+ } catch (packageJsonParseRuntimeError) {
878
1918
  console.log(`\n🛑 CRITICAL: Existing package.json is malformed or corrupt.\n`);
879
1919
  rl.close();
880
1920
  return;
881
1921
  }
882
1922
  }
883
1923
 
884
- // --- Workspace scan ---
885
1924
  console.log(`\n🔬 Scanning workspace source files...`);
886
- scanWorkspace(targetDir, stats, folderName);
887
- console.log(` Scanned ${stats.scannedFiles} source file(s) | TS: ${stats.tsFiles} | JS: ${stats.jsFiles}`);
1925
+ scanWorkspace(targetDir, stats, folderName, detectedFrameworks);
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
+ }
1947
+
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);
888
1955
 
889
- // --- Binary-to-package resolution ---
890
1956
  const binariesInScripts = existingPackageJson ? getBinariesFromPackageJson(existingPackageJson) : [];
891
1957
  const resolvedBinaryPackages = new Set();
892
1958
  for (const binary of binariesInScripts) {
893
1959
  const pkgName = BINARY_TO_PACKAGE_MAP[binary] || binary;
894
1960
  resolvedBinaryPackages.add(pkgName);
895
1961
  stats.rawDeps.add(pkgName);
896
- stats.allImportedPackages.add(pkgName); // treat as "used"
1962
+ stats.allImportedPackages.add(pkgName);
897
1963
  }
898
1964
 
899
- // ============================================================
900
- // GHOST DEPENDENCY ANALYSIS
901
- // Packages imported in code but missing from package.json
902
- // ============================================================
903
1965
  if (preExistingDeps.length > 0 || preExistingDevDeps.length > 0) {
904
- stats.ghostDependencies = detectGhostDependencies(
905
- stats.allImportedPackages,
906
- preExistingDeps,
907
- preExistingDevDeps
908
- );
909
- // Remove dev tooling from ghost list (they may be globally installed)
1966
+ stats.ghostDependencies = detectGhostDependencies(stats.allImportedPackages, preExistingDeps, preExistingDevDeps);
910
1967
  for (const dep of stats.ghostDependencies) {
911
1968
  if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
912
1969
  stats.ghostDependencies.delete(dep);
@@ -914,78 +1971,54 @@ async function main() {
914
1971
  }
915
1972
  }
916
1973
 
917
- // ============================================================
918
- // ORPHANED DEPENDENCY ANALYSIS
919
- // Packages in package.json that are never imported
920
- // ============================================================
921
1974
  if (preExistingDeps.length > 0) {
922
- stats.orphanedDependencies = detectOrphanedDependencies(
923
- preExistingDeps,
924
- stats.allImportedPackages,
925
- resolvedBinaryPackages,
926
- DEV_TOOLING_ECOSYSTEM
927
- );
928
- }
929
-
930
- // ============================================================
931
- // UNUSED IMPORTS ANALYSIS (cross-file aggregation)
932
- // A package is only truly "unused" if it's never used in ANY file
933
- // ============================================================
934
- // Build a set of packages that ARE used in at least one file
935
- const usedInAtLeastOneFile = new Set();
936
- for (const [, fileImports] of stats.unusedImportsPerFile.entries()) {
937
- // If a package appears in unusedImportsPerFile for this file,
938
- // it might still be used in another file — check allImportedPackages
939
- }
940
- // Refine: unusedDepsInCode should only include packages that are
941
- // imported but never referenced across the entire codebase
942
- const trulyUnusedImports = new Set();
943
- for (const pkg of stats.unusedDepsInCode) {
944
- // If the package is used (identifier found) in ANY file, remove from unused
945
- let foundUsedElsewhere = false;
946
- for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
947
- if (!fileUnused.has(pkg)) {
948
- // This file imports pkg and DOES use it
949
- if (stats.allImportedPackages.has(pkg)) {
950
- foundUsedElsewhere = true;
951
- break;
952
- }
953
- }
954
- }
955
- if (!foundUsedElsewhere) trulyUnusedImports.add(pkg);
956
- }
957
-
958
- // ============================================================
959
- // DISPLAY: GHOST DEPENDENCIES (critical — will break at runtime)
960
- // ============================================================
1975
+ stats.orphanedDependencies = detectOrphanedDependencies(preExistingDeps, stats.allImportedPackages, resolvedBinaryPackages, DEV_TOOLING_ECOSYSTEM);
1976
+ }
1977
+
961
1978
  if (stats.ghostDependencies.size > 0) {
962
1979
  console.log(`\n${'─'.repeat(67)}`);
963
1980
  console.log(`🚨 GHOST DEPENDENCIES DETECTED (CRITICAL — Runtime/Deploy will FAIL)`);
964
1981
  console.log(`${'─'.repeat(67)}`);
965
- console.log(` These packages are USED in your code but NOT listed in package.json.`);
966
- 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`);
967
1983
  for (const pkg of stats.ghostDependencies) {
968
- 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`);
969
1985
  }
970
1986
  console.log(`${'─'.repeat(67)}`);
971
1987
  const addGhosts = await safeQuestion(`❓ Add these missing packages to package.json automatically? (Y/n): `);
972
1988
  if (addGhosts.trim().toLowerCase() !== 'n' && addGhosts.trim().toLowerCase() !== 'no') {
973
- for (const pkg of stats.ghostDependencies) stats.rawDeps.add(pkg);
974
- 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.`);
1993
+ }
1994
+ }
1995
+
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(', ')} ]`);
975
2012
  }
2013
+ console.log(`${'─'.repeat(67)}`);
976
2014
  }
977
2015
 
978
- // ============================================================
979
- // DISPLAY: ORPHANED DEPENDENCIES (in package.json, never used)
980
- // ============================================================
981
2016
  if (stats.orphanedDependencies.size > 0) {
982
2017
  console.log(`\n${'─'.repeat(67)}`);
983
2018
  console.log(`📦 ORPHANED DEPENDENCIES DETECTED (in package.json, never imported)`);
984
2019
  console.log(`${'─'.repeat(67)}`);
985
- console.log(` These packages are declared in package.json but never imported`);
986
- console.log(` anywhere in your source code. Safe to remove.\n`);
987
2020
  for (const pkg of stats.orphanedDependencies) {
988
- console.log(` 🗑️ \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
2021
+ console.log(` 🗑️ \x1b[33m"${pkg}"\x1b[0m — declared but never imported`);
989
2022
  }
990
2023
  console.log(`${'─'.repeat(67)}`);
991
2024
  const pruneOrphans = await safeQuestion(`❓ Remove these orphaned packages from package.json? (y/N): `);
@@ -995,87 +2028,23 @@ async function main() {
995
2028
  delete existingPackageJson.dependencies?.[pkg];
996
2029
  }
997
2030
  fs.writeFileSync(pkgPath, JSON.stringify(existingPackageJson, null, 2));
998
- console.log(` 🗑️ Orphaned dependencies removed from package.json.`);
2031
+ console.log(` 🗑️ Orphaned dependencies removed from package.json.`);
999
2032
  }
1000
2033
  }
1001
2034
  }
1002
2035
 
1003
- // ============================================================
1004
- // DISPLAY: UNUSED IMPORTS (imported but never referenced in code)
1005
- // ============================================================
1006
- const allDiscoveredUnused = new Set([...trulyUnusedImports]);
1007
- // Also add packages in package.json not found in code at all
1008
- if (preExistingDeps.length > 0) {
1009
- preExistingDeps.forEach(dep => {
1010
- if (!stats.rawDeps.has(dep) && !DEV_TOOLING_ECOSYSTEM.has(dep) && !dep.startsWith('@types/')) {
1011
- allDiscoveredUnused.add(dep);
1012
- }
1013
- });
1014
- }
1015
- // Remove dev tooling from unused list
1016
- for (const dep of allDiscoveredUnused) {
1017
- if (DEV_TOOLING_ECOSYSTEM.has(dep) || dep.startsWith('@types/')) {
1018
- allDiscoveredUnused.delete(dep);
1019
- }
1020
- }
1021
-
1022
- if (allDiscoveredUnused.size > 0) {
1023
- console.log(`\n${'─'.repeat(67)}`);
1024
- console.log(`⚠️ UNUSED IMPORTS DETECTED (imported but never referenced in code)`);
1025
- console.log(`${'─'.repeat(67)}`);
1026
- console.log(` These modules are imported but their identifiers are never used`);
1027
- console.log(` in executable code paths.\n`);
1028
-
1029
- for (const dep of allDiscoveredUnused) {
1030
- // Show which files have this unused import
1031
- const filesWithUnused = [];
1032
- for (const [filePath, fileUnused] of stats.unusedImportsPerFile.entries()) {
1033
- if (fileUnused.has(dep)) {
1034
- const lines = fileUnused.get(dep);
1035
- const lineStr = lines.length > 0 ? `:${lines[0]}` : '';
1036
- filesWithUnused.push(`${path.relative(targetDir, filePath)}${lineStr}`);
1037
- }
1038
- }
1039
- if (filesWithUnused.length > 0) {
1040
- console.log(` ⚡ \x1b[33m"${dep}"\x1b[0m`);
1041
- filesWithUnused.forEach(f => console.log(` └─ ${f}`));
1042
- } else {
1043
- console.log(` ⚡ \x1b[33m"${dep}"\x1b[0m`);
1044
- }
1045
- }
1046
- console.log(`${'─'.repeat(67)}`);
1047
-
1048
- const pruneChoice = await safeQuestion(`❓ Exclude these unused imports from your package.json setup? (y/N): `);
1049
- if (pruneChoice.trim().toLowerCase() === 'y' || pruneChoice.trim().toLowerCase() === 'yes') {
1050
- for (const deadDep of allDiscoveredUnused) stats.rawDeps.delete(deadDep);
1051
- console.log(` 🗑️ Pruned unused imports from configuration blueprint.`);
1052
- }
1053
- }
1054
-
1055
- // ============================================================
1056
- // DISPLAY: DEPRECATED PACKAGES
1057
- // ============================================================
1058
2036
  if (stats.deprecatedPackages.size > 0) {
1059
2037
  console.log(`\n${'─'.repeat(67)}`);
1060
2038
  console.log(`⚠️ DEPRECATED PACKAGES DETECTED`);
1061
2039
  console.log(`${'─'.repeat(67)}`);
1062
2040
  for (const [pkg, msg] of stats.deprecatedPackages.entries()) {
1063
- 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}`);
1064
2043
  }
1065
2044
  console.log(`${'─'.repeat(67)}`);
1066
2045
  }
1067
2046
 
1068
- // ============================================================
1069
- // PHANTOM INJECTION DETECTION
1070
- // Packages used in code (by identifier) but never imported
1071
- // ============================================================
1072
- // Build phantom detection from ALL declared packages
1073
2047
  const allDeclaredForPhantom = new Set([...preExistingDeps, ...preExistingDevDeps]);
1074
- for (const [filePath] of stats.unusedImportsPerFile.entries()) {
1075
- // Already handled above
1076
- }
1077
-
1078
- // Scan for identifiers used without import (using declared package names as hints)
1079
2048
  const phantomScanContent = new Map();
1080
2049
  function collectExecutionContent(dir) {
1081
2050
  try {
@@ -1087,27 +2056,26 @@ async function main() {
1087
2056
  } else if (VALID_EXTENSIONS.has(path.extname(file))) {
1088
2057
  try {
1089
2058
  const content = readFileSyncNormalized(fullPath);
1090
- const execCode = content.split(/\r?\n/)
1091
- .filter(l => {
1092
- const t = l.trim();
1093
- return !t.startsWith('import ') && !/\brequire\s*\(/.test(t);
1094
- })
1095
- .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');
1096
2063
  phantomScanContent.set(fullPath, execCode);
1097
- } catch {}
2064
+ } catch (readExecContentError) {}
1098
2065
  }
1099
2066
  }
1100
- } catch {}
2067
+ } catch (fsCollectExecError) {}
1101
2068
  }
1102
2069
  collectExecutionContent(targetDir);
1103
2070
 
1104
2071
  for (const [filePath, execCode] of phantomScanContent.entries()) {
1105
2072
  for (const token of allDeclaredForPhantom) {
1106
2073
  const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1107
- const tokenPattern = new RegExp(`\\b${escaped}\\b`);
1108
- if (tokenPattern.test(execCode) && !stats.allImportedPackages.has(token)) {
2074
+ if (new RegExp(`\\b${escaped}\\b`).test(execCode) && !stats.allImportedPackages.has(token)) {
1109
2075
  stats.rawDeps.add(token);
1110
- 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
+ }
1111
2079
  stats.phantomInjections.get(filePath).add(token);
1112
2080
  }
1113
2081
  }
@@ -1116,7 +2084,6 @@ async function main() {
1116
2084
  const isTypeScript = stats.tsFiles > stats.jsFiles;
1117
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');
1118
2086
 
1119
- // --- dotenv suggestion ---
1120
2087
  if (stats.envVars.size > 0 && !stats.rawDeps.has('dotenv') && !isFrontendWeb) {
1121
2088
  console.log(`\n📡 CONFIGURATION COMPLIANCE GAP: UNMANAGED ENVIRONMENT VARIABLES`);
1122
2089
  console.log(`${'─'.repeat(67)}`);
@@ -1129,7 +2096,6 @@ async function main() {
1129
2096
  }
1130
2097
  }
1131
2098
 
1132
- // --- Build package.json ---
1133
2099
  const packageJson = {
1134
2100
  name: folderName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'),
1135
2101
  version: '1.0.0',
@@ -1138,11 +2104,10 @@ async function main() {
1138
2104
  author: gitInfo.author || undefined,
1139
2105
  repository: gitInfo.repository ? { type: "git", url: `git+${gitInfo.repository}.git` } : undefined,
1140
2106
  scripts: { test: stats.hasTests ? (isFrontendWeb ? 'vitest' : 'jest') : 'echo "No workspace test vectors specified" && exit 0' },
1141
- dependencies: {},
2107
+ dependencies: {},
1142
2108
  devDependencies: {}
1143
2109
  };
1144
2110
 
1145
- // --- ESLint suggestion ---
1146
2111
  const eslintConfigFile = path.join(targetDir, 'eslint.config.js');
1147
2112
  const linterPresent = fs.existsSync(eslintConfigFile) || fs.existsSync(path.join(targetDir, '.eslintrc.json')) || fs.existsSync(path.join(targetDir, '.eslintrc.js'));
1148
2113
 
@@ -1155,8 +2120,11 @@ async function main() {
1155
2120
  if (choiceLintSetup.trim().toLowerCase() !== 'n' && choiceLintSetup.trim().toLowerCase() !== 'no') {
1156
2121
  stats.bootstrapEslintSuite = true;
1157
2122
  stats.rawDeps.add('eslint');
1158
- if (isTypeScript) stats.rawDeps.add('typescript-eslint');
1159
- else stats.rawDeps.add('@eslint/js');
2123
+ if (isTypeScript) {
2124
+ stats.rawDeps.add('typescript-eslint');
2125
+ } else {
2126
+ stats.rawDeps.add('@eslint/js');
2127
+ }
1160
2128
  }
1161
2129
  }
1162
2130
 
@@ -1165,7 +2133,9 @@ async function main() {
1165
2133
  packageJson.scripts.build = 'vite build';
1166
2134
  packageJson.scripts.preview = 'vite preview';
1167
2135
  stats.rawDeps.add('vite');
1168
- if (stats.hasTests) stats.rawDeps.add('vitest');
2136
+ if (stats.hasTests) {
2137
+ stats.rawDeps.add('vitest');
2138
+ }
1169
2139
  } else {
1170
2140
  if (isTypeScript) {
1171
2141
  packageJson.scripts.build = 'tsc';
@@ -1178,10 +2148,11 @@ async function main() {
1178
2148
 
1179
2149
  if (isTypeScript) {
1180
2150
  packageJson.devDependencies.typescript = '^5.4.0';
1181
- if (!isFrontendWeb) packageJson.devDependencies['@types/node'] = '^20.11.0';
2151
+ if (!isFrontendWeb) {
2152
+ packageJson.devDependencies['@types/node'] = '^20.11.0';
2153
+ }
1182
2154
  }
1183
2155
 
1184
- // --- Resolve package versions from npm ---
1185
2156
  if (stats.rawDeps.size > 0) {
1186
2157
  console.log(`\n📡 Resolving baseline package registry definitions...`);
1187
2158
  for (const pkg of stats.rawDeps) {
@@ -1190,47 +2161,44 @@ async function main() {
1190
2161
  const check = await inspectNpmPackage(cleaned);
1191
2162
  if (check && check.error !== 'NOT_FOUND') {
1192
2163
  const version = check.version || 'latest';
1193
-
1194
- const isDevDep = [
1195
- 'vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint',
1196
- '@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup',
1197
- 'esbuild', '@swc/cli', 'tsx', 'rimraf', 'copyfiles', 'mkdirp',
1198
- 'husky', 'lint-staged', '@commitlint/cli', 'typedoc', 'c8', 'nyc',
1199
- 'mocha', 'ava', 'tap', 'jasmine', 'storybook', 'turbo', 'nx',
1200
- 'biome', '@biomejs/biome', 'oxlint', 'xo', 'standard',
1201
- ].includes(cleaned) || cleaned.startsWith('@types/');
1202
-
1203
- if (isDevDep) packageJson.devDependencies[cleaned] = `^${version}`;
1204
- 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
+ }
1205
2170
  console.log(` ✔ Synced: ${cleaned}@^${version}${check.deprecated ? ' \x1b[33m[DEPRECATED]\x1b[0m' : ''}`);
1206
2171
  }
1207
2172
  }
1208
2173
  }
1209
2174
  }
1210
2175
 
1211
- // --- Phantom injection report ---
1212
2176
  if (stats.phantomInjections.size > 0) {
1213
2177
  console.log(`\n${'─'.repeat(67)}`);
1214
2178
  console.log(`👻 PHANTOM STRUCTURE ALERT: UNIMPORTED EXECUTIONS DETECTED`);
1215
2179
  console.log(`${'─'.repeat(67)}`);
1216
2180
  for (const [filePath, missingModules] of stats.phantomInjections.entries()) {
1217
2181
  console.log(`📂 File: ${path.relative(targetDir, filePath)}`);
1218
- 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(', ')}`);
1219
2183
  }
1220
2184
  console.log(`${'─'.repeat(67)}`);
1221
2185
  }
1222
2186
 
1223
- // --- Code quality warnings ---
1224
2187
  if (stats.quality.varCount > 0 || stats.quality.hasEval || stats.quality.syncFsCount > 0) {
1225
2188
  console.log(`\n⚠️ CODE ARCHITECTURE & MODERNIZATION COMPLIANCE WARNINGS:`);
1226
2189
  console.log(`${'─'.repeat(67)}`);
1227
- if (stats.quality.varCount > 0) console.log(` ⚡ Found ${stats.quality.varCount} instances of legacy 'var'. Transition to 'let' / 'const'.`);
1228
- if (stats.quality.hasEval) console.log(` 🔥 DANGER: 'eval()' detected! Refactor to mitigate remote code execution vectors.`);
1229
- 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
+ }
1230
2199
  console.log(`${'─'.repeat(67)}`);
1231
2200
  }
1232
2201
 
1233
- // --- Security: hardcoded secrets ---
1234
2202
  if (stats.discoveredSecrets.length > 0) {
1235
2203
  console.log(`\n🚨 CRITICAL SECURITY COMPLIANCE ALERT: HARDCODED CREDENTIALS DETECTED`);
1236
2204
  console.log(`${'─'.repeat(67)}`);
@@ -1248,25 +2216,25 @@ async function main() {
1248
2216
  for (const secretMeta of stats.discoveredSecrets) {
1249
2217
  let currentCodeContent = readFileSyncNormalized(secretMeta.filePath);
1250
2218
  const envAccessor = isFrontendWeb ? `import.meta.env.${secretMeta.envVarName}` : `process.env.${secretMeta.envVarName}`;
1251
- 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');
1252
2220
  currentCodeContent = currentCodeContent.replace(exactLiteralPattern, `${secretMeta.keyName} = ${envAccessor}`);
1253
2221
  fs.writeFileSync(secretMeta.filePath, currentCodeContent);
1254
- 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
+ }
1255
2225
  console.log(` 🔒 Isolated: ${secretMeta.keyName} → ${envAccessor}`);
1256
2226
  }
1257
2227
  fs.writeFileSync(envPath, envBuffer);
1258
2228
  }
1259
2229
  }
1260
2230
 
1261
- // --- Monorepo detection ---
1262
2231
  if (stats.subWorkspaces && stats.subWorkspaces.length > 1) {
1263
2232
  console.log(`\n📂 MULTI-WORKSPACE SEGMENTATION DETECTED`);
1264
- 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(', ')}`);
1265
2234
  const setupWorkspace = await safeQuestion(`❓ Setup as a multi-package Monorepo Workspace layout? (y/N): `);
1266
2235
  if (setupWorkspace.trim().toLowerCase() === 'y' || setupWorkspace.trim().toLowerCase() === 'yes') {
1267
2236
  if (activePkgManager === 'pnpm') {
1268
- const workspaceYamlPath = path.join(targetDir, 'pnpm-workspace.yaml');
1269
- 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`);
1270
2238
  console.log(` 🏗️ Generated: pnpm-workspace.yaml`);
1271
2239
  } else {
1272
2240
  packageJson.workspaces = stats.subWorkspaces;
@@ -1275,7 +2243,6 @@ async function main() {
1275
2243
  }
1276
2244
  }
1277
2245
 
1278
- // --- License ---
1279
2246
  const licensePath = path.join(targetDir, 'LICENSE');
1280
2247
  let chosenLicenseType = preExistingLicense || 'None';
1281
2248
 
@@ -1287,14 +2254,11 @@ async function main() {
1287
2254
  console.log(` 📡 Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}"...`);
1288
2255
  const rawTemplate = await fetchRemoteLicense(cleanedInput);
1289
2256
  if (rawTemplate) {
1290
- const parsedText = rawTemplate
1291
- .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
1292
- .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);
1293
2258
  fs.writeFileSync(licensePath, parsedText);
1294
2259
  chosenLicenseType = cleanedInput.toUpperCase();
1295
2260
  console.log(` ⚖️ Provisioned: LICENSE`);
1296
2261
  } else {
1297
- console.log(` ⚠️ License "${cleanedInput}" not found. Saving custom label.`);
1298
2262
  chosenLicenseType = cleanedInput;
1299
2263
  }
1300
2264
  packageJson.license = chosenLicenseType;
@@ -1305,24 +2269,25 @@ async function main() {
1305
2269
  if (!fs.existsSync(licensePath) && ['mit', 'apache-2.0', 'gpl-3.0'].includes(preExistingLicense.toLowerCase())) {
1306
2270
  const rawTemplate = await fetchRemoteLicense(preExistingLicense);
1307
2271
  if (rawTemplate) {
1308
- const parsedText = rawTemplate
1309
- .replace(/\[year\]|<year>/gi, new Date().getFullYear().toString())
1310
- .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);
1311
2273
  fs.writeFileSync(licensePath, parsedText);
1312
2274
  }
1313
2275
  }
1314
2276
  } else if (fs.existsSync(licensePath)) {
1315
2277
  try {
1316
2278
  const currentLicenseContent = fs.readFileSync(licensePath, 'utf8');
1317
- if (currentLicenseContent.includes('MIT')) chosenLicenseType = 'MIT';
1318
- else if (currentLicenseContent.includes('Apache')) chosenLicenseType = 'Apache-2.0';
1319
- else chosenLicenseType = 'Custom';
1320
- } 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) {}
1321
2287
  }
1322
2288
  packageJson.license = chosenLicenseType;
1323
2289
  }
1324
2290
 
1325
- // --- Test scaffolding ---
1326
2291
  if (!stats.hasTests) {
1327
2292
  const bootstrapTest = await safeQuestion(`\n❓ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
1328
2293
  if (bootstrapTest.trim().toLowerCase() === 'y' || bootstrapTest.trim().toLowerCase() === 'yes') {
@@ -1335,13 +2300,12 @@ async function main() {
1335
2300
  fs.writeFileSync(testFilePath, testTemplate);
1336
2301
  packageJson.scripts.test = 'node --test';
1337
2302
  stats.hasTests = true;
1338
- console.log(` 🧪 Generated: index${testExt}`);
2303
+ console.log(` 🧪 Generated: index${testExt}`);
1339
2304
  }
1340
2305
  }
1341
2306
 
1342
2307
  console.log(`\n⚙️ Writing ecosystem configuration artifacts...`);
1343
2308
 
1344
- // --- ESLint config ---
1345
2309
  if (stats.bootstrapEslintSuite) {
1346
2310
  packageJson.scripts.lint = 'eslint .';
1347
2311
  let eslintConfigContent = '';
@@ -1355,10 +2319,9 @@ async function main() {
1355
2319
  }
1356
2320
  }
1357
2321
  fs.writeFileSync(eslintConfigFile, eslintConfigContent);
1358
- console.log(` 🎨 Provisioned: eslint.config.js`);
2322
+ console.log(` 🎨 Provisioned: eslint.config.js`);
1359
2323
  }
1360
2324
 
1361
- // --- Write / merge package.json ---
1362
2325
  if (fs.existsSync(pkgPath)) {
1363
2326
  try {
1364
2327
  const currentPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
@@ -1369,40 +2332,36 @@ async function main() {
1369
2332
  currentPackageJson.scripts.lint = packageJson.scripts.lint;
1370
2333
  }
1371
2334
  fs.writeFileSync(pkgPath, JSON.stringify(currentPackageJson, null, 2));
1372
- console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
1373
- } catch (e) {}
2335
+ console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
2336
+ } catch (mergePackageJsonError) {}
1374
2337
  } else {
1375
2338
  fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
1376
- console.log(` 📝 Generated: package.json`);
2339
+ console.log(` 📝 Generated: package.json`);
1377
2340
  }
1378
2341
 
1379
- // --- Prettier config ---
1380
2342
  const prettierPath = path.join(targetDir, '.prettierrc');
1381
2343
  if (!fs.existsSync(prettierPath)) {
1382
2344
  const useTabs = stats.style.tabCount > (stats.style.space2Count + stats.style.space4Count);
1383
2345
  const useSemi = stats.style.semiCount >= stats.style.noSemiCount;
1384
2346
  const tabWidth = stats.style.space4Count > stats.style.space2Count ? 4 : 2;
1385
2347
  fs.writeFileSync(prettierPath, JSON.stringify({ semi: useSemi, useTabs, tabWidth, singleQuote: true, trailingComma: "es5" }, null, 2));
1386
- console.log(` 🎨 Code formatting mirror locked: .prettierrc`);
2348
+ console.log(` 🎨 Code formatting mirror locked: .prettierrc`);
1387
2349
  }
1388
2350
 
1389
- // --- .env.example ---
1390
2351
  if (stats.envVars.size > 0) {
1391
2352
  const envExamplePath = path.join(targetDir, '.env.example');
1392
2353
  if (!fs.existsSync(envExamplePath)) {
1393
- fs.writeFileSync(envExamplePath, Array.from(stats.envVars).map(v => `${v}=`).join('\n') + '\n');
1394
- 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`);
1395
2356
  }
1396
2357
  }
1397
2358
 
1398
- // --- .gitignore ---
1399
2359
  const gitignorePath = path.join(targetDir, '.gitignore');
1400
2360
  if (!fs.existsSync(gitignorePath)) {
1401
2361
  fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n*.log\n`);
1402
- console.log(` ⚙️ Generated: .gitignore`);
2362
+ console.log(` ⚙️ Generated: .gitignore`);
1403
2363
  }
1404
2364
 
1405
- // --- tsconfig.json ---
1406
2365
  if (isTypeScript) {
1407
2366
  const tsconfigPath = path.join(targetDir, 'tsconfig.json');
1408
2367
  if (!fs.existsSync(tsconfigPath)) {
@@ -1410,61 +2369,29 @@ async function main() {
1410
2369
  compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" },
1411
2370
  include: ["src/**/*", "**/*.ts"]
1412
2371
  }, null, 2));
1413
- console.log(` ⚙️ Generated: tsconfig.json`);
2372
+ console.log(` ⚙️ Generated: tsconfig.json`);
1414
2373
  }
1415
2374
  }
1416
2375
 
1417
- // --- README ---
1418
2376
  const readmePath = path.join(targetDir, 'README.md');
1419
2377
  if (!fs.existsSync(readmePath)) {
1420
2378
  const pName = packageJson.name;
1421
2379
  const layoutTree = buildAsciiTree(targetDir).join('\n');
1422
- const displayDeps = Object.keys(packageJson.dependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
1423
- 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';
1424
2382
  const licenseBadgeParam = encodeURIComponent(chosenLicenseType.replace(/-/g, '_'));
1425
2383
 
1426
- const documentationTemplate =
1427
- `# ${pName}
1428
-
1429
- ![Workspace Engine](https://img.shields.io/badge/engine-node-${packageJson.type === 'module' ? 'green' : 'blue'}?style=flat)
1430
- ![License Architecture](https://img.shields.io/badge/license-${licenseBadgeParam}-orange?style=flat)
1431
- ![Development Tooling](https://img.shields.io/badge/compiled_via-${isTypeScript ? 'typescript' : 'javascript'}-blueviolet?style=flat)
1432
-
1433
- ${packageJson.description}
1434
-
1435
- ## Workspace Dependency Landscapes
1436
-
1437
- ### Core Infrastructure Runtimes (\`dependencies\`)
1438
- ${displayDeps}
1439
-
1440
- ### System Tooling Engines (\`devDependencies\`)
1441
- ${displayDevDeps}
1442
-
1443
- ---
1444
-
1445
- ## Project Architecture Layout
1446
- \`\`\`text
1447
- ${layoutTree}
1448
- \`\`\`
1449
-
1450
- ## Installation
1451
-
1452
- \`\`\`bash
1453
- ${activePkgManager} install
1454
- \`\`\`
1455
- `;
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`;
1456
2385
  fs.writeFileSync(readmePath, documentationTemplate);
1457
- console.log(` 📖 Generated: README.md`);
2386
+ console.log(` 📖 Generated: README.md`);
1458
2387
  }
1459
2388
 
1460
- // --- Phantom injection fix ---
1461
2389
  if (stats.phantomInjections.size > 0 || (stats.injectDotenvEngine && stats.filesWithEnvVars.size > 0)) {
1462
2390
  console.log(`\n💡 Source Code Modification Subsystem:`);
1463
2391
  const injectChoice = await safeQuestion(`❓ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
1464
2392
 
1465
2393
  if (injectChoice.trim().toLowerCase() === 'y' || injectChoice.trim().toLowerCase() === 'yes') {
1466
2394
  const allTargets = new Set([...stats.phantomInjections.keys(), ...stats.filesWithEnvVars]);
1467
-
1468
2395
  for (const filePath of allTargets) {
1469
2396
  const originalCode = readFileSyncNormalized(filePath);
1470
2397
  let declarationBlock = '';
@@ -1472,39 +2399,39 @@ ${activePkgManager} install
1472
2399
  const missingModules = stats.phantomInjections.get(filePath);
1473
2400
  if (missingModules) {
1474
2401
  for (const mod of missingModules) {
1475
- if (packageJson.type === 'module') declarationBlock += `import ${mod} from '${mod}';\n`;
1476
- 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
+ }
1477
2407
  }
1478
2408
  }
1479
-
1480
2409
  if (stats.injectDotenvEngine && stats.filesWithEnvVars.has(filePath) && !originalCode.includes('dotenv')) {
1481
- if (packageJson.type === 'module') declarationBlock += `import 'dotenv/config';\n`;
1482
- 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
+ }
1483
2415
  }
1484
-
1485
2416
  if (declarationBlock !== '') {
1486
2417
  fs.writeFileSync(filePath, smartPrepend(originalCode, declarationBlock));
1487
- console.log(` ⚡ Injected headers: ${path.relative(targetDir, filePath)}`);
2418
+ console.log(` ⚡ Injected headers: ${path.relative(targetDir, filePath)}`);
1488
2419
  }
1489
2420
  }
1490
2421
  }
1491
2422
  }
1492
2423
 
1493
- // --- Deprecation scan via npm-deprecated-check ---
1494
2424
  console.log(`\n🛑 INITIALIZING LIVE ECOSYSTEM DEPRECATION SECURITY SCAN...`);
1495
- console.log(` Running integrated npm-deprecated-check validation:\n`);
2425
+ console.log(` Running integrated npm-deprecated-check validation:\n`);
1496
2426
  try {
1497
2427
  const localRequire = createRequire(import.meta.url);
1498
2428
  const dependencyPkgJsonPath = localRequire.resolve('npm-deprecated-check/package.json');
1499
2429
  const dependencyPkgJson = JSON.parse(fs.readFileSync(dependencyPkgJsonPath, 'utf8'));
1500
- const binRelativeMapping = typeof dependencyPkgJson.bin === 'string'
1501
- ? dependencyPkgJson.bin
1502
- : (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']);
1503
2431
  const absoluteExecutablePath = path.join(path.dirname(dependencyPkgJsonPath), binRelativeMapping);
1504
2432
  execSync(`node "${absoluteExecutablePath}" current`, { stdio: 'inherit', cwd: targetDir });
1505
- } catch (err) {}
2433
+ } catch (deprecationBinaryRunError) {}
1506
2434
 
1507
- // --- Conflicting lockfiles ---
1508
2435
  if (stats.conflictingLockfiles.length > 1) {
1509
2436
  console.log(`\n⚠️ CONFLICTING LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
1510
2437
  const cleanLocks = await safeQuestion(`❓ Purge legacy/mismatched lockfiles to protect package integrity? (y/N): `);
@@ -1515,36 +2442,88 @@ ${activePkgManager} install
1515
2442
  if (lockfile !== operationalLockfile) {
1516
2443
  try {
1517
2444
  fs.unlinkSync(path.join(targetDir, lockfile));
1518
- console.log(` 🗑️ Cleaned: ${lockfile}`);
1519
- } catch (e) {}
2445
+ console.log(` 🗑 Cleaned: ${lockfile}`);
2446
+ } catch (lockFileUnlinkError) {}
1520
2447
  }
1521
2448
  }
1522
2449
  }
1523
2450
  }
1524
2451
 
1525
- // --- Final install prompt ---
1526
2452
  console.log(`\n📦 Auto-scaffolding pipeline complete!`);
1527
2453
 
1528
- // Summary report
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
+
1529
2463
  console.log(`\n${'═'.repeat(67)}`);
1530
2464
  console.log(`📊 DEPENDENCY INTELLIGENCE SUMMARY`);
1531
2465
  console.log(`${'═'.repeat(67)}`);
1532
- console.log(` 📁 Files scanned: ${stats.scannedFiles}`);
1533
- console.log(` 📦 Packages imported: ${stats.allImportedPackages.size}`);
1534
- if (stats.ghostDependencies.size > 0)
1535
- console.log(` 🚨 Ghost deps (missing): ${stats.ghostDependencies.size} — \x1b[31mCRITICAL\x1b[0m`);
1536
- if (stats.orphanedDependencies.size > 0)
1537
- console.log(` 🗑️ Orphaned deps (unused): ${stats.orphanedDependencies.size}`);
1538
- if (allDiscoveredUnused.size > 0)
1539
- console.log(` ⚡ Unused imports: ${allDiscoveredUnused.size}`);
1540
- if (stats.deprecatedPackages.size > 0)
1541
- console.log(` 📛 Deprecated packages: ${stats.deprecatedPackages.size}`);
1542
- if (stats.phantomInjections.size > 0)
1543
- console.log(` 👻 Phantom injections: ${stats.phantomInjections.size} file(s)`);
1544
- if (stats.discoveredSecrets.length > 0)
1545
- console.log(` 🔐 Hardcoded secrets: ${stats.discoveredSecrets.length} — \x1b[31mSECURITY RISK\x1b[0m`);
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
+ }
2474
+ if (stats.unusedExportsPerFile.size > 0) {
2475
+ console.log(` 📤 Unused public symbols: ${Array.from(stats.unusedExportsPerFile.values()).reduce((acc, val) => { return acc + val.size; }, 0)} tokens`);
2476
+ }
2477
+ if (stats.unusedFiles.size > 0) {
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
+ }
1546
2507
  console.log(`${'═'.repeat(67)}`);
1547
2508
 
2509
+ const templateManager = new TemplateManager(targetDir, safeQuestion);
2510
+ const availableTemplates = await templateManager.listAvailableTemplates();
2511
+
2512
+ if (availableTemplates.length > 0) {
2513
+ console.log(`\n🧩 \x1b[1mCustom Templating Engine Detected:\x1b[0m`);
2514
+ console.log(` Available templates: ${availableTemplates.join(", ")}`);
2515
+ const useTemplate = await safeQuestion(`❓ Do you want to generate code from a template? (y/N): `);
2516
+ if (useTemplate.toLowerCase() === 'y') {
2517
+ const chosenTemplate = await safeQuestion(`❓ Enter template name: `);
2518
+ if (availableTemplates.includes(chosenTemplate)) {
2519
+ const templateVars = await templateManager.promptForVariables(chosenTemplate);
2520
+ await templateManager.generate(chosenTemplate, templateVars);
2521
+ } else {
2522
+ console.log(` ⚠️ Template '${chosenTemplate}' not found.`);
2523
+ }
2524
+ }
2525
+ }
2526
+
1548
2527
  const userPromptChoice = await safeQuestion(`❓ Detected package manager: "${activePkgManager}". Run "${activePkgManager} install" now? (y/N): `);
1549
2528
  rl.close();
1550
2529
 
@@ -1554,7 +2533,7 @@ ${activePkgManager} install
1554
2533
  try {
1555
2534
  execSync(`${activePkgManager} install`, { stdio: 'inherit', cwd: targetDir });
1556
2535
  console.log(`\n🎉 Project fully mapped, configured, and installed successfully!`);
1557
- } catch (err) {
2536
+ } catch (installProcessRuntimeError) {
1558
2537
  console.error(`\n❌ Installation returned an issue. Please run "${activePkgManager} install" manually.`);
1559
2538
  }
1560
2539
  } else {
@@ -1562,4 +2541,4 @@ ${activePkgManager} install
1562
2541
  }
1563
2542
  }
1564
2543
 
1565
- main();
2544
+ main();