Release #3
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 | |
| # Tag-driven release. Push a tag `vX.Y.Z` (or a prerelease `vX.Y.Z-beta.N`) whose | |
| # base version matches `[workspace.package] version` in Cargo.toml. One synced | |
| # version drives crates.io (code2graph), PyPI (code2graph-rs) and npm (code2graph-rs). | |
| on: | |
| push: | |
| tags: ["v*"] | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| validate-version: | |
| name: Validate version tag | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| is_prerelease: ${{ steps.version.outputs.is_prerelease }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Validate tag against workspace version | |
| id: version | |
| run: | | |
| TAG="${GITHUB_REF_NAME}" | |
| TAG_VERSION="${TAG#v}" | |
| CARGO_VERSION=$(cargo metadata --no-deps --format-version=1 \ | |
| | jq -r '.packages[] | select(.name == "code2graph") | .version') | |
| if [[ ! "$TAG_VERSION" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-[a-zA-Z]+\.[0-9]+)?$ ]]; then | |
| echo "::error::Invalid tag '$TAG'. Expected vX.Y.Z or vX.Y.Z-label.N" | |
| exit 1 | |
| fi | |
| TAG_BASE="${BASH_REMATCH[1]}" | |
| CARGO_BASE=$(echo "$CARGO_VERSION" | grep -oP '^\d+\.\d+\.\d+') | |
| if [[ "$TAG_BASE" != "$CARGO_BASE" ]]; then | |
| echo "::error::Tag base '$TAG_BASE' != Cargo.toml version '$CARGO_BASE'" | |
| exit 1 | |
| fi | |
| if [[ "$TAG_VERSION" == *-* ]]; then | |
| echo "is_prerelease=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "is_prerelease=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "Releasing $TAG_VERSION" | |
| ci: | |
| name: Test | |
| needs: validate-version | |
| uses: ./.github/workflows/test.yml | |
| publish-crates: | |
| name: Publish to crates.io | |
| needs: [validate-version, ci] | |
| runs-on: ubuntu-latest | |
| environment: crates.io | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Stamp version | |
| run: | | |
| VERSION="${{ needs.validate-version.outputs.version }}" | |
| sed -i "0,/^version = \".*\"/s//version = \"$VERSION\"/" Cargo.toml | |
| - name: Publish code2graph | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| run: | | |
| VERSION="${{ needs.validate-version.outputs.version }}" | |
| if curl -sf -H "User-Agent: code2graph-ci (github.com/nodedb-lab/code2graph)" \ | |
| "https://crates.io/api/v1/crates/code2graph/$VERSION" > /dev/null 2>&1; then | |
| echo "code2graph@$VERSION already published — skipping" | |
| else | |
| cargo publish -p code2graph --allow-dirty --no-verify | |
| fi | |
| build-sdist: | |
| name: Build sdist | |
| needs: validate-version | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Stamp version | |
| run: | | |
| VERSION="${{ needs.validate-version.outputs.version }}" | |
| PYPI_VERSION=$(echo "$VERSION" | sed -E 's/-alpha\./a/; s/-beta\./b/; s/-rc\./rc/') | |
| sed -i "0,/^version = \".*\"/s//version = \"$VERSION\"/" Cargo.toml | |
| sed -i "s/^version = .*/version = \"$PYPI_VERSION\"/" bindings/python/pyproject.toml | |
| - name: Build sdist | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| command: sdist | |
| args: --out dist -m bindings/python/Cargo.toml | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-sdist | |
| path: dist | |
| build-wheels: | |
| name: Build wheel (${{ matrix.target }}) | |
| needs: validate-version | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| manylinux: auto | |
| - os: ubuntu-latest | |
| target: aarch64-unknown-linux-gnu | |
| manylinux: auto | |
| - os: ubuntu-latest | |
| target: x86_64-unknown-linux-musl | |
| manylinux: musllinux_1_2 | |
| - os: macos-13 | |
| target: x86_64-apple-darwin | |
| - os: macos-latest | |
| target: aarch64-apple-darwin | |
| - os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Stamp version | |
| shell: bash | |
| run: | | |
| VERSION="${{ needs.validate-version.outputs.version }}" | |
| PYPI_VERSION=$(echo "$VERSION" | sed -E 's/-alpha\./a/; s/-beta\./b/; s/-rc\./rc/') | |
| sed -i.bak "0,/^version = \".*\"/s//version = \"$VERSION\"/" Cargo.toml && rm -f Cargo.toml.bak | |
| sed -i.bak "s/^version = .*/version = \"$PYPI_VERSION\"/" bindings/python/pyproject.toml && rm -f bindings/python/pyproject.toml.bak | |
| - name: Build wheel | |
| uses: PyO3/maturin-action@v1 | |
| with: | |
| target: ${{ matrix.target }} | |
| manylinux: ${{ matrix.manylinux || 'auto' }} | |
| args: --release --out dist -m bindings/python/Cargo.toml | |
| sccache: "true" | |
| # tree-sitter grammars' generated parser.c uses C99 for-loop init and | |
| # some scanners use C11 static_assert; the manylinux cross-GCC defaults | |
| # to C89. Force C11 inside the build container (linux/docker only). | |
| docker-options: ${{ contains(matrix.target, 'linux') && '-e CFLAGS=-std=gnu11' || '' }} | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: wheels-${{ matrix.target }} | |
| path: dist | |
| publish-pypi: | |
| name: Publish to PyPI | |
| needs: [validate-version, build-wheels, build-sdist] | |
| runs-on: ubuntu-latest | |
| environment: pypi | |
| permissions: | |
| id-token: write | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| pattern: wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - name: Publish (Trusted Publishing / OIDC) | |
| uses: pypa/gh-action-pypi-publish@release/v1 | |
| with: | |
| skip-existing: true | |
| build-node: | |
| name: Build addon (${{ matrix.target }}) | |
| needs: validate-version | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| napi_cross: true | |
| - target: x86_64-unknown-linux-musl | |
| runner: ubuntu-latest | |
| zig: true | |
| - target: x86_64-apple-darwin | |
| runner: macos-13 | |
| - target: aarch64-apple-darwin | |
| runner: macos-latest | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| prefix-key: node-${{ matrix.target }} | |
| - name: Install zig (musl cross-compile) | |
| if: matrix.zig | |
| run: | | |
| pip install ziglang | |
| cargo install --locked cargo-zigbuild | |
| - name: Install napi cross-toolchain (linux aarch64) | |
| if: matrix.napi_cross | |
| run: npm install -g @napi-rs/cross-toolchain | |
| - name: Install deps | |
| working-directory: bindings/node | |
| run: npm ci | |
| - name: Build addon | |
| working-directory: bindings/node | |
| # tree-sitter grammars need C11 (for-loop init / static_assert); the napi | |
| # aarch64 cross-GCC defaults to C89. Harmless on the modern gnu/clang/zig | |
| # targets; omitted on Windows, whose MSVC rejects -std=gnu11. | |
| env: | |
| CFLAGS: ${{ !contains(matrix.target, 'windows') && '-std=gnu11' || '' }} | |
| run: npx napi build --platform --release --target ${{ matrix.target }} ${{ matrix.zig && '-x' || '' }} ${{ matrix.napi_cross && '--use-napi-cross' || '' }} | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: node-${{ matrix.target }} | |
| path: bindings/node/*.node | |
| publish-npm: | |
| name: Publish to npm | |
| needs: [validate-version, build-node, publish-crates] | |
| runs-on: ubuntu-latest | |
| environment: npm | |
| permissions: | |
| contents: read | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v5 | |
| with: | |
| node-version: "22" | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Install deps | |
| working-directory: bindings/node | |
| run: npm ci | |
| - name: Download addon artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| path: bindings/node/artifacts | |
| pattern: node-* | |
| merge-multiple: true | |
| - name: Stamp version | |
| working-directory: bindings/node | |
| run: | | |
| VERSION="${{ needs.validate-version.outputs.version }}" | |
| jq --arg v "$VERSION" '.version = $v' package.json > tmp.json && mv tmp.json package.json | |
| - name: Assemble platform packages | |
| working-directory: bindings/node | |
| run: | | |
| npx napi create-npm-dirs --npm-dir ./npm | |
| npx napi artifacts --output-dir ./artifacts --npm-dir ./npm | |
| npx napi prepublish -t npm --npm-dir ./npm --skip-optional-publish | |
| - name: Publish (idempotent, with provenance) | |
| working-directory: bindings/node | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_CONFIG_PROVENANCE: "true" | |
| run: | | |
| publish_pkg() { | |
| local dir="$1" name ver | |
| name=$(jq -r .name "$dir/package.json") | |
| ver=$(jq -r .version "$dir/package.json") | |
| if npm view "$name@$ver" version >/dev/null 2>&1; then | |
| echo "$name@$ver already published — skipping" | |
| else | |
| npm publish "$dir" --access public | |
| fi | |
| } | |
| for dir in npm/*/; do publish_pkg "$dir"; done | |
| publish_pkg . | |
| github-release: | |
| name: GitHub release | |
| needs: [validate-version, publish-crates, publish-pypi, publish-npm] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| generate_release_notes: true | |
| prerelease: ${{ needs.validate-version.outputs.is_prerelease == 'true' }} |