#!/usr/bin/env node /** * 安装 npm 到 nodejs/node/node_modules(当 node 自带 npm 缺失时使用) * 优先从 nodejs/backup/npm 拷贝;备份不可用再从 registry 下载。 */ const fs = require('fs'); const path = require('path'); const https = require('https'); const zlib = require('zlib'); const NPM_REGISTRY = 'https://registry.npmmirror.com'; const nodeDir = path.join(__dirname, 'node'); const nodeModulesDir = path.join(nodeDir, 'node_modules'); const npmDir = path.join(nodeModulesDir, 'npm'); const npmBackupDir = path.join(__dirname, 'backup', 'npm'); function isNpmBundleComplete(dir) { if (!fs.existsSync(path.join(dir, 'bin', 'npm-cli.js'))) return false; // npm's own lib/cli/entry.js requires graceful-fs from npm/node_modules return fs.existsSync(path.join(dir, 'node_modules', 'graceful-fs', 'package.json')); } function get(url) { return new Promise((resolve, reject) => { const req = https.get(url, { headers: { 'User-Agent': 'Node/bootstrap-npm' } }, (res) => { if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) { const loc = res.headers.location; return get(loc.startsWith('http') ? loc : new URL(loc, url).href).then(resolve).catch(reject); } const chunks = []; res.on('data', (c) => chunks.push(c)); res.on('end', () => resolve(Buffer.concat(chunks))); res.on('error', reject); }); req.on('error', reject); }); } function extractTar(buffer, outDir) { let offset = 0; while (offset + 512 <= buffer.length) { const header = buffer.slice(offset, offset + 512); if (header.every(b => b === 0)) break; const name = header.slice(0, 100).toString('utf8').replace(/\0/g, ''); const size = parseInt(header.slice(124, 136).toString('utf8').trim(), 8) || 0; const typeflag = (header[156] && header[156] !== 0) ? String.fromCharCode(header[156]) : '0'; offset += 512; const content = buffer.slice(offset, offset + size); offset += Math.ceil(size / 512) * 512; if (!name || name.includes('..')) continue; const dest = path.join(outDir, name); if (typeflag === '5' || name.endsWith('/')) { if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true }); } else if (size >= 0) { const dir = path.dirname(dest); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(dest, content); } } } async function main() { if (!fs.existsSync(path.join(nodeDir, 'node.exe'))) { console.error('[X] nodejs/node/node.exe not found'); process.exit(1); } if (isNpmBundleComplete(npmDir)) { console.log('[OK] npm already present in node_modules'); return; } console.log('[i] npm install order: (1) copy from nodejs/backup/npm if complete (2) else download from registry'); if (fs.existsSync(npmDir)) { console.log('[WARN] npm folder missing or incomplete; will replace...'); try { fs.rmSync(npmDir, { recursive: true, force: true }); } catch (e) { console.error('[X] Could not remove broken npm folder:', npmDir, e.message); process.exit(1); } } if (isNpmBundleComplete(npmBackupDir)) { if (!fs.existsSync(nodeModulesDir)) fs.mkdirSync(nodeModulesDir, { recursive: true }); fs.cpSync(npmBackupDir, npmDir, { recursive: true }); console.log('[OK] npm restored from nodejs/backup/npm (no registry download)'); return; } console.log('[WARN] nodejs/backup/npm missing or incomplete; fetching npm from registry...'); console.log('Fetching npm metadata from registry...'); const meta = JSON.parse((await get(`${NPM_REGISTRY}/npm/latest`)).toString()); const version = meta.version; const tarball = meta.dist?.tarball || `${NPM_REGISTRY}/npm/-/npm-${version}.tgz`; console.log('Downloading npm@' + version + '...'); const tgz = await get(tarball); if (tgz.length < 1000 && tgz.toString().includes(' { console.error(err); process.exit(1); });