Skip to content

Commit 59da663

Browse files
Mossakaclaude
andauthored
feat: add esbuild single-file bundle as lightweight distribution (#1581)
* feat: add esbuild single-file bundle as lightweight distribution Add a ~350KB esbuild bundle (release/awf-bundle.js) as an alternative to the ~50MB pkg binary. GitHub hosted runners already have Node.js 22, making the bundled Node.js 18 runtime in pkg redundant. Changes: - Add scripts/build-bundle.mjs: esbuild config that bundles dist/cli.js into a single CJS file with the seccomp profile inlined via `define` - Modify src/docker-manager.ts: support embedded seccomp profile (__AWF_SECCOMP_PROFILE__) and guard --build-local for bundle users - Add esbuild to devDependencies and build:bundle script - Update release.yml to build, smoke-test, and publish the bundle - Update install.sh to prefer the bundle when Node.js >= 20 is available (opt out with AWF_FORCE_BINARY=1) - Add release/ to .gitignore Closes #1577 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address review feedback on esbuild bundle PR - Tighten Node.js version check in install.sh to >=20.12.0 (matches engines.node in package.json) instead of just >=20 - Remove redundant `npm install esbuild` in release workflow since esbuild is already installed via `npm ci` from devDependencies - Add build-time JSON.parse validation for seccomp profile to fail fast on malformed JSON - Set executable bit (chmod 755) on bundle output file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0199067 commit 59da663

7 files changed

Lines changed: 684 additions & 43 deletions

File tree

.github/workflows/release.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,15 @@ jobs:
367367
- name: Build TypeScript
368368
run: npm run build
369369

370+
- name: Create esbuild bundle
371+
run: |
372+
node scripts/build-bundle.mjs
373+
echo "=== Bundle size ==="
374+
ls -lh release/awf-bundle.js
375+
echo "=== Bundle smoke test ==="
376+
node release/awf-bundle.js --version
377+
node release/awf-bundle.js --help | head -5
378+
370379
- name: Install pkg for binary creation
371380
run: npm install -g pkg
372381

@@ -550,6 +559,7 @@ jobs:
550559
release/awf-linux-arm64
551560
release/awf-darwin-x64
552561
release/awf-darwin-arm64
562+
release/awf-bundle.js
553563
release/awf.tgz
554564
release/checksums.txt
555565
env:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ _codeql_detected_source_root
2424

2525
# Design docs (working drafts, not checked in)
2626
design-docs/
27+
28+
# Release build artifacts
29+
release/

install.sh

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ REPO="github/gh-aw-firewall"
3535
BINARY_NAME="" # Set dynamically by check_platform
3636
INSTALL_DIR="/usr/local/bin"
3737
INSTALL_NAME="awf"
38+
USE_BUNDLE=false # Set by check_node
3839

3940
# Colors for output
4041
RED='\033[0;31m'
@@ -142,6 +143,28 @@ check_platform() {
142143
info "Detected platform: $os $arch (binary: $BINARY_NAME)"
143144
}
144145

146+
# Check for Node.js >= 20.12.0 to decide between bundle and pkg binary
147+
# (matches engines.node requirement in package.json)
148+
check_node() {
149+
if [ "${AWF_FORCE_BINARY:-}" = "1" ]; then
150+
info "AWF_FORCE_BINARY=1 set, using standalone binary"
151+
USE_BUNDLE=false
152+
return
153+
fi
154+
if command -v node &> /dev/null; then
155+
NODE_VERSION=$(node -v | sed 's/^v//')
156+
NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1)
157+
NODE_MINOR=$(echo "$NODE_VERSION" | cut -d. -f2)
158+
if [ "$NODE_MAJOR" -gt 20 ] || { [ "$NODE_MAJOR" -eq 20 ] && [ "$NODE_MINOR" -ge 12 ]; }; then
159+
info "Node.js v${NODE_VERSION} detected (>= 20.12.0), using lightweight bundle"
160+
USE_BUNDLE=true
161+
return
162+
fi
163+
warn "Node.js v${NODE_VERSION} detected but < 20.12.0, using standalone binary"
164+
fi
165+
USE_BUNDLE=false
166+
}
167+
145168
# Validate version format (should be like v1.0.0, v1.2.3, etc.)
146169
validate_version() {
147170
local version="$1"
@@ -263,54 +286,83 @@ main() {
263286
check_sudo
264287
check_requirements
265288
check_platform
266-
289+
check_node
290+
267291
# Get version (from argument, env var, or fetch latest)
268292
set_version "$1"
269-
293+
270294
# Create temp directory with prefix for identification
271295
# mktemp creates secure temporary directories with proper permissions (0700)
272296
TEMP_DIR=$(mktemp -d -t awf-install.XXXXXX)
273-
297+
274298
# Validate temp directory was created
275299
if [ -z "$TEMP_DIR" ] || [ ! -d "$TEMP_DIR" ]; then
276300
error "Failed to create temporary directory"
277301
exit 1
278302
fi
279-
303+
280304
# Set up cleanup trap (mktemp already ensures secure location)
281305
trap 'rm -rf "$TEMP_DIR"' EXIT
282-
306+
283307
# Download URLs
284308
BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"
285-
BINARY_URL="${BASE_URL}/${BINARY_NAME}"
286309
CHECKSUMS_URL="${BASE_URL}/checksums.txt"
287-
288-
# Download binary and checksums
289-
download_file "$BINARY_URL" "$TEMP_DIR/$BINARY_NAME"
290-
download_file "$CHECKSUMS_URL" "$TEMP_DIR/checksums.txt"
291-
292-
# Verify checksum
293-
verify_checksum "$TEMP_DIR/$BINARY_NAME" "$TEMP_DIR/checksums.txt"
294-
295-
# Make binary executable
296-
chmod +x "$TEMP_DIR/$BINARY_NAME"
297-
298-
# Test if it's a valid executable (ELF on Linux, Mach-O on macOS)
299-
local file_type
300-
file_type=$(file "$TEMP_DIR/$BINARY_NAME")
301-
if echo "$file_type" | grep -q "ELF.*executable"; then
302-
info "Valid Linux ELF executable"
303-
elif echo "$file_type" | grep -q "Mach-O 64-bit"; then
304-
info "Valid macOS Mach-O executable"
310+
311+
if [ "$USE_BUNDLE" = true ]; then
312+
# Lightweight bundle path — requires Node.js >= 20
313+
ASSET_NAME="awf-bundle.js"
314+
ASSET_URL="${BASE_URL}/${ASSET_NAME}"
315+
316+
download_file "$ASSET_URL" "$TEMP_DIR/$ASSET_NAME"
317+
download_file "$CHECKSUMS_URL" "$TEMP_DIR/checksums.txt"
318+
319+
# Verify checksum (reuse BINARY_NAME for the checksum lookup)
320+
BINARY_NAME="$ASSET_NAME"
321+
verify_checksum "$TEMP_DIR/$ASSET_NAME" "$TEMP_DIR/checksums.txt"
322+
323+
# Validate the file starts with the expected shebang
324+
if head -c 20 "$TEMP_DIR/$ASSET_NAME" | grep -q '#!/usr/bin/env node'; then
325+
info "Valid Node.js bundle"
326+
else
327+
error "Downloaded file does not appear to be a valid Node.js bundle"
328+
exit 1
329+
fi
330+
331+
# Make executable and install
332+
chmod +x "$TEMP_DIR/$ASSET_NAME"
333+
info "Installing bundle to $INSTALL_DIR/$INSTALL_NAME..."
334+
mv "$TEMP_DIR/$ASSET_NAME" "$INSTALL_DIR/$INSTALL_NAME"
305335
else
306-
error "Downloaded file is not a valid executable: $file_type"
307-
exit 1
336+
# Standalone pkg binary path
337+
BINARY_URL="${BASE_URL}/${BINARY_NAME}"
338+
339+
# Download binary and checksums
340+
download_file "$BINARY_URL" "$TEMP_DIR/$BINARY_NAME"
341+
download_file "$CHECKSUMS_URL" "$TEMP_DIR/checksums.txt"
342+
343+
# Verify checksum
344+
verify_checksum "$TEMP_DIR/$BINARY_NAME" "$TEMP_DIR/checksums.txt"
345+
346+
# Make binary executable
347+
chmod +x "$TEMP_DIR/$BINARY_NAME"
348+
349+
# Test if it's a valid executable (ELF on Linux, Mach-O on macOS)
350+
local file_type
351+
file_type=$(file "$TEMP_DIR/$BINARY_NAME")
352+
if echo "$file_type" | grep -q "ELF.*executable"; then
353+
info "Valid Linux ELF executable"
354+
elif echo "$file_type" | grep -q "Mach-O 64-bit"; then
355+
info "Valid macOS Mach-O executable"
356+
else
357+
error "Downloaded file is not a valid executable: $file_type"
358+
exit 1
359+
fi
360+
361+
# Install binary
362+
info "Installing to $INSTALL_DIR/$INSTALL_NAME..."
363+
mv "$TEMP_DIR/$BINARY_NAME" "$INSTALL_DIR/$INSTALL_NAME"
308364
fi
309-
310-
# Install binary
311-
info "Installing to $INSTALL_DIR/$INSTALL_NAME..."
312-
mv "$TEMP_DIR/$BINARY_NAME" "$INSTALL_DIR/$INSTALL_NAME"
313-
365+
314366
# Verify installation
315367
if [ -x "$INSTALL_DIR/$INSTALL_NAME" ]; then
316368
info "Installation successful! ✓"

0 commit comments

Comments
 (0)