Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions .github/workflows/build-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,27 @@ jobs:
node-version: '22'
registry-url: 'https://registry.npmjs.org'

- name: Build binaries
run: ./scripts/build-binaries.sh
- name: Create source archive
run: |
set -euo pipefail

VERSION="${RELEASE_TAG#v}"
mkdir -p release-assets
./scripts/create-source-archive.sh \
--version "${VERSION}" \
--ref HEAD \
--out "release-assets/pi-${VERSION}-source.tar.gz"

- name: Build binaries from source archive
run: |
set -euo pipefail

VERSION="${RELEASE_TAG#v}"
build_root="$(mktemp -d)"
trap 'rm -rf "${build_root}"' EXIT
tar -xzf "release-assets/pi-${VERSION}-source.tar.gz" -C "${build_root}"
"${build_root}/pi-${VERSION}/scripts/build-binaries.sh" \
--out "${GITHUB_WORKSPACE}/packages/coding-agent/binaries"

- name: Prepare GitHub release payload
run: |
Expand Down Expand Up @@ -83,7 +102,9 @@ jobs:
cp "${binary_assets[@]}" "${GITHUB_WORKSPACE}/release-assets/"

cd "${GITHUB_WORKSPACE}/release-assets"
source_asset="pi-${VERSION}-source.tar.gz"
release_assets=(
"${source_asset}"
pi-darwin-arm64.tar.gz
pi-darwin-x64.tar.gz
pi-linux-x64.tar.gz
Expand Down Expand Up @@ -125,7 +146,10 @@ jobs:

cd release-assets

VERSION="${RELEASE_TAG#v}"
source_asset="pi-${VERSION}-source.tar.gz"
expected_assets=(
"${source_asset}"
pi-darwin-arm64.tar.gz
pi-darwin-x64.tar.gz
pi-linux-x64.tar.gz
Expand All @@ -144,15 +168,18 @@ jobs:

sha256sum -c SHA256SUMS

- name: Create draft GitHub Release and upload binaries
- name: Create draft GitHub Release and upload assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail

cd release-assets

VERSION="${RELEASE_TAG#v}"
source_asset="pi-${VERSION}-source.tar.gz"
release_assets=(
"${source_asset}"
pi-darwin-arm64.tar.gz
pi-darwin-x64.tar.gz
pi-linux-x64.tar.gz
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ npm run check # Lint, format, and type check
./pi-test.sh # Run pi from sources (can be run from any directory)
```

## Building standalone binaries from release source

GitHub releases include a versioned source archive covered by the release's `SHA256SUMS` file. Extract it and run the same build script used for the official standalone binaries:

```bash
VERSION="<release-version>"
tar -xzf "pi-${VERSION}-source.tar.gz"
cd "pi-${VERSION}"
./scripts/build-binaries.sh --platform linux-x64 --out "$PWD/out"
```

The script installs dependencies, builds the monorepo, compiles the Bun executable, and stages its runtime assets. Package maintainers who provide dependencies separately can pass `--skip-install --skip-deps`.

## Supply-chain hardening

We treat npm dependency changes as reviewed code changes.
Expand Down
117 changes: 117 additions & 0 deletions scripts/create-source-archive.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Create the deterministic source archive uploaded with GitHub releases.
#
# Usage:
# ./scripts/create-source-archive.sh --version <version> --ref <git-ref> --out <archive.tar.gz>

set -euo pipefail

version=""
source_ref="HEAD"
output=""
invocation_dir="$PWD"

usage() {
echo "Usage: $0 --version <version> [--ref <git-ref>] --out <archive.tar.gz>"
}

require_value() {
if [[ $# -lt 2 || -z "$2" ]]; then
echo "$1 requires a value" >&2
usage >&2
exit 1
fi
}

while [[ $# -gt 0 ]]; do
case "$1" in
--version)
require_value "$@"
version="$2"
shift 2
;;
--ref)
require_value "$@"
source_ref="$2"
shift 2
;;
--out)
require_value "$@"
output="$2"
shift 2
;;
--help)
usage
exit 0
;;
*)
echo "Unknown option: $1" >&2
exit 1
;;
esac
done

if [[ -z "$version" || -z "$output" ]]; then
usage >&2
exit 1
fi

if [[ ! "$version" =~ ^[0-9A-Za-z][0-9A-Za-z._-]*$ ]]; then
echo "Invalid version: $version" >&2
exit 1
fi

repo_root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$repo_root"

commit="$(git rev-parse --verify --end-of-options "${source_ref}^{commit}")"

package_version="$(git show "${commit}:packages/coding-agent/package.json" | node -p 'JSON.parse(require("fs").readFileSync(0, "utf8")).version')"
if [[ "$package_version" != "$version" ]]; then
echo "Version ${version} does not match package version ${package_version} at ${source_ref}" >&2
exit 1
fi

if [[ "$output" != /* ]]; then
output="$invocation_dir/$output"
fi
mkdir -p "$(dirname "$output")"
output="$(cd "$(dirname "$output")" && pwd)/$(basename "$output")"

temporary_archive="$(mktemp "${output}.tmp.XXXXXX")"
manifest="$(mktemp "${output}.manifest.XXXXXX")"
trap 'rm -f "$temporary_archive" "$manifest"' EXIT

archive_root="pi-${version}"
git archive --format=tar --prefix="${archive_root}/" "$commit" | gzip -n -9 > "$temporary_archive"
tar -tzf "$temporary_archive" > "$manifest"

required_paths=(
"package.json"
"package-lock.json"
"scripts/build-binaries.sh"
"packages/coding-agent/package.json"
"packages/coding-agent/src/utils/image-resize-worker.ts"
"packages/coding-agent/src/core/export-html/template.css"
)

for path in "${required_paths[@]}"; do
if ! grep -Fxq "${archive_root}/${path}" "$manifest"; then
echo "Source archive is missing required path: $path" >&2
exit 1
fi
done

if ! awk -v prefix="${archive_root}/" 'index($0, prefix) != 1 { exit 1 }' "$manifest"; then
echo "Source archive contains a path outside ${archive_root}/" >&2
exit 1
fi

if grep -Eq '(^|/)node_modules/|(^|/)packages/coding-agent/binaries/' "$manifest"; then
echo "Source archive contains generated dependencies or binaries" >&2
exit 1
fi

mv "$temporary_archive" "$output"
trap 'rm -f "$manifest"' EXIT
printf '%s\n' "$output"