pkg-scaffold 2.1.0 → 2.3.0

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