nothumanallowed 16.0.23 → 16.0.24
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/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +650 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.24",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '16.0.
|
|
8
|
+
export const VERSION = '16.0.24';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -155,19 +155,40 @@ class SandboxManager {
|
|
|
155
155
|
}
|
|
156
156
|
emit({ type: 'status', msg: `Entry point: ${entryFile}` });
|
|
157
157
|
|
|
158
|
-
// ── Phase 2: Dependencies
|
|
158
|
+
// ── Phase 2: Dependencies (pre-scan + batch install) ──────────────────
|
|
159
|
+
// Pre-scan the project source files for require()/import statements and
|
|
160
|
+
// diff against package.json + node_modules. Install everything missing in
|
|
161
|
+
// ONE batch BEFORE spawning the sandbox, so the Tier 1 retry-on-crash
|
|
162
|
+
// becomes a fallback, not the main code path.
|
|
159
163
|
if (fs.existsSync(path.join(projectDir, 'package.json'))) {
|
|
164
|
+
const scanned = _scanProjectImports(projectDir);
|
|
165
|
+
const declared = _declaredDeps(projectDir);
|
|
166
|
+
const installed = _installedDeps(projectDir);
|
|
167
|
+
const missing = [...scanned].filter(m => !declared.has(m) && !installed.has(m) && !_SHIMMED_MODULES.has(m));
|
|
168
|
+
|
|
169
|
+
if (missing.length > 0) {
|
|
170
|
+
emit({ type: 'phase', phase: 'deps-prescan', msg: `Pre-scan: ${missing.length} missing module${missing.length === 1 ? '' : 's'} → ${missing.slice(0, 8).join(', ')}${missing.length > 8 ? '...' : ''}` });
|
|
171
|
+
}
|
|
172
|
+
|
|
160
173
|
emit({ type: 'phase', phase: 'deps', msg: 'Installing dependencies...' });
|
|
174
|
+
const installCmd = missing.length > 0
|
|
175
|
+
? `npm install --save --prefer-offline --no-audit --no-fund ${missing.map(m => JSON.stringify(m)).join(' ')} 2>&1`
|
|
176
|
+
: 'npm install --prefer-offline --no-audit --no-fund 2>&1';
|
|
161
177
|
try {
|
|
162
|
-
const { stdout } = await execAsync(
|
|
178
|
+
const { stdout } = await execAsync(installCmd, {
|
|
163
179
|
cwd: projectDir,
|
|
164
|
-
timeout:
|
|
180
|
+
timeout: 180_000,
|
|
165
181
|
env: { ...process.env, NODE_ENV: 'development' },
|
|
166
182
|
});
|
|
167
183
|
const added = stdout.match(/added (\d+) package/)?.[1] || '0';
|
|
168
|
-
emit({ type: 'status', msg: `Dependencies installed (${added} packages)` });
|
|
184
|
+
emit({ type: 'status', msg: `Dependencies installed (${added} packages${missing.length ? `, batch: ${missing.join(', ')}` : ''})` });
|
|
169
185
|
} catch (e) {
|
|
170
|
-
|
|
186
|
+
const diag = _classifyInstallError(e);
|
|
187
|
+
emit({ type: 'warn', msg: `npm install failed — reason: ${diag.reason}. ${diag.hint}` });
|
|
188
|
+
if (diag.offlineFallback) {
|
|
189
|
+
emit({ type: 'status', msg: 'Activating NHA_OFFLINE_SHIM=1 fallback for missing modules.' });
|
|
190
|
+
this._offlineShim = true;
|
|
191
|
+
}
|
|
171
192
|
}
|
|
172
193
|
}
|
|
173
194
|
|
|
@@ -201,14 +222,16 @@ class SandboxManager {
|
|
|
201
222
|
|
|
202
223
|
// Capture stderr for missing module detection
|
|
203
224
|
let stderrBuf = '';
|
|
225
|
+
const childEnv = {
|
|
226
|
+
...process.env,
|
|
227
|
+
PORT: String(port),
|
|
228
|
+
NODE_ENV: 'development',
|
|
229
|
+
NHA_SANDBOX: '1',
|
|
230
|
+
};
|
|
231
|
+
if (this._offlineShim) childEnv.NHA_OFFLINE_SHIM = '1';
|
|
204
232
|
const proc = spawn('node', [patchedEntry], {
|
|
205
233
|
cwd: projectDir,
|
|
206
|
-
env:
|
|
207
|
-
...process.env,
|
|
208
|
-
PORT: String(port),
|
|
209
|
-
NODE_ENV: 'development',
|
|
210
|
-
NHA_SANDBOX: '1',
|
|
211
|
-
},
|
|
234
|
+
env: childEnv,
|
|
212
235
|
detached: true,
|
|
213
236
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
214
237
|
});
|
|
@@ -291,25 +314,38 @@ class SandboxManager {
|
|
|
291
314
|
emit({ type: 'warn', msg: 'Process exited but stderr is EMPTY — could be: process killed by OS, spawn failed before any output, or stdio mis-routed. Run "node .nha-launcher.js" manually in the project dir to reproduce.' });
|
|
292
315
|
}
|
|
293
316
|
|
|
294
|
-
// ── Tier 1: missing module →
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
317
|
+
// ── Tier 1: missing module → batch install all missing + retry ───────
|
|
318
|
+
// The pre-scan in Phase 2 catches most cases. Tier 1 here is the safety
|
|
319
|
+
// net for files generated AFTER pre-scan (e.g. user added a require()
|
|
320
|
+
// during chat) or for transitive crashes that surface only at runtime.
|
|
321
|
+
const missingModules = [...stderrBuf.matchAll(/Cannot find module ['"]([^'"]+)['"]/g)]
|
|
322
|
+
.map(m => m[1])
|
|
323
|
+
.filter(m => !m.startsWith('.') && !m.startsWith('/') && !m.startsWith('node:'))
|
|
324
|
+
.map(m => m.startsWith('@') ? m.split('/').slice(0, 2).join('/') : m.split('/')[0]);
|
|
325
|
+
const uniqueMissing = [...new Set(missingModules)].filter(m => !_SHIMMED_MODULES.has(m));
|
|
326
|
+
if (uniqueMissing.length > 0 && _attempt < MAX_RETRIES) {
|
|
327
|
+
emit({ type: 'phase', phase: 'autofix', msg: `Missing module${uniqueMissing.length === 1 ? '' : 's'}: ${uniqueMissing.join(', ')} — batch installing...` });
|
|
328
|
+
try {
|
|
329
|
+
await execAsync(`npm install --save --no-audit --no-fund ${uniqueMissing.map(m => JSON.stringify(m)).join(' ')}`, {
|
|
330
|
+
cwd: projectDir,
|
|
331
|
+
timeout: 120_000,
|
|
332
|
+
env: { ...process.env, NODE_ENV: 'development' },
|
|
333
|
+
});
|
|
334
|
+
emit({ type: 'status', msg: `Installed ${uniqueMissing.join(', ')} — retrying (attempt ${_attempt + 1}/${MAX_RETRIES})...` });
|
|
335
|
+
return this.start(projectName, projectDir, emit, _attempt + 1);
|
|
336
|
+
} catch (installErr) {
|
|
337
|
+
const diag = _classifyInstallError(installErr);
|
|
338
|
+
emit({ type: 'warn', msg: `Batch install failed — reason: ${diag.reason}. ${diag.hint}` });
|
|
339
|
+
if (diag.offlineFallback) {
|
|
340
|
+
emit({ type: 'status', msg: `Activating NHA_OFFLINE_SHIM=1 fallback — retrying with offline shim...` });
|
|
341
|
+
this._offlineShim = true;
|
|
308
342
|
return this.start(projectName, projectDir, emit, _attempt + 1);
|
|
309
|
-
} catch (installErr) {
|
|
310
|
-
emit({ type: 'warn', msg: `Failed to install ${pkgName}: ${installErr.message.slice(0, 200)}` });
|
|
311
343
|
}
|
|
312
344
|
}
|
|
345
|
+
} else if (missingModules.length > 0 && uniqueMissing.length === 0) {
|
|
346
|
+
// All missing modules are shimmable — should never reach here because
|
|
347
|
+
// the shim handles them transparently. If it does, log the surprise.
|
|
348
|
+
emit({ type: 'warn', msg: `Crash mentions modules that ARE shimmed (${missingModules.join(', ')}). The shim index.js may not have been preloaded — check .nha-launcher.js wiring.` });
|
|
313
349
|
}
|
|
314
350
|
|
|
315
351
|
// ── Tier 2: runtime errors that need code fix (require/import mismatch,
|
|
@@ -3399,7 +3435,137 @@ function _patchEntry(projectDir, entryFile, shimDir, port) {
|
|
|
3399
3435
|
return '.nha-launcher.js';
|
|
3400
3436
|
}
|
|
3401
3437
|
|
|
3402
|
-
|
|
3438
|
+
// Authoritative list of modules covered by our offline-safe shims.
|
|
3439
|
+
// Used by both the pre-scan (to skip them from npm install) and the Tier 1
|
|
3440
|
+
// retry (to detect "missing module" stderr that's already covered by a shim).
|
|
3441
|
+
export const _SHIMMED_MODULES = new Set([
|
|
3442
|
+
'pg', 'redis', 'ioredis', 'helmet', 'mongoose', 'sequelize',
|
|
3443
|
+
'dotenv', 'cors', 'morgan', 'body-parser', 'cookie-parser',
|
|
3444
|
+
'compression', 'express-rate-limit', 'jsonwebtoken', 'bcryptjs', 'bcrypt',
|
|
3445
|
+
'uuid', 'lodash', 'debug', 'chalk', 'multer', 'axios',
|
|
3446
|
+
]);
|
|
3447
|
+
|
|
3448
|
+
/** Read declared dependencies from package.json (deps + devDeps + peer). */
|
|
3449
|
+
function _declaredDeps(projectDir) {
|
|
3450
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
3451
|
+
if (!fs.existsSync(pkgPath)) return new Set();
|
|
3452
|
+
try {
|
|
3453
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
3454
|
+
return new Set([
|
|
3455
|
+
...Object.keys(pkg.dependencies || {}),
|
|
3456
|
+
...Object.keys(pkg.devDependencies || {}),
|
|
3457
|
+
...Object.keys(pkg.peerDependencies || {}),
|
|
3458
|
+
...Object.keys(pkg.optionalDependencies || {}),
|
|
3459
|
+
]);
|
|
3460
|
+
} catch { return new Set(); }
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
/** List top-level node_modules entries already on disk. */
|
|
3464
|
+
function _installedDeps(projectDir) {
|
|
3465
|
+
const nm = path.join(projectDir, 'node_modules');
|
|
3466
|
+
if (!fs.existsSync(nm)) return new Set();
|
|
3467
|
+
try {
|
|
3468
|
+
const out = new Set();
|
|
3469
|
+
for (const entry of fs.readdirSync(nm)) {
|
|
3470
|
+
if (entry.startsWith('.')) continue;
|
|
3471
|
+
if (entry.startsWith('@')) {
|
|
3472
|
+
const scopedDir = path.join(nm, entry);
|
|
3473
|
+
try {
|
|
3474
|
+
for (const sub of fs.readdirSync(scopedDir)) out.add(`${entry}/${sub}`);
|
|
3475
|
+
} catch {}
|
|
3476
|
+
} else {
|
|
3477
|
+
out.add(entry);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
return out;
|
|
3481
|
+
} catch { return new Set(); }
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
/** Walk project files and extract all bare-import module names. */
|
|
3485
|
+
export function _scanProjectImports(projectDir, maxFiles = 500, maxBytes = 200_000) {
|
|
3486
|
+
const found = new Set();
|
|
3487
|
+
const exts = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx']);
|
|
3488
|
+
const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next', 'coverage']);
|
|
3489
|
+
const reRequire = /\brequire\s*\(\s*['"]([^'".][^'"]*)['"]\s*\)/g;
|
|
3490
|
+
const reImport = /\bimport\s+(?:[^'"]*\s+from\s+)?['"]([^'".][^'"]*)['"]/g;
|
|
3491
|
+
const reImportSide = /\bimport\s*\(\s*['"]([^'".][^'"]*)['"]\s*\)/g;
|
|
3492
|
+
const stack = [projectDir];
|
|
3493
|
+
let scanned = 0;
|
|
3494
|
+
|
|
3495
|
+
while (stack.length && scanned < maxFiles) {
|
|
3496
|
+
const dir = stack.pop();
|
|
3497
|
+
let entries;
|
|
3498
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
3499
|
+
for (const entry of entries) {
|
|
3500
|
+
if (skipDirs.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
3501
|
+
const full = path.join(dir, entry.name);
|
|
3502
|
+
if (entry.isDirectory()) { stack.push(full); continue; }
|
|
3503
|
+
if (!exts.has(path.extname(entry.name))) continue;
|
|
3504
|
+
let content;
|
|
3505
|
+
try {
|
|
3506
|
+
const stat = fs.statSync(full);
|
|
3507
|
+
if (stat.size > maxBytes) continue;
|
|
3508
|
+
content = fs.readFileSync(full, 'utf-8');
|
|
3509
|
+
} catch { continue; }
|
|
3510
|
+
scanned++;
|
|
3511
|
+
for (const re of [reRequire, reImport, reImportSide]) {
|
|
3512
|
+
re.lastIndex = 0;
|
|
3513
|
+
let m;
|
|
3514
|
+
while ((m = re.exec(content)) !== null) {
|
|
3515
|
+
const spec = m[1];
|
|
3516
|
+
if (spec.startsWith('node:')) continue;
|
|
3517
|
+
const pkg = spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : spec.split('/')[0];
|
|
3518
|
+
if (pkg) found.add(pkg);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
return found;
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
/** Classify npm install errors into actionable categories. */
|
|
3527
|
+
export function _classifyInstallError(err) {
|
|
3528
|
+
const msg = String(err?.message || err?.stderr || err?.stdout || err || '').toLowerCase();
|
|
3529
|
+
if (/enotfound|etimedout|econnrefused|econnreset|network/.test(msg)) {
|
|
3530
|
+
return { reason: 'offline (npm registry unreachable)', offlineFallback: true,
|
|
3531
|
+
hint: 'Check VM network bridge/NAT, DNS, or corporate proxy (HTTP_PROXY/HTTPS_PROXY). Activating shim fallback.' };
|
|
3532
|
+
}
|
|
3533
|
+
if (/e404|notarget|not found in the npm registry/.test(msg)) {
|
|
3534
|
+
return { reason: 'package does not exist on npm', offlineFallback: false,
|
|
3535
|
+
hint: 'The package name from the LLM-generated code is likely a hallucination. Tier 2 LLM-rewrite will rename it.' };
|
|
3536
|
+
}
|
|
3537
|
+
if (/eacces|eperm|permission denied/.test(msg)) {
|
|
3538
|
+
return { reason: 'permissions denied', offlineFallback: false,
|
|
3539
|
+
hint: 'Run `sudo chown -R $USER ~/.nha` or move the project out of a root-owned directory.' };
|
|
3540
|
+
}
|
|
3541
|
+
if (/engine|unsupported.*node|requires node/.test(msg)) {
|
|
3542
|
+
return { reason: 'Node version mismatch', offlineFallback: false,
|
|
3543
|
+
hint: 'The package requires a different Node version. Check `node --version` and consider using nvm.' };
|
|
3544
|
+
}
|
|
3545
|
+
if (/eintegrity|sha-?(?:1|512) integrity|tarball/.test(msg)) {
|
|
3546
|
+
return { reason: 'package integrity failure', offlineFallback: false,
|
|
3547
|
+
hint: 'Try `rm -rf node_modules package-lock.json && npm install` to clear the cache.' };
|
|
3548
|
+
}
|
|
3549
|
+
return { reason: 'unknown', offlineFallback: false,
|
|
3550
|
+
hint: (err?.message || '').slice(0, 200) };
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
export function _writeShims(shimDir) {
|
|
3554
|
+
// ── Functional offline-safe shims for the 14 most common npm dependencies.
|
|
3555
|
+
// These are NOT no-ops: they implement enough of the real API to keep code
|
|
3556
|
+
// generated by the LLM running even when npm install is unavailable (VMs
|
|
3557
|
+
// without network, corporate proxies, CI cache misses, etc.).
|
|
3558
|
+
//
|
|
3559
|
+
// Categories:
|
|
3560
|
+
// • Storage stubs: pg, redis, ioredis, mongoose, sequelize
|
|
3561
|
+
// • Security stubs: helmet, jsonwebtoken, bcryptjs
|
|
3562
|
+
// • Express middlew: cors, morgan, body-parser, cookie-parser,
|
|
3563
|
+
// compression, express-rate-limit, multer
|
|
3564
|
+
// • Utility stubs: dotenv, uuid, lodash, debug, chalk, axios
|
|
3565
|
+
//
|
|
3566
|
+
// Every shim exports both CJS `module.exports` AND `.default` so it works
|
|
3567
|
+
// under both `require('x')` and `import x from 'x'` interop.
|
|
3568
|
+
|
|
3403
3569
|
// In-memory pg replacement
|
|
3404
3570
|
const pgShim = `
|
|
3405
3571
|
const EventEmitter = require('events');
|
|
@@ -3476,35 +3642,471 @@ handler.xssFilter = handler;
|
|
|
3476
3642
|
module.exports = handler;
|
|
3477
3643
|
`;
|
|
3478
3644
|
|
|
3479
|
-
//
|
|
3480
|
-
const
|
|
3645
|
+
// dotenv — actually parses .env files and sets process.env
|
|
3646
|
+
const dotenvShim = `
|
|
3647
|
+
const fs = require('fs');
|
|
3648
|
+
const path = require('path');
|
|
3649
|
+
function parse(src) {
|
|
3650
|
+
const out = {};
|
|
3651
|
+
const s = src.toString();
|
|
3652
|
+
const re = /^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*?)\\s*$/;
|
|
3653
|
+
for (const raw of s.split(/\\r?\\n/)) {
|
|
3654
|
+
if (!raw || raw.trim().startsWith('#')) continue;
|
|
3655
|
+
const m = raw.match(re);
|
|
3656
|
+
if (!m) continue;
|
|
3657
|
+
let v = m[2];
|
|
3658
|
+
if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) {
|
|
3659
|
+
v = v.slice(1, -1);
|
|
3660
|
+
}
|
|
3661
|
+
out[m[1]] = v;
|
|
3662
|
+
}
|
|
3663
|
+
return out;
|
|
3664
|
+
}
|
|
3665
|
+
function config(opts) {
|
|
3666
|
+
opts = opts || {};
|
|
3667
|
+
try {
|
|
3668
|
+
const p = opts.path || path.join(process.cwd(), '.env');
|
|
3669
|
+
if (!fs.existsSync(p)) return { parsed: {} };
|
|
3670
|
+
const parsed = parse(fs.readFileSync(p, 'utf-8'));
|
|
3671
|
+
for (const k of Object.keys(parsed)) {
|
|
3672
|
+
if (opts.override || !(k in process.env)) process.env[k] = parsed[k];
|
|
3673
|
+
}
|
|
3674
|
+
return { parsed };
|
|
3675
|
+
} catch (e) { return { error: e }; }
|
|
3676
|
+
}
|
|
3677
|
+
module.exports = { config, parse };
|
|
3678
|
+
module.exports.default = module.exports;
|
|
3679
|
+
`;
|
|
3680
|
+
|
|
3681
|
+
// cors — full Express middleware factory
|
|
3682
|
+
const corsShim = `
|
|
3683
|
+
function cors(opts) {
|
|
3684
|
+
opts = opts || {};
|
|
3685
|
+
const origin = opts.origin === undefined ? '*' : opts.origin;
|
|
3686
|
+
const methods = opts.methods || 'GET,HEAD,PUT,PATCH,POST,DELETE';
|
|
3687
|
+
const credentials = opts.credentials === true;
|
|
3688
|
+
const allowedHeaders = opts.allowedHeaders || 'Content-Type,Authorization';
|
|
3689
|
+
return function (req, res, next) {
|
|
3690
|
+
const o = typeof origin === 'function' ? origin(req) : origin;
|
|
3691
|
+
res.setHeader('Access-Control-Allow-Origin', Array.isArray(o) ? o.join(',') : o);
|
|
3692
|
+
res.setHeader('Access-Control-Allow-Methods', methods);
|
|
3693
|
+
res.setHeader('Access-Control-Allow-Headers', allowedHeaders);
|
|
3694
|
+
if (credentials) res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
3695
|
+
if (req.method === 'OPTIONS') { res.statusCode = 204; return res.end(); }
|
|
3696
|
+
next();
|
|
3697
|
+
};
|
|
3698
|
+
}
|
|
3699
|
+
module.exports = cors;
|
|
3700
|
+
module.exports.default = cors;
|
|
3701
|
+
`;
|
|
3702
|
+
|
|
3703
|
+
// morgan — minimal request logger
|
|
3704
|
+
const morganShim = `
|
|
3705
|
+
function morgan(format) {
|
|
3706
|
+
return function (req, res, next) {
|
|
3707
|
+
const start = Date.now();
|
|
3708
|
+
res.on('finish', () => {
|
|
3709
|
+
const ms = Date.now() - start;
|
|
3710
|
+
console.log(\`[\${new Date().toISOString()}] \${req.method} \${req.url} \${res.statusCode} \${ms}ms\`);
|
|
3711
|
+
});
|
|
3712
|
+
next();
|
|
3713
|
+
};
|
|
3714
|
+
}
|
|
3715
|
+
morgan.token = () => morgan;
|
|
3716
|
+
morgan.format = () => morgan;
|
|
3717
|
+
module.exports = morgan;
|
|
3718
|
+
module.exports.default = morgan;
|
|
3719
|
+
`;
|
|
3720
|
+
|
|
3721
|
+
// body-parser — JSON + urlencoded
|
|
3722
|
+
const bodyParserShim = `
|
|
3723
|
+
function readBody(req, limit) {
|
|
3724
|
+
return new Promise((resolve, reject) => {
|
|
3725
|
+
let data = '';
|
|
3726
|
+
let size = 0;
|
|
3727
|
+
req.on('data', (chunk) => {
|
|
3728
|
+
size += chunk.length;
|
|
3729
|
+
if (size > limit) { req.destroy(); return reject(new Error('Payload too large')); }
|
|
3730
|
+
data += chunk;
|
|
3731
|
+
});
|
|
3732
|
+
req.on('end', () => resolve(data));
|
|
3733
|
+
req.on('error', reject);
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
function jsonMw(opts) {
|
|
3737
|
+
const limit = (opts && opts.limit) ? 1024 * 1024 * 10 : 1024 * 1024;
|
|
3738
|
+
return async function (req, res, next) {
|
|
3739
|
+
if (!/json/i.test(req.headers['content-type'] || '')) return next();
|
|
3740
|
+
try { const raw = await readBody(req, limit); req.body = raw ? JSON.parse(raw) : {}; next(); }
|
|
3741
|
+
catch (e) { res.statusCode = 400; res.end('Invalid JSON'); }
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
function urlencodedMw(opts) {
|
|
3745
|
+
return async function (req, res, next) {
|
|
3746
|
+
if (!/x-www-form-urlencoded/i.test(req.headers['content-type'] || '')) return next();
|
|
3747
|
+
try {
|
|
3748
|
+
const raw = await readBody(req, 1024 * 1024);
|
|
3749
|
+
const body = {};
|
|
3750
|
+
for (const pair of raw.split('&')) {
|
|
3751
|
+
const [k, v] = pair.split('=').map(decodeURIComponent);
|
|
3752
|
+
if (k) body[k] = v || '';
|
|
3753
|
+
}
|
|
3754
|
+
req.body = body;
|
|
3755
|
+
next();
|
|
3756
|
+
} catch (e) { res.statusCode = 400; res.end('Invalid form data'); }
|
|
3757
|
+
};
|
|
3758
|
+
}
|
|
3759
|
+
const bp = { json: jsonMw, urlencoded: urlencodedMw, raw: () => (req, res, next) => next(), text: () => (req, res, next) => next() };
|
|
3760
|
+
module.exports = bp;
|
|
3761
|
+
module.exports.default = bp;
|
|
3762
|
+
`;
|
|
3763
|
+
|
|
3764
|
+
// cookie-parser
|
|
3765
|
+
const cookieParserShim = `
|
|
3766
|
+
function parse(str) {
|
|
3767
|
+
const out = {};
|
|
3768
|
+
if (!str) return out;
|
|
3769
|
+
for (const pair of str.split(';')) {
|
|
3770
|
+
const idx = pair.indexOf('=');
|
|
3771
|
+
if (idx < 0) continue;
|
|
3772
|
+
const k = pair.slice(0, idx).trim();
|
|
3773
|
+
const v = pair.slice(idx + 1).trim();
|
|
3774
|
+
out[k] = decodeURIComponent(v);
|
|
3775
|
+
}
|
|
3776
|
+
return out;
|
|
3777
|
+
}
|
|
3778
|
+
function cookieParser() {
|
|
3779
|
+
return function (req, res, next) { req.cookies = parse(req.headers.cookie || ''); next(); };
|
|
3780
|
+
}
|
|
3781
|
+
module.exports = cookieParser;
|
|
3782
|
+
module.exports.default = cookieParser;
|
|
3783
|
+
`;
|
|
3784
|
+
|
|
3785
|
+
// compression — pass-through (no-op middleware, real compression needs zlib)
|
|
3786
|
+
const compressionShim = `
|
|
3787
|
+
function compression() { return function (req, res, next) { next(); }; }
|
|
3788
|
+
module.exports = compression;
|
|
3789
|
+
module.exports.default = compression;
|
|
3790
|
+
`;
|
|
3791
|
+
|
|
3792
|
+
// express-rate-limit — in-memory bucket
|
|
3793
|
+
const rateLimitShim = `
|
|
3794
|
+
function rateLimit(opts) {
|
|
3795
|
+
opts = opts || {};
|
|
3796
|
+
const max = opts.max || 100;
|
|
3797
|
+
const windowMs = opts.windowMs || 60_000;
|
|
3798
|
+
const store = new Map();
|
|
3799
|
+
return function (req, res, next) {
|
|
3800
|
+
const key = (req.ip || req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'anon') + ':' + (req.path || req.url || '/');
|
|
3801
|
+
const now = Date.now();
|
|
3802
|
+
const rec = store.get(key) || { count: 0, reset: now + windowMs };
|
|
3803
|
+
if (now > rec.reset) { rec.count = 0; rec.reset = now + windowMs; }
|
|
3804
|
+
rec.count++;
|
|
3805
|
+
store.set(key, rec);
|
|
3806
|
+
res.setHeader('X-RateLimit-Limit', String(max));
|
|
3807
|
+
res.setHeader('X-RateLimit-Remaining', String(Math.max(0, max - rec.count)));
|
|
3808
|
+
if (rec.count > max) { res.statusCode = 429; return res.end('Too many requests'); }
|
|
3809
|
+
next();
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3812
|
+
module.exports = rateLimit;
|
|
3813
|
+
module.exports.default = rateLimit;
|
|
3814
|
+
`;
|
|
3815
|
+
|
|
3816
|
+
// jsonwebtoken — HS256 sign/verify (sandbox-grade, NOT for production secrets)
|
|
3817
|
+
const jwtShim = `
|
|
3818
|
+
const crypto = require('crypto');
|
|
3819
|
+
function b64url(buf) { return Buffer.from(buf).toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, ''); }
|
|
3820
|
+
function b64urlDecode(s) { s = s.replace(/-/g, '+').replace(/_/g, '/'); while (s.length % 4) s += '='; return Buffer.from(s, 'base64'); }
|
|
3821
|
+
function sign(payload, secret, opts) {
|
|
3822
|
+
opts = opts || {};
|
|
3823
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
3824
|
+
const p = Object.assign({}, payload);
|
|
3825
|
+
if (opts.expiresIn) {
|
|
3826
|
+
const sec = typeof opts.expiresIn === 'string' ? parseInt(opts.expiresIn) * (opts.expiresIn.endsWith('h') ? 3600 : opts.expiresIn.endsWith('d') ? 86400 : opts.expiresIn.endsWith('m') ? 60 : 1) : opts.expiresIn;
|
|
3827
|
+
p.exp = Math.floor(Date.now() / 1000) + sec;
|
|
3828
|
+
}
|
|
3829
|
+
const h = b64url(JSON.stringify(header));
|
|
3830
|
+
const b = b64url(JSON.stringify(p));
|
|
3831
|
+
const sig = b64url(crypto.createHmac('sha256', String(secret)).update(h + '.' + b).digest());
|
|
3832
|
+
return h + '.' + b + '.' + sig;
|
|
3833
|
+
}
|
|
3834
|
+
function verify(token, secret) {
|
|
3835
|
+
const parts = String(token).split('.');
|
|
3836
|
+
if (parts.length !== 3) throw new Error('jwt malformed');
|
|
3837
|
+
const [h, b, s] = parts;
|
|
3838
|
+
const expected = b64url(crypto.createHmac('sha256', String(secret)).update(h + '.' + b).digest());
|
|
3839
|
+
if (expected !== s) throw new Error('invalid signature');
|
|
3840
|
+
const payload = JSON.parse(b64urlDecode(b).toString('utf-8'));
|
|
3841
|
+
if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) throw new Error('jwt expired');
|
|
3842
|
+
return payload;
|
|
3843
|
+
}
|
|
3844
|
+
function decode(token) { try { return JSON.parse(b64urlDecode(String(token).split('.')[1]).toString('utf-8')); } catch { return null; } }
|
|
3845
|
+
module.exports = { sign, verify, decode };
|
|
3846
|
+
module.exports.default = module.exports;
|
|
3847
|
+
`;
|
|
3848
|
+
|
|
3849
|
+
// bcryptjs — pbkdf2-based, NOT real bcrypt (sandbox-grade)
|
|
3850
|
+
const bcryptShim = `
|
|
3851
|
+
const crypto = require('crypto');
|
|
3852
|
+
function hashSync(pwd, rounds) {
|
|
3853
|
+
const salt = crypto.randomBytes(16);
|
|
3854
|
+
const iter = Math.pow(2, Math.min(rounds || 10, 14));
|
|
3855
|
+
const hash = crypto.pbkdf2Sync(String(pwd), salt, iter, 32, 'sha256');
|
|
3856
|
+
return '$nha$' + iter + '$' + salt.toString('base64') + '$' + hash.toString('base64');
|
|
3857
|
+
}
|
|
3858
|
+
function compareSync(pwd, stored) {
|
|
3859
|
+
const m = String(stored).match(/^\\$nha\\$(\\d+)\\$([^$]+)\\$(.+)$/);
|
|
3860
|
+
if (!m) return false;
|
|
3861
|
+
const iter = parseInt(m[1]);
|
|
3862
|
+
const salt = Buffer.from(m[2], 'base64');
|
|
3863
|
+
const expected = Buffer.from(m[3], 'base64');
|
|
3864
|
+
const actual = crypto.pbkdf2Sync(String(pwd), salt, iter, 32, 'sha256');
|
|
3865
|
+
return crypto.timingSafeEqual(expected, actual);
|
|
3866
|
+
}
|
|
3867
|
+
async function hash(pwd, rounds) { return hashSync(pwd, rounds); }
|
|
3868
|
+
async function compare(pwd, stored) { return compareSync(pwd, stored); }
|
|
3869
|
+
function genSaltSync() { return 10; }
|
|
3870
|
+
async function genSalt() { return 10; }
|
|
3871
|
+
module.exports = { hash, hashSync, compare, compareSync, genSalt, genSaltSync };
|
|
3872
|
+
module.exports.default = module.exports;
|
|
3873
|
+
`;
|
|
3874
|
+
|
|
3875
|
+
// uuid — v4 only
|
|
3876
|
+
const uuidShim = `
|
|
3877
|
+
const crypto = require('crypto');
|
|
3878
|
+
function v4() {
|
|
3879
|
+
const b = crypto.randomBytes(16);
|
|
3880
|
+
b[6] = (b[6] & 0x0f) | 0x40;
|
|
3881
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
3882
|
+
const h = b.toString('hex');
|
|
3883
|
+
return h.slice(0, 8) + '-' + h.slice(8, 12) + '-' + h.slice(12, 16) + '-' + h.slice(16, 20) + '-' + h.slice(20);
|
|
3884
|
+
}
|
|
3885
|
+
function v1() { return v4(); }
|
|
3886
|
+
function validate(s) { return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(String(s)); }
|
|
3887
|
+
module.exports = { v1, v4, validate };
|
|
3888
|
+
module.exports.default = module.exports;
|
|
3889
|
+
`;
|
|
3481
3890
|
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3891
|
+
// lodash — minimal subset (the 95th-percentile-used functions)
|
|
3892
|
+
const lodashShim = `
|
|
3893
|
+
const _ = {};
|
|
3894
|
+
_.isArray = Array.isArray;
|
|
3895
|
+
_.isObject = (x) => x !== null && typeof x === 'object';
|
|
3896
|
+
_.isString = (x) => typeof x === 'string';
|
|
3897
|
+
_.isNumber = (x) => typeof x === 'number' && !isNaN(x);
|
|
3898
|
+
_.isFunction = (x) => typeof x === 'function';
|
|
3899
|
+
_.isEmpty = (x) => x == null || (Array.isArray(x) && x.length === 0) || (typeof x === 'object' && Object.keys(x).length === 0) || (typeof x === 'string' && x.length === 0);
|
|
3900
|
+
_.get = (obj, path, def) => {
|
|
3901
|
+
const keys = Array.isArray(path) ? path : String(path).split('.');
|
|
3902
|
+
let cur = obj;
|
|
3903
|
+
for (const k of keys) { if (cur == null) return def; cur = cur[k]; }
|
|
3904
|
+
return cur === undefined ? def : cur;
|
|
3905
|
+
};
|
|
3906
|
+
_.set = (obj, path, val) => {
|
|
3907
|
+
const keys = Array.isArray(path) ? path : String(path).split('.');
|
|
3908
|
+
let cur = obj;
|
|
3909
|
+
for (let i = 0; i < keys.length - 1; i++) { if (cur[keys[i]] == null) cur[keys[i]] = {}; cur = cur[keys[i]]; }
|
|
3910
|
+
cur[keys[keys.length - 1]] = val;
|
|
3911
|
+
return obj;
|
|
3912
|
+
};
|
|
3913
|
+
_.cloneDeep = (x) => JSON.parse(JSON.stringify(x));
|
|
3914
|
+
_.merge = (target, ...sources) => Object.assign(target, ...sources);
|
|
3915
|
+
_.pick = (obj, keys) => keys.reduce((o, k) => (k in obj ? (o[k] = obj[k], o) : o), {});
|
|
3916
|
+
_.omit = (obj, keys) => Object.keys(obj).reduce((o, k) => (!keys.includes(k) ? (o[k] = obj[k], o) : o), {});
|
|
3917
|
+
_.uniq = (arr) => [...new Set(arr)];
|
|
3918
|
+
_.uniqBy = (arr, fn) => { const seen = new Set(); return arr.filter(x => { const k = typeof fn === 'function' ? fn(x) : x[fn]; if (seen.has(k)) return false; seen.add(k); return true; }); };
|
|
3919
|
+
_.chunk = (arr, n) => { const out = []; for (let i = 0; i < arr.length; i += n) out.push(arr.slice(i, i + n)); return out; };
|
|
3920
|
+
_.flatten = (arr) => arr.flat();
|
|
3921
|
+
_.flattenDeep = (arr) => arr.flat(Infinity);
|
|
3922
|
+
_.groupBy = (arr, fn) => arr.reduce((o, x) => { const k = typeof fn === 'function' ? fn(x) : x[fn]; (o[k] = o[k] || []).push(x); return o; }, {});
|
|
3923
|
+
_.sortBy = (arr, fn) => [...arr].sort((a, b) => { const av = typeof fn === 'function' ? fn(a) : a[fn]; const bv = typeof fn === 'function' ? fn(b) : b[fn]; return av < bv ? -1 : av > bv ? 1 : 0; });
|
|
3924
|
+
_.keyBy = (arr, fn) => arr.reduce((o, x) => { o[typeof fn === 'function' ? fn(x) : x[fn]] = x; return o; }, {});
|
|
3925
|
+
_.debounce = (fn, wait) => { let t; return function (...args) { clearTimeout(t); t = setTimeout(() => fn.apply(this, args), wait); }; };
|
|
3926
|
+
_.throttle = (fn, wait) => { let last = 0; return function (...args) { const now = Date.now(); if (now - last >= wait) { last = now; fn.apply(this, args); } }; };
|
|
3927
|
+
_.range = (start, end, step) => { if (end === undefined) { end = start; start = 0; } step = step || 1; const out = []; for (let i = start; step > 0 ? i < end : i > end; i += step) out.push(i); return out; };
|
|
3928
|
+
_.sum = (arr) => arr.reduce((a, b) => a + b, 0);
|
|
3929
|
+
_.mean = (arr) => arr.length ? _.sum(arr) / arr.length : 0;
|
|
3930
|
+
_.max = (arr) => arr.length ? Math.max(...arr) : undefined;
|
|
3931
|
+
_.min = (arr) => arr.length ? Math.min(...arr) : undefined;
|
|
3932
|
+
_.capitalize = (s) => String(s).charAt(0).toUpperCase() + String(s).slice(1).toLowerCase();
|
|
3933
|
+
_.camelCase = (s) => String(s).replace(/[-_\\s]+(.)?/g, (_m, c) => c ? c.toUpperCase() : '');
|
|
3934
|
+
_.kebabCase = (s) => String(s).replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[\\s_]+/g, '-').toLowerCase();
|
|
3935
|
+
_.snakeCase = (s) => String(s).replace(/([a-z])([A-Z])/g, '$1_$2').replace(/[\\s-]+/g, '_').toLowerCase();
|
|
3936
|
+
module.exports = _;
|
|
3937
|
+
module.exports.default = _;
|
|
3938
|
+
`;
|
|
3488
3939
|
|
|
3489
|
-
//
|
|
3940
|
+
// debug — namespace-aware logger
|
|
3941
|
+
const debugShim = `
|
|
3942
|
+
const enabled = (process.env.DEBUG || '').split(',').filter(Boolean);
|
|
3943
|
+
function isEnabled(ns) { return enabled.some(p => p === '*' || ns === p || (p.endsWith('*') && ns.startsWith(p.slice(0, -1)))); }
|
|
3944
|
+
function debug(namespace) {
|
|
3945
|
+
const fn = function (...args) { if (isEnabled(namespace)) console.error('[' + namespace + ']', ...args); };
|
|
3946
|
+
fn.namespace = namespace;
|
|
3947
|
+
fn.enabled = isEnabled(namespace);
|
|
3948
|
+
fn.extend = (ns) => debug(namespace + ':' + ns);
|
|
3949
|
+
return fn;
|
|
3950
|
+
}
|
|
3951
|
+
debug.enable = (ns) => { enabled.push(...ns.split(',')); };
|
|
3952
|
+
debug.disable = () => { enabled.length = 0; };
|
|
3953
|
+
module.exports = debug;
|
|
3954
|
+
module.exports.default = debug;
|
|
3955
|
+
`;
|
|
3956
|
+
|
|
3957
|
+
// chalk — ANSI color codes
|
|
3958
|
+
const chalkShim = `
|
|
3959
|
+
const codes = { reset: [0, 0], bold: [1, 22], dim: [2, 22], italic: [3, 23], underline: [4, 24],
|
|
3960
|
+
black: [30, 39], red: [31, 39], green: [32, 39], yellow: [33, 39], blue: [34, 39], magenta: [35, 39], cyan: [36, 39], white: [37, 39], gray: [90, 39],
|
|
3961
|
+
bgBlack: [40, 49], bgRed: [41, 49], bgGreen: [42, 49], bgYellow: [43, 49], bgBlue: [44, 49] };
|
|
3962
|
+
function wrap(open, close, text) { return '\\x1b[' + open + 'm' + text + '\\x1b[' + close + 'm'; }
|
|
3963
|
+
function build(styles) {
|
|
3964
|
+
const fn = function (...args) {
|
|
3965
|
+
let s = args.join(' ');
|
|
3966
|
+
for (let i = styles.length - 1; i >= 0; i--) { const [o, c] = codes[styles[i]]; s = wrap(o, c, s); }
|
|
3967
|
+
return s;
|
|
3968
|
+
};
|
|
3969
|
+
for (const k of Object.keys(codes)) Object.defineProperty(fn, k, { get: () => build([...styles, k]) });
|
|
3970
|
+
return fn;
|
|
3971
|
+
}
|
|
3972
|
+
const chalk = build([]);
|
|
3973
|
+
chalk.level = 1;
|
|
3974
|
+
chalk.supportsColor = { level: 1, hasBasic: true };
|
|
3975
|
+
module.exports = chalk;
|
|
3976
|
+
module.exports.default = chalk;
|
|
3977
|
+
`;
|
|
3978
|
+
|
|
3979
|
+
// multer — file upload middleware (no-op, no actual disk write)
|
|
3980
|
+
const multerShim = `
|
|
3981
|
+
function multer(opts) {
|
|
3982
|
+
return {
|
|
3983
|
+
single: () => (req, res, next) => { req.file = null; next(); },
|
|
3984
|
+
array: () => (req, res, next) => { req.files = []; next(); },
|
|
3985
|
+
fields: () => (req, res, next) => { req.files = {}; next(); },
|
|
3986
|
+
any: () => (req, res, next) => { req.files = []; next(); },
|
|
3987
|
+
none: () => (req, res, next) => next(),
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
multer.diskStorage = (opts) => ({ _kind: 'disk', opts });
|
|
3991
|
+
multer.memoryStorage = () => ({ _kind: 'memory' });
|
|
3992
|
+
module.exports = multer;
|
|
3993
|
+
module.exports.default = multer;
|
|
3994
|
+
`;
|
|
3995
|
+
|
|
3996
|
+
// axios — minimal fetch-based replacement
|
|
3997
|
+
const axiosShim = `
|
|
3998
|
+
async function request(config) {
|
|
3999
|
+
const url = typeof config === 'string' ? config : config.url;
|
|
4000
|
+
const method = (typeof config === 'object' && config.method) || 'GET';
|
|
4001
|
+
const headers = (typeof config === 'object' && config.headers) || {};
|
|
4002
|
+
const body = typeof config === 'object' ? config.data : undefined;
|
|
4003
|
+
const init = { method, headers: { ...headers }, };
|
|
4004
|
+
if (body !== undefined) {
|
|
4005
|
+
if (typeof body === 'object' && !(body instanceof URLSearchParams)) {
|
|
4006
|
+
init.body = JSON.stringify(body);
|
|
4007
|
+
if (!init.headers['Content-Type']) init.headers['Content-Type'] = 'application/json';
|
|
4008
|
+
} else { init.body = body; }
|
|
4009
|
+
}
|
|
4010
|
+
const res = await fetch(url, init);
|
|
4011
|
+
const ct = res.headers.get('content-type') || '';
|
|
4012
|
+
const data = ct.includes('json') ? await res.json().catch(() => null) : await res.text();
|
|
4013
|
+
if (res.status >= 400) { const err = new Error('Request failed with status code ' + res.status); err.response = { status: res.status, data, headers: Object.fromEntries(res.headers) }; throw err; }
|
|
4014
|
+
return { data, status: res.status, statusText: res.statusText, headers: Object.fromEntries(res.headers) };
|
|
4015
|
+
}
|
|
4016
|
+
const axios = function (config) { return request(config); };
|
|
4017
|
+
axios.request = request;
|
|
4018
|
+
axios.get = (url, config) => request({ ...config, url, method: 'GET' });
|
|
4019
|
+
axios.post = (url, data, config) => request({ ...config, url, method: 'POST', data });
|
|
4020
|
+
axios.put = (url, data, config) => request({ ...config, url, method: 'PUT', data });
|
|
4021
|
+
axios.patch = (url, data, config) => request({ ...config, url, method: 'PATCH', data });
|
|
4022
|
+
axios.delete = (url, config) => request({ ...config, url, method: 'DELETE' });
|
|
4023
|
+
axios.create = (defaults) => axios;
|
|
4024
|
+
module.exports = axios;
|
|
4025
|
+
module.exports.default = axios;
|
|
4026
|
+
`;
|
|
4027
|
+
|
|
4028
|
+
// Generic no-op shim — deeply chainable proxy. Survives any .a().b.c().d
|
|
4029
|
+
// access chain because every get/apply returns another proxy of the same shape.
|
|
4030
|
+
const noopShim = `
|
|
4031
|
+
const noop = function () {};
|
|
4032
|
+
const handler = {
|
|
4033
|
+
get(target, prop) {
|
|
4034
|
+
if (prop === 'then' || prop === Symbol.toPrimitive || prop === Symbol.iterator) return undefined;
|
|
4035
|
+
if (prop === 'default') return proxy;
|
|
4036
|
+
if (prop === 'toString') return () => '[nha-noop]';
|
|
4037
|
+
return proxy;
|
|
4038
|
+
},
|
|
4039
|
+
apply() { return proxy; },
|
|
4040
|
+
construct() { return proxy; },
|
|
4041
|
+
};
|
|
4042
|
+
const proxy = new Proxy(noop, handler);
|
|
4043
|
+
module.exports = proxy;
|
|
4044
|
+
module.exports.default = proxy;
|
|
4045
|
+
`;
|
|
4046
|
+
|
|
4047
|
+
const shimFiles = {
|
|
4048
|
+
'pg.js': pgShim,
|
|
4049
|
+
'redis.js': redisShim,
|
|
4050
|
+
'ioredis.js': redisShim,
|
|
4051
|
+
'helmet.js': helmetShim,
|
|
4052
|
+
'mongoose.js': noopShim,
|
|
4053
|
+
'sequelize.js': noopShim,
|
|
4054
|
+
'dotenv.js': dotenvShim,
|
|
4055
|
+
'cors.js': corsShim,
|
|
4056
|
+
'morgan.js': morganShim,
|
|
4057
|
+
'body-parser.js': bodyParserShim,
|
|
4058
|
+
'cookie-parser.js': cookieParserShim,
|
|
4059
|
+
'compression.js': compressionShim,
|
|
4060
|
+
'express-rate-limit.js': rateLimitShim,
|
|
4061
|
+
'jsonwebtoken.js': jwtShim,
|
|
4062
|
+
'bcryptjs.js': bcryptShim,
|
|
4063
|
+
'bcrypt.js': bcryptShim,
|
|
4064
|
+
'uuid.js': uuidShim,
|
|
4065
|
+
'lodash.js': lodashShim,
|
|
4066
|
+
'debug.js': debugShim,
|
|
4067
|
+
'chalk.js': chalkShim,
|
|
4068
|
+
'multer.js': multerShim,
|
|
4069
|
+
'axios.js': axiosShim,
|
|
4070
|
+
'noop.js': noopShim,
|
|
4071
|
+
};
|
|
4072
|
+
for (const [name, content] of Object.entries(shimFiles)) {
|
|
4073
|
+
fs.writeFileSync(path.join(shimDir, name), content, 'utf-8');
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
// Shim index — only activates a shim if the REAL package is missing.
|
|
4077
|
+
// This lets a project that has its own dotenv/cors/etc. installed use the
|
|
4078
|
+
// real implementation, and only falls back to our shim when resolution fails.
|
|
4079
|
+
// Also supports NHA_OFFLINE_SHIM=1 to no-op any unresolvable module.
|
|
4080
|
+
const shimList = JSON.stringify(Object.keys(shimFiles).filter(f => f !== 'noop.js').map(f => f.replace(/\.js$/, '')));
|
|
3490
4081
|
const shimIndex = `
|
|
3491
4082
|
const Module = require('module');
|
|
3492
4083
|
const path = require('path');
|
|
3493
4084
|
const __shimDir = ${JSON.stringify(shimDir)};
|
|
4085
|
+
const SHIM_NAMES = new Set(${shimList});
|
|
4086
|
+
const ALIAS = { 'bcrypt': 'bcryptjs' };
|
|
4087
|
+
const OFFLINE = process.env.NHA_OFFLINE_SHIM === '1';
|
|
4088
|
+
const _original = Module._resolveFilename.bind(Module);
|
|
3494
4089
|
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
'helmet': path.join(__shimDir, 'helmet.js'),
|
|
3500
|
-
'mongoose': path.join(__shimDir, 'mongoose.js'),
|
|
3501
|
-
'sequelize': path.join(__shimDir, 'sequelize.js'),
|
|
3502
|
-
};
|
|
4090
|
+
function shimPath(name) {
|
|
4091
|
+
const f = (ALIAS[name] || name) + '.js';
|
|
4092
|
+
return path.join(__shimDir, f);
|
|
4093
|
+
}
|
|
3503
4094
|
|
|
3504
|
-
const _original = Module._resolveFilename.bind(Module);
|
|
3505
4095
|
Module._resolveFilename = function(request, parent, isMain, options) {
|
|
3506
|
-
|
|
3507
|
-
|
|
4096
|
+
try {
|
|
4097
|
+
return _original(request, parent, isMain, options);
|
|
4098
|
+
} catch (err) {
|
|
4099
|
+
if (err && err.code === 'MODULE_NOT_FOUND') {
|
|
4100
|
+
if (SHIM_NAMES.has(request) || ALIAS[request]) {
|
|
4101
|
+
return shimPath(request);
|
|
4102
|
+
}
|
|
4103
|
+
if (OFFLINE && !request.startsWith('.') && !request.startsWith('/') && !request.startsWith('node:')) {
|
|
4104
|
+
try { console.error('[nha-shim] offline-noop for missing module: ' + request); } catch {}
|
|
4105
|
+
return path.join(__shimDir, 'noop.js');
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
throw err;
|
|
4109
|
+
}
|
|
3508
4110
|
};
|
|
3509
4111
|
`;
|
|
3510
4112
|
fs.writeFileSync(path.join(shimDir, 'index.js'), shimIndex, 'utf-8');
|