Skip to content

ci(release): invoke azuresigntool in lowercase for Linux compat #5

ci(release): invoke azuresigntool in lowercase for Linux compat

ci(release): invoke azuresigntool in lowercase for Linux compat #5

Workflow file for this run

name: Release
# Triggered only by tag pushes matching v*. The release environment's
# deployment_branch_policy further restricts execution to tag refs
# matching v*; subscribed environment reviewers must approve before
# the publish job runs. Build jobs do NOT require approval (they
# produce unsigned artifacts that are useless without the publish
# step's signing + npm push), so review friction is one-time.
on:
push:
tags:
- 'v*'
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write # gh release create
id-token: write # OIDC for Azure + npm provenance
jobs:
build:
name: Build ${{ matrix.rid }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: true
matrix:
include:
- rid: win-x64
runner: windows-latest
platform: win32-x64
exe: ripple.exe
- rid: linux-x64
runner: ubuntu-latest
platform: linux-x64
exe: ripple
- rid: osx-arm64
runner: macos-latest
platform: darwin-arm64
exe: ripple
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: dotnet restore
run: dotnet restore ripple.csproj -r ${{ matrix.rid }}
- name: dotnet publish (NativeAOT)
run: dotnet publish ripple.csproj -c Release -r ${{ matrix.rid }} -o dist --no-restore
# Run the full --test suite on the produced binary. This is the
# real gate — a build that compiles but breaks at runtime on
# this platform should not be published. No special
# adapter-test setup; the tests that exercise cross-cutting
# concerns (capture, render, OSC parsing, etc.) don't need
# external shells / REPLs to be installed.
- name: Run unit tests
shell: bash
run: |
chmod +x "dist/${{ matrix.exe }}" || true
"./dist/${{ matrix.exe }}" --test
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: ripple-${{ matrix.platform }}
path: dist/${{ matrix.exe }}
if-no-files-found: error
retention-days: 7
publish:
name: Sign, publish, release
needs: build
runs-on: ubuntu-latest
environment: release # required-reviewer gate + NPM_TOKEN scope
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET 9
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Verify tag matches all version fields
shell: bash
run: |
set -euo pipefail
tag='${{ github.ref_name }}'
version="${tag#v}"
csproj_version=$(grep -oE '<Version>[^<]+</Version>' ripple.csproj | head -n1 | sed -E 's/<Version>([^<]+)<\/Version>/\1/')
meta_version=$(node -p "require('./npm/package.json').version")
echo "Tag: $version"
echo "csproj: $csproj_version"
echo "meta: $meta_version"
[[ "$version" == "$csproj_version" ]] || { echo "csproj version mismatch" >&2; exit 1; }
[[ "$version" == "$meta_version" ]] || { echo "meta package.json version mismatch" >&2; exit 1; }
for plat in win32-x64 linux-x64 darwin-arm64; do
pv=$(node -p "require('./npm/platforms/${plat}/package.json').version")
echo "$plat: $pv"
[[ "$version" == "$pv" ]] || { echo "$plat subpackage version mismatch" >&2; exit 1; }
done
- name: Download all platform binaries
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Place binaries into subpackages + fix executable bits
shell: bash
run: |
set -euo pipefail
cp artifacts/ripple-win32-x64/ripple.exe npm/platforms/win32-x64/bin/ripple.exe
cp artifacts/ripple-linux-x64/ripple npm/platforms/linux-x64/bin/ripple
cp artifacts/ripple-darwin-arm64/ripple npm/platforms/darwin-arm64/bin/ripple
chmod +x npm/platforms/linux-x64/bin/ripple
chmod +x npm/platforms/darwin-arm64/bin/ripple
# Remove placeholder .gitkeep files so they don't ship in the tarball.
rm -f npm/platforms/*/bin/.gitkeep
ls -la npm/platforms/*/bin/
- name: Azure OIDC login
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Install AzureSignTool
# dotnet tool install --global lands the binary in $HOME/.dotnet/tools
# but, on Linux runners, that directory is NOT on $PATH by default
# (Windows runners add it automatically). Export it via $GITHUB_PATH
# so the next step's `AzureSignTool` invocation resolves.
run: |
dotnet tool install --global AzureSignTool
echo "$HOME/.dotnet/tools" >> "$GITHUB_PATH"
# AzureSignTool is a cross-platform .NET tool; Authenticode
# signing works from Linux as long as we have the Key Vault
# access token. The invocation is `azuresigntool` (lowercase) —
# v7+ installs the binary under that name, and Linux's
# case-sensitive filesystem won't resolve `AzureSignTool`.
# Windows runners happen to be case-insensitive but the
# lowercase form is portable.
- name: Sign Windows binary via Azure Key Vault
env:
AZURE_KV_URL: ${{ vars.KEY_VAULT_URL }}
AZURE_KV_CERT: ${{ vars.SIGNING_CERT_NAME }}
EXPECTED_THUMBPRINT: '74E5208228DFB12A067747D536BF497B6E98C73C'
shell: bash
run: |
set -euo pipefail
token=$(az account get-access-token --resource https://vault.azure.net --query accessToken -o tsv)
[[ -n "$token" ]] || { echo "Failed to acquire Key Vault access token" >&2; exit 1; }
azuresigntool sign \
--azure-key-vault-url "$AZURE_KV_URL" \
--azure-key-vault-certificate "$AZURE_KV_CERT" \
--azure-key-vault-accesstoken "$token" \
--file-digest sha256 \
--timestamp-rfc3161 'http://timestamp.digicert.com' \
--description 'ripple - declarative shell adapter framework' \
--description-url 'https://github.com/yotsuda/ripple' \
npm/platforms/win32-x64/bin/ripple.exe
# Publish subpackages BEFORE meta. Meta's optionalDependencies
# reference these exact versions; if meta published first and a
# subpackage publish later failed, users installing the meta
# would see missing optional deps and degraded UX. Sequential
# ordering also lets a failure halt before meta is published
# (avoids broken meta pointing at a missing subpackage).
- name: Publish subpackages with provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: bash
run: |
set -euo pipefail
for plat in win32-x64 linux-x64 darwin-arm64; do
echo "=== publishing @ytsuda/ripple-${plat} ==="
(cd "npm/platforms/${plat}" && npm publish --access public --provenance)
done
- name: Publish meta package with provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: bash
run: |
set -euo pipefail
cd npm
npm publish --access public --provenance
- name: Extract changelog entry
shell: bash
run: |
set -euo pipefail
version='${{ github.ref_name }}'
version="${version#v}"
awk -v v="$version" '
/^## +\[/ {
if (capture) exit
if ($0 ~ "^## +\\[" v "\\]") { capture = 1; next }
}
capture { print }
' CHANGELOG.md > release-notes.md
if [[ ! -s release-notes.md ]]; then
echo "No CHANGELOG section for v$version" >&2
exit 1
fi
wc -l release-notes.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create '${{ github.ref_name }}' \
--title "ripple ${{ github.ref_name }}" \
--notes-file release-notes.md \
--verify-tag \
'npm/platforms/win32-x64/bin/ripple.exe#ripple-${{ github.ref_name }}-win32-x64.exe' \
'npm/platforms/linux-x64/bin/ripple#ripple-${{ github.ref_name }}-linux-x64' \
'npm/platforms/darwin-arm64/bin/ripple#ripple-${{ github.ref_name }}-darwin-arm64'