From 0be69750ef0f489adb46010f892f3928951f10fe Mon Sep 17 00:00:00 2001 From: thesiti92 Date: Mon, 28 Sep 2026 22:45:30 -0400 Subject: [PATCH] Publish CLI-only archives for npm binary downloads AI assistance: implemented and validated with OpenAI Codex. --- .github/workflows/release.yml | 8 ++++++-- diffr-ts/README.md | 3 ++- diffr-ts/bin/fetch.mjs | 4 +++- diffr-ts/scripts/pin.mjs | 5 +++-- diffr-ts/src/fetch.test.ts | 20 ++++++++++++++++-- scripts/release.py | 29 ++++++++++++++------------ scripts/smoke_cli_archive.py | 38 +++++++++++++++++++++++++++++++++++ 7 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 scripts/smoke_cli_archive.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e1c071494..b3a66ad44 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,11 +46,15 @@ jobs: fi cargo xtask install --root "$RUNNER_TEMP/install" python3 scripts/release.py pack --version "$version" --target "$TARGET" --install "$RUNNER_TEMP/install" --output dist - - name: Verify extracted archive without Bun or checkout assets + - name: Verify extracted archives without Bun or checkout assets + env: + TARGET: ${{ matrix.target }} run: | + version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1) mkdir -p "$RUNNER_TEMP/extracted/bin" - tar -xzf dist/*.tar.gz -C "$RUNNER_TEMP/extracted/bin" + tar -xzf "dist/diffr-$version-$TARGET.tar.gz" -C "$RUNNER_TEMP/extracted/bin" python3 xtask/tests/smoke_install.py "$RUNNER_TEMP/extracted" + python3 scripts/smoke_cli_archive.py "dist/diffr-cli-$version-$TARGET.tar.gz" "$version" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: ${{ matrix.target }} diff --git a/diffr-ts/README.md b/diffr-ts/README.md index 66790ef35..512959162 100644 --- a/diffr-ts/README.md +++ b/diffr-ts/README.md @@ -30,7 +30,8 @@ Keep these files in sync: ## Release After tagging the matching Rust release and building with `cargo build --locked`, -run from `diffr-ts`: +run from `diffr-ts`. New pins select the CLI-only release archives; older pins +continue to use the full archives: ```sh bun install --frozen-lockfile diff --git a/diffr-ts/bin/fetch.mjs b/diffr-ts/bin/fetch.mjs index e579b4241..79420b87f 100755 --- a/diffr-ts/bin/fetch.mjs +++ b/diffr-ts/bin/fetch.mjs @@ -49,7 +49,9 @@ async function main() { } if (values.check) throw new Error(`${binary} is missing or not diffr ${version} (${target})`); - const asset = `diffr-${version}-${target}.tar.gz`; + const artifact = pins.artifact ?? "diffr"; + if (!["diffr", "diffr-cli"].includes(artifact)) throw new Error(`unknown pinned artifact: ${artifact}`); + const asset = `${artifact}-${version}-${target}.tar.gz`; const url = `https://github.com/devdotfast/diffr/releases/download/${version}/${asset}`; let bytes; try { diff --git a/diffr-ts/scripts/pin.mjs b/diffr-ts/scripts/pin.mjs index 423770def..70df95dfe 100755 --- a/diffr-ts/scripts/pin.mjs +++ b/diffr-ts/scripts/pin.mjs @@ -8,9 +8,10 @@ const here = dirname(fileURLToPath(import.meta.url)); const root = join(here, ".."); const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version; const targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"]; +const artifact = "diffr-cli"; const sha256 = {}; for (const target of targets) { - const url = `https://github.com/devdotfast/diffr/releases/download/${version}/diffr-${version}-${target}.tar.gz`; + const url = `https://github.com/devdotfast/diffr/releases/download/${version}/${artifact}-${version}-${target}.tar.gz`; const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120_000) }); if (!response.ok) { console.error(`pin: GET ${url} failed with ${response.status} ${response.statusText}`); @@ -19,5 +20,5 @@ for (const target of targets) { sha256[target] = createHash("sha256").update(Buffer.from(await response.arrayBuffer())).digest("hex"); console.log(`${target} ${sha256[target]}`); } -writeFileSync(join(root, "pins.json"), `${JSON.stringify({ version, sha256 }, null, 2)}\n`); +writeFileSync(join(root, "pins.json"), `${JSON.stringify({ version, artifact, sha256 }, null, 2)}\n`); console.log(`wrote pins.json for ${version}`); diff --git a/diffr-ts/src/fetch.test.ts b/diffr-ts/src/fetch.test.ts index 3ba87f302..6e78ae5ad 100644 --- a/diffr-ts/src/fetch.test.ts +++ b/diffr-ts/src/fetch.test.ts @@ -13,7 +13,7 @@ afterEach(() => { for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); }); -function fixture(entry = "diffr") { +function fixture(entry = "diffr", artifact?: string) { if (!target) throw new Error("Fetch tests require a supported release platform"); const dir = mkdtempSync(join(tmpdir(), "diffr-fetch-test-")); dirs.push(dir); @@ -23,7 +23,7 @@ function fixture(entry = "diffr") { expect(Bun.spawnSync(["tar", "-czf", archive, "-C", dir, entry]).exitCode).toBe(0); const hash = createHash("sha256").update(readFileSync(archive)).digest("hex"); const pins = join(dir, "pins.json"); - writeFileSync(pins, JSON.stringify({ version, sha256: { [target]: hash } })); + writeFileSync(pins, JSON.stringify({ version, artifact, sha256: { [target]: hash } })); const mock = join(dir, "mock.mjs"); // Intercept only the network boundary, leaving the actual CLI and tar intact. writeFileSync(mock, ` @@ -119,3 +119,19 @@ test("missing option values fail clearly", () => { expect(f.run(["--pins"]).code).toBe(1); expect(f.run(["--into"]).code).toBe(1); }); + +test("CLI-only pins download the CLI archive", () => { + const f = fixture("diffr", "diffr-cli"); + const result = f.run(["--required"]); + expect(result.code, result.err).toBe(0); + expect(readFileSync(join(f.dir, "requested"), "utf8")).toBe( + `https://github.com/devdotfast/diffr/releases/download/${version}/diffr-cli-${version}-${target}.tar.gz`, + ); + expect(readFileSync(join(f.into, "diffr"), "utf8")).toBe(f.content); +}); + +test("unknown pinned artifacts fail before downloading", () => { + const f = fixture("diffr", "other"); + expect(f.run().err).toContain("unknown pinned artifact"); + expect(existsSync(join(f.dir, "requested"))).toBe(false); +}); diff --git a/scripts/release.py b/scripts/release.py index 3d8848b37..fad9aa55f 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -10,28 +10,31 @@ ROOT = Path(__file__).resolve().parent.parent -def archive_name(version, target): - return f"diffr-{version}-{target}.tar.gz" +def archive_name(version, target, artifact="diffr"): + return f"{artifact}-{version}-{target}.tar.gz" def pack(version, target, install, output): actual = subprocess.check_output([install / "bin/diffr", "--version"], text=True).strip() if actual != f"diffr {version}": raise ValueError(f"Release version mismatch: {actual}") - with tarfile.open(output / archive_name(version, target), "w:gz") as archive: - for name in ("diffr", "diffr-tui"): - archive.add(install / "bin" / name, arcname=name) - for name in ("LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE"): - archive.add(ROOT / name, arcname=name) + for artifact in ("diffr", "diffr-cli"): + with tarfile.open(output / archive_name(version, target, artifact), "w:gz") as archive: + binaries = ("diffr", "diffr-tui") if artifact == "diffr" else ("diffr",) + for name in binaries: + archive.add(install / "bin" / name, arcname=name) + for name in ("LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE"): + archive.add(ROOT / name, arcname=name) def formula(version, output): checksums = {} for target in TARGETS: - name = archive_name(version, target) - checksums[target] = hashlib.sha256((output / name).read_bytes()).hexdigest() + for artifact in ("diffr", "diffr-cli"): + name = archive_name(version, target, artifact) + checksums[name] = hashlib.sha256((output / name).read_bytes()).hexdigest() (output / "SHA256SUMS").write_text("".join( - f"{checksums[target]} {archive_name(version, target)}\n" for target in TARGETS + f"{checksum} {name}\n" for name, checksum in checksums.items() )) url = f"https://github.com/devdotfast/diffr/releases/download/{version}/diffr-{version}" (output / "diffr.rb").write_text(f'''class Diffr < Formula @@ -43,18 +46,18 @@ def formula(version, output): on_macos do on_arm do url "{url}-aarch64-apple-darwin.tar.gz" - sha256 "{checksums['aarch64-apple-darwin']}" + sha256 "{checksums[archive_name(version, 'aarch64-apple-darwin')]}" end on_intel do url "{url}-x86_64-apple-darwin.tar.gz" - sha256 "{checksums['x86_64-apple-darwin']}" + sha256 "{checksums[archive_name(version, 'x86_64-apple-darwin')]}" end end on_linux do depends_on arch: :x86_64 url "{url}-x86_64-unknown-linux-gnu.tar.gz" - sha256 "{checksums['x86_64-unknown-linux-gnu']}" + sha256 "{checksums[archive_name(version, 'x86_64-unknown-linux-gnu')]}" end def install diff --git a/scripts/smoke_cli_archive.py b/scripts/smoke_cli_archive.py new file mode 100644 index 000000000..9a6df44d7 --- /dev/null +++ b/scripts/smoke_cli_archive.py @@ -0,0 +1,38 @@ +"""Verify a CLI-only release archive without Bun, Git, or user configuration.""" +import json +from pathlib import Path +import subprocess +import sys +import tarfile +import tempfile + + +def check(archive_path, version): + with tempfile.TemporaryDirectory(prefix="diffr-cli-smoke-") as directory: + root = Path(directory) + with tarfile.open(archive_path) as archive: + assert set(archive.getnames()) == { + "diffr", "LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE", + } + archive.extractall(root, filter="data") + env = {"PATH": "", "HOME": str(root), "XDG_CONFIG_HOME": str(root / "config")} + + def run(*args): + return subprocess.check_output( + [root / "diffr", *args], cwd=root, env=env, text=True, timeout=30, + ) + + assert run("--version").strip() == f"diffr {version}" + (root / "before.rs").write_text("fn main() { let x = 1; }\n") + (root / "after.rs").write_text("fn main() { let x = 2; }\n") + records = [json.loads(line) for line in run( + "--no-index", "--format", "ndjson", "--syntax", "--", "before.rs", "after.rs", + ).splitlines()] + assert records[-1]["type"] == "complete", records + assert records[-1]["succeeded"] == 1 and records[-1]["failed"] == 0, records + assert any(record["type"] == "file" and "diff" in record for record in records), records + print(f"PASS CLI-only archive: {archive_path}") + + +if __name__ == "__main__": + check(Path(sys.argv[1]).resolve(), sys.argv[2])