#!/usr/bin/env node import { createHash } from "node:crypto"; import { execFile } from "node:child_process"; import { access, copyFile, mkdir, mkdtemp, readFile, rename, rm, writeFile, } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { fileURLToPath } from "node:url"; const execute = promisify(execFile); const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const details = JSON.parse( await readFile(path.join(root, "package.json"), "utf8"), ); const filename = `add-ideas-toolbox-helpers-${details.version}.tgz`; const output = path.join(root, "release", filename); const checksumOutput = `${output}.sha256`; const force = process.argv.includes("--force"); const exists = (file) => access(file).then( () => true, () => false, ); if (!force && ((await exists(output)) || (await exists(checksumOutput)))) throw new Error( "Library package output already exists; use --force to replace it", ); for (const required of [ "lib-dist/index.js", "lib-dist/types/index.d.ts", "LICENSE", "README.md", "SOURCE.md", ]) await access(path.join(root, required)); await mkdir(path.dirname(output), { recursive: true }); const staging = await mkdtemp(path.join(os.tmpdir(), "helper-npm-pack-")); try { const { stdout } = await execute( "npm", ["pack", "--json", "--pack-destination", staging], { cwd: root, env: { ...process.env, TZ: "UTC", SOURCE_DATE_EPOCH: "315532800" }, maxBuffer: 4 * 1024 * 1024, }, ); const packed = JSON.parse(stdout)[0]; if (packed?.filename !== filename) throw new Error(`Unexpected npm package name: ${packed?.filename}`); const packedPaths = new Set( Array.isArray(packed?.files) ? packed.files.map((entry) => entry?.path).filter(Boolean) : [], ); for (const required of [ "LICENSE", "README.md", "SOURCE.md", "SECURITY.md", "THIRD_PARTY_NOTICES.md", "docs/API.md", "lib-dist/index.js", "lib-dist/types/index.d.ts", ]) { if (!packedPaths.has(required)) throw new Error(`npm package is missing required file: ${required}`); } const staged = path.join(staging, filename); const archive = await readFile(staged); const digest = createHash("sha256").update(archive).digest("hex"); const tempOutput = `${output}.new`; const tempChecksum = `${checksumOutput}.new`; await copyFile(staged, tempOutput); await writeFile(tempChecksum, `${digest} ${filename}\n`, { mode: 0o644 }); if (force) { await rm(output, { force: true }); await rm(checksumOutput, { force: true }); } await rename(tempOutput, output); await rename(tempChecksum, checksumOutput); console.log( `Created release/${filename} (${archive.byteLength} bytes)\nSHA-256 ${digest}`, ); } finally { await rm(staging, { recursive: true, force: true }); }