// usage: node editor/toolchain.mjs // Row 154 (#1648, Q168): is the pinned esbuild installed, and is it the pinned binary? // The editor bundle is only worth re-checking with the esbuild it was built with. Install it in packages/webui with // `npm ci --ignore-scripts --no-audit --no-fund` (package-lock.json is the record). This file never imports esbuild, so // it runs, and says why, when esbuild is not installed. // absent esbuild is not installed here, or it is another version (the root install has one that comes in through // pi-coding-agent and changes when pi changes). A routine run skips the rebuild and prints the reason. // With MOSAIC_REQUIRE_EDITOR_BUILD=1 it is a failure instead; the landing gate sets that. // refused esbuild is the pinned version but not the pinned binary (sha256 differs, no sha recorded for this // platform, or ESBUILD_BINARY_PATH points somewhere else). Always a failure. // Exit: 0 usable, or absent without the switch (prints "skip: "); 3 absent with the switch; 4 refused. import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; export const ESBUILD = '0.28.2'; // sha256 of node_modules/@esbuild/-/bin/esbuild in 0.28.2. Add a platform only after checking it // against the registry tarball; a platform with no entry here is refused, not trusted. export const BINARY_SHA256 = { 'linux-x64': 'e1698a3d5c6c0798fee4fd3b5cc816651f460c63d390a7a26ea4beb0b1884100', }; const sha256 = buf => createHash('sha256').update(buf).digest('hex'); export function toolchain(from = import.meta.url) { const here = createRequire(from); let pkgPath; try { pkgPath = here.resolve('esbuild/package.json'); } catch { return { ok: false, kind: 'absent', reason: `esbuild is not installed; the editor rebuild needs exactly ${ESBUILD} (npm ci --ignore-scripts in packages/webui)` }; } const { version } = JSON.parse(readFileSync(pkgPath, 'utf8')); if (version !== ESBUILD) return { ok: false, kind: 'absent', reason: `esbuild ${version} is installed at ${dirname(pkgPath)}; the editor rebuild needs exactly ${ESBUILD} (npm ci --ignore-scripts in packages/webui)` }; if (process.env.ESBUILD_BINARY_PATH) return { ok: false, kind: 'refused', reason: 'ESBUILD_BINARY_PATH is set, so esbuild would run a binary that was not checked' }; const platform = `${process.platform}-${process.arch}`, expected = BINARY_SHA256[platform]; if (!expected) return { ok: false, kind: 'refused', reason: `no binary sha256 is recorded for ${platform}` }; let binary; try { binary = createRequire(pkgPath).resolve(`@esbuild/${platform}/bin/esbuild`); } catch { return { ok: false, kind: 'refused', reason: `esbuild ${version} is installed without its @esbuild/${platform} binary` }; } const actual = sha256(readFileSync(binary)); if (actual !== expected) return { ok: false, kind: 'refused', reason: `the esbuild binary ${binary} is sha256 ${actual}; the pinned binary for ${platform} is ${expected}` }; return { ok: true, version, binary, sha256: actual }; } // MOSAIC_REQUIRE_EDITOR_BUILD: "1" requires the rebuild; unset, "" and "0" do not. Anything else throws, so a typo // such as "true" cannot quietly turn the requirement off. export function required(env = process.env) { const v = env.MOSAIC_REQUIRE_EDITOR_BUILD; if (v === undefined || v === '' || v === '0') return false; if (v === '1') return true; throw new Error(`MOSAIC_REQUIRE_EDITOR_BUILD is "${v}"; it is 1 (require the editor rebuild) or unset`); } // run: rebuild and compare. skip: say why and go on. fail: stop. export const verdict = (t, req) => (t.ok ? 'run' : t.kind === 'absent' && !req ? 'skip' : 'fail'); if (process.argv[1] === fileURLToPath(import.meta.url)) { const t = toolchain(), v = verdict(t, required()); if (v === 'run') console.log(`esbuild ${t.version} ${t.binary} sha256 ${t.sha256}`); else if (v === 'skip') console.log(`skip: ${t.reason}`); else { console.error(`${t.kind === 'absent' ? 'missing' : 'refused'}: ${t.reason}`); process.exit(t.kind === 'absent' ? 3 : 4); } }