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
379 changes: 379 additions & 0 deletions .github/workflows/sdk-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
name: Release Graft SDK

on:
pull_request:
branches: ["main"]
paths:
- ".github/workflows/sdk-release.yml"
- "Cargo.lock"
- "Cargo.toml"
- "crates/graft-sdk/**"
- "crates/graft-sdk-node/**"
- "crates/graft-sqlite/**"
- "crates/graft/**"
- "packages/graft-sdk/**"
push:
tags:
- "graft-sdk-v*"
workflow_dispatch:

concurrency:
group: graft-sdk-release-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
metadata:
name: Resolve SDK release metadata
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.meta.outputs.version }}
prerelease: ${{ steps.meta.outputs.prerelease }}
is_release: ${{ steps.meta.outputs.is_release }}
steps:
- name: Checkout release commit
uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0
persist-credentials: false

- name: Validate tag, commit, and package versions
id: meta
shell: bash
run: |
set -euo pipefail

package_version="$(node -p "require('./packages/graft-sdk/package.json').version")"
version="$package_version"
is_release="false"
if [[ "$GITHUB_REF_TYPE" == "tag" ]]; then
version="${GITHUB_REF_NAME#graft-sdk-v}"
is_release="true"
fi
version_regex='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'
if [[ ! "$version" =~ $version_regex ]]; then
echo "::error::SDK release tag must match graft-sdk-vX.Y.Z or a SemVer prerelease"
exit 1
fi

if [[ "$is_release" == "true" ]]; then
git fetch --no-tags origin main:refs/remotes/origin/main
head_commit="$(git rev-parse HEAD)"
if ! git merge-base --is-ancestor "$head_commit" origin/main; then
echo "::error::release commit $head_commit is not reachable from origin/main"
exit 1
fi
if ! git ls-remote --exit-code origin "refs/tags/${GITHUB_REF_NAME}^{}" >/dev/null; then
echo "::error::release tag ${GITHUB_REF_NAME} must be annotated"
exit 1
fi
fi

rust_version="$(cargo pkgid -p graft-sdk | sed 's/.*#//')"
node_rust_version="$(cargo pkgid -p graft-sdk-node | sed 's/.*#//')"
for actual in "$package_version" "$rust_version" "$node_rust_version"; do
if [[ "$actual" != "$version" ]]; then
echo "::error::SDK package version $actual does not match tag version $version"
exit 1
fi
done
cargo metadata --locked --format-version 1 --no-deps >/dev/null

prerelease="false"
[[ "$version" == *-* ]] && prerelease="true"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
echo "is_release=$is_release" >> "$GITHUB_OUTPUT"

build:
name: Build ${{ matrix.target }}
needs: metadata
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
target: aarch64-apple-darwin
suffix: darwin-arm64
- runner: macos-15-intel
target: x86_64-apple-darwin
suffix: darwin-x64
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
suffix: linux-arm64-gnu
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
suffix: linux-x64-gnu
- runner: windows-2022
target: x86_64-pc-windows-msvc
suffix: win32-x64-msvc
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Install Linux dependencies
if: startsWith(matrix.runner, 'ubuntu')
run: |
sudo apt-get update
sudo apt-get install -y clang libclang-dev build-essential

- name: Configure macOS libclang
if: startsWith(matrix.runner, 'macos')
run: |
brew install llvm
llvm_prefix="$(brew --prefix llvm)"
echo "LIBCLANG_PATH=${llvm_prefix}/lib" >> "$GITHUB_ENV"

- name: Install Rust
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
with:
toolchain: stable
targets: ${{ matrix.target }}

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
key: graft-sdk-${{ matrix.target }}

- name: Build Node-API addon
run: packages/graft-sdk/scripts/build-native.sh "${{ matrix.target }}" "$PWD/sdk-artifact"

- name: Upload addon
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: graft-sdk-${{ matrix.target }}
path: sdk-artifact/graft-sdk.${{ matrix.suffix }}.node
if-no-files-found: error
retention-days: 7

test:
name: Test ${{ matrix.settings.target }} on Node.js ${{ matrix.node }}
needs: [metadata, build]
runs-on: ${{ matrix.settings.runner }}
strategy:
fail-fast: false
matrix:
node: [20, 24]
settings:
- runner: macos-15
target: aarch64-apple-darwin
suffix: darwin-arm64
- runner: macos-15-intel
target: x86_64-apple-darwin
suffix: darwin-x64
- runner: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
suffix: linux-arm64-gnu
- runner: ubuntu-24.04
target: x86_64-unknown-linux-gnu
suffix: linux-x64-gnu
- runner: windows-2022
target: x86_64-pc-windows-msvc
suffix: win32-x64-msvc
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: ${{ matrix.node }}
package-manager-cache: false

- name: Download addon
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: graft-sdk-${{ matrix.settings.target }}
path: sdk-artifact

- name: Test native session contract
env:
GRAFT_SDK_NATIVE_PATH: ${{ github.workspace }}/sdk-artifact/graft-sdk.${{ matrix.settings.suffix }}.node
run: node --test packages/graft-sdk/test/*.test.js

package:
name: Verify npm package assembly
needs: [metadata, test]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24
package-manager-cache: false

- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
version: 10.14.0

- name: Install packaging dependencies
run: pnpm install --frozen-lockfile

- name: Create native package directories
run: pnpm --dir packages/graft-sdk release:create-packages

- name: Download all tested addons
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
pattern: graft-sdk-*
path: packages/graft-sdk/artifacts

- name: Assemble and verify package contents
run: |
pnpm --dir packages/graft-sdk release:collect
pnpm --dir packages/graft-sdk release:prepare
pnpm --dir packages/graft-sdk release:verify
for directory in packages/graft-sdk/npm/* packages/graft-sdk; do
npm pack "./$directory" --dry-run --ignore-scripts >/dev/null
done

publish:
name: Publish npm packages and GitHub release
needs: [metadata, test, package]
if: needs.metadata.outputs.is_release == 'true'
runs-on: ubuntu-24.04
environment: npm
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Node.js and npm registry
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Install npm with trusted-publishing support
run: npm install --global npm@11.16.0

- uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0
with:
version: 10.14.0

- name: Install packaging dependencies
run: pnpm install --frozen-lockfile

- name: Create native package directories
run: pnpm --dir packages/graft-sdk release:create-packages

- name: Download all addons
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
pattern: graft-sdk-*
path: packages/graft-sdk/artifacts

- name: Collect and verify release packages
run: |
pnpm --dir packages/graft-sdk release:collect
pnpm --dir packages/graft-sdk release:prepare
pnpm --dir packages/graft-sdk release:verify
for directory in packages/graft-sdk/npm/* packages/graft-sdk; do
npm pack "./$directory" --dry-run --ignore-scripts >/dev/null
done

- name: Prepare GitHub release assets
run: |
mkdir -p release-assets
find packages/graft-sdk/npm -name '*.node' -exec cp {} release-assets/ \;
(cd release-assets && sha256sum *.node > SHA256SUMS)

- name: Publish platform packages then root package
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: pnpm --dir packages/graft-sdk release:publish

- name: Create or update SDK GitHub release
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
name: Graft SDK v${{ needs.metadata.outputs.version }}
tag_name: ${{ github.ref_name }}
target_commitish: ${{ github.sha }}
prerelease: ${{ needs.metadata.outputs.prerelease }}
make_latest: false
fail_on_unmatched_files: true
generate_release_notes: true
files: release-assets/*

verify-published:
name: Verify npm install on ${{ matrix.suffix }}
needs: [metadata, publish]
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
suffix: darwin-arm64
- runner: macos-15-intel
suffix: darwin-x64
- runner: ubuntu-24.04-arm
suffix: linux-arm64-gnu
- runner: ubuntu-24.04
suffix: linux-x64-gnu
- runner: windows-2022
suffix: win32-x64-msvc
defaults:
run:
shell: bash
steps:
- name: Setup Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: 24
registry-url: https://registry.npmjs.org
package-manager-cache: false

- name: Install and open a repository session
env:
SDK_VERSION: ${{ needs.metadata.outputs.version }}
run: |
fixture="$(mktemp -d)"
cd "$fixture"
npm init -y >/dev/null
npm install --ignore-scripts "@eidos.space/graft@${SDK_VERSION}"
node - <<'NODE'
const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { RepositorySession, sdkVersion } = require('@eidos.space/graft')

const root = fs.mkdtempSync(path.join(os.tmpdir(), 'graft-sdk-install-'))
RepositorySession.open(root)
.then(async (session) => {
await session.init()
const status = await session.status()
if (status.dirty !== false || sdkVersion() !== process.env.SDK_VERSION) {
throw new Error('published SDK smoke returned unexpected metadata')
}
await session.close()
})
.catch((error) => {
console.error(error)
process.exitCode = 1
})
NODE
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
/dist/
/distx/
/node_modules/
packages/graft-sdk/native/*.node
packages/graft-sdk/*.node
packages/graft-sdk/artifacts/
packages/graft-sdk/npm/
packages/graft-sdk/node_modules/
**/*.rs.bk
*.pdb
.env
Expand Down
Loading
Loading