// Télécharge un runtime Node standalone (>= 22.16, épinglé) dans build/node, avec vérification // SHA256. Le daemon tourne SUR ce Node (pas celui d'Electron) pour garantir node:sqlite sans flag // et l'ABI node-pty attendue (prefixe `node.`). Options : --platform / --arch (défaut : hôte). import { execFileSync } from 'node:child_process'; import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'; import { createHash } from 'node:crypto'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const NODE_VERSION = '22.21.1'; const HERE = dirname(fileURLToPath(import.meta.url)); const BUILD = join(HERE, '..', 'build'); const NODE_DIR = join(BUILD, 'node'); const args = process.argv.slice(2); const arg = (name, def) => args.find((a) => a.startsWith(`--${name}=`))?.split('=')[1] ?? def; const platform = arg('platform', process.platform); const arch = arg('arch', process.arch); const OS = { linux: 'linux', darwin: 'darwin', win32: 'win' }[platform]; if (!OS) throw new Error(`plateforme non supportee: ${platform}`); const ext = platform === 'win32' ? 'zip' : 'tar.xz'; const name = `node-v${NODE_VERSION}-${OS}-${arch}`; const base = `https://nodejs.org/dist/v${NODE_VERSION}`; async function get(url) { const res = await fetch(url); if (!res.ok) throw new Error(`GET ${url} -> ${res.status}`); return res; } rmSync(NODE_DIR, { recursive: true, force: true }); mkdirSync(NODE_DIR, { recursive: true }); const tarball = Buffer.from(await (await get(`${base}/${name}.${ext}`)).arrayBuffer()); const shasums = await (await get(`${base}/SHASUMS256.txt`)).text(); const expected = shasums.split('\n').find((l) => l.endsWith(`${name}.${ext}`))?.split(/\s+/)[0]; const actual = createHash('sha256').update(tarball).digest('hex'); if (!expected) throw new Error(`SHA introuvable pour ${name}.${ext}`); if (expected !== actual) throw new Error(`SHA256 mismatch pour ${name}.${ext}`); const archive = join(BUILD, `${name}.${ext}`); writeFileSync(archive, tarball); // `tar` de Windows 10+ (bsdtar) lit aussi les .zip : une seule commande pour les trois plateformes, // là où `unzip` n'existe pas sur un Windows standard. execFileSync('tar', [ext === 'zip' ? '-xf' : '-xJf', archive, '-C', BUILD], { stdio: 'inherit' }); // Aplatir node-vX-os-arch/ -> build/node/ avec l'API Node (l'ancien `bash -c 'cp -R … && rm -rf …'` // rendait ce script inexécutable sur Windows, où il n'y a ni bash, ni cp, ni rm). const extracted = join(BUILD, name); cpSync(extracted, NODE_DIR, { recursive: true }); rmSync(extracted, { recursive: true, force: true }); rmSync(archive, { force: true }); // --- élagage --------------------------------------------------------------------------------- // On n'embarque QUE de quoi exécuter le daemon. La distribution complète pèse ~205 Mo, dont l'essentiel // est inutile ici : en-têtes de compilation, docs, et surtout npm/corepack (le `npm install --omit=dev` // du daemon a lieu au BUILD, jamais au runtime). const PRUNE = ['include', 'share', 'lib', 'CHANGELOG.md', 'README.md']; for (const rel of PRUNE) rmSync(join(NODE_DIR, rel), { recursive: true, force: true }); // les shims npm/npx/corepack (POSIX : bin/, Windows : racine) for (const shim of ['npm', 'npx', 'corepack', 'npm.cmd', 'npx.cmd', 'corepack.cmd', 'npm.ps1', 'npx.ps1', 'corepack.ps1']) { rmSync(join(NODE_DIR, 'bin', shim), { force: true }); rmSync(join(NODE_DIR, shim), { force: true }); } // Garde-fou : le binaire doit avoir survécu à l'élagage. const nodeBin = platform === 'win32' ? join(NODE_DIR, 'node.exe') : join(NODE_DIR, 'bin', 'node'); if (!existsSync(nodeBin)) throw new Error(`binaire Node introuvable apres extraction: ${nodeBin}`); console.log(`fetch-node: Node ${NODE_VERSION} (${OS}-${arch}) -> build/node (${duMb(NODE_DIR)} Mo)`); /** Taille approximative d'un dossier, en Mo (diagnostic de l'élagage). */ function duMb(dir) { let total = 0; const walk = (d) => { for (const entry of readdirSync(d, { withFileTypes: true })) { const p = join(d, entry.name); if (entry.isDirectory()) walk(p); else if (entry.isFile()) total += statSync(p).size; } }; walk(dir); return Math.round(total / 1024 / 1024); }