Skip to content

Commit e3e94aa

Browse files
rubnogueiraclaude
andcommitted
feat: ship binaries via GitHub Releases (v7.2.0); ESM-only
Mirrors the gifsicle-bin / optipng-bin refactor, adapted for jpegtran (part of libjpeg-turbo 1.5.1): - GH Actions matrix builds jpegtran from vendor/source/libjpeg-turbo-1.5.1.tar.gz on darwin (x64 cross-built, arm64 native), linux x64/arm64 glibc + musl, win32 x64 (MSYS2 mingw, statically linked, includes NASM for SIMD). - On version-bump merge to main/master/cjs, the workflow auto-tags v<version> and attaches one tarball per matrix variant. - Dropped the Windows libjpeg-62.dll runtime dependency by using --disable-shared everywhere — produces a single static jpegtran[.exe] with no separate DLL. - New lib/install.js downloads the matching tarball via fetch(), extracts with system tar, falls back to bin-build source build. The smoke-test step uses jpegtran -h instead of the old "minify a real fixture" hack — `-h` exits 1 but proves dynamic-link and main() resolved. - Dropped bin-wrapper (PruvoNet fork), bin-check, execa, compare-size. No more `new BinWrapper().src(url, 'darwin', 'arm64').src(url, ...) .src(libjpegDllUrl, 'win32', 'x64')`. - Converted to ESM. Engines tightened to >=18. - Tests rewritten on node:child_process + ava + tempy. Removed the stale test/fixtures/test-optimized.jpg artifact that the old install.js used to leave behind as part of its smoke test. Verified locally with NASM via brew: install.js 404s gracefully on the not-yet-existing v7.2.0 release, falls back to source build, produces a working static jpegtran; ava tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 130cbc3 commit e3e94aa

18 files changed

Lines changed: 439 additions & 164 deletions

File tree

.github/workflows/build.yml

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
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/libjpeg-turbo-1.5.1.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+
# libjpeg-turbo 1.5.1 uses NASM for x86_64 SIMD; arm64 builds don't
55+
# need it (uses different intrinsics path), but it's harmless to
56+
# always install.
57+
- name: Build (macOS)
58+
if: startsWith(matrix.runner, 'macos')
59+
env:
60+
CROSS_ARCH: ${{ matrix.cross_arch }}
61+
run: |
62+
set -euo pipefail
63+
brew install --quiet automake nasm
64+
tmp=$(mktemp -d)
65+
tar -xzf "$SOURCE_ARCHIVE" -C "$tmp"
66+
cd "$tmp"/libjpeg-turbo-*
67+
host_arg=""
68+
if [ -n "${CROSS_ARCH:-}" ]; then
69+
export CFLAGS="-arch $CROSS_ARCH"
70+
export LDFLAGS="-arch $CROSS_ARCH"
71+
host_arg="--host=$CROSS_ARCH-apple-darwin"
72+
fi
73+
touch configure.ac aclocal.m4 configure Makefile.am Makefile.in
74+
./configure $host_arg --disable-shared
75+
make -j"$(sysctl -n hw.ncpu)"
76+
strip jpegtran
77+
mkdir -p "$GITHUB_WORKSPACE/out"
78+
cp jpegtran "$GITHUB_WORKSPACE/out/"
79+
80+
# ── Linux glibc ──
81+
- name: Build (Linux glibc)
82+
if: startsWith(matrix.runner, 'ubuntu') && !matrix.musl
83+
run: |
84+
set -euo pipefail
85+
sudo apt-get update -qq
86+
sudo apt-get install -qq -y build-essential autoconf automake libtool nasm
87+
tmp=$(mktemp -d)
88+
tar -xzf "$SOURCE_ARCHIVE" -C "$tmp"
89+
cd "$tmp"/libjpeg-turbo-*
90+
touch configure.ac aclocal.m4 configure Makefile.am Makefile.in
91+
./configure --disable-shared
92+
make -j"$(nproc)"
93+
strip jpegtran
94+
mkdir -p "$GITHUB_WORKSPACE/out"
95+
cp jpegtran "$GITHUB_WORKSPACE/out/"
96+
97+
# ── Linux musl via Alpine docker ──
98+
- name: Build (Linux musl via Alpine docker)
99+
if: matrix.musl
100+
run: |
101+
set -euo pipefail
102+
docker run --rm \
103+
-v "$PWD":/work -w /work \
104+
alpine:3.20 sh -c '
105+
set -e
106+
apk add --no-cache build-base autoconf automake libtool nasm tar
107+
tmp=$(mktemp -d)
108+
tar -xzf vendor/source/libjpeg-turbo-1.5.1.tar.gz -C "$tmp"
109+
cd "$tmp"/libjpeg-turbo-*
110+
touch configure.ac aclocal.m4 configure Makefile.am Makefile.in
111+
./configure --disable-shared
112+
make -j"$(nproc)"
113+
strip jpegtran
114+
mkdir -p /work/out
115+
cp jpegtran /work/out/
116+
'
117+
118+
# ── Windows via MSYS2 mingw (statically linked, includes NASM) ──
119+
- name: Set up MSYS2
120+
if: matrix.windows
121+
uses: msys2/setup-msys2@v2
122+
with:
123+
msystem: MINGW64
124+
update: true
125+
install: >-
126+
mingw-w64-x86_64-gcc
127+
mingw-w64-x86_64-autotools
128+
mingw-w64-x86_64-nasm
129+
make
130+
131+
- name: Build (Windows MSYS2)
132+
if: matrix.windows
133+
shell: msys2 {0}
134+
run: |
135+
set -euo pipefail
136+
tmp=$(mktemp -d)
137+
tar -xzf vendor/source/libjpeg-turbo-1.5.1.tar.gz -C "$tmp"
138+
cd "$tmp"/libjpeg-turbo-*
139+
touch configure.ac aclocal.m4 configure Makefile.am Makefile.in
140+
./configure --disable-shared LDFLAGS="-static -static-libgcc"
141+
make -j"$(nproc)"
142+
strip jpegtran.exe
143+
mkdir -p "$GITHUB_WORKSPACE/out"
144+
cp jpegtran.exe "$GITHUB_WORKSPACE/out/"
145+
146+
- name: Smoke test
147+
if: matrix.id != 'darwin-x64'
148+
shell: bash
149+
run: |
150+
set -euo pipefail
151+
ls -la out/
152+
# jpegtran's -h exits 1 (prints help to stderr); that's expected.
153+
# We just want to confirm it loads at all.
154+
if [ -f out/jpegtran.exe ]; then
155+
./out/jpegtran.exe -h 2>&1 | head -3 || true
156+
else
157+
./out/jpegtran -h 2>&1 | head -3 || true
158+
fi
159+
160+
- name: Tarball
161+
shell: bash
162+
env:
163+
TAG: ${{ steps.version.outputs.tag }}
164+
run: |
165+
set -euo pipefail
166+
mkdir -p artifacts
167+
name="jpegtran-bin-${TAG}-${{ matrix.id }}.tar.gz"
168+
tar -czf "artifacts/${name}" -C out .
169+
ls -la artifacts/
170+
171+
- uses: actions/upload-artifact@v7
172+
with:
173+
name: bin-${{ matrix.id }}
174+
path: artifacts/*.tar.gz
175+
if-no-files-found: error
176+
retention-days: 30
177+
178+
release:
179+
name: auto-release on version bump
180+
needs: build
181+
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' || github.ref == 'refs/heads/cjs')
182+
runs-on: ubuntu-latest
183+
permissions:
184+
contents: write
185+
steps:
186+
- uses: actions/checkout@v5
187+
188+
- name: Read version from package.json
189+
id: version
190+
run: |
191+
set -euo pipefail
192+
v=$(node -p "require('./package.json').version")
193+
echo "version=$v" >> "$GITHUB_OUTPUT"
194+
echo "tag=v$v" >> "$GITHUB_OUTPUT"
195+
196+
- name: Check whether tag already exists on origin
197+
id: check_tag
198+
run: |
199+
set -euo pipefail
200+
tag="${{ steps.version.outputs.tag }}"
201+
if git ls-remote --tags origin "refs/tags/$tag" | grep -q .; then
202+
echo "exists=true" >> "$GITHUB_OUTPUT"
203+
echo "Tag $tag already exists — nothing to release."
204+
else
205+
echo "exists=false" >> "$GITHUB_OUTPUT"
206+
echo "Tag $tag does not exist — will release."
207+
fi
208+
209+
- if: steps.check_tag.outputs.exists == 'false'
210+
uses: actions/download-artifact@v8
211+
with:
212+
pattern: bin-*
213+
path: artifacts
214+
merge-multiple: true
215+
216+
- if: steps.check_tag.outputs.exists == 'false'
217+
run: ls -la artifacts/
218+
219+
- if: steps.check_tag.outputs.exists == 'false'
220+
uses: softprops/action-gh-release@v2
221+
with:
222+
tag_name: ${{ steps.version.outputs.tag }}
223+
target_commitish: ${{ github.sha }}
224+
name: ${{ steps.version.outputs.tag }}
225+
files: artifacts/*.tar.gz
226+
generate_release_notes: true
227+
fail_on_unmatched_files: true

.github/workflows/test.yml

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,29 @@
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:
10+
fail-fast: false
1011
matrix:
11-
node-version:
12-
- 22
13-
- 20
14-
- 18
15-
- 16
16-
- 14
17-
os:
18-
- ubuntu-latest
19-
- macos-latest
20-
- windows-latest
12+
node-version: ['24.15.0', '26.1.0']
13+
os: [ubuntu-latest, macos-14, windows-latest]
2114
steps:
22-
- uses: actions/checkout@v2
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).
2319
- if: contains(matrix.os, 'ubuntu')
24-
run: sudo apt-get install nasm
20+
run: sudo apt-get update -qq && sudo apt-get install -qq -y build-essential nasm
2521
- if: contains(matrix.os, 'macos')
26-
run: brew install automake nasm
27-
- uses: actions/setup-node@v2
22+
run: brew install --quiet automake nasm
23+
24+
- uses: actions/setup-node@v5
2825
with:
2926
node-version: ${{ matrix.node-version }}
27+
3028
- run: npm install
3129
- run: npm test

.gitignore

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

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 jpegtran = require('.');
2+
import {spawn} from 'node:child_process';
3+
import process from 'node:process';
4+
import jpegtran from './index.js';
55

6-
const input = process.argv.slice(2);
7-
8-
spawn(jpegtran, input, {stdio: 'inherit'})
9-
.on('exit', process.exit);
6+
const child = spawn(jpegtran, 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').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' ? 'jpegtran.exe' : 'jpegtran';
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 & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)