Skip to content

fix(ci): pass deployment body as raw JSON for empty required_contexts… #13

fix(ci): pass deployment body as raw JSON for empty required_contexts…

fix(ci): pass deployment body as raw JSON for empty required_contexts… #13

Workflow file for this run

name: Release
# Triggered on a git tag push (v*). Builds AOT-published binaries for four
# platforms (linux-x64, linux-arm64, osx-arm64, win-x64), stages each full
# publish tree into binaries/<rid>/, then publishes the npm tarball from
# the ubuntu publish job.
#
# Intel Mac (osx-x64) is NOT shipped — all modern Apple hardware is arm64.
# If Intel Mac support is re-added later, either use a macos-14-large
# Intel runner or cross-compile osx-x64 from macos-14 arm64.
#
# IMPORTANT: the AOT binary depends on sibling native libs (libe_sqlite3,
# libonnxruntime, vec0, models/). The matrix job stages the ENTIRE publish
# tree into binaries/<rid>/ — copying just the executable would yield a
# DllNotFoundException at runtime.
#
# .NET SDK version is pinned by /global.json at the repo root so every
# matrix leg uses the same SDK regardless of what's pre-installed on the
# runner. Currently pinned to .NET 10 (see global.json).
#
# No untrusted github.event.* fields are interpolated into run: blocks;
# only the static matrix values and secrets.NPM_TOKEN (via env:) are used.
on:
push:
tags:
- 'v*'
permissions:
contents: write
deployments: write
jobs:
build:
name: Build ${{ matrix.rid }}
strategy:
fail-fast: false
matrix:
include:
- rid: linux-x64
os: ubuntu-latest
artifact: total-recall-linux-x64
exe: total-recall
- rid: linux-arm64
os: ubuntu-24.04-arm
artifact: total-recall-linux-arm64
exe: total-recall
- rid: osx-arm64
os: macos-14
artifact: total-recall-osx-arm64
exe: total-recall
- rid: win-x64
os: windows-latest
artifact: total-recall-win-x64
exe: total-recall.exe
runs-on: ${{ matrix.os }}
steps:
- name: Checkout (with LFS for ONNX model files)
uses: actions/checkout@v4
with:
lfs: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install npm dependencies (provides sqlite-vec native lib)
run: npm ci
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Install clang for NativeAOT (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev
# The tag name drives -p:Version so the AOT binary reports the right
# string from `total-recall --version` (CliApp.ResolveAppVersion reads
# AssemblyInformationalVersionAttribute at runtime). Stripping the
# leading "v" matches the SemVer convention embedded in package.json.
# Pre-release suffixes (e.g. "0.8.0-beta.5") flow through unchanged
# because InformationalVersion accepts SemVer 2.0 strings.
#
# REF_NAME is passed via env: (never interpolated directly into run:)
# per GitHub Actions script-injection guidance.
- name: Publish (Unix)
if: runner.os != 'Windows'
shell: bash
env:
RID: ${{ matrix.rid }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
VERSION="${REF_NAME#v}"
dotnet publish src/TotalRecall.Host/TotalRecall.Host.csproj \
-c Release \
-r "$RID" \
-p:PublishAot=true \
-p:Version="$VERSION" \
-p:InformationalVersion="$VERSION"
- name: Publish (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
RID: ${{ matrix.rid }}
REF_NAME: ${{ github.ref_name }}
run: |
$version = $env:REF_NAME -replace '^v',''
dotnet publish src/TotalRecall.Host/TotalRecall.Host.csproj `
-c Release `
-r "$env:RID" `
-p:PublishAot=true `
-p:Version="$version" `
-p:InformationalVersion="$version"
- name: Stage publish tree into binaries (Unix)
if: runner.os != 'Windows'
shell: bash
env:
RID: ${{ matrix.rid }}
EXE: ${{ matrix.exe }}
run: |
set -euo pipefail
mkdir -p "binaries/$RID"
cp -a "src/TotalRecall.Host/bin/Release/net8.0/$RID/publish/." "binaries/$RID/"
echo "--- staged tree ---"
ls -la "binaries/$RID"
test -f "binaries/$RID/$EXE"
- name: Stage publish tree into binaries (Windows)
if: runner.os == 'Windows'
shell: pwsh
env:
RID: ${{ matrix.rid }}
EXE: ${{ matrix.exe }}
run: |
$dest = "binaries/$env:RID"
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Copy-Item -Recurse -Force "src/TotalRecall.Host/bin/Release/net8.0/$env:RID/publish/*" $dest
Write-Host "--- staged tree ---"
Get-ChildItem $dest
if (-not (Test-Path "$dest/$env:EXE")) {
throw "Expected executable $dest/$env:EXE missing after staging"
}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: binaries/${{ matrix.rid }}
if-no-files-found: error
retention-days: 7
publish:
name: Publish npm package
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- name: Download linux-x64 artifact
uses: actions/download-artifact@v4
with:
name: total-recall-linux-x64
path: binaries/linux-x64
- name: Download linux-arm64 artifact
uses: actions/download-artifact@v4
with:
name: total-recall-linux-arm64
path: binaries/linux-arm64
- name: Download osx-arm64 artifact
uses: actions/download-artifact@v4
with:
name: total-recall-osx-arm64
path: binaries/osx-arm64
- name: Download win-x64 artifact
uses: actions/download-artifact@v4
with:
name: total-recall-win-x64
path: binaries/win-x64
- name: Verify all four RIDs staged
shell: bash
run: |
set -euo pipefail
missing=0
for f in \
binaries/linux-x64/total-recall \
binaries/linux-arm64/total-recall \
binaries/osx-arm64/total-recall \
binaries/win-x64/total-recall.exe; do
if [ ! -f "$f" ]; then
echo "MISSING: $f" >&2
missing=1
else
echo "OK: $f ($(stat -c%s "$f") bytes)"
fi
done
if [ "$missing" -ne 0 ]; then
echo "One or more RIDs missing from staged binaries/ tree" >&2
exit 1
fi
echo "--- full binaries/ tree ---"
find binaries -maxdepth 2 -type f | sort
- name: Restore executable bit on Unix binaries
shell: bash
run: |
chmod +x binaries/linux-x64/total-recall
chmod +x binaries/linux-arm64/total-recall
chmod +x binaries/osx-arm64/total-recall
- name: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# Prerelease-aware dist-tag routing. If package.json version contains
# a dash (e.g. 0.8.0-beta.1), publish under the matching dist-tag
# (beta, rc, alpha) so "latest" is NOT clobbered. Only stable versions
# publish to latest. No untrusted github.event.* input is read here;
# the version string comes from the committed package.json at the
# tagged SHA.
run: |
VERSION=$(node -p "require('./package.json').version")
case "$VERSION" in
*-*)
PRERELEASE_TAG="${VERSION##*-}"
PRERELEASE_TAG="${PRERELEASE_TAG%%.*}"
echo "Publishing $VERSION under dist-tag $PRERELEASE_TAG"
npm publish --tag "$PRERELEASE_TAG"
;;
*)
echo "Publishing $VERSION under dist-tag latest"
npm publish
;;
esac
- name: Create GitHub deployment
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(node -p "require('./package.json').version")
DEPLOY_ID=$(gh api repos/${{ github.repository }}/deployments \
--input - \
--jq '.id' <<PAYLOAD
{"ref":"${{ github.sha }}","environment":"npm-publish","description":"v${VERSION} published to npm","auto_merge":false,"required_contexts":[]}
PAYLOAD
)
gh api "repos/${{ github.repository }}/deployments/${DEPLOY_ID}/statuses" \
-f state="success" \
-f description="v${VERSION} published to npm" \
-f environment_url="https://www.npmjs.com/package/@strvmarv/total-recall/v/${VERSION}"
- name: Stage per-RID release assets
shell: bash
# The AOT binary alone is NOT runnable — it depends on sibling
# libonnxruntime.dylib, vec0.dylib (via sqlite-vec), the models/
# subtree, and any other native runtime assets staged into
# binaries/<rid>/ by the matrix publish step. Uploading just the
# executable (as 0.8.0-beta.4 did) produces a TypeInitializationException
# at first DB open because Microsoft.ML.OnnxRuntime cannot P/Invoke
# into the missing libonnxruntime.dylib.
#
# Ship the entire publish tree per RID as a compressed archive:
# - Unix RIDs -> .tar.gz (preserves executable bit + symlinks)
# - Windows -> .zip (native Windows tooling)
#
# scripts/fetch-binary.js downloads the matching archive, extracts
# it into binaries/<rid>/, and re-asserts the executable bit.
run: |
set -euo pipefail
mkdir -p release-assets
# tar -C <dir> -czf <out> . packs the contents of <dir> at the
# archive root so extraction with `tar -xzf archive -C dest`
# drops them straight into dest/ without a leading <rid>/ dir.
#
# All RIDs (including win-x64) use .tar.gz. The publish runner is
# ubuntu-latest where `tar` is GNU tar, NOT bsdtar — GNU tar's -a
# selects a compression program from the suffix and falls through
# to uncompressed for .zip, producing a tar file with a misleading
# extension. v0.8.0-beta.6's win-x64.zip was actually a POSIX tar
# archive that broke any Windows extraction tool requiring real
# PKZIP format. Switching the Windows leg to .tar.gz too keeps
# one archive format and one extraction code path. Windows 10+
# ships tar.exe (bsdtar/libarchive) which handles .tar.gz natively
# since 1803, and scripts/fetch-binary.js calls it directly on
# Windows installs.
tar -C binaries/linux-x64 -czf release-assets/total-recall-linux-x64.tar.gz .
tar -C binaries/linux-arm64 -czf release-assets/total-recall-linux-arm64.tar.gz .
tar -C binaries/osx-arm64 -czf release-assets/total-recall-osx-arm64.tar.gz .
tar -C binaries/win-x64 -czf release-assets/total-recall-win-x64.tar.gz .
ls -la release-assets/
echo "--- archive contents preview ---"
tar -tzf release-assets/total-recall-osx-arm64.tar.gz | head -20
- name: Attach archives to GitHub release
uses: softprops/action-gh-release@v2
with:
files: |
release-assets/total-recall-linux-x64.tar.gz
release-assets/total-recall-linux-arm64.tar.gz
release-assets/total-recall-osx-arm64.tar.gz
release-assets/total-recall-win-x64.tar.gz
prerelease: ${{ contains(github.ref_name, '-') }}
fail_on_unmatched_files: true