Skip to content

Merge pull request #106 from Lythaeon/fix/ts-sdk-windows-publish-shell #11

Merge pull request #106 from Lythaeon/fix/ts-sdk-windows-publish-shell

Merge pull request #106 from Lythaeon/fix/ts-sdk-windows-publish-shell #11

name: Release TypeScript SDK
on:
push:
tags:
- "ts-sdk-v*.*.*"
workflow_dispatch:
inputs:
publish:
description: "Publish to npm instead of packing artifacts only"
required: false
default: false
type: boolean
permissions:
actions: read
contents: read
id-token: write
concurrency:
group: release-typescript-sdk-${{ github.ref }}
cancel-in-progress: false
env:
NODE_VERSION: "22"
SDK_DIRECTORY: sdks/typescript
jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
sdk_version: ${{ steps.sdk-version.outputs.value }}
publish_release: ${{ steps.release-mode.outputs.value }}
sdk_already_published: ${{ steps.sdk-presence.outputs.value }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.28.2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
registry-url: https://registry.npmjs.org
- name: Decide release mode
id: release-mode
shell: bash
run: |
set -euo pipefail
if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then
echo "value=true" >> "${GITHUB_OUTPUT}"
elif [[ "${{ inputs.publish }}" == "true" ]]; then
echo "value=true" >> "${GITHUB_OUTPUT}"
else
echo "value=false" >> "${GITHUB_OUTPUT}"
fi
- name: Read SDK version
id: sdk-version
shell: bash
run: |
set -euo pipefail
value="$(node -p "require('./${SDK_DIRECTORY}/package.json').version")"
echo "value=${value}" >> "${GITHUB_OUTPUT}"
- name: Verify tagged commit is on main
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
set -euo pipefail
git fetch --no-tags origin main
if ! git merge-base --is-ancestor "${GITHUB_SHA}" "FETCH_HEAD"; then
echo "Tag ${GITHUB_REF_NAME} points to commit ${GITHUB_SHA} which is not on origin/main"
exit 1
fi
- name: Verify tag matches SDK version
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
set -euo pipefail
expected_tag="ts-sdk-v${{ steps.sdk-version.outputs.value }}"
if [[ "${GITHUB_REF_NAME}" != "${expected_tag}" ]]; then
echo "Expected tag ${expected_tag}, got ${GITHUB_REF_NAME}"
exit 1
fi
- name: Check whether SDK package version already exists on npm
id: sdk-presence
shell: bash
run: |
set -euo pipefail
if [[ "${{ steps.release-mode.outputs.value }}" != "true" ]]; then
echo "value=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
package_name="$(node -p "require('./${SDK_DIRECTORY}/package.json').name")"
package_version="${{ steps.sdk-version.outputs.value }}"
if pnpm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
echo "value=true" >> "${GITHUB_OUTPUT}"
echo ">>> ${package_name}@${package_version} already exists on npm; SDK verification/publish steps will skip where possible."
else
echo "value=false" >> "${GITHUB_OUTPUT}"
fi
- name: Install workspace dependencies
if: steps.sdk-presence.outputs.value != 'true'
run: pnpm install --frozen-lockfile
- name: Run TypeScript SDK checks
if: steps.sdk-presence.outputs.value != 'true'
run: pnpm --dir "${SDK_DIRECTORY}" run check
publish-native:
needs: verify
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
continue-on-error: true
strategy:
fail-fast: false
matrix:
include:
- package_dir: sdks/typescript/native/linux-x64
package_name: "@lythaeon-sof/sdk-native-linux-x64"
runner: ubuntu-24.04
- package_dir: sdks/typescript/native/linux-arm64
package_name: "@lythaeon-sof/sdk-native-linux-arm64"
runner: ubuntu-24.04-arm
- package_dir: sdks/typescript/native/darwin-x64
package_name: "@lythaeon-sof/sdk-native-darwin-x64"
runner: macos-15-intel
- package_dir: sdks/typescript/native/darwin-arm64
package_name: "@lythaeon-sof/sdk-native-darwin-arm64"
runner: macos-15
- package_dir: sdks/typescript/native/win32-x64
package_name: "@lythaeon-sof/sdk-native-win32-x64"
runner: windows-2025
- package_dir: sdks/typescript/native/win32-arm64
package_name: "@lythaeon-sof/sdk-native-win32-arm64"
runner: windows-11-arm
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.28.2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
registry-url: https://registry.npmjs.org
- name: Check whether native runtime package version already exists on npm
id: native-package-presence
if: needs.verify.outputs.publish_release == 'true'
shell: bash
run: |
set -euo pipefail
package_name="${{ matrix.package_name }}"
package_version="${{ needs.verify.outputs.sdk_version }}"
if pnpm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
echo "value=true" >> "${GITHUB_OUTPUT}"
echo ">>> ${package_name}@${package_version} already exists on npm; skipping native runtime build/publish."
else
echo "value=false" >> "${GITHUB_OUTPUT}"
fi
- name: Setup Rust CI toolchain and tools
if: needs.verify.outputs.publish_release != 'true' || steps.native-package-presence.outputs.value != 'true'
uses: ./.github/actions/rust-ci-setup
with:
cargo-tools: cargo-make,cargo-nextest
- name: Install workspace dependencies
if: needs.verify.outputs.publish_release != 'true' || steps.native-package-presence.outputs.value != 'true'
run: pnpm install --frozen-lockfile
- name: Publish native runtime package
if: needs.verify.outputs.publish_release == 'true' && steps.native-package-presence.outputs.value != 'true'
working-directory: ${{ matrix.package_dir }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: bash
run: pnpm publish --access public --no-git-checks
- name: Pack native runtime package
if: needs.verify.outputs.publish_release != 'true'
working-directory: ${{ matrix.package_dir }}
shell: bash
run: pnpm pack --pack-destination ../../../dist/npm
publish-sdk:
if: ${{ !cancelled() && needs.verify.result == 'success' }}
needs:
- verify
- publish-native
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10.28.2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
registry-url: https://registry.npmjs.org
- name: Check whether SDK package version already exists on npm
id: sdk-package-presence
if: needs.verify.outputs.publish_release == 'true'
shell: bash
run: |
set -euo pipefail
package_name="$(node -p "require('./${SDK_DIRECTORY}/package.json').name")"
package_version="${{ needs.verify.outputs.sdk_version }}"
if pnpm view "${package_name}@${package_version}" version >/dev/null 2>&1; then
echo "value=true" >> "${GITHUB_OUTPUT}"
echo ">>> ${package_name}@${package_version} already exists on npm; skipping SDK install/publish."
else
echo "value=false" >> "${GITHUB_OUTPUT}"
fi
- name: Check native publish job conclusions
id: native-publish-status
if: needs.verify.outputs.publish_release == 'true' && steps.sdk-package-presence.outputs.value != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
jobs_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100"
jobs_json="$(curl --fail --silent --show-error \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"${jobs_api}")"
cancelled="$(node -e '
const payload = JSON.parse(process.argv[1]);
const nativeJobs = payload.jobs.filter((job) => job.name.startsWith("publish-native ("));
process.stdout.write(nativeJobs.some((job) => job.conclusion === "cancelled") ? "true" : "false");
' "${jobs_json}")"
echo "cancelled=${cancelled}" >> "${GITHUB_OUTPUT}"
if [[ "${cancelled}" == "true" ]]; then
echo "::warning::Skipping SDK publish because one or more native publish jobs were cancelled."
fi
- name: Install workspace dependencies
if: needs.verify.outputs.publish_release != 'true' || (steps.sdk-package-presence.outputs.value != 'true' && steps.native-publish-status.outputs.cancelled != 'true')
run: pnpm install --frozen-lockfile
- name: Wait for native packages to index on npm
if: needs.verify.outputs.publish_release == 'true' && steps.sdk-package-presence.outputs.value != 'true' && steps.native-publish-status.outputs.cancelled != 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail
trap 'echo "::warning::Cancellation received; stopping SDK publish wait."; exit 130' INT TERM
version="${{ needs.verify.outputs.sdk_version }}"
packages="$(node -p "JSON.stringify(Object.keys(require('./${SDK_DIRECTORY}/package.json').optionalDependencies).sort())")"
missing_packages=()
indexed_packages=()
native_jobs_cancelled() {
local jobs_api
local jobs_json
jobs_api="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100"
jobs_json="$(curl --fail --silent --show-error \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
-H "Accept: application/vnd.github+json" \
"${jobs_api}")"
node -e '
const payload = JSON.parse(process.argv[1]);
const nativeJobs = payload.jobs.filter((job) => job.name.startsWith("publish-native ("));
process.exit(nativeJobs.some((job) => job.conclusion === "cancelled") ? 0 : 1);
' "${jobs_json}"
}
wait_until_indexed() {
local pkg="$1"
local attempt
for attempt in $(seq 1 30); do
if native_jobs_cancelled; then
echo "::warning::Stopping SDK publish wait because one or more native publish jobs were cancelled."
return 2
fi
if pnpm view "${pkg}@${version}" version >/dev/null 2>&1; then
echo ">>> ${pkg}@${version} is visible on npm"
return 0
fi
echo ">>> Waiting for ${pkg}@${version} to appear on npm (${attempt}/30)..."
sleep 10
done
echo ">>> ${pkg}@${version} did not appear on npm in time"
return 1
}
mapfile -t native_packages < <(node -e "for (const pkg of ${packages}) console.log(pkg)")
for package_name in "${native_packages[@]}"; do
if wait_until_indexed "${package_name}"; then
indexed_packages+=("${package_name}")
elif [[ $? -eq 2 ]]; then
exit 0
else
missing_packages+=("${package_name}")
fi
done
if ((${#indexed_packages[@]} > 0)); then
printf 'Indexed native packages: %s\n' "${indexed_packages[*]}"
fi
if ((${#missing_packages[@]} > 0)); then
printf 'Missing native packages: %s\n' "${missing_packages[*]}"
echo "::warning::Proceeding with SDK publish even though some native runtime packages are still missing or failed to publish."
fi
- name: Publish TypeScript SDK
if: needs.verify.outputs.publish_release == 'true' && steps.sdk-package-presence.outputs.value != 'true' && steps.native-publish-status.outputs.cancelled != 'true'
working-directory: ${{ env.SDK_DIRECTORY }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
shell: bash
run: pnpm publish --access public --no-git-checks
- name: Pack TypeScript SDK
if: needs.verify.outputs.publish_release != 'true'
working-directory: ${{ env.SDK_DIRECTORY }}
shell: bash
run: pnpm pack --pack-destination ../dist/npm