docs(release): prepare v1.0.95 #103
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release CLI | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| jobs: | |
| meta: | |
| name: Resolve tag metadata | |
| runs-on: ubuntu-latest | |
| outputs: | |
| tag: ${{ steps.resolve.outputs.tag }} | |
| version: ${{ steps.resolve.outputs.version }} | |
| prerelease: ${{ steps.resolve.outputs.prerelease }} | |
| steps: | |
| - id: resolve | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| tag="${GITHUB_REF_NAME}" | |
| if [[ ! "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?)$ ]]; then | |
| echo "Tag '$tag' is not a valid semver release tag (expected vX.Y.Z)." >&2 | |
| exit 1 | |
| fi | |
| version="${BASH_REMATCH[1]}" | |
| prerelease="false" | |
| if [[ "$version" == *"-"* ]]; then | |
| prerelease="true" | |
| fi | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "version=$version" >> "$GITHUB_OUTPUT" | |
| echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT" | |
| publish_npm: | |
| name: Publish @pushpalsdev/cli to npm | |
| runs-on: ubuntu-latest | |
| needs: | |
| - meta | |
| - build_binaries | |
| - build_runtime_binaries | |
| - smoke_windows_runtime | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Set CLI package version from tag | |
| shell: bash | |
| run: | | |
| node -e "const fs=require('fs');const path='packages/cli/package.json';const pkg=JSON.parse(fs.readFileSync(path,'utf8'));pkg.version='${{ needs.meta.outputs.version }}';fs.writeFileSync(path,JSON.stringify(pkg,null,2)+'\n');" | |
| - name: Check whether CLI version is already published | |
| id: npm_publish_check | |
| shell: bash | |
| run: | | |
| published_version="$(npm view "@pushpalsdev/cli@${{ needs.meta.outputs.version }}" version 2>/dev/null || true)" | |
| if [ "$published_version" = "${{ needs.meta.outputs.version }}" ]; then | |
| echo "already_published=true" >> "$GITHUB_OUTPUT" | |
| echo "@pushpalsdev/cli@${{ needs.meta.outputs.version }} is already published on npm; skipping publish job." | |
| else | |
| echo "already_published=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Verify npm publish token | |
| if: steps.npm_publish_check.outputs.already_published != 'true' | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${NODE_AUTH_TOKEN:-}" ]; then | |
| echo "::error::NPM_TOKEN secret is required to publish @pushpalsdev/cli." >&2 | |
| exit 1 | |
| fi | |
| whoami_stderr="$RUNNER_TEMP/npm-whoami.stderr" | |
| token_user="$(npm whoami --registry=https://registry.npmjs.org 2>"$whoami_stderr" || true)" | |
| if [ -z "$token_user" ]; then | |
| echo "::error::NPM_TOKEN is set, but npm whoami failed. The token is invalid, expired, or not accepted by registry.npmjs.org." >&2 | |
| cat "$whoami_stderr" >&2 || true | |
| exit 1 | |
| fi | |
| echo "NPM_TOKEN authenticates as npm user: ${token_user}" | |
| owners_stderr="$RUNNER_TEMP/npm-owner-ls.stderr" | |
| owners="$(npm owner ls @pushpalsdev/cli --registry=https://registry.npmjs.org 2>"$owners_stderr" || true)" | |
| if [ -z "$owners" ]; then | |
| echo "::error::Could not read @pushpalsdev/cli npm owners. The token may not be able to access the package." >&2 | |
| cat "$owners_stderr" >&2 || true | |
| exit 1 | |
| fi | |
| if ! printf '%s\n' "$owners" | awk '{print $1}' | grep -Fxq "$token_user"; then | |
| echo "::error::NPM_TOKEN authenticates as '${token_user}', but that user is not listed as an owner for @pushpalsdev/cli. Create the secret from a package owner or grant this npm user publish rights." >&2 | |
| echo "Current @pushpalsdev/cli owners:" >&2 | |
| printf '%s\n' "$owners" >&2 | |
| exit 1 | |
| fi | |
| access_json="$RUNNER_TEMP/npm-access-collaborators.json" | |
| access_stderr="$RUNNER_TEMP/npm-access-collaborators.stderr" | |
| if npm access list collaborators @pushpalsdev/cli --json --registry=https://registry.npmjs.org >"$access_json" 2>"$access_stderr"; then | |
| ACCESS_JSON="$access_json" TOKEN_USER="$token_user" node <<'JS' | |
| const fs = require("fs"); | |
| const raw = fs.readFileSync(process.env.ACCESS_JSON, "utf8").replace(/^\uFEFF/, ""); | |
| const access = JSON.parse(raw); | |
| const permission = access[process.env.TOKEN_USER]; | |
| if (!permission || !String(permission).includes("write")) { | |
| console.error( | |
| `::error::NPM_TOKEN user ${process.env.TOKEN_USER} does not have write access to @pushpalsdev/cli. Observed permission: ${permission || "(none)"}.`, | |
| ); | |
| process.exit(1); | |
| } | |
| console.log(`Verified npm write access for @pushpalsdev/cli: ${permission}`); | |
| JS | |
| else | |
| echo "::warning::Could not verify @pushpalsdev/cli collaborator permissions through npm access. Package-scoped granular tokens can be blocked from metadata endpoints; continuing to npm publish after owner verification." >&2 | |
| cat "$access_stderr" >&2 || true | |
| fi | |
| - name: Build CLI package payload | |
| if: steps.npm_publish_check.outputs.already_published != 'true' | |
| run: bun run --cwd packages/cli build | |
| - name: Publish to npm | |
| if: steps.npm_publish_check.outputs.already_published != 'true' | |
| working-directory: packages/cli | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| run: | | |
| if [ -z "${NODE_AUTH_TOKEN:-}" ]; then | |
| echo "NPM_TOKEN secret is required to publish @pushpalsdev/cli." >&2 | |
| exit 1 | |
| fi | |
| npm publish --access public --provenance | |
| smoke_published_cli_linux: | |
| name: Smoke published CLI package on Linux | |
| runs-on: ubuntu-latest | |
| needs: | |
| - meta | |
| - publish_npm | |
| - publish_release | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Run installed-package Linux smoke | |
| run: | | |
| bun run scripts/release-installed-cli-smoke.ts \ | |
| --package-spec @pushpalsdev/cli@${{ needs.meta.outputs.version }} | |
| smoke_published_cli_windows: | |
| name: Smoke published CLI package on Windows | |
| runs-on: windows-2022 | |
| needs: | |
| - meta | |
| - publish_npm | |
| - publish_release | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Run installed-package Windows smoke | |
| shell: pwsh | |
| run: | | |
| bun run scripts/release-installed-cli-smoke.ts ` | |
| --package-spec @pushpalsdev/cli@${{ needs.meta.outputs.version }} | |
| build_binaries: | |
| name: Build standalone CLI binaries | |
| needs: meta | |
| env: | |
| WINDOWS_CODESIGN_PFX_BASE64: ${{ secrets.WINDOWS_CODESIGN_PFX_BASE64 }} | |
| WINDOWS_CODESIGN_PFX_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PFX_PASSWORD }} | |
| MACOS_CODESIGN_CERT_P12_BASE64: ${{ secrets.MACOS_CODESIGN_CERT_P12_BASE64 }} | |
| MACOS_CODESIGN_CERT_PASSWORD: ${{ secrets.MACOS_CODESIGN_CERT_PASSWORD }} | |
| MACOS_CODESIGN_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_CODESIGN_KEYCHAIN_PASSWORD }} | |
| MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: bun-linux-x64 | |
| output_name: pushpals-linux-x64 | |
| runs_on: ubuntu-latest | |
| - target: bun-windows-x64 | |
| output_name: pushpals-windows-x64.exe | |
| runs_on: windows-2022 | |
| - target: bun-darwin-x64 | |
| output_name: pushpals-macos-x64 | |
| runs_on: macos-14 | |
| - target: bun-darwin-arm64 | |
| output_name: pushpals-macos-arm64 | |
| runs_on: macos-14 | |
| runs-on: ${{ matrix.runs_on }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build standalone binary | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| output="dist/${{ matrix.output_name }}" | |
| bun build scripts/pushpals-cli.ts --compile --target=${{ matrix.target }} --outfile "$output" | |
| if [[ "${{ matrix.target }}" != "bun-windows-x64" ]]; then | |
| chmod +x "$output" | |
| fi | |
| - name: Install Windows signing tool | |
| if: matrix.target == 'bun-windows-x64' && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os != 'Windows' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y osslsigncode | |
| - name: Sign Windows CLI binary | |
| if: matrix.target == 'bun-windows-x64' && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| WINDOWS_CODESIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_CODESIGN_TIMESTAMP_URL }} | |
| run: | | |
| set -euo pipefail | |
| cert_path="$(mktemp)" | |
| printf '%s' "$WINDOWS_CODESIGN_PFX_BASE64" | base64 -d > "$cert_path" | |
| output="dist/${{ matrix.output_name }}" | |
| signed_output="${output}.signed" | |
| timestamp_url="${WINDOWS_CODESIGN_TIMESTAMP_URL:-http://timestamp.digicert.com}" | |
| osslsigncode sign \ | |
| -pkcs12 "$cert_path" \ | |
| -pass "$WINDOWS_CODESIGN_PFX_PASSWORD" \ | |
| -h sha256 \ | |
| -n "PushPals CLI" \ | |
| -i "https://github.com/PushPalsDev/pushpals" \ | |
| -ts "$timestamp_url" \ | |
| -in "$output" \ | |
| -out "$signed_output" | |
| mv "$signed_output" "$output" | |
| rm -f "$cert_path" | |
| - name: Sign Windows CLI binary (signtool) | |
| if: matrix.target == 'bun-windows-x64' && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| WINDOWS_CODESIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_CODESIGN_TIMESTAMP_URL }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $certPath = Join-Path $env:RUNNER_TEMP 'pushpals-cli-codesign-cert.pfx' | |
| [System.IO.File]::WriteAllBytes( | |
| $certPath, | |
| [System.Convert]::FromBase64String($env:WINDOWS_CODESIGN_PFX_BASE64) | |
| ) | |
| $signtool = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Recurse -Filter signtool.exe | | |
| Sort-Object FullName -Descending | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $signtool) { | |
| throw 'signtool.exe not found on runner.' | |
| } | |
| $timestampUrl = if ($env:WINDOWS_CODESIGN_TIMESTAMP_URL) { | |
| $env:WINDOWS_CODESIGN_TIMESTAMP_URL | |
| } else { | |
| 'http://timestamp.digicert.com' | |
| } | |
| & $signtool sign ` | |
| /f $certPath ` | |
| /p $env:WINDOWS_CODESIGN_PFX_PASSWORD ` | |
| /fd SHA256 ` | |
| /td SHA256 ` | |
| /tr $timestampUrl ` | |
| /d 'PushPals CLI' ` | |
| /du 'https://github.com/PushPalsDev/pushpals' ` | |
| "dist/${{ matrix.output_name }}" | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "signtool failed with exit code $LASTEXITCODE" | |
| } | |
| - name: Import macOS signing certificate | |
| if: startsWith(matrix.target, 'bun-darwin-') && env.MACOS_CODESIGN_CERT_P12_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cert_path="$RUNNER_TEMP/pushpals-codesign-cert.p12" | |
| keychain_path="$RUNNER_TEMP/pushpals-signing.keychain-db" | |
| python3 - <<'PY' | |
| import base64, os, pathlib | |
| pathlib.Path(os.environ["RUNNER_TEMP"], "pushpals-codesign-cert.p12").write_bytes( | |
| base64.b64decode(os.environ["MACOS_CODESIGN_CERT_P12_BASE64"]) | |
| ) | |
| PY | |
| security create-keychain -p "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" "$keychain_path" | |
| security set-keychain-settings -lut 21600 "$keychain_path" | |
| security unlock-keychain -p "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" "$keychain_path" | |
| security import "$cert_path" \ | |
| -k "$keychain_path" \ | |
| -P "$MACOS_CODESIGN_CERT_PASSWORD" \ | |
| -T /usr/bin/codesign \ | |
| -T /usr/bin/security | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple:,codesign: \ | |
| -s \ | |
| -k "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" \ | |
| "$keychain_path" | |
| security list-keychains -d user -s "$keychain_path" | |
| - name: Sign macOS CLI binary | |
| if: startsWith(matrix.target, 'bun-darwin-') && env.MACOS_CODESIGN_CERT_P12_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| output="dist/${{ matrix.output_name }}" | |
| codesign --force --sign "$MACOS_CODESIGN_IDENTITY" --timestamp --options runtime "$output" | |
| codesign --verify --verbose "$output" | |
| - name: Upload binary artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.output_name }} | |
| path: dist/${{ matrix.output_name }} | |
| build_runtime_binaries: | |
| name: Build standalone runtime service binaries | |
| needs: meta | |
| env: | |
| WINDOWS_CODESIGN_PFX_BASE64: ${{ secrets.WINDOWS_CODESIGN_PFX_BASE64 }} | |
| WINDOWS_CODESIGN_PFX_PASSWORD: ${{ secrets.WINDOWS_CODESIGN_PFX_PASSWORD }} | |
| MACOS_CODESIGN_CERT_P12_BASE64: ${{ secrets.MACOS_CODESIGN_CERT_P12_BASE64 }} | |
| MACOS_CODESIGN_CERT_PASSWORD: ${{ secrets.MACOS_CODESIGN_CERT_PASSWORD }} | |
| MACOS_CODESIGN_KEYCHAIN_PASSWORD: ${{ secrets.MACOS_CODESIGN_KEYCHAIN_PASSWORD }} | |
| MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: bun-linux-x64 | |
| platform_key: linux-x64 | |
| runs_on: ubuntu-latest | |
| - target: bun-windows-x64 | |
| platform_key: windows-x64 | |
| runs_on: windows-2022 | |
| - target: bun-darwin-x64 | |
| platform_key: macos-x64 | |
| runs_on: macos-14 | |
| - target: bun-darwin-arm64 | |
| platform_key: macos-arm64 | |
| runs_on: macos-14 | |
| runs-on: ${{ matrix.runs_on }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Install dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build protocol workspace artifacts | |
| run: bun run protocol:build | |
| - name: Build runtime binaries | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p dist | |
| ext="" | |
| if [[ "${{ matrix.platform_key }}" == windows-* ]]; then | |
| ext=".exe" | |
| fi | |
| bun build apps/server/src/server_main.ts --compile --target=${{ matrix.target }} --outfile "dist/pushpals-runtime-server-${{ matrix.platform_key }}${ext}" | |
| bun build apps/localbuddy/src/localbuddy_main.ts --compile --target=${{ matrix.target }} --outfile "dist/pushpals-runtime-localbuddy-${{ matrix.platform_key }}${ext}" | |
| bun build apps/remotebuddy/src/remotebuddy_main.ts --compile --target=${{ matrix.target }} --outfile "dist/pushpals-runtime-remotebuddy-${{ matrix.platform_key }}${ext}" | |
| bun build apps/workerpals/src/workerpals_main.ts --compile --target=${{ matrix.target }} --outfile "dist/pushpals-runtime-workerpals-${{ matrix.platform_key }}${ext}" | |
| bun build apps/source_control_manager/src/source_control_manager_main.ts --compile --target=${{ matrix.target }} --outfile "dist/pushpals-runtime-source-control-manager-${{ matrix.platform_key }}${ext}" | |
| if [[ "${{ matrix.platform_key }}" != windows-* ]]; then | |
| chmod +x dist/pushpals-runtime-*-${{ matrix.platform_key }} | |
| fi | |
| - name: Install Windows signing tool | |
| if: startsWith(matrix.platform_key, 'windows-') && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os != 'Windows' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y osslsigncode | |
| - name: Sign Windows runtime binaries | |
| if: startsWith(matrix.platform_key, 'windows-') && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| WINDOWS_CODESIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_CODESIGN_TIMESTAMP_URL }} | |
| run: | | |
| set -euo pipefail | |
| cert_path="$(mktemp)" | |
| printf '%s' "$WINDOWS_CODESIGN_PFX_BASE64" | base64 -d > "$cert_path" | |
| timestamp_url="${WINDOWS_CODESIGN_TIMESTAMP_URL:-http://timestamp.digicert.com}" | |
| for output in dist/pushpals-runtime-*-${{ matrix.platform_key }}.exe; do | |
| signed_output="${output}.signed" | |
| osslsigncode sign \ | |
| -pkcs12 "$cert_path" \ | |
| -pass "$WINDOWS_CODESIGN_PFX_PASSWORD" \ | |
| -h sha256 \ | |
| -n "PushPals Runtime" \ | |
| -i "https://github.com/PushPalsDev/pushpals" \ | |
| -ts "$timestamp_url" \ | |
| -in "$output" \ | |
| -out "$signed_output" | |
| mv "$signed_output" "$output" | |
| done | |
| rm -f "$cert_path" | |
| - name: Sign Windows runtime binaries (signtool) | |
| if: startsWith(matrix.platform_key, 'windows-') && env.WINDOWS_CODESIGN_PFX_BASE64 != '' && runner.os == 'Windows' | |
| shell: pwsh | |
| env: | |
| WINDOWS_CODESIGN_TIMESTAMP_URL: ${{ vars.WINDOWS_CODESIGN_TIMESTAMP_URL }} | |
| run: | | |
| $ErrorActionPreference = 'Stop' | |
| $certPath = Join-Path $env:RUNNER_TEMP 'pushpals-runtime-codesign-cert.pfx' | |
| [System.IO.File]::WriteAllBytes( | |
| $certPath, | |
| [System.Convert]::FromBase64String($env:WINDOWS_CODESIGN_PFX_BASE64) | |
| ) | |
| $signtool = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Recurse -Filter signtool.exe | | |
| Sort-Object FullName -Descending | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $signtool) { | |
| throw 'signtool.exe not found on runner.' | |
| } | |
| $timestampUrl = if ($env:WINDOWS_CODESIGN_TIMESTAMP_URL) { | |
| $env:WINDOWS_CODESIGN_TIMESTAMP_URL | |
| } else { | |
| 'http://timestamp.digicert.com' | |
| } | |
| Get-ChildItem 'dist\pushpals-runtime-*-windows-x64.exe' | ForEach-Object { | |
| & $signtool sign ` | |
| /f $certPath ` | |
| /p $env:WINDOWS_CODESIGN_PFX_PASSWORD ` | |
| /fd SHA256 ` | |
| /td SHA256 ` | |
| /tr $timestampUrl ` | |
| /d 'PushPals Runtime' ` | |
| /du 'https://github.com/PushPalsDev/pushpals' ` | |
| $_.FullName | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "signtool failed for $($_.FullName) with exit code $LASTEXITCODE" | |
| } | |
| } | |
| - name: Import macOS signing certificate | |
| if: startsWith(matrix.platform_key, 'macos-') && env.MACOS_CODESIGN_CERT_P12_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| cert_path="$RUNNER_TEMP/pushpals-codesign-cert.p12" | |
| keychain_path="$RUNNER_TEMP/pushpals-signing.keychain-db" | |
| python3 - <<'PY' | |
| import base64, os, pathlib | |
| pathlib.Path(os.environ["RUNNER_TEMP"], "pushpals-codesign-cert.p12").write_bytes( | |
| base64.b64decode(os.environ["MACOS_CODESIGN_CERT_P12_BASE64"]) | |
| ) | |
| PY | |
| security create-keychain -p "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" "$keychain_path" | |
| security set-keychain-settings -lut 21600 "$keychain_path" | |
| security unlock-keychain -p "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" "$keychain_path" | |
| security import "$cert_path" \ | |
| -k "$keychain_path" \ | |
| -P "$MACOS_CODESIGN_CERT_PASSWORD" \ | |
| -T /usr/bin/codesign \ | |
| -T /usr/bin/security | |
| security set-key-partition-list \ | |
| -S apple-tool:,apple:,codesign: \ | |
| -s \ | |
| -k "$MACOS_CODESIGN_KEYCHAIN_PASSWORD" \ | |
| "$keychain_path" | |
| security list-keychains -d user -s "$keychain_path" | |
| - name: Sign macOS runtime binaries | |
| if: startsWith(matrix.platform_key, 'macos-') && env.MACOS_CODESIGN_CERT_P12_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| for output in dist/pushpals-runtime-*-${{ matrix.platform_key }}; do | |
| codesign --force --sign "$MACOS_CODESIGN_IDENTITY" --timestamp --options runtime "$output" | |
| codesign --verify --verbose "$output" | |
| done | |
| - name: Upload runtime artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: runtime-${{ matrix.platform_key }} | |
| path: dist/pushpals-runtime-* | |
| smoke_windows_runtime: | |
| name: Smoke Windows runtime binaries | |
| needs: | |
| - meta | |
| - build_runtime_binaries | |
| runs-on: windows-2022 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: "1.3.9" | |
| - name: Download Windows runtime artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: runtime-windows-x64 | |
| path: dist/runtime-windows-x64 | |
| - name: Run autonomy-enabled Windows runtime smoke | |
| shell: pwsh | |
| run: | | |
| bun run scripts/release-windows-runtime-smoke.ts ` | |
| --runtime-bin-dir dist/runtime-windows-x64 ` | |
| --prompts-root . | |
| publish_release: | |
| name: Publish GitHub release assets | |
| runs-on: ubuntu-latest | |
| needs: | |
| - meta | |
| - build_binaries | |
| - build_runtime_binaries | |
| - smoke_windows_runtime | |
| - publish_npm | |
| env: | |
| LINUX_RELEASE_GPG_PRIVATE_KEY_BASE64: ${{ secrets.LINUX_RELEASE_GPG_PRIVATE_KEY_BASE64 }} | |
| LINUX_RELEASE_GPG_PASSPHRASE: ${{ secrets.LINUX_RELEASE_GPG_PASSPHRASE }} | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Download binaries | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: dist | |
| merge-multiple: true | |
| - name: Generate checksums | |
| shell: bash | |
| run: | | |
| cd dist | |
| sha256sum pushpals-* > SHA256SUMS.txt | |
| - name: Import Linux release signing key | |
| if: env.LINUX_RELEASE_GPG_PRIVATE_KEY_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export GNUPGHOME="$RUNNER_TEMP/gnupg" | |
| mkdir -p "$GNUPGHOME" | |
| chmod 700 "$GNUPGHOME" | |
| python3 - <<'PY' | |
| import base64, os, pathlib | |
| pathlib.Path(os.environ["RUNNER_TEMP"], "pushpals-linux-release.key").write_bytes( | |
| base64.b64decode(os.environ["LINUX_RELEASE_GPG_PRIVATE_KEY_BASE64"]) | |
| ) | |
| PY | |
| gpg --batch --import "$RUNNER_TEMP/pushpals-linux-release.key" | |
| - name: Sign Linux release artifacts | |
| if: env.LINUX_RELEASE_GPG_PRIVATE_KEY_BASE64 != '' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export GNUPGHOME="$RUNNER_TEMP/gnupg" | |
| shopt -s nullglob | |
| for artifact in dist/*linux* dist/SHA256SUMS.txt; do | |
| if [[ -f "$artifact" ]]; then | |
| gpg --batch --yes --pinentry-mode loopback \ | |
| --passphrase "$LINUX_RELEASE_GPG_PASSPHRASE" \ | |
| --armor --detach-sign "$artifact" | |
| fi | |
| done | |
| - name: Resolve release log file | |
| id: release_log | |
| shell: bash | |
| run: | | |
| path="release_log.md" | |
| if [[ -f "$path" ]]; then | |
| echo "found=true" >> "$GITHUB_OUTPUT" | |
| echo "path=$path" >> "$GITHUB_OUTPUT" | |
| echo "Using release log at $path" | |
| else | |
| echo "found=false" >> "$GITHUB_OUTPUT" | |
| echo "path=" >> "$GITHUB_OUTPUT" | |
| echo "No release log found at $path; using default release body." | |
| fi | |
| - name: Create GitHub release (release log) | |
| if: steps.release_log.outputs.found == 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.meta.outputs.tag }} | |
| name: PushPals CLI ${{ needs.meta.outputs.tag }} | |
| prerelease: ${{ needs.meta.outputs.prerelease == 'true' }} | |
| generate_release_notes: true | |
| body_path: ${{ steps.release_log.outputs.path }} | |
| files: | | |
| dist/pushpals-* | |
| dist/SHA256SUMS.txt | |
| dist/*.asc | |
| - name: Create GitHub release (default body) | |
| if: steps.release_log.outputs.found != 'true' | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.meta.outputs.tag }} | |
| name: PushPals CLI ${{ needs.meta.outputs.tag }} | |
| prerelease: ${{ needs.meta.outputs.prerelease == 'true' }} | |
| generate_release_notes: true | |
| body: | | |
| ## Install | |
| - npm: `npm i -g @pushpalsdev/cli` | |
| - bun: `bun install -g @pushpalsdev/cli` | |
| ## Direct downloads | |
| Use the platform binaries attached to this release when npm/Bun is unavailable. | |
| files: | | |
| dist/pushpals-* | |
| dist/SHA256SUMS.txt | |
| dist/*.asc |