name: Release on: push: tags: ["v*"] workflow_dispatch: env: CARGO_TERM_COLOR: always jobs: # -------------------------------------------------------------------------- # crates.io: three crates in dependency order # -------------------------------------------------------------------------- cargo-publish: name: Publish to crates.io runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 # Idempotent publishing: if the version is already on crates.io we # treat that as success so re-runs of the workflow don't fail. - name: Publish wickra-core (idempotent) run: | out=$(cargo publish -p wickra-core --token "$CARGO_REGISTRY_TOKEN" 2>&1) && echo "$out" \ || (echo "$out" | grep -q "already uploaded\|already exists" && echo "skip: already published" \ || (echo "$out"; exit 1)) env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - name: Wait for crates.io index to refresh run: sleep 45 - name: Publish wickra-data (idempotent) run: | out=$(cargo publish -p wickra-data --token "$CARGO_REGISTRY_TOKEN" 2>&1) && echo "$out" \ || (echo "$out" | grep -q "already uploaded\|already exists" && echo "skip: already published" \ || (echo "$out"; exit 1)) env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - name: Wait for crates.io index to refresh run: sleep 45 - name: Publish wickra (idempotent) run: | out=$(cargo publish -p wickra --token "$CARGO_REGISTRY_TOKEN" 2>&1) && echo "$out" \ || (echo "$out" | grep -q "already uploaded\|already exists" && echo "skip: already published" \ || (echo "$out"; exit 1)) env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # -------------------------------------------------------------------------- # PyPI: cross-platform wheels + sdist # -------------------------------------------------------------------------- python-wheels: name: Build wheels (${{ matrix.target }} on ${{ matrix.os }}) strategy: fail-fast: false matrix: include: - { os: ubuntu-latest, target: x86_64, manylinux: auto } - { os: ubuntu-latest, target: aarch64, manylinux: auto } - { os: macos-latest, target: x86_64, manylinux: auto } - { os: macos-latest, target: aarch64, manylinux: auto } - { os: windows-latest, target: x64, manylinux: auto } runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.11" - uses: PyO3/maturin-action@v1 with: working-directory: bindings/python target: ${{ matrix.target }} args: --release --strip --out dist manylinux: ${{ matrix.manylinux }} - uses: actions/upload-artifact@v4 with: name: wheels-${{ matrix.os }}-${{ matrix.target }} path: bindings/python/dist/* python-sdist: name: Build Python sdist runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: PyO3/maturin-action@v1 with: working-directory: bindings/python command: sdist args: --out dist - uses: actions/upload-artifact@v4 with: name: wheels-sdist path: bindings/python/dist/* python-publish: name: Publish to PyPI needs: [python-wheels, python-sdist] runs-on: ubuntu-latest steps: - uses: actions/download-artifact@v4 with: path: dist pattern: wheels-* merge-multiple: true - name: Upload to PyPI uses: PyO3/maturin-action@v1 with: command: upload args: --skip-existing dist/* env: MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} # -------------------------------------------------------------------------- # npm: per-platform native binaries + main meta-package # -------------------------------------------------------------------------- node-build: name: Node build (${{ matrix.target }}) strategy: fail-fast: false matrix: include: - { host: ubuntu-latest, target: x86_64-unknown-linux-gnu } - { host: macos-latest, target: x86_64-apple-darwin } - { host: macos-latest, target: aarch64-apple-darwin } - { host: windows-latest, target: x86_64-pc-windows-msvc } runs-on: ${{ matrix.host }} steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" - uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - uses: Swatinem/rust-cache@v2 - name: Install Node deps working-directory: bindings/node run: npm install - name: Build native module working-directory: bindings/node run: npx napi build --platform --release --target ${{ matrix.target }} - name: Upload artifact uses: actions/upload-artifact@v4 with: name: bindings-${{ matrix.target }} path: bindings/node/wickra.*.node if-no-files-found: error node-publish: name: Publish to npm needs: node-build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" registry-url: "https://registry.npmjs.org" - name: Install Node deps working-directory: bindings/node run: npm install - name: Download all platform binaries uses: actions/download-artifact@v4 with: path: bindings/node/artifacts pattern: bindings-* # The npm//package.json templates are committed to the # repo (see bindings/node/npm/), so `napi artifacts` only needs to # drop the freshly built .node binary into the matching directory. - name: Move binaries into platform package layout working-directory: bindings/node run: npx napi artifacts --dir artifacts # Publish each platform package individually so one failure doesn't kill # the others. Skip versions that are already on npm. Tolerate spam-filter # 403s with a one-time retry after a short delay (spam detection is # often rate-limit-based and clears on the next request). - name: Publish platform packages (idempotent) working-directory: bindings/node env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | set +e version=$(node -p "require('./package.json').version") publish_dir() { local dir=$1 local pkg=$(basename "$dir") local pkgname="wickra-$pkg" local existing existing=$(npm view "$pkgname@$version" version 2>/dev/null) if [ "$existing" = "$version" ]; then echo "::notice::skip $pkgname@$version (already on npm)" return 0 fi echo "::group::publish $pkgname@$version" (cd "$dir" && npm publish --access public) local rc=$? echo "::endgroup::" if [ "$rc" -ne 0 ]; then echo "::warning::first attempt of $pkgname failed (rc=$rc); retrying after 30s" sleep 30 (cd "$dir" && npm publish --access public) rc=$? fi if [ "$rc" -ne 0 ]; then echo "::warning::$pkgname could not be published; the main package will skip the missing optional dep" fi return 0 } for dir in npm/*/; do publish_dir "$dir" done - name: Publish main package to npm (idempotent) working-directory: bindings/node env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} shell: bash run: | # GitHub Actions defaults to `bash -e`. `npm view` on a not-yet- # published package returns 1, which would kill the step before # we got to publish. Force a graceful empty result instead. set +e version=$(node -p "require('./package.json').version") existing=$(npm view "wickra@$version" version 2>/dev/null) if [ "$existing" = "$version" ]; then echo "::notice::skip wickra@$version (already on npm)" exit 0 fi npm publish --access public rc=$? if [ "$rc" -ne 0 ]; then echo "::warning::first attempt failed (rc=$rc); retrying after 30s" sleep 30 npm publish --access public rc=$? fi exit $rc # -------------------------------------------------------------------------- # WASM: wasm-pack build + npm publish (as `wickra-wasm`) # -------------------------------------------------------------------------- wasm-publish: name: Publish wickra-wasm to npm runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: "20" registry-url: "https://registry.npmjs.org" - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: jetli/wasm-pack-action@v0.4.0 - name: Build WASM package (bundler target) run: wasm-pack build bindings/wasm --target bundler --release --features panic-hook - name: Enrich generated package.json with author + repo metadata working-directory: bindings/wasm/pkg run: | node -e " const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.author = 'kingchenc '; pkg.repository = { type: 'git', url: 'https://github.com/kingchenc/wickra' }; pkg.homepage = 'https://github.com/kingchenc/wickra'; pkg.bugs = { url: 'https://github.com/kingchenc/wickra/issues' }; pkg.license = 'SEE LICENSE IN LICENSE'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)); " - name: Publish wickra-wasm to npm (idempotent) working-directory: bindings/wasm/pkg run: | out=$(npm publish --access public 2>&1) && echo "$out" \ || (echo "$out" | grep -q "You cannot publish over" && echo "skip: version already on npm" \ || (echo "$out"; exit 1)) env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}