clank-together/tools/build-td/build.mjs
clank-bot 022e0a2c78 Ragnarok rodecomp deployment on memento
Changes to upstream:
- git.js: coordinator commit identity from TD_GIT_AUTHOR_NAME/EMAIL.
- build.js: docker runner can share job dirs through a named volume
  (jobVolume/jobVolumeRoot) for containerized workers.
- install.js: staged installer output with logs, start prompt for regions.
- web: feed layout, cache-busting asset versions, multi-agent help section.
- build-td: fetch checksummed Windows runtime when building on non-Windows.

Additions:
- deploy/rodecomp: compose stack, Dockerfiles, Caddyfile, server.json,
  bulk import/verify scripts, and README.md with the deployment notes
  (the root README stays identical to upstream).
- toolchain/msvc-v142: MSVC v142 under Wine (Docker), SDK 10.0.19041.0.
- tools/rodecomp: project preparation, Forgejo provisioning, history and
  identity tools.
2026-10-25 14:40:13 +01:00

88 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
// Builds `td` as standalone executables (Node single executable applications): contributors need
// no Node.js. Output in dist/: td-windows-x64.exe, td-linux-x64, SHA256SUMS.
//
// node tools/build-td/build.mjs
//
// Needs network access once (esbuild and postject through npx, the Linux Node runtime from
// nodejs.org, checked against its published SHA256SUMS). Uses the Node runtime that runs this
// script for Windows, so build with the same Node version as NODE_VERSION.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
const DIST = path.join(ROOT, 'dist');
const NODE_VERSION = 'v22.21.0';
const FUSE = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2';
const SCRIPTS = ['ApplySymbols.java', 'DecompileAt.java', 'DisassembleAt.java', 'ExportFunctions.java', 'ExportOriginals.java'];
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
const sh = (cmd, args) => execFileSync(cmd, args, { cwd: ROOT, stdio: 'inherit', shell: process.platform === 'win32' });
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
if (process.version !== NODE_VERSION) throw new Error(`build with Node ${NODE_VERSION} (this is ${process.version})`);
fs.rmSync(DIST, { recursive: true, force: true });
fs.mkdirSync(DIST, { recursive: true });
// 1. One CommonJS file (single executable applications run a CommonJS script).
sh(npx, ['--yes', 'esbuild@0.24.0', 'src/client/cli.js', '--bundle', '--platform=node', '--target=node22', '--format=cjs', '--log-level=warning', `--outfile=${path.join(DIST, 'td.cjs')}`]);
// 2. The preparation blob: code plus the embedded Ghidra scripts. No code cache or snapshot, so
// the same blob works for every platform's Node runtime.
const config = {
main: path.join(DIST, 'td.cjs'),
output: path.join(DIST, 'td.blob'),
disableExperimentalSEAWarning: true,
useCodeCache: false,
useSnapshot: false,
assets: Object.fromEntries(SCRIPTS.map((s) => [s, path.join(ROOT, 'ghidra_scripts', s)])),
};
fs.writeFileSync(path.join(DIST, 'sea-config.json'), JSON.stringify(config, null, 2));
execFileSync(process.execPath, ['--experimental-sea-config', path.join(DIST, 'sea-config.json')], { stdio: 'inherit' });
// 3. Runtimes: this Node for Windows (when building on Windows), the official Linux build otherwise.
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'td-build-'));
const targets = [];
if (process.platform === 'win32') {
const exe = path.join(DIST, 'td-windows-x64.exe');
fs.copyFileSync(process.execPath, exe);
targets.push(exe);
} else {
const base = `https://nodejs.org/dist/${NODE_VERSION}`;
const sums = await (await fetch(`${base}/SHASUMS256.txt`)).text();
const want = /^([0-9a-f]{64})\s+win-x64\/node\.exe$/m.exec(sums)?.[1];
if (!want) throw new Error('no checksum for the Windows runtime');
const exe = path.join(DIST, 'td-windows-x64.exe');
fs.writeFileSync(exe, Buffer.from(await (await fetch(`${base}/win-x64/node.exe`)).arrayBuffer()));
if (sha256(exe) !== want) throw new Error('Windows runtime checksum mismatch');
targets.push(exe);
}
{
const name = `node-${NODE_VERSION}-linux-x64`;
const base = `https://nodejs.org/dist/${NODE_VERSION}`;
const sums = await (await fetch(`${base}/SHASUMS256.txt`)).text();
const want = new RegExp(`^([0-9a-f]{64})\\s+${name}\\.tar\\.gz$`, 'm').exec(sums)?.[1];
if (!want) throw new Error('no checksum for the Linux runtime');
const tgz = path.join(tmp, `${name}.tar.gz`);
fs.writeFileSync(tgz, Buffer.from(await (await fetch(`${base}/${name}.tar.gz`)).arrayBuffer()));
if (sha256(tgz) !== want) throw new Error('Linux runtime checksum mismatch');
execFileSync(process.platform === 'win32' ? path.join(process.env.SystemRoot, 'System32', 'tar.exe') : 'tar', ['-xzf', tgz, '-C', tmp, `${name}/bin/node`], { stdio: 'inherit' });
const bin = path.join(DIST, 'td-linux-x64');
fs.copyFileSync(path.join(tmp, name, 'bin', 'node'), bin);
targets.push(bin);
}
// 4. Inject the blob into each runtime.
for (const t of targets) {
const args = ['--yes', 'postject@1.0.0-alpha.6', t, 'NODE_SEA_BLOB', config.output, '--sentinel-fuse', FUSE];
sh(npx, args);
}
fs.writeFileSync(path.join(DIST, 'SHA256SUMS'), targets.map((t) => `${sha256(t)} ${path.basename(t)}`).join('\n') + '\n');
for (const f of ['td.cjs', 'td.blob', 'sea-config.json']) fs.rmSync(path.join(DIST, f));
fs.rmSync(tmp, { recursive: true, force: true });
console.log(fs.readFileSync(path.join(DIST, 'SHA256SUMS'), 'utf8'));