#!/usr/bin/env node import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { chmodSync, cpSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { pathToFileURL } from "node:url"; import { binaryAssets, setBinaryVersion, validateBinaryAssets, } from "../packages/coding-agent/scripts/copy-binary-assets.mjs"; import { releasePlatforms } from "./release-platforms.mjs"; const platforms = releasePlatforms; export function assembleBinaryArchives({ binaryDir, artifactsDir, version, requireAll = true }) { if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) throw new Error(`Invalid binary version: ${version}`); const available = readdirSync(binaryDir); const targets = platforms.filter((platform) => available.includes(platform)); if (targets.length === 0 || (requireAll && targets.length !== platforms.length)) { throw new Error(`Missing compiled binaries; build all ${platforms.length} platforms before packing a release`); } mkdirSync(artifactsDir, { recursive: true }); const archives = []; for (const platform of targets) { const source = join(binaryDir, platform); validateBinaryAssets(source); const binary = join(source, "prime-agent"); if (!statSync(binary).isFile() || !(statSync(binary).mode & 0o111)) throw new Error(`Missing executable: ${binary}`); const staging = mkdtempSync(join(tmpdir(), "prime-agent-archive-")); try { for (const name of ["prime-agent", ...binaryAssets]) cpSync(join(source, name), join(staging, name), { recursive: true }); setBinaryVersion(staging, version); chmodSync(join(staging, "prime-agent"), 0o755); const file = `prime-agent-${version}-${platform}.tar.gz`; const output = join(artifactsDir, file); execFileSync("tar", ["-czf", output, "-C", staging, "prime-agent", ...binaryAssets], { env: { ...process.env, COPYFILE_DISABLE: "1" }, }); archives.push({ platform, file, sha256: createHash("sha256").update(readFileSync(output)).digest("hex"), executableSha256: createHash("sha256").update(readFileSync(binary)).digest("hex"), }); } finally { rmSync(staging, { recursive: true, force: true }); } } return archives; } if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) { const [binaryDir, artifactsDir, version] = process.argv.slice(2); if (!binaryDir || !artifactsDir || !version) throw new Error("Usage: node scripts/assemble-release-archives.mjs "); const archives = assembleBinaryArchives({ binaryDir: resolve(binaryDir), artifactsDir: resolve(artifactsDir), version, requireAll: false, }); writeFileSync(join(artifactsDir, "SHA256SUMS"), archives.map(({ sha256, file }) => `${sha256} ${file}\n`).join("")); writeFileSync( join(artifactsDir, "binaries.json"), `${JSON.stringify({ version: `v${version}`, binaries: archives }, null, 2)}\n`, ); }