From 7fab69de1ce98a2f09e2ec40e34beb4986139699 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 23 May 2026 08:18:47 +0000 Subject: [PATCH 1/5] feat: add rainix-rs-autopublish reusable workflow Generic autopublish-on-push-to-main for pure-Rust crates. Parametrized by the crate name. Consumers call it with a thin wrapper that passes inputs.crate and secrets: inherit. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/rainix-rs-autopublish.yaml | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .github/workflows/rainix-rs-autopublish.yaml diff --git a/.github/workflows/rainix-rs-autopublish.yaml b/.github/workflows/rainix-rs-autopublish.yaml new file mode 100644 index 0000000..0b2181f --- /dev/null +++ b/.github/workflows/rainix-rs-autopublish.yaml @@ -0,0 +1,84 @@ +name: rainix-rs-autopublish +on: + workflow_call: + inputs: + crate: + description: cargo package name to publish (-p target) + required: true + type: string + secrets: + PUBLISH_PRIVATE_KEY: + required: true + CI_GIT_EMAIL: + required: true + CI_GIT_USER: + required: true + CARGO_REGISTRY_TOKEN: + required: true +jobs: + release: + if: ${{ !startsWith(github.event.head_commit.message, 'Package Release') }} + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + steps: + - uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.PUBLISH_PRIVATE_KEY }} + fetch-depth: 0 + - uses: nixbuild/nix-quick-install-action@v30 + with: + nix_conf: | + keep-env-derivations = true + keep-outputs = true + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v6 + with: + primary-key: nix-${{ runner.os }}-${{ hashFiles('**/*.nix', '**/flake.lock') }} + restore-prefixes-first-match: nix-${{ runner.os }}- + gc-max-store-size-linux: 1G + - name: Cargo test + run: nix develop github:rainlanguage/rainix#rust-shell -c cargo test -p ${{ inputs.crate }} + - name: Git config + run: | + git config --global user.email "${{ secrets.CI_GIT_EMAIL }}" + git config --global user.name "${{ secrets.CI_GIT_USER }}" + - name: Cargo hashes + id: cargo + run: | + NEW=$(nix develop github:rainlanguage/rainix#rust-shell -c cargo package -p ${{ inputs.crate }} --allow-dirty --quiet --list | LC_ALL=C sort | sha256sum | cut -d' ' -f1) + OLD_VERSION=$(curl -fsSL "https://crates.io/api/v1/crates/${{ inputs.crate }}" | python3 -c "import sys, json; print(json.load(sys.stdin)['crate']['max_version'])" 2>/dev/null || echo "none") + if [ "$OLD_VERSION" = "none" ]; then + OLD="none" + else + curl -fsSL "https://crates.io/api/v1/crates/${{ inputs.crate }}/$OLD_VERSION/download" -o /tmp/old.crate + OLD=$(tar -tzf /tmp/old.crate 2>/dev/null | LC_ALL=C sort | sha256sum | cut -d' ' -f1) + fi + echo "old=$OLD" >> $GITHUB_OUTPUT + echo "new=$NEW" >> $GITHUB_OUTPUT + if [ "$OLD" = "$NEW" ]; then echo "changed=false" >> $GITHUB_OUTPUT; else echo "changed=true" >> $GITHUB_OUTPUT; fi + - name: Bump Cargo version + if: ${{ steps.cargo.outputs.changed == 'true' }} + run: | + nix develop github:rainlanguage/rainix#rust-shell -c cargo release --no-confirm --execute --no-tag --no-push --no-publish -p ${{ inputs.crate }} patch + echo "CARGO_VERSION=v$(nix develop github:rainlanguage/rainix#rust-shell -c cargo pkgid -p ${{ inputs.crate }} | cut -d@ -f2)" >> $GITHUB_ENV + - name: Tag and push + if: ${{ steps.cargo.outputs.changed == 'true' }} + run: | + git tag crate-${{ env.CARGO_VERSION }} + git push origin HEAD + git push origin --tags + - name: Publish to crates.io + if: ${{ steps.cargo.outputs.changed == 'true' }} + run: nix develop github:rainlanguage/rainix#rust-shell -c cargo publish -p ${{ inputs.crate }} + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + - name: GitHub Release + if: ${{ steps.cargo.outputs.changed == 'true' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: crate-${{ env.CARGO_VERSION }} + name: Cargo Crate Release crate-${{ env.CARGO_VERSION }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a412b3d563df38a42586d53b67cf55ce612fc595 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 23 May 2026 08:41:11 +0000 Subject: [PATCH 2/5] feat: extend autopublish with optional npm and soldeer publishing --- .github/workflows/rainix-rs-autopublish.yaml | 97 +++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rainix-rs-autopublish.yaml b/.github/workflows/rainix-rs-autopublish.yaml index 0b2181f..b52705f 100644 --- a/.github/workflows/rainix-rs-autopublish.yaml +++ b/.github/workflows/rainix-rs-autopublish.yaml @@ -6,6 +6,16 @@ on: description: cargo package name to publish (-p target) required: true type: string + npm-package: + description: optional npm package name (e.g. @rainlanguage/float). When set, the workflow also detects/bumps/publishes an npm package alongside the crate. + required: false + type: string + default: '' + soldeer-package: + description: optional Soldeer registry package name (e.g. rain-erc). When set, the workflow also publishes the current foundry.toml version to Soldeer when it diverges from the latest registry entry. Soldeer version is read from foundry.toml [package].version; no auto-bump (dev manages soldeer versions explicitly). + required: false + type: string + default: '' secrets: PUBLISH_PRIVATE_KEY: required: true @@ -15,6 +25,10 @@ on: required: true CARGO_REGISTRY_TOKEN: required: true + NPM_PUBLISH_PRIVATE_TOKEN: + required: false + SOLDEER_API_TOKEN: + required: false jobs: release: if: ${{ !startsWith(github.event.head_commit.message, 'Package Release') }} @@ -44,6 +58,7 @@ jobs: run: | git config --global user.email "${{ secrets.CI_GIT_EMAIL }}" git config --global user.name "${{ secrets.CI_GIT_USER }}" + # Detect cargo changes. - name: Cargo hashes id: cargo run: | @@ -58,23 +73,82 @@ jobs: echo "old=$OLD" >> $GITHUB_OUTPUT echo "new=$NEW" >> $GITHUB_OUTPUT if [ "$OLD" = "$NEW" ]; then echo "changed=false" >> $GITHUB_OUTPUT; else echo "changed=true" >> $GITHUB_OUTPUT; fi + # Detect npm changes. + - name: NPM hashes + if: ${{ inputs.npm-package != '' }} + id: npm + run: | + nix develop github:rainlanguage/rainix#rust-node-shell -c bash -c ' + OLD=$(npm view ${{ inputs.npm-package }}@latest dist.shasum 2>/dev/null || echo "none") + NEW=$(npm pack --silent | xargs shasum | cut -c1-40) + rm -f *.tgz + echo "old=$OLD" >> $GITHUB_OUTPUT + echo "new=$NEW" >> $GITHUB_OUTPUT + if [ "$OLD" = "$NEW" ]; then echo "changed=false" >> $GITHUB_OUTPUT; else echo "changed=true" >> $GITHUB_OUTPUT; fi + ' + # Detect soldeer changes (version-divergence only — dev manages + # foundry.toml [package].version explicitly). + - name: Soldeer version check + if: ${{ inputs.soldeer-package != '' }} + id: soldeer + run: | + LOCAL=$(awk -F\" '/^version[[:space:]]*=/ { print $2; exit }' foundry.toml) + REMOTE=$(curl -fsSL "https://api.soldeer.xyz/api/v1/revisions/all?package_name=${{ inputs.soldeer-package }}&offset=0&limit=1" \ + | python3 -c "import sys, json; d=json.load(sys.stdin); print(d['data'][0]['version'] if d.get('data') else 'none')" 2>/dev/null \ + || echo "none") + echo "local=$LOCAL" >> $GITHUB_OUTPUT + echo "remote=$REMOTE" >> $GITHUB_OUTPUT + if [ "$LOCAL" = "$REMOTE" ]; then echo "changed=false" >> $GITHUB_OUTPUT; else echo "changed=true" >> $GITHUB_OUTPUT; fi + # Bump versions (cargo + npm auto-bump on content change; soldeer + # uses the dev-set foundry.toml version, no bump here). - name: Bump Cargo version if: ${{ steps.cargo.outputs.changed == 'true' }} run: | nix develop github:rainlanguage/rainix#rust-shell -c cargo release --no-confirm --execute --no-tag --no-push --no-publish -p ${{ inputs.crate }} patch echo "CARGO_VERSION=v$(nix develop github:rainlanguage/rainix#rust-shell -c cargo pkgid -p ${{ inputs.crate }} | cut -d@ -f2)" >> $GITHUB_ENV + - name: Bump NPM version + if: ${{ inputs.npm-package != '' && steps.npm.outputs.changed == 'true' }} + run: | + NEW=$(nix develop github:rainlanguage/rainix#rust-node-shell -c npm version patch --no-git-tag-version) + echo "NPM_VERSION=$NEW" >> $GITHUB_ENV + git add package.json package-lock.json + git commit -m "Package Release npm-${NEW}" + # Tag + push everything in one shot. Tags only created for items + # that actually changed. - name: Tag and push - if: ${{ steps.cargo.outputs.changed == 'true' }} + if: ${{ steps.cargo.outputs.changed == 'true' || steps.npm.outputs.changed == 'true' || steps.soldeer.outputs.changed == 'true' }} run: | - git tag crate-${{ env.CARGO_VERSION }} + if [ -n "${{ env.CARGO_VERSION }}" ]; then git tag crate-${{ env.CARGO_VERSION }}; fi + if [ -n "${{ env.NPM_VERSION }}" ]; then git tag npm-${{ env.NPM_VERSION }}; fi + if [ "${{ steps.soldeer.outputs.changed }}" = "true" ]; then git tag sol-v${{ steps.soldeer.outputs.local }}; fi git push origin HEAD git push origin --tags + # Package npm tarball for upload step. + - name: NPM pack + if: ${{ inputs.npm-package != '' && steps.npm.outputs.changed == 'true' }} + run: | + TARBALL=$(nix develop github:rainlanguage/rainix#rust-node-shell -c npm pack --silent) + mv "$TARBALL" npm_package_${{ env.NPM_VERSION }}.tgz + # Publishes. - name: Publish to crates.io if: ${{ steps.cargo.outputs.changed == 'true' }} run: nix develop github:rainlanguage/rainix#rust-shell -c cargo publish -p ${{ inputs.crate }} env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - - name: GitHub Release + - name: Publish to NPM + if: ${{ inputs.npm-package != '' && steps.npm.outputs.changed == 'true' }} + uses: JS-DevTools/npm-publish@v3 + with: + token: ${{ secrets.NPM_PUBLISH_PRIVATE_TOKEN }} + access: public + package: npm_package_${{ env.NPM_VERSION }}.tgz + - name: Publish to Soldeer + if: ${{ inputs.soldeer-package != '' && steps.soldeer.outputs.changed == 'true' }} + env: + SOLDEER_API_TOKEN: ${{ secrets.SOLDEER_API_TOKEN }} + run: nix develop github:rainlanguage/rainix#sol-shell -c forge soldeer push "${{ inputs.soldeer-package }}~${{ steps.soldeer.outputs.local }}" + # GitHub Releases. + - name: GitHub Release (cargo) if: ${{ steps.cargo.outputs.changed == 'true' }} uses: softprops/action-gh-release@v2 with: @@ -82,3 +156,20 @@ jobs: name: Cargo Crate Release crate-${{ env.CARGO_VERSION }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: GitHub Release (npm) + if: ${{ inputs.npm-package != '' && steps.npm.outputs.changed == 'true' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: npm-${{ env.NPM_VERSION }} + name: NPM Package Release ${{ env.NPM_VERSION }} + files: npm_package_${{ env.NPM_VERSION }}.tgz + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: GitHub Release (soldeer) + if: ${{ inputs.soldeer-package != '' && steps.soldeer.outputs.changed == 'true' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: sol-v${{ steps.soldeer.outputs.local }} + name: Soldeer Release sol-v${{ steps.soldeer.outputs.local }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From a3abdd51190055da0b73186358c00086055d9b71 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 23 May 2026 08:42:32 +0000 Subject: [PATCH 3/5] rename: rainix-rs-autopublish -> rainix-autopublish (covers cargo + npm + soldeer) --- .../{rainix-rs-autopublish.yaml => rainix-autopublish.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{rainix-rs-autopublish.yaml => rainix-autopublish.yaml} (100%) diff --git a/.github/workflows/rainix-rs-autopublish.yaml b/.github/workflows/rainix-autopublish.yaml similarity index 100% rename from .github/workflows/rainix-rs-autopublish.yaml rename to .github/workflows/rainix-autopublish.yaml From 28bb4251d812de55b6972712b9d54bf7baedcc9e Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 23 May 2026 08:44:22 +0000 Subject: [PATCH 4/5] ci: retrigger From 90372abeb946f3098aa770d30755798ef88f2b05 Mon Sep 17 00:00:00 2001 From: David Meister Date: Sat, 23 May 2026 08:45:09 +0000 Subject: [PATCH 5/5] rename: workflow name field too --- .github/workflows/rainix-autopublish.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rainix-autopublish.yaml b/.github/workflows/rainix-autopublish.yaml index b52705f..ddb65a6 100644 --- a/.github/workflows/rainix-autopublish.yaml +++ b/.github/workflows/rainix-autopublish.yaml @@ -1,4 +1,4 @@ -name: rainix-rs-autopublish +name: rainix-autopublish on: workflow_call: inputs: