pkg-scaffold 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +523 -141
- package/package.json +3 -1
package/index.js
CHANGED
|
@@ -6,47 +6,55 @@ import { builtinModules, createRequire } from 'module';
|
|
|
6
6
|
import { execSync } from 'child_process';
|
|
7
7
|
import readline from 'readline/promises';
|
|
8
8
|
|
|
9
|
+
// --- Bulletproof AST Infrastructure Engines ---
|
|
10
|
+
import * as acorn from 'acorn';
|
|
11
|
+
import * as walk from 'acorn-walk';
|
|
12
|
+
|
|
9
13
|
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', 'coverage', 'out']);
|
|
10
14
|
const VALID_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs']);
|
|
11
15
|
|
|
12
|
-
// ---
|
|
16
|
+
// --- Refined Target Signature Dictionaries ---
|
|
13
17
|
const REGEX_PATTERNS = {
|
|
14
|
-
imports: /import\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
15
|
-
cjs: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
16
|
-
dynamic: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
17
|
-
exports: /export\s+(?:[\w\s{},*]*\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
18
18
|
env: /(?:process\.env|import\.meta\.env)\.([A-Z_][A-Z0-9_]*)/g,
|
|
19
19
|
testFile: /\.(test|spec)\.(js|ts|jsx|tsx)$/i,
|
|
20
20
|
|
|
21
|
-
// Quality & Code Smell
|
|
21
|
+
// Modern Quality & Structural Code Smell Monitors
|
|
22
22
|
legacyVar: /\bvar\s+[a-zA-Z_]/g,
|
|
23
23
|
dangerousEval: /\beval\s*\(/g,
|
|
24
24
|
syncFsCalls: /\.readFileSync|\.writeFileSync|\.mkdirSync|\.existsSync/g,
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
// Cryptographic Risk & Hardcoded Keyholes
|
|
27
|
+
secretKeys: /\b(secret|passwd|password|token|api_?key|private_?key)\s*=\s*['"`]([a-zA-Z0-9_\-\.]{8,})['"`]/gi
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
const COMMON_EXTERNAL_TOKENS = new Set(['axios', 'lodash', 'dotenv', 'cors', 'zod', 'mongoose', 'jsonwebtoken', 'chalk', 'helmet', 'prisma', 'redis', 'pg']);
|
|
29
31
|
|
|
30
32
|
function getGitIdentity() {
|
|
31
|
-
const identity = { name: "Developer", author: "", repository: "" };
|
|
33
|
+
const identity = { name: "Developer", author: "Developer", repository: "" };
|
|
32
34
|
try {
|
|
33
35
|
const name = execSync('git config user.name', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
34
36
|
const email = execSync('git config user.email', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
35
37
|
if (name) {
|
|
36
38
|
identity.name = name;
|
|
37
|
-
identity.author = `${name}
|
|
39
|
+
identity.author = email ? `${name} <${email}>` : name;
|
|
38
40
|
}
|
|
39
|
-
|
|
40
|
-
const url = execSync('git config --get remote.origin.url', { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
41
|
-
if (url) identity.repository = url.replace(/\.git$/, '');
|
|
42
41
|
} catch (e) {}
|
|
43
42
|
return identity;
|
|
44
43
|
}
|
|
45
44
|
|
|
46
|
-
function detectPackageManager(targetDir) {
|
|
47
|
-
|
|
48
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
49
|
-
if (fs.existsSync(path.join(targetDir, '
|
|
45
|
+
function detectPackageManager(targetDir, stats = null) {
|
|
46
|
+
const detectedLockfiles = [];
|
|
47
|
+
if (fs.existsSync(path.join(targetDir, 'pnpm-lock.yaml'))) detectedLockfiles.push('pnpm-lock.yaml');
|
|
48
|
+
if (fs.existsSync(path.join(targetDir, 'yarn.lock'))) detectedLockfiles.push('yarn.lock');
|
|
49
|
+
if (fs.existsSync(path.join(targetDir, 'package-lock.json'))) detectedLockfiles.push('package-lock.json');
|
|
50
|
+
|
|
51
|
+
if (detectedLockfiles.length > 1 && stats) {
|
|
52
|
+
stats.conflictingLockfiles = detectedLockfiles;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (detectedLockfiles.includes('pnpm-lock.yaml')) return 'pnpm';
|
|
56
|
+
if (detectedLockfiles.includes('yarn.lock')) return 'yarn';
|
|
57
|
+
if (detectedLockfiles.includes('package-lock.json')) return 'npm';
|
|
50
58
|
|
|
51
59
|
try { execSync('pnpm --version', { stdio: 'ignore' }); return 'pnpm'; } catch {}
|
|
52
60
|
try { execSync('yarn --version', { stdio: 'ignore' }); return 'yarn'; } catch {}
|
|
@@ -81,10 +89,29 @@ function cleanPackageName(importString) {
|
|
|
81
89
|
return importString.split('/')[0];
|
|
82
90
|
}
|
|
83
91
|
|
|
92
|
+
function smartPrepend(originalCode, declarationBlock) {
|
|
93
|
+
const lines = originalCode.split(/\r?\n/);
|
|
94
|
+
let insertIdx = 0;
|
|
95
|
+
|
|
96
|
+
while (insertIdx < lines.length) {
|
|
97
|
+
const line = lines[insertIdx].trim();
|
|
98
|
+
if (line.startsWith('#!') || line === '"use strict";' || line === "'use strict';" || line === '`use strict`;') {
|
|
99
|
+
insertIdx++;
|
|
100
|
+
} else if (line === '') {
|
|
101
|
+
insertIdx++;
|
|
102
|
+
} else {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
lines.splice(insertIdx, 0, declarationBlock);
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
async function inspectNpmPackage(pkgName) {
|
|
85
112
|
try {
|
|
86
113
|
const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
87
|
-
headers: { 'User-Agent': 'pkg-scaffold-dx-client/
|
|
114
|
+
headers: { 'User-Agent': 'pkg-scaffold-dx-client/1.1' },
|
|
88
115
|
signal: AbortSignal.timeout(4000)
|
|
89
116
|
});
|
|
90
117
|
if (response.status === 200) {
|
|
@@ -101,7 +128,7 @@ async function inspectNpmPackage(pkgName) {
|
|
|
101
128
|
async function fetchRemoteLicense(licenseKey) {
|
|
102
129
|
try {
|
|
103
130
|
const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
|
|
104
|
-
headers: { 'User-Agent': 'pkg-scaffold-dx-client/
|
|
131
|
+
headers: { 'User-Agent': 'pkg-scaffold-dx-client/1.1' },
|
|
105
132
|
signal: AbortSignal.timeout(5000)
|
|
106
133
|
});
|
|
107
134
|
if (response.status === 200) {
|
|
@@ -115,11 +142,33 @@ async function fetchRemoteLicense(licenseKey) {
|
|
|
115
142
|
function readFileSyncNormalized(fullPath) {
|
|
116
143
|
const buffer = fs.readFileSync(fullPath);
|
|
117
144
|
if (buffer[0] === 0xFF && buffer[1] === 0xFE) return buffer.toString('utf16le');
|
|
118
|
-
if (buffer[0] === 0xFE && buffer[1] === 0xFF) return buffer.toString('
|
|
145
|
+
if (buffer[0] === 0xFE && buffer[1] === 0xFF) return buffer.toString('utf8');
|
|
119
146
|
return buffer.toString('utf8');
|
|
120
147
|
}
|
|
121
148
|
|
|
122
|
-
function
|
|
149
|
+
function buildAsciiTree(dir, prefix = '') {
|
|
150
|
+
const results = [];
|
|
151
|
+
try {
|
|
152
|
+
const files = fs.readdirSync(dir);
|
|
153
|
+
const filtered = files.filter(f => !IGNORED_DIRS.has(f) && !f.startsWith('.'));
|
|
154
|
+
|
|
155
|
+
filtered.forEach((file, index) => {
|
|
156
|
+
const isLast = index === filtered.length - 1;
|
|
157
|
+
const marker = isLast ? '└── ' : '├── ';
|
|
158
|
+
results.push(`${prefix}${marker}${file}`);
|
|
159
|
+
|
|
160
|
+
const fullPath = path.join(dir, file);
|
|
161
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
162
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
163
|
+
results.push(...buildAsciiTree(fullPath, newPrefix));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
} catch (e) {}
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- High Performance AST Workspace Parsing Engine ---
|
|
171
|
+
function scanWorkspace(dir, stats, rootNamespace) {
|
|
123
172
|
const files = fs.readdirSync(dir);
|
|
124
173
|
|
|
125
174
|
for (const file of files) {
|
|
@@ -127,7 +176,7 @@ function scanWorkspace(dir, stats) {
|
|
|
127
176
|
const stat = fs.statSync(fullPath);
|
|
128
177
|
|
|
129
178
|
if (stat.isDirectory()) {
|
|
130
|
-
if (!IGNORED_DIRS.has(file) && !file.startsWith('.')) scanWorkspace(fullPath, stats);
|
|
179
|
+
if (!IGNORED_DIRS.has(file) && !file.startsWith('.')) scanWorkspace(fullPath, stats, rootNamespace);
|
|
131
180
|
} else {
|
|
132
181
|
const ext = path.extname(file);
|
|
133
182
|
|
|
@@ -137,94 +186,267 @@ function scanWorkspace(dir, stats) {
|
|
|
137
186
|
if (ext === '.js' || ext === '.jsx' || ext === '.mjs') stats.jsFiles++;
|
|
138
187
|
|
|
139
188
|
if (VALID_EXTENSIONS.has(ext)) {
|
|
140
|
-
const
|
|
141
|
-
|
|
189
|
+
const rawContent = readFileSyncNormalized(fullPath);
|
|
190
|
+
const content = rawContent.replace(/[^\x09\x0A\x0D\x20-\x7E]/g, '');
|
|
142
191
|
|
|
192
|
+
const codeLines = content.split(/\r?\n/);
|
|
193
|
+
const importedIdentifiers = new Map();
|
|
194
|
+
const fileRawDeps = new Set();
|
|
195
|
+
|
|
143
196
|
analyzeCodeStyle(content, stats);
|
|
144
197
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
198
|
+
// Universal Cryptographic Leak Interception
|
|
199
|
+
REGEX_PATTERNS.secretKeys.lastIndex = 0;
|
|
200
|
+
let secretMatch;
|
|
201
|
+
while ((secretMatch = REGEX_PATTERNS.secretKeys.exec(content)) !== null) {
|
|
202
|
+
const keyName = secretMatch[1];
|
|
203
|
+
const secretValue = secretMatch[2];
|
|
204
|
+
const envVarName = `${rootNamespace.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_${keyName.toUpperCase()}`;
|
|
205
|
+
stats.discoveredSecrets.push({ filePath: fullPath, keyName, secretValue, envVarName });
|
|
206
|
+
stats.envVars.add(envVarName);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// --- Global Regex Environmental Extraction Module ---
|
|
210
|
+
let fileHasEnv = false;
|
|
211
|
+
let envMatch;
|
|
212
|
+
REGEX_PATTERNS.env.lastIndex = 0;
|
|
213
|
+
while ((envMatch = REGEX_PATTERNS.env.exec(content)) !== null) {
|
|
214
|
+
stats.envVars.add(envMatch[1]);
|
|
215
|
+
fileHasEnv = true;
|
|
155
216
|
}
|
|
217
|
+
if (fileHasEnv) {
|
|
218
|
+
stats.filesWithEnvVars.add(fullPath);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (content.includes('import ') || content.includes('export ')) stats.usesEsm = true;
|
|
156
222
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
223
|
+
// --- Abstract Syntax Tree Engine Execution Block ---
|
|
224
|
+
let ast = null;
|
|
225
|
+
try {
|
|
226
|
+
ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true });
|
|
227
|
+
} catch (e) {
|
|
228
|
+
try {
|
|
229
|
+
ast = acorn.parse(content, { ecmaVersion: 'latest', sourceType: 'script', allowHashBang: true });
|
|
230
|
+
} catch (err) {}
|
|
161
231
|
}
|
|
162
232
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
233
|
+
if (ast) {
|
|
234
|
+
walk.simple(ast, {
|
|
235
|
+
ImportDeclaration(node) {
|
|
236
|
+
const pkg = cleanPackageName(node.source.value);
|
|
237
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
238
|
+
fileRawDeps.add(pkg);
|
|
239
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
240
|
+
node.specifiers.forEach(spec => importedIdentifiers.get(pkg).add(spec.local.name));
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
VariableDeclarator(node) {
|
|
244
|
+
if (node.init && node.init.type === 'CallExpression' &&
|
|
245
|
+
node.init.callee.type === 'Identifier' && node.init.callee.name === 'require') {
|
|
246
|
+
const arg = node.init.arguments[0];
|
|
247
|
+
if (arg && arg.type === 'Literal' && typeof arg.value === 'string') {
|
|
248
|
+
const pkg = cleanPackageName(arg.value);
|
|
249
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
250
|
+
fileRawDeps.add(pkg);
|
|
251
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
252
|
+
|
|
253
|
+
const extractBindings = (idNode) => {
|
|
254
|
+
if (idNode.type === 'Identifier') {
|
|
255
|
+
importedIdentifiers.get(pkg).add(idNode.name);
|
|
256
|
+
} else if (idNode.type === 'ObjectPattern') {
|
|
257
|
+
idNode.properties.forEach(p => {
|
|
258
|
+
if (p.value && p.value.type === 'Identifier') importedIdentifiers.get(pkg).add(p.value.name);
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
extractBindings(node.id);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
ImportExpression(node) {
|
|
268
|
+
if (node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
269
|
+
const pkg = cleanPackageName(node.source.value);
|
|
270
|
+
if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
ExportNamedDeclaration(node) {
|
|
274
|
+
if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
275
|
+
const pkg = cleanPackageName(node.source.value);
|
|
276
|
+
if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
ExportAllDeclaration(node) {
|
|
280
|
+
if (node.source && node.source.type === 'Literal' && typeof node.source.value === 'string') {
|
|
281
|
+
const pkg = cleanPackageName(node.source.value);
|
|
282
|
+
if (pkg && !builtinModules.includes(pkg)) fileRawDeps.add(pkg);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
// --- Text-Isolated Fallback Track (TypeScript/TSX Support Matrix) ---
|
|
288
|
+
for (const line of codeLines) {
|
|
289
|
+
let esmMatch = line.match(/\bimport\s+(?:\*+\s+as\s+)?([a-zA-Z0-9_]+)\s+from\s+['"]([^'"]+)['"]/);
|
|
290
|
+
if (esmMatch) {
|
|
291
|
+
const id = esmMatch[1];
|
|
292
|
+
const pkg = cleanPackageName(esmMatch[2]);
|
|
293
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
294
|
+
fileRawDeps.add(pkg);
|
|
295
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
296
|
+
importedIdentifiers.get(pkg).add(id);
|
|
297
|
+
}
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let esmNamedMatch = line.match(/\bimport\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/);
|
|
302
|
+
if (esmNamedMatch) {
|
|
303
|
+
const pkg = cleanPackageName(esmNamedMatch[2]);
|
|
304
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
305
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
306
|
+
fileRawDeps.add(pkg);
|
|
307
|
+
esmNamedMatch[1].split(',').forEach(part => {
|
|
308
|
+
const chunk = part.trim();
|
|
309
|
+
if (!chunk) return;
|
|
310
|
+
const id = chunk.includes(' as ') ? chunk.split(' as ')[1].trim() : chunk;
|
|
311
|
+
importedIdentifiers.get(pkg).add(id);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
167
316
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
317
|
+
let cjsMatch = line.match(/\b(?:const|let|var)\s+([a-zA-Z0-9_]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
318
|
+
if (cjsMatch) {
|
|
319
|
+
const id = cjsMatch[1];
|
|
320
|
+
const pkg = cleanPackageName(cjsMatch[2]);
|
|
321
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
322
|
+
fileRawDeps.add(pkg);
|
|
323
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
324
|
+
importedIdentifiers.get(pkg).add(id);
|
|
325
|
+
}
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let cjsDestructMatch = line.match(/\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
330
|
+
if (cjsDestructMatch) {
|
|
331
|
+
const pkg = cleanPackageName(cjsDestructMatch[2]);
|
|
332
|
+
if (pkg && !builtinModules.includes(pkg)) {
|
|
333
|
+
if (!importedIdentifiers.has(pkg)) importedIdentifiers.set(pkg, new Set());
|
|
334
|
+
fileRawDeps.add(pkg);
|
|
335
|
+
cjsDestructMatch[1].split(',').forEach(part => {
|
|
336
|
+
const chunk = part.trim();
|
|
337
|
+
if (!chunk) return;
|
|
338
|
+
const id = chunk.includes(':') ? chunk.split(':')[1].trim() : chunk;
|
|
339
|
+
importedIdentifiers.get(pkg).add(id);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
continue;
|
|
172
343
|
}
|
|
173
344
|
}
|
|
174
345
|
}
|
|
175
346
|
|
|
176
|
-
|
|
177
|
-
|
|
347
|
+
fileRawDeps.forEach(dep => stats.rawDeps.add(dep));
|
|
348
|
+
|
|
349
|
+
const functionalExecutionCodeOnly = codeLines
|
|
350
|
+
.filter(l => !/\bimport\b/.test(l) && !/\brequire\s*\(/.test(l))
|
|
351
|
+
.join('\n');
|
|
352
|
+
|
|
353
|
+
for (const [pkg, identifiers] of importedIdentifiers.entries()) {
|
|
354
|
+
let tokenReferenced = false;
|
|
355
|
+
for (const identifier of identifiers) {
|
|
356
|
+
const usagePattern = new RegExp(`\\b${identifier}\\b`);
|
|
357
|
+
if (usagePattern.test(functionalExecutionCodeOnly)) {
|
|
358
|
+
tokenReferenced = true;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (!tokenReferenced && identifiers.size > 0) {
|
|
363
|
+
stats.unusedDepsInCode.add(pkg);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
COMMON_EXTERNAL_TOKENS.forEach(token => {
|
|
368
|
+
const tokenPattern = new RegExp(`\\b${token}\\b`);
|
|
369
|
+
if (tokenPattern.test(functionalExecutionCodeOnly) && !importedIdentifiers.has(token)) {
|
|
370
|
+
stats.rawDeps.add(token);
|
|
371
|
+
if (!stats.phantomInjections.has(fullPath)) stats.phantomInjections.set(fullPath, new Set());
|
|
372
|
+
stats.phantomInjections.get(fullPath).add(token);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
178
375
|
}
|
|
179
376
|
}
|
|
180
377
|
}
|
|
181
378
|
}
|
|
182
379
|
|
|
183
|
-
function buildAsciiTree(dir, prefix = '') {
|
|
184
|
-
let treeLines = [];
|
|
185
|
-
try {
|
|
186
|
-
const files = fs.readdirSync(dir).filter(f => !IGNORED_DIRS.has(f) && !f.startsWith('.'));
|
|
187
|
-
files.forEach((file, idx) => {
|
|
188
|
-
const isLast = idx === files.length - 1;
|
|
189
|
-
const fullPath = path.join(dir, file);
|
|
190
|
-
const stat = fs.statSync(fullPath);
|
|
191
|
-
|
|
192
|
-
treeLines.push(`${prefix}${isLast ? '└── ' : '├── '}${file}`);
|
|
193
|
-
if (stat.isDirectory()) {
|
|
194
|
-
treeLines = treeLines.concat(buildAsciiTree(fullPath, prefix + (isLast ? ' ' : '│ ')));
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
} catch (e) {}
|
|
198
|
-
return treeLines;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
380
|
async function main() {
|
|
202
381
|
const targetDir = process.cwd();
|
|
203
382
|
const folderName = path.basename(targetDir);
|
|
204
383
|
const gitInfo = getGitIdentity();
|
|
205
|
-
|
|
384
|
+
|
|
385
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
386
|
+
|
|
387
|
+
const stats = {
|
|
388
|
+
tsFiles: 0, jsFiles: 0, usesEsm: false, hasHtml: false, hasTests: false,
|
|
389
|
+
rawDeps: new Set(), envVars: new Set(),
|
|
390
|
+
style: { semiCount: 0, noSemiCount: 0, tabCount: 0, space2Count: 0, space4Count: 0 },
|
|
391
|
+
quality: { varCount: 0, hasEval: false, syncFsCount: 0 },
|
|
392
|
+
phantomInjections: new Map(),
|
|
393
|
+
discoveredSecrets: [],
|
|
394
|
+
subWorkspaces: [],
|
|
395
|
+
conflictingLockfiles: [],
|
|
396
|
+
unusedDepsInCode: new Set(),
|
|
397
|
+
filesWithEnvVars: new Set(),
|
|
398
|
+
injectDotenvEngine: false,
|
|
399
|
+
bootstrapEslintSuite: false
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
const activePkgManager = detectPackageManager(targetDir, stats);
|
|
206
403
|
const pkgPath = path.join(targetDir, 'package.json');
|
|
207
404
|
let preExistingLicense = null;
|
|
405
|
+
let preExistingDeps = [];
|
|
208
406
|
|
|
209
407
|
console.log(`\n===================================================================`);
|
|
210
|
-
console.log(`🚀 pkg-scaffold
|
|
408
|
+
console.log(`🚀 pkg-scaffold v1.1: Deep Intelligence Workspace Diagnostic Run`);
|
|
211
409
|
console.log(`===================================================================\n`);
|
|
212
410
|
|
|
213
|
-
|
|
411
|
+
const topLevelItems = fs.readdirSync(targetDir);
|
|
412
|
+
const potentialSubModules = [];
|
|
413
|
+
for (const item of topLevelItems) {
|
|
414
|
+
const fullPath = path.join(targetDir, item);
|
|
415
|
+
if (!IGNORED_DIRS.has(item) && !item.startsWith('.') && fs.statSync(fullPath).isDirectory()) {
|
|
416
|
+
let containsSourceCode = false;
|
|
417
|
+
const examineDirectory = (d) => {
|
|
418
|
+
const subEntries = fs.readdirSync(d);
|
|
419
|
+
for (const entry of subEntries) {
|
|
420
|
+
const entryPath = path.join(d, entry);
|
|
421
|
+
if (fs.statSync(entryPath).isDirectory()) {
|
|
422
|
+
if (!IGNORED_DIRS.has(entry) && !entry.startsWith('.')) examineDirectory(entryPath);
|
|
423
|
+
} else if (VALID_EXTENSIONS.has(path.extname(entry))) {
|
|
424
|
+
containsSourceCode = true;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
try { examineDirectory(fullPath); } catch {}
|
|
430
|
+
if (containsSourceCode) potentialSubModules.push(item);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (potentialSubModules.length > 1) {
|
|
434
|
+
stats.subWorkspaces = potentialSubModules;
|
|
435
|
+
}
|
|
436
|
+
|
|
214
437
|
if (fs.existsSync(pkgPath)) {
|
|
215
438
|
console.log(`⚠️ An existing package.json was found in this working directory.`);
|
|
216
439
|
console.log(`📡 Analyzing existing installation arrays for invalid metrics...`);
|
|
217
440
|
try {
|
|
218
441
|
const existingData = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
219
|
-
|
|
220
|
-
// Capture license choice from the pre-existing package definition profile
|
|
221
442
|
if (existingData.license && typeof existingData.license === 'string' && existingData.license.toLowerCase() !== 'none') {
|
|
222
443
|
preExistingLicense = existingData.license;
|
|
223
444
|
}
|
|
445
|
+
if (existingData.dependencies) preExistingDeps.push(...Object.keys(existingData.dependencies));
|
|
446
|
+
if (existingData.devDependencies) preExistingDeps.push(...Object.keys(existingData.devDependencies));
|
|
224
447
|
|
|
225
448
|
const combinedDeps = Object.keys({ ...existingData.dependencies, ...existingData.devDependencies });
|
|
226
449
|
let brokenEcosystem = combinedDeps.length === 0;
|
|
227
|
-
|
|
228
450
|
for (const dep of combinedDeps) {
|
|
229
451
|
const check = await inspectNpmPackage(dep);
|
|
230
452
|
if (check && check.error === 'NOT_FOUND') {
|
|
@@ -232,35 +454,67 @@ async function main() {
|
|
|
232
454
|
console.log(` ❌ Identified non-existent package on registry tracks: "${dep}"`);
|
|
233
455
|
}
|
|
234
456
|
}
|
|
235
|
-
|
|
236
457
|
if (brokenEcosystem) {
|
|
237
458
|
console.log(`\n🛑 CRITICAL COMPLIANCE BREAK: Your current package.json is empty or contains non-existent packages.`);
|
|
238
|
-
console.log(`👉 Action Required: Please remove or backup the existing 'package.json' from this folder
|
|
239
|
-
|
|
459
|
+
console.log(`👉 Action Required: Please remove or backup the existing 'package.json' from this folder.\n`);
|
|
460
|
+
rl.close();
|
|
240
461
|
return;
|
|
241
|
-
} else {
|
|
242
|
-
console.log(` ℹ️ Existing package.json appears structurally sound. Skipping generation arrays to prevent asset loss.`);
|
|
243
462
|
}
|
|
244
463
|
} catch (err) {
|
|
245
|
-
console.log(`\n🛑 CRITICAL: Existing package.json is malformed or corrupt
|
|
246
|
-
|
|
464
|
+
console.log(`\n🛑 CRITICAL: Existing package.json is malformed or corrupt.\n`);
|
|
465
|
+
rl.close();
|
|
247
466
|
return;
|
|
248
467
|
}
|
|
249
468
|
}
|
|
250
469
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
470
|
+
scanWorkspace(targetDir, stats, folderName);
|
|
471
|
+
|
|
472
|
+
// --- Unused Dependency Filtration Matrix ---
|
|
473
|
+
const allDiscoveredUnused = new Set([...stats.unusedDepsInCode]);
|
|
474
|
+
if (preExistingDeps.length > 0) {
|
|
475
|
+
preExistingDeps.forEach(dep => { if (!stats.rawDeps.has(dep)) allDiscoveredUnused.add(dep); });
|
|
476
|
+
}
|
|
258
477
|
|
|
259
|
-
|
|
478
|
+
const devToolingEcosystem = new Set([
|
|
479
|
+
'eslint', 'prettier', 'typescript', 'typescript-eslint', '@eslint/js',
|
|
480
|
+
'nodemon', 'ts-node', 'tsup', 'vite', 'vitest', 'jest'
|
|
481
|
+
]);
|
|
482
|
+
for (const dep of allDiscoveredUnused) {
|
|
483
|
+
if (devToolingEcosystem.has(dep) || dep.startsWith('@types/')) {
|
|
484
|
+
allDiscoveredUnused.delete(dep);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (allDiscoveredUnused.size > 0) {
|
|
489
|
+
console.log(`\n📦 UNUSED WORKSPACE DEPENDENCIES DETECTED`);
|
|
490
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
491
|
+
console.log(` The following modules are imported or installed but never invoked inside executable code paths:`);
|
|
492
|
+
console.log(` ${Array.from(allDiscoveredUnused).map(d => `\x1b[33m"${d}"\x1b[0m`).join(', ')}`);
|
|
493
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
494
|
+
|
|
495
|
+
const pruneChoice = await rl.question(`❓ Exclude these unused dependencies from your package.json setup? (y/N): `);
|
|
496
|
+
if (pruneChoice.trim().toLowerCase() === 'y' || pruneChoice.trim().toLowerCase() === 'yes') {
|
|
497
|
+
for (const deadDep of allDiscoveredUnused) stats.rawDeps.delete(deadDep);
|
|
498
|
+
console.log(` 🗑️ Pruned unused dependencies from your configuration blueprint.`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
260
501
|
|
|
261
502
|
const isTypeScript = stats.tsFiles > stats.jsFiles;
|
|
262
503
|
const isFrontendWeb = stats.hasHtml || stats.rawDeps.has('react') || stats.rawDeps.has('vue') || stats.rawDeps.has('vite');
|
|
263
504
|
|
|
505
|
+
if (stats.envVars.size > 0 && !stats.rawDeps.has('dotenv') && !isFrontendWeb) {
|
|
506
|
+
console.log(`\n📡 CONFIGURATION COMPLIANCE GAP: UNMANAGED ENVIRONMENT VARIABLES`);
|
|
507
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
508
|
+
console.log(` Workspace utilizes 'process.env' variables but 'dotenv' is missing.`);
|
|
509
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
510
|
+
const choiceEnv = await rl.question(`❓ Add 'dotenv' and automatically wire initialization hooks into your files? (Y/n): `);
|
|
511
|
+
|
|
512
|
+
if (choiceEnv.trim().toLowerCase() !== 'n' && choiceEnv.trim().toLowerCase() !== 'no') {
|
|
513
|
+
stats.rawDeps.add('dotenv');
|
|
514
|
+
stats.injectDotenvEngine = true;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
264
518
|
const packageJson = {
|
|
265
519
|
name: folderName.toLowerCase().replace(/[^a-z0-9-_]/g, '-'),
|
|
266
520
|
version: '1.0.0',
|
|
@@ -273,6 +527,24 @@ async function main() {
|
|
|
273
527
|
devDependencies: {}
|
|
274
528
|
};
|
|
275
529
|
|
|
530
|
+
const eslintConfigFile = path.join(targetDir, 'eslint.config.js');
|
|
531
|
+
const linterPresent = fs.existsSync(eslintConfigFile) || fs.existsSync(path.join(targetDir, '.eslintrc.json')) || fs.existsSync(path.join(targetDir, '.eslintrc.js'));
|
|
532
|
+
|
|
533
|
+
if (!linterPresent && (stats.quality.varCount > 0 || stats.quality.hasEval || stats.phantomInjections.size > 0)) {
|
|
534
|
+
console.log(`\n🎨 QUALITY LAYER AUDITOR: SYNTAX VALIDATION SYSTEM REQUIRED`);
|
|
535
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
536
|
+
console.log(` Code anomalies (legacy 'var' choices or 'eval()') require static linter guards.`);
|
|
537
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
538
|
+
const choiceLintSetup = await rl.question(`❓ Bootstrap standard ESLint flat verification rules into workspace? (Y/n): `);
|
|
539
|
+
|
|
540
|
+
if (choiceLintSetup.trim().toLowerCase() !== 'n' && choiceLintSetup.trim().toLowerCase() !== 'no') {
|
|
541
|
+
stats.bootstrapEslintSuite = true;
|
|
542
|
+
stats.rawDeps.add('eslint');
|
|
543
|
+
if (isTypeScript) stats.rawDeps.add('typescript-eslint');
|
|
544
|
+
else stats.rawDeps.add('@eslint/js');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
276
548
|
if (isFrontendWeb) {
|
|
277
549
|
packageJson.scripts.dev = 'vite';
|
|
278
550
|
packageJson.scripts.build = 'vite build';
|
|
@@ -295,20 +567,22 @@ async function main() {
|
|
|
295
567
|
}
|
|
296
568
|
|
|
297
569
|
if (stats.rawDeps.size > 0) {
|
|
298
|
-
console.log(
|
|
570
|
+
console.log(`\n📡 Resolving baseline package registry definitions...`);
|
|
299
571
|
for (const pkg of stats.rawDeps) {
|
|
300
572
|
const cleaned = cleanPackageName(pkg);
|
|
301
573
|
if (cleaned && !builtinModules.includes(cleaned)) {
|
|
302
574
|
const check = await inspectNpmPackage(cleaned);
|
|
303
575
|
if (check && check.error !== 'NOT_FOUND') {
|
|
304
576
|
const version = check.version || 'latest';
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
577
|
+
|
|
578
|
+
const isDevDep = [
|
|
579
|
+
'vite', 'vitest', 'typescript', 'eslint', 'typescript-eslint',
|
|
580
|
+
'@eslint/js', 'prettier', 'jest', 'nodemon', 'ts-node', 'tsup'
|
|
581
|
+
].includes(cleaned) || cleaned.startsWith('@types/');
|
|
582
|
+
|
|
583
|
+
if (isDevDep) packageJson.devDependencies[cleaned] = `^${version}`;
|
|
584
|
+
else packageJson.dependencies[cleaned] = `^${version}`;
|
|
585
|
+
console.log(` ¼ Synced verified package parameters: ${cleaned}@^${version}`);
|
|
312
586
|
}
|
|
313
587
|
}
|
|
314
588
|
}
|
|
@@ -333,18 +607,64 @@ async function main() {
|
|
|
333
607
|
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
334
608
|
}
|
|
335
609
|
|
|
336
|
-
|
|
610
|
+
if (stats.discoveredSecrets.length > 0) {
|
|
611
|
+
console.log(`\n🚨 CRITICAL SECURITY COMPLIANCE ALERT: HARDCODED CREDENTIALS DETECTED`);
|
|
612
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
613
|
+
for (const secretMeta of stats.discoveredSecrets) {
|
|
614
|
+
console.log(`📂 File: ${path.relative(targetDir, secretMeta.filePath)}`);
|
|
615
|
+
console.log(` ⚠️ Hardcoded raw credential instance found mapping to signature value [${secretMeta.keyName}]`);
|
|
616
|
+
}
|
|
617
|
+
console.log(`───────────────────────────────────────────────────────────────────`);
|
|
618
|
+
|
|
619
|
+
const fixSecrets = await rl.question(`❓ Automatically extract credentials into environment mappings safely? (y/N): `);
|
|
620
|
+
|
|
621
|
+
if (fixSecrets.trim().toLowerCase() === 'y' || fixSecrets.trim().toLowerCase() === 'yes') {
|
|
622
|
+
const envPath = path.join(targetDir, '.env');
|
|
623
|
+
let envBuffer = fs.existsSync(envPath) ? readFileSyncNormalized(envPath) : '';
|
|
624
|
+
|
|
625
|
+
for (const secretMeta of stats.discoveredSecrets) {
|
|
626
|
+
let currentCodeContent = readFileSyncNormalized(secretMeta.filePath);
|
|
627
|
+
const envAccessor = isFrontendWeb ? `import.meta.env.${secretMeta.envVarName}` : `process.env.${secretMeta.envVarName}`;
|
|
628
|
+
|
|
629
|
+
const exactLiteralPattern = new RegExp(`\\b${secretMeta.keyName}\\s*=\\s*['"\`]${secretMeta.secretValue.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}['"\`]`, 'g');
|
|
630
|
+
currentCodeContent = currentCodeContent.replace(exactLiteralPattern, `${secretMeta.keyName} = ${envAccessor}`);
|
|
631
|
+
fs.writeFileSync(secretMeta.filePath, currentCodeContent);
|
|
632
|
+
|
|
633
|
+
if (!envBuffer.includes(`${secretMeta.envVarName}=`)) envBuffer += `${secretMeta.envVarName}=${secretMeta.secretValue}\n`;
|
|
634
|
+
console.log(` 🔒 Safely isolated credential string -> ${envAccessor} inside ${path.relative(targetDir, secretMeta.filePath)}`);
|
|
635
|
+
}
|
|
636
|
+
fs.writeFileSync(envPath, envBuffer);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (stats.subWorkspaces && stats.subWorkspaces.length > 1) {
|
|
641
|
+
console.log(`\n📂 MULTI-WORKSPACE SEGMENTATION DETECTED`);
|
|
642
|
+
console.log(` Identified independent sub-module paths: ${stats.subWorkspaces.map(w => `/${w}`).join(', ')}`);
|
|
643
|
+
|
|
644
|
+
const setupWorkspace = await rl.question(`❓ Setup layout architecture as a multi-package Monorepo Workspace layout? (y/N): `);
|
|
645
|
+
|
|
646
|
+
if (setupWorkspace.trim().toLowerCase() === 'y' || setupWorkspace.trim().toLowerCase() === 'yes') {
|
|
647
|
+
if (activePkgManager === 'pnpm') {
|
|
648
|
+
const workspaceYamlPath = path.join(targetDir, 'pnpm-workspace.yaml');
|
|
649
|
+
const workspaceYamlTemplate = `packages:\n${stats.subWorkspaces.map(w => ` - '${w}'`).join('\n')}\n`;
|
|
650
|
+
fs.writeFileSync(workspaceYamlPath, workspaceYamlTemplate);
|
|
651
|
+
console.log(` 🏗️ Generated monorepo configuration layer: pnpm-workspace.yaml`);
|
|
652
|
+
} else {
|
|
653
|
+
packageJson.workspaces = stats.subWorkspaces.map(w => `${w}`);
|
|
654
|
+
console.log(` 🏗️ Injected 'workspaces' definitions directly into root layout blueprint.`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
337
659
|
const licensePath = path.join(targetDir, 'LICENSE');
|
|
338
660
|
let chosenLicenseType = preExistingLicense || 'None';
|
|
339
661
|
|
|
340
662
|
if (!fs.existsSync(licensePath) && !preExistingLicense) {
|
|
341
|
-
console.log(`\n⚖️ Legal Compliance Auditor: No LICENSE file
|
|
342
|
-
const
|
|
343
|
-
const licInput = await rlLicense.question(`❓ Select an Open Source License to pull from registry (mit / apache-2.0 / gpl-3.0 / skip): `);
|
|
344
|
-
rlLicense.close();
|
|
663
|
+
console.log(`\n⚖️ Legal Compliance Auditor: No LICENSE file located.`);
|
|
664
|
+
const licInput = await rl.question(`❓ Enter Open Source License to register (e.g. MIT, Apache-2.0, ISC, BSD-3-Clause, skip): `);
|
|
345
665
|
|
|
346
|
-
const cleanedInput = licInput.trim()
|
|
347
|
-
if (
|
|
666
|
+
const cleanedInput = licInput.trim();
|
|
667
|
+
if (cleanedInput.toLowerCase() !== 'skip' && cleanedInput.toLowerCase() !== 'none' && cleanedInput !== '') {
|
|
348
668
|
console.log(` 📡 Querying GitHub Legal Databases for "${cleanedInput.toUpperCase()}" template...`);
|
|
349
669
|
const rawTemplate = await fetchRemoteLicense(cleanedInput);
|
|
350
670
|
|
|
@@ -355,32 +675,26 @@ async function main() {
|
|
|
355
675
|
|
|
356
676
|
fs.writeFileSync(licensePath, parsedText);
|
|
357
677
|
chosenLicenseType = cleanedInput.toUpperCase();
|
|
358
|
-
|
|
359
|
-
console.log(` ⚖️ Successfully provisioned, stamped, and generated legal asset: LICENSE`);
|
|
678
|
+
console.log(` ⚖️ Successfully provisioned legal asset: LICENSE`);
|
|
360
679
|
} else {
|
|
361
|
-
console.log(`
|
|
680
|
+
console.log(` ⚠️ License model "${cleanedInput}" not indexed on GitHub database registers. Saving custom structural label configuration.`);
|
|
681
|
+
chosenLicenseType = cleanedInput;
|
|
362
682
|
}
|
|
683
|
+
packageJson.license = chosenLicenseType;
|
|
363
684
|
}
|
|
364
685
|
} else {
|
|
365
|
-
// Skip prompt routing because metadata parameter indicators exist
|
|
366
686
|
if (preExistingLicense) {
|
|
367
|
-
console.log(`\n⚖️ Legal Compliance Auditor: License "${preExistingLicense}" detected in package.json. Skipping interactive step.`);
|
|
368
687
|
chosenLicenseType = preExistingLicense;
|
|
369
|
-
|
|
370
|
-
// Auto-provisioning loop if the descriptor is preset but the physical asset is absent
|
|
371
688
|
if (!fs.existsSync(licensePath) && ['mit', 'apache-2.0', 'gpl-3.0'].includes(preExistingLicense.toLowerCase())) {
|
|
372
|
-
console.log(` 📡 Auto-provisioning missing physical LICENSE layer text files for "${preExistingLicense.toUpperCase()}"...`);
|
|
373
689
|
const rawTemplate = await fetchRemoteLicense(preExistingLicense);
|
|
374
690
|
if (rawTemplate) {
|
|
375
691
|
const parsedText = rawTemplate
|
|
376
692
|
.replace(/\[year\]|<year>/gi, '2026')
|
|
377
693
|
.replace(/\[fullname\]|\[name of copyright owner\]|<copyright holders>|<name of author>/gi, gitInfo.name);
|
|
378
694
|
fs.writeFileSync(licensePath, parsedText);
|
|
379
|
-
console.log(` ⚖️ Successfully mirrored and synthesized missing license file artifacts.`);
|
|
380
695
|
}
|
|
381
696
|
}
|
|
382
697
|
} else if (fs.existsSync(licensePath)) {
|
|
383
|
-
console.log(`\n⚖️ Legal Compliance Auditor: Existing physical LICENSE file detected. Skipping interactive step.`);
|
|
384
698
|
try {
|
|
385
699
|
const currentLicenseContent = fs.readFileSync(licensePath, 'utf8');
|
|
386
700
|
if (currentLicenseContent.includes('MIT')) chosenLicenseType = 'MIT';
|
|
@@ -391,9 +705,55 @@ async function main() {
|
|
|
391
705
|
packageJson.license = chosenLicenseType;
|
|
392
706
|
}
|
|
393
707
|
|
|
708
|
+
if (!stats.hasTests) {
|
|
709
|
+
const bootstrapTest = await rl.question(`\n❓ No test files detected. Scaffold a zero-bloat testing harness via Node native test runner? (y/N): `);
|
|
710
|
+
|
|
711
|
+
if (bootstrapTest.trim().toLowerCase() === 'y' || bootstrapTest.trim().toLowerCase() === 'yes') {
|
|
712
|
+
const isEsm = packageJson.type === 'module';
|
|
713
|
+
const testExt = isTypeScript ? '.test.ts' : '.test.js';
|
|
714
|
+
const targetTestFile = `index${testExt}`;
|
|
715
|
+
const testFilePath = path.join(targetDir, targetTestFile);
|
|
716
|
+
|
|
717
|
+
const testTemplate = isEsm
|
|
718
|
+
? `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`
|
|
719
|
+
: `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`;
|
|
720
|
+
|
|
721
|
+
fs.writeFileSync(testFilePath, testTemplate);
|
|
722
|
+
packageJson.scripts.test = 'node --test';
|
|
723
|
+
stats.hasTests = true;
|
|
724
|
+
console.log(` 🧪 Generated native functional testing fixture: ${targetTestFile}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
394
728
|
console.log(`\n⚙️ Writing ecosystem configuration artifacts...`);
|
|
395
729
|
|
|
396
|
-
if (
|
|
730
|
+
if (stats.bootstrapEslintSuite) {
|
|
731
|
+
packageJson.scripts.lint = 'eslint .';
|
|
732
|
+
let eslintConfigContent = '';
|
|
733
|
+
if (isTypeScript) {
|
|
734
|
+
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`;
|
|
735
|
+
} else {
|
|
736
|
+
if (packageJson.type === 'module') {
|
|
737
|
+
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`;
|
|
738
|
+
} else {
|
|
739
|
+
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`;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
fs.writeFileSync(eslintConfigFile, eslintConfigContent);
|
|
743
|
+
console.log(` 🎨 Provisioned automated static syntax layout: eslint.config.js`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (fs.existsSync(pkgPath)) {
|
|
747
|
+
try {
|
|
748
|
+
const currentPackageJson = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
749
|
+
currentPackageJson.dependencies = { ...packageJson.dependencies, ...currentPackageJson.dependencies };
|
|
750
|
+
currentPackageJson.devDependencies = { ...packageJson.devDependencies, ...currentPackageJson.devDependencies };
|
|
751
|
+
if (packageJson.scripts.lint && !currentPackageJson.scripts.lint) currentPackageJson.scripts.lint = packageJson.scripts.lint;
|
|
752
|
+
|
|
753
|
+
fs.writeFileSync(pkgPath, JSON.stringify(currentPackageJson, null, 2));
|
|
754
|
+
console.log(` 🔄 Safely merged discovered dependencies into existing package.json`);
|
|
755
|
+
} catch (e) {}
|
|
756
|
+
} else {
|
|
397
757
|
fs.writeFileSync(pkgPath, JSON.stringify(packageJson, null, 2));
|
|
398
758
|
console.log(` 📝 Injected: package.json`);
|
|
399
759
|
}
|
|
@@ -416,30 +776,33 @@ async function main() {
|
|
|
416
776
|
}
|
|
417
777
|
|
|
418
778
|
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
419
|
-
if (!fs.existsSync(gitignorePath)) {
|
|
779
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
780
|
+
fs.writeFileSync(gitignorePath, `node_modules/\ndist/\nbuild/\n.env\n.env.local\n.DS_Store\n`);
|
|
781
|
+
console.log(` ⚙️ Structural default configurations locked: .gitignore`);
|
|
782
|
+
}
|
|
420
783
|
|
|
421
784
|
if (isTypeScript) {
|
|
422
785
|
const tsconfigPath = path.join(targetDir, 'tsconfig.json');
|
|
423
786
|
if (!fs.existsSync(tsconfigPath)) {
|
|
424
787
|
fs.writeFileSync(tsconfigPath, JSON.stringify({ compilerOptions: { target: "ES2022", module: "NodeNext", moduleResolution: "NodeNext", esModuleInterop: true, strict: true, skipLibCheck: true, outDir: "./dist" }, include: ["src/**/*", "**/*.ts"] }, null, 2));
|
|
425
|
-
console.log(`
|
|
788
|
+
console.log(` ⚙️ Structural default configurations locked: tsconfig.json`);
|
|
426
789
|
}
|
|
427
790
|
}
|
|
428
791
|
|
|
429
|
-
// --- Adaptive Flat Shields.io README Layout ---
|
|
430
792
|
const readmePath = path.join(targetDir, 'README.md');
|
|
431
793
|
if (!fs.existsSync(readmePath)) {
|
|
432
794
|
const pName = packageJson.name;
|
|
433
795
|
const layoutTree = buildAsciiTree(targetDir).join('\n');
|
|
434
|
-
|
|
435
796
|
const displayDeps = Object.keys(packageJson.dependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
|
|
436
797
|
const displayDevDeps = Object.keys(packageJson.devDependencies).map(d => `* \`${d}\``).join('\n') || '* None extracted';
|
|
798
|
+
const licenseBadgeParam = encodeURIComponent(chosenLicenseType.replace(/-/g, '_'));
|
|
437
799
|
|
|
438
800
|
const documentationTemplate =
|
|
439
801
|
`# ${pName}
|
|
440
802
|
|
|
441
|
-
|
|
442
|
-
|
|
803
|
+

|
|
804
|
+

|
|
805
|
+

|
|
443
806
|
|
|
444
807
|
${packageJson.description}
|
|
445
808
|
|
|
@@ -473,52 +836,71 @@ ${activePkgManager} install
|
|
|
473
836
|
console.log(` 📖 Auto-generated system asset metrics: README.md`);
|
|
474
837
|
}
|
|
475
838
|
|
|
476
|
-
|
|
477
|
-
if (stats.phantomInjections.size > 0) {
|
|
839
|
+
if (stats.phantomInjections.size > 0 || (stats.injectDotenvEngine && stats.filesWithEnvVars.size > 0)) {
|
|
478
840
|
console.log(`\n💡 Source Code Modification Subsystem:`);
|
|
479
|
-
const
|
|
480
|
-
const injectChoice = await rlPhantom.question(`❓ Found unimported dependencies in code execution chains. Inject statements automatically? (y/N): `);
|
|
481
|
-
rlPhantom.close();
|
|
841
|
+
const injectChoice = await rl.question(`❓ Found phantom modules or unmanaged env components. Mutate file headers cleanly now? (y/N): `);
|
|
482
842
|
|
|
483
843
|
if (injectChoice.trim().toLowerCase() === 'y' || injectChoice.trim().toLowerCase() === 'yes') {
|
|
484
|
-
|
|
844
|
+
const allTargets = new Set([...stats.phantomInjections.keys(), ...stats.filesWithEnvVars]);
|
|
845
|
+
|
|
846
|
+
for (const filePath of allTargets) {
|
|
485
847
|
const originalCode = readFileSyncNormalized(filePath);
|
|
486
848
|
let declarationBlock = '';
|
|
487
849
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
declarationBlock += `const ${mod} = require('${mod}');\n`;
|
|
850
|
+
const missingModules = stats.phantomInjections.get(filePath);
|
|
851
|
+
if (missingModules) {
|
|
852
|
+
for (const mod of missingModules) {
|
|
853
|
+
if (packageJson.type === 'module') declarationBlock += `import ${mod} from '${mod}';\n`;
|
|
854
|
+
else declarationBlock += `const ${mod} = require('${mod}');\n`;
|
|
493
855
|
}
|
|
494
856
|
}
|
|
495
|
-
|
|
496
|
-
|
|
857
|
+
|
|
858
|
+
if (stats.injectDotenvEngine && stats.filesWithEnvVars.has(filePath) && !originalCode.includes('dotenv')) {
|
|
859
|
+
if (packageJson.type === 'module') declarationBlock += `import 'dotenv/config';\n`;
|
|
860
|
+
else declarationBlock += `require('dotenv').config();\n`;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (declarationBlock !== '') {
|
|
864
|
+
fs.writeFileSync(filePath, smartPrepend(originalCode, declarationBlock));
|
|
865
|
+
console.log(` ⚡ Injected contextual runtime headers safely: ${path.relative(targetDir, filePath)}`);
|
|
866
|
+
}
|
|
497
867
|
}
|
|
498
868
|
}
|
|
499
869
|
}
|
|
500
870
|
|
|
501
|
-
// --- Dynamic Self-Contained Local Auditing via npm-deprecated-check ---
|
|
502
871
|
console.log(`\n🛑 INITIALIZING LIVE ECOSYSTEM DEPRECATION SECURITY SCAN...`);
|
|
503
872
|
console.log(` Running integrated npm-deprecated-check validation algorithms:\n`);
|
|
504
873
|
try {
|
|
505
874
|
const localRequire = createRequire(import.meta.url);
|
|
506
875
|
const dependencyPkgJsonPath = localRequire.resolve('npm-deprecated-check/package.json');
|
|
507
876
|
const dependencyPkgJson = JSON.parse(fs.readFileSync(dependencyPkgJsonPath, 'utf8'));
|
|
508
|
-
|
|
509
|
-
const binRelativeMapping = typeof dependencyPkgJson.bin === 'string'
|
|
510
|
-
? dependencyPkgJson.bin
|
|
511
|
-
: (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
|
|
512
|
-
|
|
877
|
+
const binRelativeMapping = typeof dependencyPkgJson.bin === 'string' ? dependencyPkgJson.bin : (dependencyPkgJson.bin['npm-deprecated-check'] || dependencyPkgJson.bin['ndc']);
|
|
513
878
|
const absoluteExecutablePath = path.join(path.dirname(dependencyPkgJsonPath), binRelativeMapping);
|
|
514
879
|
execSync(`node "${absoluteExecutablePath}" current`, { stdio: 'inherit', cwd: targetDir });
|
|
515
880
|
} catch (err) {}
|
|
516
881
|
|
|
517
|
-
|
|
882
|
+
if (stats.conflictingLockfiles.length > 1) {
|
|
883
|
+
console.log(`\n⚠️ CONFLICTING ACCUMULATED LOCKFILES DETECTED: [${stats.conflictingLockfiles.join(', ')}]`);
|
|
884
|
+
const cleanLocks = await rl.question(`❓ Purge legacy/mismatched lockfiles to protect systemic package integrity? (y/N): `);
|
|
885
|
+
|
|
886
|
+
if (cleanLocks.trim().toLowerCase() === 'y' || cleanLocks.trim().toLowerCase() === 'yes') {
|
|
887
|
+
const packageEngineLockmap = { npm: 'package-lock.json', pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock' };
|
|
888
|
+
const operationalLockfile = packageEngineLockmap[activePkgManager];
|
|
889
|
+
for (const lockfile of stats.conflictingLockfiles) {
|
|
890
|
+
if (lockfile !== operationalLockfile) {
|
|
891
|
+
try {
|
|
892
|
+
fs.unlinkSync(path.join(targetDir, lockfile));
|
|
893
|
+
console.log(` 🗑️ Cleaned up duplicate lockfile artifact: ${lockfile}`);
|
|
894
|
+
} catch (e) {}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
518
900
|
console.log(`\n📦 Auto-scaffolding pipeline complete!`);
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
901
|
+
const userPromptChoice = await rl.question(`❓ Detected system default manager: "${activePkgManager}". Run "${activePkgManager} install" automatically now? (y/N): `);
|
|
902
|
+
|
|
903
|
+
rl.close();
|
|
522
904
|
|
|
523
905
|
const normalizedAnswer = userPromptChoice.trim().toLowerCase();
|
|
524
906
|
if (normalizedAnswer === 'y' || normalizedAnswer === 'yes') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pkg-scaffold",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Zero-config workspace initializer that scans your source code, generates modern configuration layers, corrects missing imports, and automatically audits legacy dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"author": "DreamLongYT",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"acorn": "^8.17.0",
|
|
24
|
+
"acorn-walk": "^8.3.5",
|
|
23
25
|
"npm-deprecated-check": "^1.4.0"
|
|
24
26
|
}
|
|
25
27
|
}
|