v0.6.0 #31
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: npm-build-publish | |
| # Builds hyperdb-mcp and hyperdb-api-node npm packages with bundled | |
| # hyperd binaries. Triggered after a successful release workflow creates | |
| # a GitHub release, or manually via workflow_dispatch. | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag or branch to build (e.g. v0.1.0). Leave empty for current default branch." | |
| required: false | |
| default: "" | |
| release: | |
| types: [published] | |
| concurrency: | |
| group: npm-build-publish | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| HYPERD_VERSION: "0.0.25080" | |
| HYPERD_BUILD_ID: "r2bfd835b" | |
| jobs: | |
| verify-ci: | |
| name: verify CI passed | |
| if: github.repository == 'tableau/hyper-api-rust' && github.event_name == 'release' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 35 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.release.tag_name }} | |
| - name: Wait for CI to pass | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| SHA=$(git rev-parse HEAD) | |
| echo "Waiting for required CI check-runs on $SHA" | |
| # GitHub Actions report results via check-runs, not the legacy | |
| # commit-status API. Poll check-runs by name; ignore the | |
| # verify-ci job from this workflow (it would self-reference). | |
| MAX_ATTEMPTS=30 | |
| SLEEP_SECONDS=60 | |
| # Exact-name membership against the canonical check-run list. | |
| # We previously used a regex via `test()` here, but the names | |
| # contain `(` and `+` which both have regex meaning — escaping | |
| # them through bash → jq → Oniguruma was fragile and broke | |
| # silently on the v0.2.2 release. Exact-string match avoids | |
| # the whole class of regex-escaping bugs. | |
| REQUIRED_JSON='[ | |
| "rustfmt","clippy","cargo-audit","cargo-deny", | |
| "version consistency","publish dry-run", | |
| "test (ubuntu-latest)","test (macos-14)","test (windows-latest)", | |
| "hyperdb-api-node (build + smoke)" | |
| ]' | |
| for i in $(seq 1 "$MAX_ATTEMPTS"); do | |
| RUNS=$(gh api "repos/$REPO/commits/$SHA/check-runs?per_page=100" \ | |
| --jq "[.check_runs[] | select(.name as \$n | $REQUIRED_JSON | index(\$n))]") | |
| TOTAL=$(echo "$RUNS" | jq 'length') | |
| COMPLETED=$(echo "$RUNS" | jq '[.[] | select(.status=="completed")] | length') | |
| FAILED=$(echo "$RUNS" | jq '[.[] | select(.status=="completed" and (.conclusion!="success" and .conclusion!="skipped" and .conclusion!="neutral"))] | length') | |
| echo "Attempt $i/$MAX_ATTEMPTS: total=$TOTAL completed=$COMPLETED failed=$FAILED" | |
| if [[ "$FAILED" -gt 0 ]]; then | |
| echo "::error::CI failed for $SHA. Failing check-runs:" | |
| echo "$RUNS" | jq -r '.[] | select(.status=="completed" and (.conclusion!="success" and .conclusion!="skipped" and .conclusion!="neutral")) | " - \(.name): \(.conclusion)"' | |
| exit 1 | |
| fi | |
| if [[ "$TOTAL" -gt 0 && "$COMPLETED" == "$TOTAL" ]]; then | |
| echo "CI passed ($TOTAL required check-runs completed successfully)." | |
| exit 0 | |
| fi | |
| echo "CI still pending, waiting ${SLEEP_SECONDS}s..." | |
| sleep "$SLEEP_SECONDS" | |
| done | |
| echo "::error::Timed out waiting for CI to pass ($SHA)." | |
| exit 1 | |
| build-npm: | |
| name: build-npm (${{ matrix.platform }}) | |
| needs: verify-ci | |
| if: always() && (needs.verify-ci.result == 'success' || needs.verify-ci.result == 'skipped') | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: darwin-arm64 | |
| os: macos-14 | |
| target: aarch64-apple-darwin | |
| hyperd-slug: macos-arm64 | |
| hyperd-sha256: "2b0fa3fefcf4eba60f052e1cb51abfc32d8c84354274513763760f9549b45991" | |
| # TODO: re-enable when macos-13 runners are more available | |
| # - platform: darwin-x64 | |
| # os: macos-13 | |
| # target: x86_64-apple-darwin | |
| # hyperd-slug: macos-x86_64 | |
| # hyperd-sha256: "d5d3dae60ce071aed45e534fb6a4fc6c7edce47e31d664d131af9f76d9b3a2aa" | |
| - platform: linux-x64-gnu | |
| os: ubuntu-latest | |
| target: x86_64-unknown-linux-gnu | |
| hyperd-slug: linux-x86_64 | |
| hyperd-sha256: "3d3fd2104f55f7fad832470592394dc78f350a03d52e89d36c5288b202dd0bc0" | |
| - platform: win32-x64-msvc | |
| os: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| hyperd-slug: windows-x86_64 | |
| hyperd-sha256: "9dc4851d416e0e6e00f0367ee6b45fcd676e7ba3a110d4644e3bec871b9aa1de" | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.event.release.tag_name || github.ref }} | |
| - name: Install system libraries (Linux) | |
| if: runner.os == 'Linux' | |
| run: sudo apt-get update -q && sudo apt-get install -y libfontconfig1-dev mold protobuf-compiler | |
| - name: Install protobuf (macOS) | |
| if: runner.os == 'macOS' | |
| run: brew install protobuf | |
| - name: Install protobuf (Windows) | |
| if: runner.os == 'Windows' | |
| run: choco install protoc -y | |
| - uses: actions-rust-lang/setup-rust-toolchain@v1 | |
| with: | |
| toolchain: stable | |
| target: ${{ matrix.target }} | |
| cache-key: npm-${{ matrix.target }} | |
| rustflags: "" | |
| - name: Build hyperdb-mcp | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: cargo build --release --target "$TARGET" -p hyperdb-mcp | |
| - name: Build hyperdb-api-node | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: cargo build --release --target "$TARGET" -p hyperdb-api-node | |
| - name: Download and verify hyperd | |
| env: | |
| SLUG: ${{ matrix.hyperd-slug }} | |
| EXPECTED_SHA256: ${{ matrix.hyperd-sha256 }} | |
| run: | | |
| set -euo pipefail | |
| URL="https://downloads.tableau.com/tssoftware/tableauhyperapi-java-${SLUG}-release-main.${HYPERD_VERSION}.${HYPERD_BUILD_ID}.zip" | |
| echo "Downloading: $URL" | |
| curl --fail --silent --show-error --location --output hyperd-archive.zip "$URL" | |
| if command -v sha256sum &>/dev/null; then | |
| ACTUAL_SHA256=$(sha256sum hyperd-archive.zip | awk '{print $1}') | |
| else | |
| ACTUAL_SHA256=$(shasum -a 256 hyperd-archive.zip | awk '{print $1}') | |
| fi | |
| if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then | |
| echo "::error::SHA256 mismatch: expected $EXPECTED_SHA256, got $ACTUAL_SHA256" | |
| exit 1 | |
| fi | |
| echo "SHA256 verified: $ACTUAL_SHA256" | |
| - name: Extract hyperd from archive | |
| run: | | |
| set -euo pipefail | |
| mkdir -p hyperd-extracted | |
| unzip -q hyperd-archive.zip -d hyperd-raw | |
| # Find the lib/hyper or bin/hyper directory (matches extract.rs logic) | |
| HYPER_DIR=$(find hyperd-raw -type d -name "hyper" -path "*/lib/hyper" -o -type d -name "hyper" -path "*/bin/hyper" | head -1) | |
| if [[ -z "$HYPER_DIR" ]]; then | |
| echo "::error::Could not find lib/hyper or bin/hyper in archive" | |
| exit 1 | |
| fi | |
| cp -R "$HYPER_DIR"/* hyperd-extracted/ | |
| ls -la hyperd-extracted/ | |
| # Verify hyperd binary exists | |
| if [[ -f hyperd-extracted/hyperd ]] || [[ -f hyperd-extracted/hyperd.exe ]]; then | |
| echo "hyperd binary found" | |
| else | |
| echo "::error::hyperd binary not found in extracted archive" | |
| exit 1 | |
| fi | |
| - name: Extract LICENSE from hyperd archive | |
| run: | | |
| set -euo pipefail | |
| LICENSE_FILE=$(find hyperd-raw -iname "LICENSE*" -o -iname "NOTICE*" | head -1) | |
| if [[ -n "$LICENSE_FILE" ]]; then | |
| cp "$LICENSE_FILE" LICENSE-HYPERD | |
| else | |
| cat > LICENSE-HYPERD << 'LICEOF' | |
| The hyperd binary is part of the Tableau Hyper API, Copyright Salesforce, Inc. | |
| Licensed under the Apache License, Version 2.0. | |
| See https://www.apache.org/licenses/LICENSE-2.0 | |
| LICEOF | |
| fi | |
| - name: Assemble hyperdb-mcp npm platform package | |
| env: | |
| PLATFORM: ${{ matrix.platform }} | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| DEST="hyperdb-mcp/npm/${PLATFORM}" | |
| EXE="" | |
| HYPERD_BIN="hyperd" | |
| if [[ "$PLATFORM" == "win32-x64-msvc" ]]; then | |
| EXE=".exe" | |
| HYPERD_BIN="hyperd.exe" | |
| fi | |
| cp "target/${TARGET}/release/hyperdb-mcp${EXE}" "$DEST/" | |
| cp "hyperd-extracted/${HYPERD_BIN}" "$DEST/" | |
| # Copy shared libraries that hyperd needs | |
| find hyperd-extracted -name "*.so*" -o -name "*.dylib" -o -name "*.dll" | while read f; do | |
| cp "$f" "$DEST/" | |
| done | |
| cp LICENSE-HYPERD "$DEST/" | |
| chmod +x "$DEST/hyperdb-mcp${EXE}" "$DEST/${HYPERD_BIN}" 2>/dev/null || true | |
| echo "Contents of $DEST:" | |
| ls -la "$DEST/" | |
| - name: Assemble hyperdb-api-node npm platform package | |
| env: | |
| PLATFORM: ${{ matrix.platform }} | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| DEST="hyperdb-api-node/npm/${PLATFORM}" | |
| EXE="" | |
| HYPERD_BIN="hyperd" | |
| if [[ "$PLATFORM" == "win32-x64-msvc" ]]; then | |
| EXE=".exe" | |
| HYPERD_BIN="hyperd.exe" | |
| fi | |
| # Copy the .node native addon | |
| node hyperdb-api-node/scripts/ci-copy-artifact.js "$TARGET" | |
| # Copy hyperd + shared libraries | |
| cp "hyperd-extracted/${HYPERD_BIN}" "$DEST/" | |
| find hyperd-extracted -name "*.so*" -o -name "*.dylib" -o -name "*.dll" | while read f; do | |
| cp "$f" "$DEST/" | |
| done | |
| cp LICENSE-HYPERD "$DEST/" | |
| chmod +x "$DEST/${HYPERD_BIN}" 2>/dev/null || true | |
| echo "Contents of $DEST:" | |
| ls -la "$DEST/" | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: npm-${{ matrix.platform }} | |
| path: | | |
| hyperdb-mcp/npm/${{ matrix.platform }}/ | |
| hyperdb-api-node/npm/${{ matrix.platform }}/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| publish-npm: | |
| name: publish to npm | |
| needs: build-npm | |
| if: always() && needs.build-npm.result == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.event.release.tag_name || github.ref }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 20 | |
| registry-url: "https://registry.npmjs.org" | |
| - name: Download all platform artifacts | |
| uses: actions/download-artifact@v8 | |
| with: | |
| pattern: npm-* | |
| merge-multiple: true | |
| - name: Restore executable bits on platform binaries | |
| # actions/upload-artifact preserves perms via tarball but | |
| # actions/download-artifact extracts without them. Re-apply | |
| # +x so the binaries are runnable after `npm install`. | |
| run: | | |
| set -euo pipefail | |
| for d in hyperdb-mcp/npm/* hyperdb-api-node/npm/*; do | |
| [[ -d "$d" ]] || continue | |
| for f in "$d"/hyperdb-mcp "$d"/hyperdb-mcp.exe "$d"/hyperd "$d"/hyperd.exe; do | |
| [[ -f "$f" ]] && chmod +x "$f" || true | |
| done | |
| done | |
| - name: Copy README into hyperdb-mcp npm publish directory | |
| run: cp hyperdb-mcp/README.md hyperdb-mcp/npm/README.md | |
| - name: Resolve version and dist-tag from event | |
| id: version | |
| # Source-of-truth: the git tag that triggered the workflow. | |
| # release events carry it in github.event.release.tag_name; | |
| # workflow_dispatch carries it in inputs.tag. Neither path | |
| # reads from package.json, so the platform/umbrella package | |
| # files don't need to track the version in source control. | |
| env: | |
| RAW_VERSION: ${{ github.event.inputs.tag || github.event.release.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| if [[ -z "${RAW_VERSION:-}" ]]; then | |
| echo "::error::No tag/version available; refusing to publish." | |
| exit 1 | |
| fi | |
| VERSION="${RAW_VERSION#v}" | |
| # Defense in depth: tags from workflow_dispatch are user-supplied. | |
| # Enforce strict vX.Y.Z / vX.Y.Z-rc.N shape before the value | |
| # flows into npm pkg set / npm publish commands. | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-(rc|alpha|beta)\.[0-9]+)?$ ]]; then | |
| echo "::error::Invalid version: $VERSION (expected X.Y.Z or X.Y.Z-rc.N)" | |
| exit 1 | |
| fi | |
| case "$VERSION" in | |
| *-rc.*) TAG=rc ;; | |
| *-alpha.*) TAG=alpha ;; | |
| *-beta.*) TAG=beta ;; | |
| *) TAG=latest ;; | |
| esac | |
| { | |
| echo "version=$VERSION" | |
| echo "tag=$TAG" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "Resolved version=$VERSION dist-tag=$TAG" | |
| - name: Verify tag matches workspace Cargo.toml version | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| # Defense against a tag that doesn't match what `cargo publish` | |
| # will publish in release.yml. If they diverge, npm and crates.io | |
| # would ship at different versions. Match the same field | |
| # release-please patches: [workspace.package].version. | |
| run: | | |
| set -euo pipefail | |
| CARGO_VERSION=$(awk ' | |
| /^\[workspace\.package\]/ { in_section = 1; next } | |
| /^\[/ { in_section = 0 } | |
| in_section && /^version[[:space:]]*=/ { | |
| gsub(/[[:space:]"]/, "", $0) | |
| sub(/^version=/, "", $0) | |
| exit | |
| } | |
| ' Cargo.toml) | |
| if [[ "$VERSION" != "$CARGO_VERSION" ]]; then | |
| echo "::error::Tag version ($VERSION) does not match Cargo.toml workspace version ($CARGO_VERSION). Refusing to publish to avoid registry skew between npm and crates.io." | |
| exit 1 | |
| fi | |
| - name: Inject version into all npm package.json files | |
| env: | |
| VERSION: ${{ steps.version.outputs.version }} | |
| # The platform and umbrella package.json files are stripped of | |
| # `version` and `optionalDependencies` in source. We materialize | |
| # them here from the tag-derived $VERSION just before publish. | |
| # | |
| # PLATFORMS is the SINGLE source of truth for which platforms | |
| # ship. It must match (a) the build-npm matrix above, (b) the | |
| # publish loops below, and (c) the `napi.targets` array in | |
| # hyperdb-api-node/package.json. hyperdb-mcp and hyperdb-api-node | |
| # currently ship the same 3 platforms; if that ever diverges, | |
| # split into MCP_PLATFORMS / NODE_PLATFORMS arrays here AND | |
| # below in the publish steps. | |
| run: | | |
| set -euo pipefail | |
| PLATFORMS=(darwin-arm64 linux-x64-gnu win32-x64-msvc) | |
| for p in "${PLATFORMS[@]}"; do | |
| (cd "hyperdb-mcp/npm/$p" && npm pkg set "version=$VERSION") | |
| (cd "hyperdb-api-node/npm/$p" && npm pkg set "version=$VERSION") | |
| done | |
| # Umbrellas: own version + every optionalDependency exact-pinned | |
| # to ==VERSION so installs resolve to the matching platform tarball. | |
| ( | |
| cd hyperdb-mcp/npm | |
| npm pkg set "version=$VERSION" | |
| for p in "${PLATFORMS[@]}"; do | |
| npm pkg set "optionalDependencies.hyperdb-mcp-$p=$VERSION" | |
| done | |
| ) | |
| ( | |
| cd hyperdb-api-node | |
| npm pkg set "version=$VERSION" | |
| for p in "${PLATFORMS[@]}"; do | |
| npm pkg set "optionalDependencies.hyperdb-api-node-$p=$VERSION" | |
| done | |
| ) | |
| - name: Publish hyperdb-mcp platform packages | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| for dir in hyperdb-mcp/npm/darwin-arm64 hyperdb-mcp/npm/linux-x64-gnu hyperdb-mcp/npm/win32-x64-msvc; do | |
| echo "::group::Publishing $(basename $dir)" | |
| cd "$dir" | |
| npm publish --access public --tag "$NPM_TAG" | |
| cd - | |
| echo "::endgroup::" | |
| done | |
| - name: Publish hyperdb-mcp main package | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| cd hyperdb-mcp/npm | |
| npm publish --access public --tag "$NPM_TAG" | |
| - name: Publish hyperdb-api-node platform packages | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_TAG: ${{ steps.version.outputs.tag }} | |
| run: | | |
| set -euo pipefail | |
| for dir in hyperdb-api-node/npm/darwin-arm64 hyperdb-api-node/npm/linux-x64-gnu hyperdb-api-node/npm/win32-x64-msvc; do | |
| echo "::group::Publishing $(basename $dir)" | |
| cd "$dir" | |
| npm publish --access public --tag "$NPM_TAG" | |
| cd - | |
| echo "::endgroup::" | |
| done | |
| - name: Publish hyperdb-api-node main package | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| NPM_TAG: ${{ steps.version.outputs.tag }} | |
| # --ignore-scripts skips the prepublishOnly hook (`napi prepublish`), | |
| # which would try to re-orchestrate the platform packages our CI | |
| # has already built and published independently. | |
| run: | | |
| cd hyperdb-api-node | |
| npm publish --access public --tag "$NPM_TAG" --ignore-scripts |