bootstrap-npm.js 3.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. #!/usr/bin/env node
  2. /**
  3. * 安装 npm 到 nodejs/node/node_modules(当 node 自带 npm 缺失时使用)
  4. */
  5. const fs = require('fs');
  6. const path = require('path');
  7. const https = require('https');
  8. const zlib = require('zlib');
  9. const NPM_REGISTRY = 'https://registry.npmmirror.com';
  10. const nodeDir = path.join(__dirname, 'node');
  11. const nodeModulesDir = path.join(nodeDir, 'node_modules');
  12. const npmDir = path.join(nodeModulesDir, 'npm');
  13. if (fs.existsSync(path.join(npmDir, 'bin', 'npm-cli.js'))) {
  14. console.log('[OK] npm already present in node_modules');
  15. process.exit(0);
  16. }
  17. function get(url) {
  18. return new Promise((resolve, reject) => {
  19. const req = https.get(url, { headers: { 'User-Agent': 'Node/bootstrap-npm' } }, (res) => {
  20. if ([301, 302, 307, 308].includes(res.statusCode) && res.headers.location) {
  21. const loc = res.headers.location;
  22. return get(loc.startsWith('http') ? loc : new URL(loc, url).href).then(resolve).catch(reject);
  23. }
  24. const chunks = [];
  25. res.on('data', (c) => chunks.push(c));
  26. res.on('end', () => resolve(Buffer.concat(chunks)));
  27. res.on('error', reject);
  28. });
  29. req.on('error', reject);
  30. });
  31. }
  32. function extractTar(buffer, outDir) {
  33. let offset = 0;
  34. while (offset + 512 <= buffer.length) {
  35. const header = buffer.slice(offset, offset + 512);
  36. if (header.every(b => b === 0)) break;
  37. const name = header.slice(0, 100).toString('utf8').replace(/\0/g, '');
  38. const size = parseInt(header.slice(124, 136).toString('utf8').trim(), 8) || 0;
  39. const typeflag = (header[156] && header[156] !== 0) ? String.fromCharCode(header[156]) : '0';
  40. offset += 512;
  41. const content = buffer.slice(offset, offset + size);
  42. offset += Math.ceil(size / 512) * 512;
  43. if (!name || name.includes('..')) continue;
  44. const dest = path.join(outDir, name);
  45. if (typeflag === '5' || name.endsWith('/')) {
  46. if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
  47. } else if (size >= 0) {
  48. const dir = path.dirname(dest);
  49. if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
  50. fs.writeFileSync(dest, content);
  51. }
  52. }
  53. }
  54. async function main() {
  55. if (!fs.existsSync(path.join(nodeDir, 'node.exe'))) {
  56. console.error('[X] nodejs/node/node.exe not found');
  57. process.exit(1);
  58. }
  59. console.log('Fetching npm metadata from registry...');
  60. const meta = JSON.parse((await get(`${NPM_REGISTRY}/npm/latest`)).toString());
  61. const version = meta.version;
  62. const tarball = meta.dist?.tarball || `${NPM_REGISTRY}/npm/-/npm-${version}.tgz`;
  63. console.log('Downloading npm@' + version + '...');
  64. const tgz = await get(tarball);
  65. if (tgz.length < 1000 && tgz.toString().includes('<!')) {
  66. throw new Error('Registry returned HTML instead of tarball.');
  67. }
  68. if (tgz[0] !== 0x1f || tgz[1] !== 0x8b) {
  69. throw new Error('Downloaded file is not gzip.');
  70. }
  71. if (!fs.existsSync(nodeModulesDir)) fs.mkdirSync(nodeModulesDir, { recursive: true });
  72. const extractDir = path.join(nodeDir, 'npm-extract');
  73. if (fs.existsSync(extractDir)) { try { fs.rmSync(extractDir, { recursive: true }); } catch (_) {} }
  74. fs.mkdirSync(extractDir, { recursive: true });
  75. const tar = zlib.gunzipSync(tgz);
  76. extractTar(tar, extractDir);
  77. const pkgDir = path.join(extractDir, 'package');
  78. if (fs.existsSync(npmDir)) fs.rmSync(npmDir, { recursive: true });
  79. fs.renameSync(fs.existsSync(pkgDir) ? pkgDir : path.join(extractDir, fs.readdirSync(extractDir)[0]), npmDir);
  80. try { fs.rmSync(extractDir, { recursive: true }); } catch (_) {}
  81. console.log('[OK] npm installed to node_modules');
  82. }
  83. main().catch((err) => { console.error(err); process.exit(1); });