Skip to content

Commit 34f8822

Browse files
rubnogueiraclaude
andcommitted
feat: ship binaries via GitHub Releases (v9.2.0); ESM-only
Mirrors the refactor just landed in gifsicle-bin, adapted for optipng: - GH Actions matrix builds optipng from vendor/source/optipng-0.7.8.tar.gz on darwin x64/arm64 (x64 cross-built), linux x64/arm64 glibc + musl, win32 x64 via MSYS2 mingw (statically linked). - On version-bump merge to main/master/cjs, the workflow auto-tags v<version> and attaches one tarball per matrix variant. - New lib/install.js downloads the matching tarball via fetch(), extracts with system tar, falls back to bin-build source build. - Dropped bin-wrapper (PruvoNet fork), bin-check, execa, compare-size. No more new BinWrapper().src(url, 'darwin', 'arm64').src(...). - Converted to ESM ("type": "module") for consistency with gifsicle-bin and node-expat. - Engines tightened to >=18. - Tests rewritten on node:child_process + ava + tempy. Verified locally: install.js 404s gracefully on the not-yet-existing v9.2.0 release, falls back to source build, produces a working binary; ava tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent ffb7e8f commit 34f8822

16 files changed

Lines changed: 416 additions & 145 deletions

File tree

.github/workflows/build.yml

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
name: build
2+
3+
on:
4+
push:
5+
branches: [main, master, cjs]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
12+
env:
13+
SOURCE_ARCHIVE: vendor/source/optipng-0.7.8.tar.gz
14+
15+
jobs:
16+
build:
17+
name: ${{ matrix.id }}
18+
runs-on: ${{ matrix.runner }}
19+
strategy:
20+
fail-fast: false
21+
matrix:
22+
include:
23+
- id: darwin-x64
24+
runner: macos-14
25+
cross_arch: x86_64
26+
- id: darwin-arm64
27+
runner: macos-14
28+
- id: linux-x64
29+
runner: ubuntu-latest
30+
- id: linux-x64-musl
31+
runner: ubuntu-latest
32+
musl: true
33+
- id: linux-arm64
34+
runner: ubuntu-24.04-arm
35+
- id: linux-arm64-musl
36+
runner: ubuntu-24.04-arm
37+
musl: true
38+
- id: win32-x64
39+
runner: windows-latest
40+
windows: true
41+
42+
steps:
43+
- uses: actions/checkout@v5
44+
45+
- name: Read version
46+
id: version
47+
shell: bash
48+
run: |
49+
v=$(node -p "require('./package.json').version")
50+
echo "version=$v" >> "$GITHUB_OUTPUT"
51+
echo "tag=v$v" >> "$GITHUB_OUTPUT"
52+
53+
# ── macOS ─ native arm64 OR x64 cross-built from arm64 ──
54+
- name: Build (macOS)
55+
if: startsWith(matrix.runner, 'macos')
56+
env:
57+
CROSS_ARCH: ${{ matrix.cross_arch }}
58+
run: |
59+
set -euo pipefail
60+
tmp=$(mktemp -d)
61+
tar -xzf "$SOURCE_ARCHIVE" -C "$tmp"
62+
cd "$tmp"/optipng-*
63+
host_arg=""
64+
if [ -n "${CROSS_ARCH:-}" ]; then
65+
export CFLAGS="-arch $CROSS_ARCH"
66+
export LDFLAGS="-arch $CROSS_ARCH"
67+
host_arg="--host=$CROSS_ARCH-apple-darwin"
68+
fi
69+
./configure $host_arg --with-system-zlib
70+
make -j"$(sysctl -n hw.ncpu)"
71+
strip src/optipng/optipng
72+
mkdir -p "$GITHUB_WORKSPACE/out"
73+
cp src/optipng/optipng "$GITHUB_WORKSPACE/out/"
74+
75+
# ── Linux glibc ──
76+
- name: Build (Linux glibc)
77+
if: startsWith(matrix.runner, 'ubuntu') && !matrix.musl
78+
run: |
79+
set -euo pipefail
80+
sudo apt-get update -qq
81+
sudo apt-get install -qq -y build-essential zlib1g-dev
82+
tmp=$(mktemp -d)
83+
tar -xzf "$SOURCE_ARCHIVE" -C "$tmp"
84+
cd "$tmp"/optipng-*
85+
./configure --with-system-zlib
86+
make -j"$(nproc)"
87+
strip src/optipng/optipng
88+
mkdir -p "$GITHUB_WORKSPACE/out"
89+
cp src/optipng/optipng "$GITHUB_WORKSPACE/out/"
90+
91+
# ── Linux musl via Alpine docker ──
92+
- name: Build (Linux musl via Alpine docker)
93+
if: matrix.musl
94+
run: |
95+
set -euo pipefail
96+
docker run --rm \
97+
-v "$PWD":/work -w /work \
98+
alpine:3.20 sh -c '
99+
set -e
100+
apk add --no-cache build-base zlib-dev tar
101+
tmp=$(mktemp -d)
102+
tar -xzf vendor/source/optipng-0.7.8.tar.gz -C "$tmp"
103+
cd "$tmp"/optipng-*
104+
./configure --with-system-zlib
105+
make -j"$(nproc)"
106+
strip src/optipng/optipng
107+
mkdir -p /work/out
108+
cp src/optipng/optipng /work/out/
109+
'
110+
111+
# ── Windows via MSYS2 mingw ──
112+
- name: Set up MSYS2
113+
if: matrix.windows
114+
uses: msys2/setup-msys2@v2
115+
with:
116+
msystem: MINGW64
117+
update: true
118+
install: >-
119+
mingw-w64-x86_64-gcc
120+
mingw-w64-x86_64-zlib
121+
make
122+
123+
- name: Build (Windows MSYS2)
124+
if: matrix.windows
125+
shell: msys2 {0}
126+
run: |
127+
set -euo pipefail
128+
tmp=$(mktemp -d)
129+
tar -xzf vendor/source/optipng-0.7.8.tar.gz -C "$tmp"
130+
cd "$tmp"/optipng-*
131+
./configure --with-system-zlib LDFLAGS="-static -static-libgcc"
132+
make -j"$(nproc)"
133+
strip src/optipng/optipng.exe
134+
mkdir -p "$GITHUB_WORKSPACE/out"
135+
cp src/optipng/optipng.exe "$GITHUB_WORKSPACE/out/"
136+
137+
- name: Smoke test
138+
if: matrix.id != 'darwin-x64'
139+
shell: bash
140+
run: |
141+
set -euo pipefail
142+
ls -la out/
143+
if [ -f out/optipng.exe ]; then
144+
./out/optipng.exe --version
145+
else
146+
./out/optipng --version
147+
fi
148+
149+
- name: Tarball
150+
shell: bash
151+
env:
152+
TAG: ${{ steps.version.outputs.tag }}
153+
run: |
154+
set -euo pipefail
155+
mkdir -p artifacts
156+
name="optipng-bin-${TAG}-${{ matrix.id }}.tar.gz"
157+
tar -czf "artifacts/${name}" -C out .
158+
ls -la artifacts/
159+
160+
- uses: actions/upload-artifact@v7
161+
with:
162+
name: bin-${{ matrix.id }}
163+
path: artifacts/*.tar.gz
164+
if-no-files-found: error
165+
retention-days: 30
166+
167+
release:
168+
name: auto-release on version bump
169+
needs: build
170+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/cjs')
171+
runs-on: ubuntu-latest
172+
permissions:
173+
contents: write
174+
steps:
175+
- uses: actions/checkout@v5
176+
177+
- name: Read version from package.json
178+
id: version
179+
run: |
180+
set -euo pipefail
181+
v=$(node -p "require('./package.json').version")
182+
echo "version=$v" >> "$GITHUB_OUTPUT"
183+
echo "tag=v$v" >> "$GITHUB_OUTPUT"
184+
185+
- name: Check whether tag already exists on origin
186+
id: check_tag
187+
run: |
188+
set -euo pipefail
189+
tag="${{ steps.version.outputs.tag }}"
190+
if git ls-remote --tags origin "refs/tags/$tag" | grep -q .; then
191+
echo "exists=true" >> "$GITHUB_OUTPUT"
192+
echo "Tag $tag already exists — nothing to release."
193+
else
194+
echo "exists=false" >> "$GITHUB_OUTPUT"
195+
echo "Tag $tag does not exist — will release."
196+
fi
197+
198+
- if: steps.check_tag.outputs.exists == 'false'
199+
uses: actions/download-artifact@v8
200+
with:
201+
pattern: bin-*
202+
path: artifacts
203+
merge-multiple: true
204+
205+
- if: steps.check_tag.outputs.exists == 'false'
206+
run: ls -la artifacts/
207+
208+
- if: steps.check_tag.outputs.exists == 'false'
209+
uses: softprops/action-gh-release@v2
210+
with:
211+
tag_name: ${{ steps.version.outputs.tag }}
212+
target_commitish: ${{ github.sha }}
213+
name: ${{ steps.version.outputs.tag }}
214+
files: artifacts/*.tar.gz
215+
generate_release_notes: true
216+
fail_on_unmatched_files: true

.github/workflows/test.yml

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
11
name: test
2-
on:
3-
- push
4-
- pull_request
2+
3+
on: [push, pull_request]
4+
55
jobs:
66
test:
77
name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
88
runs-on: ${{ matrix.os }}
99
strategy:
1010
fail-fast: false
1111
matrix:
12-
node-version:
13-
- 22
14-
- 20
15-
- 18
16-
- 16
17-
- 14
18-
os:
19-
- ubuntu-latest
20-
- macos-latest
21-
- windows-latest
12+
node-version: ['24.15.0', '26.1.0']
13+
os: [ubuntu-latest, macos-14, windows-latest]
2214
steps:
23-
- uses: actions/checkout@v3
24-
- uses: actions/setup-node@v3
15+
- uses: actions/checkout@v5
16+
17+
# Source-build fallback deps (install.js tries the GitHub Release
18+
# download first; we still want the fallback to compile cleanly).
19+
- if: contains(matrix.os, 'ubuntu')
20+
run: sudo apt-get update -qq && sudo apt-get install -qq -y build-essential zlib1g-dev
21+
22+
- uses: actions/setup-node@v5
2523
with:
2624
node-version: ${{ matrix.node-version }}
25+
2726
- run: npm install
2827
- run: npm test

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules
2+
vendor/*
3+
!vendor/source
24
yarn.lock
3-
vendor/optipng*
4-
vendor/temp
5+
.DS_Store

cli.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#!/usr/bin/env node
2-
'use strict';
3-
const {spawn} = require('child_process');
4-
const optipng = require('.');
2+
import {spawn} from 'node:child_process';
3+
import process from 'node:process';
4+
import optipng from './index.js';
55

6-
const input = process.argv.slice(2);
7-
8-
spawn(optipng, input, {stdio: 'inherit'})
9-
.on('exit', process.exit);
6+
const child = spawn(optipng, process.argv.slice(2), {stdio: 'inherit'});
7+
child.on('exit', code => process.exit(code ?? 0));
8+
child.on('error', error => {
9+
console.error(error);
10+
process.exit(1);
11+
});

index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
'use strict';
2-
module.exports = require('./lib/index.js').path();
1+
import {BINARY_PATH} from './lib/binary.js';
2+
3+
export default BINARY_PATH;

lib/binary.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import path from 'node:path';
2+
import process from 'node:process';
3+
import {fileURLToPath} from 'node:url';
4+
5+
export const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
6+
export const VENDOR_DIR = path.join(PACKAGE_ROOT, 'vendor');
7+
8+
export function binaryName() {
9+
return process.platform === 'win32' ? 'optipng.exe' : 'optipng';
10+
}
11+
12+
export const BINARY_PATH = path.join(VENDOR_DIR, binaryName());

lib/filename.js

Lines changed: 0 additions & 21 deletions
This file was deleted.

lib/index.js

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)