| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113 |
- #!/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('<!')) {
- throw new Error('Registry returned HTML instead of tarball.');
- }
- if (tgz[0] !== 0x1f || tgz[1] !== 0x8b) {
- throw new Error('Downloaded file is not gzip.');
- }
- if (!fs.existsSync(nodeModulesDir)) fs.mkdirSync(nodeModulesDir, { recursive: true });
- const extractDir = path.join(nodeDir, 'npm-extract');
- if (fs.existsSync(extractDir)) { try { fs.rmSync(extractDir, { recursive: true }); } catch (_) {} }
- fs.mkdirSync(extractDir, { recursive: true });
- const tar = zlib.gunzipSync(tgz);
- extractTar(tar, extractDir);
- const pkgDir = path.join(extractDir, 'package');
- if (fs.existsSync(npmDir)) fs.rmSync(npmDir, { recursive: true });
- fs.renameSync(fs.existsSync(pkgDir) ? pkgDir : path.join(extractDir, fs.readdirSync(extractDir)[0]), npmDir);
- try { fs.rmSync(extractDir, { recursive: true }); } catch (_) {}
- console.log('[OK] npm installed to node_modules');
- }
- main().catch((err) => { console.error(err); process.exit(1); });
|