Skip to content

Commit 65403dc

Browse files
authored
Publish CLI-only archives for npm binary downloads (#44)
AI assistance: implemented and validated with OpenAI Codex.
1 parent 8b2fdf8 commit 65403dc

7 files changed

Lines changed: 86 additions & 21 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,15 @@ jobs:
4646
fi
4747
cargo xtask install --root "$RUNNER_TEMP/install"
4848
python3 scripts/release.py pack --version "$version" --target "$TARGET" --install "$RUNNER_TEMP/install" --output dist
49-
- name: Verify extracted archive without Bun or checkout assets
49+
- name: Verify extracted archives without Bun or checkout assets
50+
env:
51+
TARGET: ${{ matrix.target }}
5052
run: |
53+
version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
5154
mkdir -p "$RUNNER_TEMP/extracted/bin"
52-
tar -xzf dist/*.tar.gz -C "$RUNNER_TEMP/extracted/bin"
55+
tar -xzf "dist/diffr-$version-$TARGET.tar.gz" -C "$RUNNER_TEMP/extracted/bin"
5356
python3 xtask/tests/smoke_install.py "$RUNNER_TEMP/extracted"
57+
python3 scripts/smoke_cli_archive.py "dist/diffr-cli-$version-$TARGET.tar.gz" "$version"
5458
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
5559
with:
5660
name: ${{ matrix.target }}

‎diffr-ts/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ Keep these files in sync:
3030
## Release
3131

3232
After tagging the matching Rust release and building with `cargo build --locked`,
33-
run from `diffr-ts`:
33+
run from `diffr-ts`. New pins select the CLI-only release archives; older pins
34+
continue to use the full archives:
3435

3536
```sh
3637
bun install --frozen-lockfile

‎diffr-ts/bin/fetch.mjs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ async function main() {
4949
}
5050
if (values.check) throw new Error(`${binary} is missing or not diffr ${version} (${target})`);
5151

52-
const asset = `diffr-${version}-${target}.tar.gz`;
52+
const artifact = pins.artifact ?? "diffr";
53+
if (!["diffr", "diffr-cli"].includes(artifact)) throw new Error(`unknown pinned artifact: ${artifact}`);
54+
const asset = `${artifact}-${version}-${target}.tar.gz`;
5355
const url = `https://github.com/devdotfast/diffr/releases/download/${version}/${asset}`;
5456
let bytes;
5557
try {

‎diffr-ts/scripts/pin.mjs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ const here = dirname(fileURLToPath(import.meta.url));
88
const root = join(here, "..");
99
const version = JSON.parse(readFileSync(join(root, "package.json"), "utf8")).version;
1010
const targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"];
11+
const artifact = "diffr-cli";
1112
const sha256 = {};
1213
for (const target of targets) {
13-
const url = `https://github.com/devdotfast/diffr/releases/download/${version}/diffr-${version}-${target}.tar.gz`;
14+
const url = `https://github.com/devdotfast/diffr/releases/download/${version}/${artifact}-${version}-${target}.tar.gz`;
1415
const response = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120_000) });
1516
if (!response.ok) {
1617
console.error(`pin: GET ${url} failed with ${response.status} ${response.statusText}`);
@@ -19,5 +20,5 @@ for (const target of targets) {
1920
sha256[target] = createHash("sha256").update(Buffer.from(await response.arrayBuffer())).digest("hex");
2021
console.log(`${target} ${sha256[target]}`);
2122
}
22-
writeFileSync(join(root, "pins.json"), `${JSON.stringify({ version, sha256 }, null, 2)}\n`);
23+
writeFileSync(join(root, "pins.json"), `${JSON.stringify({ version, artifact, sha256 }, null, 2)}\n`);
2324
console.log(`wrote pins.json for ${version}`);

‎diffr-ts/src/fetch.test.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ afterEach(() => {
1313
for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true });
1414
});
1515

16-
function fixture(entry = "diffr") {
16+
function fixture(entry = "diffr", artifact?: string) {
1717
if (!target) throw new Error("Fetch tests require a supported release platform");
1818
const dir = mkdtempSync(join(tmpdir(), "diffr-fetch-test-"));
1919
dirs.push(dir);
@@ -23,7 +23,7 @@ function fixture(entry = "diffr") {
2323
expect(Bun.spawnSync(["tar", "-czf", archive, "-C", dir, entry]).exitCode).toBe(0);
2424
const hash = createHash("sha256").update(readFileSync(archive)).digest("hex");
2525
const pins = join(dir, "pins.json");
26-
writeFileSync(pins, JSON.stringify({ version, sha256: { [target]: hash } }));
26+
writeFileSync(pins, JSON.stringify({ version, artifact, sha256: { [target]: hash } }));
2727
const mock = join(dir, "mock.mjs");
2828
// Intercept only the network boundary, leaving the actual CLI and tar intact.
2929
writeFileSync(mock, `
@@ -119,3 +119,19 @@ test("missing option values fail clearly", () => {
119119
expect(f.run(["--pins"]).code).toBe(1);
120120
expect(f.run(["--into"]).code).toBe(1);
121121
});
122+
123+
test("CLI-only pins download the CLI archive", () => {
124+
const f = fixture("diffr", "diffr-cli");
125+
const result = f.run(["--required"]);
126+
expect(result.code, result.err).toBe(0);
127+
expect(readFileSync(join(f.dir, "requested"), "utf8")).toBe(
128+
`https://github.com/devdotfast/diffr/releases/download/${version}/diffr-cli-${version}-${target}.tar.gz`,
129+
);
130+
expect(readFileSync(join(f.into, "diffr"), "utf8")).toBe(f.content);
131+
});
132+
133+
test("unknown pinned artifacts fail before downloading", () => {
134+
const f = fixture("diffr", "other");
135+
expect(f.run().err).toContain("unknown pinned artifact");
136+
expect(existsSync(join(f.dir, "requested"))).toBe(false);
137+
});

‎scripts/release.py‎

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,31 @@
1010
ROOT = Path(__file__).resolve().parent.parent
1111

1212

13-
def archive_name(version, target):
14-
return f"diffr-{version}-{target}.tar.gz"
13+
def archive_name(version, target, artifact="diffr"):
14+
return f"{artifact}-{version}-{target}.tar.gz"
1515

1616

1717
def pack(version, target, install, output):
1818
actual = subprocess.check_output([install / "bin/diffr", "--version"], text=True).strip()
1919
if actual != f"diffr {version}":
2020
raise ValueError(f"Release version mismatch: {actual}")
21-
with tarfile.open(output / archive_name(version, target), "w:gz") as archive:
22-
for name in ("diffr", "diffr-tui"):
23-
archive.add(install / "bin" / name, arcname=name)
24-
for name in ("LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE"):
25-
archive.add(ROOT / name, arcname=name)
21+
for artifact in ("diffr", "diffr-cli"):
22+
with tarfile.open(output / archive_name(version, target, artifact), "w:gz") as archive:
23+
binaries = ("diffr", "diffr-tui") if artifact == "diffr" else ("diffr",)
24+
for name in binaries:
25+
archive.add(install / "bin" / name, arcname=name)
26+
for name in ("LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE"):
27+
archive.add(ROOT / name, arcname=name)
2628

2729

2830
def formula(version, output):
2931
checksums = {}
3032
for target in TARGETS:
31-
name = archive_name(version, target)
32-
checksums[target] = hashlib.sha256((output / name).read_bytes()).hexdigest()
33+
for artifact in ("diffr", "diffr-cli"):
34+
name = archive_name(version, target, artifact)
35+
checksums[name] = hashlib.sha256((output / name).read_bytes()).hexdigest()
3336
(output / "SHA256SUMS").write_text("".join(
34-
f"{checksums[target]} {archive_name(version, target)}\n" for target in TARGETS
37+
f"{checksum} {name}\n" for name, checksum in checksums.items()
3538
))
3639
url = f"https://github.com/devdotfast/diffr/releases/download/{version}/diffr-{version}"
3740
(output / "diffr.rb").write_text(f'''class Diffr < Formula
@@ -43,18 +46,18 @@ def formula(version, output):
4346
on_macos do
4447
on_arm do
4548
url "{url}-aarch64-apple-darwin.tar.gz"
46-
sha256 "{checksums['aarch64-apple-darwin']}"
49+
sha256 "{checksums[archive_name(version, 'aarch64-apple-darwin')]}"
4750
end
4851
on_intel do
4952
url "{url}-x86_64-apple-darwin.tar.gz"
50-
sha256 "{checksums['x86_64-apple-darwin']}"
53+
sha256 "{checksums[archive_name(version, 'x86_64-apple-darwin')]}"
5154
end
5255
end
5356
5457
on_linux do
5558
depends_on arch: :x86_64
5659
url "{url}-x86_64-unknown-linux-gnu.tar.gz"
57-
sha256 "{checksums['x86_64-unknown-linux-gnu']}"
60+
sha256 "{checksums[archive_name(version, 'x86_64-unknown-linux-gnu')]}"
5861
end
5962
6063
def install

‎scripts/smoke_cli_archive.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Verify a CLI-only release archive without Bun, Git, or user configuration."""
2+
import json
3+
from pathlib import Path
4+
import subprocess
5+
import sys
6+
import tarfile
7+
import tempfile
8+
9+
10+
def check(archive_path, version):
11+
with tempfile.TemporaryDirectory(prefix="diffr-cli-smoke-") as directory:
12+
root = Path(directory)
13+
with tarfile.open(archive_path) as archive:
14+
assert set(archive.getnames()) == {
15+
"diffr", "LICENSE", "NOTICE", "tui/LICENSE", "tui/themes/LICENSE",
16+
}
17+
archive.extractall(root, filter="data")
18+
env = {"PATH": "", "HOME": str(root), "XDG_CONFIG_HOME": str(root / "config")}
19+
20+
def run(*args):
21+
return subprocess.check_output(
22+
[root / "diffr", *args], cwd=root, env=env, text=True, timeout=30,
23+
)
24+
25+
assert run("--version").strip() == f"diffr {version}"
26+
(root / "before.rs").write_text("fn main() { let x = 1; }\n")
27+
(root / "after.rs").write_text("fn main() { let x = 2; }\n")
28+
records = [json.loads(line) for line in run(
29+
"--no-index", "--format", "ndjson", "--syntax", "--", "before.rs", "after.rs",
30+
).splitlines()]
31+
assert records[-1]["type"] == "complete", records
32+
assert records[-1]["succeeded"] == 1 and records[-1]["failed"] == 0, records
33+
assert any(record["type"] == "file" and "diff" in record for record in records), records
34+
print(f"PASS CLI-only archive: {archive_path}")
35+
36+
37+
if __name__ == "__main__":
38+
check(Path(sys.argv[1]).resolve(), sys.argv[2])

0 commit comments

Comments
 (0)