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.
- package/index.js +1556 -577
- 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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
'
|
|
124
|
-
|
|
125
|
-
'
|
|
126
|
-
|
|
127
|
-
'
|
|
128
|
-
|
|
129
|
-
'
|
|
130
|
-
|
|
131
|
-
'
|
|
132
|
-
|
|
133
|
-
'
|
|
134
|
-
|
|
135
|
-
'
|
|
136
|
-
|
|
137
|
-
'
|
|
138
|
-
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
'
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 = {
|
|
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
|
-
|
|
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 (
|
|
231
|
-
|
|
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
|
-
|
|
238
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
239
|
-
|
|
240
|
-
|
|
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')))
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
if (detectedLockfiles.includes('
|
|
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 {
|
|
252
|
-
|
|
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('*'))
|
|
548
|
+
if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
261
551
|
|
|
262
|
-
if (trimmed.endsWith(';'))
|
|
263
|
-
|
|
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'))
|
|
266
|
-
|
|
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)
|
|
269
|
-
|
|
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))
|
|
274
|
-
|
|
275
|
-
|
|
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))
|
|
296
|
-
|
|
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 {
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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 (
|
|
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)
|
|
353
|
-
|
|
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 (
|
|
984
|
+
} catch (asciiTreeTreeError) {}
|
|
375
985
|
return results;
|
|
376
986
|
}
|
|
377
987
|
|
|
378
988
|
// ============================================================
|
|
379
|
-
//
|
|
380
|
-
// type-only imports, re-exports, and dynamic imports
|
|
989
|
+
// STRUKTURELLES IMPLEMENTIERUNGSHELFER SYSTEM
|
|
381
990
|
// ============================================================
|
|
382
|
-
|
|
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
|
|
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))
|
|
389
|
-
|
|
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))
|
|
419
|
-
|
|
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')
|
|
428
|
-
|
|
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))
|
|
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))
|
|
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))
|
|
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
|
-
|
|
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
|
|
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))
|
|
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))
|
|
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
|
|
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))
|
|
1244
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1245
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1246
|
+
}
|
|
500
1247
|
importedIdentifiers.get(pkg).add(id);
|
|
501
|
-
if (!importedLocations.has(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
|
|
1268
|
+
const importSource = esmNamedMatch[2];
|
|
1269
|
+
const pkg = cleanPackageName(importSource);
|
|
511
1270
|
if (pkg && !builtinModules.includes(pkg)) {
|
|
512
|
-
if (!importedIdentifiers.has(pkg))
|
|
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)
|
|
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))
|
|
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
|
|
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))
|
|
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))
|
|
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
|
-
|
|
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[
|
|
546
|
-
const
|
|
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))
|
|
1343
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1344
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1345
|
+
}
|
|
550
1346
|
importedIdentifiers.get(pkg).add(id);
|
|
551
|
-
if (!importedLocations.has(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
|
-
|
|
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
|
|
1367
|
+
const importSource = cjsDestructMatch[3];
|
|
1368
|
+
const pkg = cleanPackageName(importSource);
|
|
561
1369
|
if (pkg && !builtinModules.includes(pkg)) {
|
|
562
|
-
if (!importedIdentifiers.has(pkg))
|
|
1370
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1371
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1372
|
+
}
|
|
563
1373
|
fileRawDeps.add(pkg);
|
|
564
|
-
cjsDestructMatch[
|
|
1374
|
+
cjsDestructMatch[2].split(',').forEach(part => {
|
|
565
1375
|
const chunk = part.trim();
|
|
566
|
-
if (!chunk)
|
|
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
|
-
|
|
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
|
|
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))
|
|
1415
|
+
if (!importedIdentifiers.has(pkg)) {
|
|
1416
|
+
importedIdentifiers.set(pkg, new Set());
|
|
1417
|
+
}
|
|
583
1418
|
importedIdentifiers.get(pkg).add('__DYNAMIC__');
|
|
584
|
-
if (!importedLocations.has(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
|
-
|
|
593
|
-
|
|
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 (
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
736
|
-
extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations);
|
|
1582
|
+
extractImportsFromText(codeLines, fileRawDeps, importedIdentifiers, importedLocations, stats, fullPath);
|
|
737
1583
|
}
|
|
738
1584
|
|
|
739
|
-
|
|
740
|
-
fileRawDeps.forEach(dep => stats.
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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)
|
|
778
|
-
|
|
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,
|
|
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: {
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
|
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:
|
|
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('.'))
|
|
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)
|
|
1862
|
+
if (containsSourceCode) {
|
|
1863
|
+
potentialSubModules.push(item);
|
|
1864
|
+
}
|
|
837
1865
|
}
|
|
838
1866
|
}
|
|
839
|
-
if (potentialSubModules.length > 1)
|
|
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)
|
|
851
|
-
|
|
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 =
|
|
1891
|
+
let brokenEcosystem = false;
|
|
855
1892
|
|
|
856
|
-
// Check for non-existent AND deprecated packages
|
|
857
1893
|
if (combinedDeps.length > 0) {
|
|
858
|
-
console.log(`
|
|
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
|
-
|
|
863
|
-
|
|
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(`
|
|
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
|
|
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 (
|
|
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(`
|
|
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);
|
|
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
|
-
|
|
924
|
-
|
|
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(`
|
|
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(`
|
|
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)
|
|
974
|
-
|
|
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(`
|
|
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(`
|
|
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
|
-
|
|
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
|
-
.
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
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))
|
|
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)
|
|
1159
|
-
|
|
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)
|
|
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)
|
|
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
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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}"
|
|
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)
|
|
1228
|
-
|
|
1229
|
-
|
|
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*['"
|
|
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}=`))
|
|
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}
|
|
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
|
-
|
|
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'))
|
|
1318
|
-
|
|
1319
|
-
else
|
|
1320
|
-
|
|
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(`
|
|
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(`
|
|
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(`
|
|
1373
|
-
} catch (
|
|
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(`
|
|
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(`
|
|
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}
|
|
1394
|
-
console.log(`
|
|
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(`
|
|
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(`
|
|
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}
|
|
1423
|
-
const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => `* \`${d}
|
|
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
|
-

|
|
1430
|
-

|
|
1431
|
-

|
|
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\n\n\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(`
|
|
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')
|
|
1476
|
-
|
|
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')
|
|
1482
|
-
|
|
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(`
|
|
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(`
|
|
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 (
|
|
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(`
|
|
1519
|
-
} catch (
|
|
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
|
-
|
|
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(`
|
|
1533
|
-
console.log(`
|
|
1534
|
-
if (stats.ghostDependencies.size > 0)
|
|
1535
|
-
console.log(`
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
if (stats.
|
|
1541
|
-
console.log(`
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
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 (
|
|
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();
|