Release @datarecce/ui #205
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 @datarecce/ui | |
| on: | |
| # Triggered when nightly build completes | |
| workflow_run: | |
| workflows: | |
| - "Recce Nightly Build" | |
| - "Official Recce Release" | |
| types: | |
| - completed | |
| # Manual trigger | |
| workflow_dispatch: | |
| inputs: | |
| release_type: | |
| description: 'Select the type of release' | |
| type: choice | |
| required: true | |
| options: | |
| - Nightly | |
| - Official | |
| version: | |
| description: 'Version to release (for Official only, e.g., 0.2.2). Leave empty to use package.json version.' | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| id-token: write # Required for OIDC npm publishing with provenance | |
| jobs: | |
| build-and-publish: | |
| name: Build and Publish @datarecce/ui | |
| runs-on: ubuntu-latest | |
| # Only run if workflow_run succeeded or if manually triggered | |
| if: | | |
| github.event_name == 'workflow_dispatch' || | |
| (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # For workflow_run, checkout the same ref that triggered the parent workflow | |
| ref: ${{ github.event.workflow_run.head_sha || github.sha }} | |
| - uses: pnpm/action-setup@v6 | |
| with: | |
| version: 11.1.1 | |
| - name: Setup Node.js 26 | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version-file: 'js/.nvmrc' # Node 26 includes npm 11+ with OIDC support | |
| cache: 'pnpm' | |
| cache-dependency-path: js/pnpm-lock.yaml | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Verify npm version | |
| run: | | |
| NPM_VERSION=$(npm --version) | |
| echo "npm version: $NPM_VERSION" | |
| # npm 11.5.1+ required for OIDC trusted publishing | |
| - name: Install dependencies | |
| run: | | |
| cd js | |
| pnpm install --frozen-lockfile | |
| - name: Run lint and type check | |
| run: | | |
| cd js | |
| pnpm lint | |
| pnpm type:check | |
| - name: Run tests | |
| run: | | |
| cd js | |
| pnpm test | |
| - name: Determine release type | |
| id: release_type | |
| run: | | |
| if [[ "${{ github.event_name }}" == "workflow_run" ]]; then | |
| workflow_name="${{ github.event.workflow_run.name }}" | |
| if [[ "$workflow_name" == "Recce Nightly Build" ]]; then | |
| echo "type=nightly" >> $GITHUB_OUTPUT | |
| elif [[ "$workflow_name" == "Official Recce Release" ]]; then | |
| echo "type=official" >> $GITHUB_OUTPUT | |
| else | |
| echo "type=nightly" >> $GITHUB_OUTPUT | |
| fi | |
| elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| if [[ "${{ inputs.release_type }}" == "Official" ]]; then | |
| echo "type=official" >> $GITHUB_OUTPUT | |
| else | |
| echo "type=nightly" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "type=nightly" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Download version artifact (Nightly) | |
| id: download_nightly | |
| if: github.event_name == 'workflow_run' && steps.release_type.outputs.type == 'nightly' | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: recce-nightly-version | |
| path: .version-artifact | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Download version artifact (Official) | |
| id: download_official | |
| if: github.event_name == 'workflow_run' && steps.release_type.outputs.type == 'official' | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: recce-release-version | |
| path: .version-artifact | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Check if artifact exists (skip alpha builds) | |
| id: check_artifact | |
| if: github.event_name == 'workflow_run' | |
| run: | | |
| if [[ ! -f ".version-artifact/VERSION" ]]; then | |
| echo "::notice::No version artifact found. This is likely an alpha build - skipping npm release." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "skip=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Convert Python version to npm semver | |
| id: convert_version | |
| if: steps.check_artifact.outputs.skip != 'true' | |
| run: | | |
| cd js/packages/ui | |
| if [[ "${{ github.event_name }}" == "workflow_run" ]]; then | |
| # Read version from artifact using explicit workspace path | |
| version_file="${GITHUB_WORKSPACE}/.version-artifact/VERSION" | |
| if [[ ! -f "$version_file" ]]; then | |
| echo "::error::Version artifact not found at $version_file" | |
| exit 1 | |
| fi | |
| python_version=$(cat "$version_file") | |
| echo "Python version from artifact: ${python_version}" | |
| # Convert Python version to npm semver | |
| # Note: Alpha builds (e.g., 0.3.0.20260202a1) are filtered out earlier in the | |
| # workflow_run path, but we keep the conversion logic for defensive programming | |
| # and potential future use via manual dispatch. | |
| # Examples: | |
| # 0.3.0.20260202 -> 0.3.0-nightly.20260202 | |
| # 0.3.0.20260202-post1 -> 0.3.0-nightly.20260202.1 | |
| # 0.3.0.20260202a1 -> 0.3.0-alpha.20260202.1 (filtered earlier, kept for safety) | |
| # 0.2.2 -> 0.2.2 | |
| if [[ "${{ steps.release_type.outputs.type }}" == "nightly" ]]; then | |
| # Handle nightly versions | |
| if [[ "$python_version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.([0-9]{8})a([0-9]+)$ ]]; then | |
| # Alpha version: 0.3.0.20260202a1 -> 0.3.0-alpha.20260202.1 | |
| base="${BASH_REMATCH[1]}" | |
| date="${BASH_REMATCH[2]}" | |
| alpha_num="${BASH_REMATCH[3]}" | |
| npm_version="${base}-alpha.${date}.${alpha_num}" | |
| elif [[ "$python_version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.([0-9]{8})-post([0-9]+)$ ]]; then | |
| # Post version: 0.3.0.20260202-post1 -> 0.3.0-nightly.20260202.1 | |
| base="${BASH_REMATCH[1]}" | |
| date="${BASH_REMATCH[2]}" | |
| post_num="${BASH_REMATCH[3]}" | |
| npm_version="${base}-nightly.${date}.${post_num}" | |
| elif [[ "$python_version" =~ ^([0-9]+\.[0-9]+\.[0-9]+)\.([0-9]{8})$ ]]; then | |
| # Regular nightly: 0.3.0.20260202 -> 0.3.0-nightly.20260202 | |
| base="${BASH_REMATCH[1]}" | |
| date="${BASH_REMATCH[2]}" | |
| npm_version="${base}-nightly.${date}" | |
| else | |
| echo "::error::Unable to parse nightly version: ${python_version}" | |
| echo "::error::Expected formats:" | |
| echo "::error:: - X.Y.Z.YYYYMMDD (e.g., 0.3.0.20260202)" | |
| echo "::error:: - X.Y.Z.YYYYMMDD-postN (e.g., 0.3.0.20260202-post1)" | |
| echo "::error:: - X.Y.Z.YYYYMMDDaN (e.g., 0.3.0.20260202a1)" | |
| exit 1 | |
| fi | |
| echo "npm_tag=nightly" >> $GITHUB_OUTPUT | |
| else | |
| # Official version: use as-is (should be semver like 0.2.2) | |
| npm_version="${python_version}" | |
| echo "npm_tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| # Manual dispatch - use input version or recce/VERSION | |
| if [[ -n "${{ inputs.version }}" ]]; then | |
| npm_version="${{ inputs.version }}" | |
| else | |
| # Read base version from recce/VERSION (e.g., "1.44.0.dev0" -> "1.44.0") | |
| python_version=$(cat "${GITHUB_WORKSPACE}/recce/VERSION") | |
| npm_version=$(echo "$python_version" | sed -E 's/\.dev[0-9]*$//') | |
| fi | |
| if [[ "${{ steps.release_type.outputs.type }}" == "nightly" ]]; then | |
| # Add nightly suffix if not present | |
| if [[ ! "$npm_version" =~ -nightly ]]; then | |
| today=$(date -u '+%Y%m%d') | |
| npm_version="${npm_version}-nightly.${today}" | |
| fi | |
| echo "npm_tag=nightly" >> $GITHUB_OUTPUT | |
| else | |
| # Validate official release version is plain semver (no pre-release tags) | |
| if [[ ! "$npm_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| echo "::error::Official releases must use a plain semver version (e.g., 0.2.3) without pre-release or build tags." | |
| echo "::error::Got: ${npm_version}" | |
| exit 1 | |
| fi | |
| echo "npm_tag=latest" >> $GITHUB_OUTPUT | |
| fi | |
| fi | |
| echo "npm_version=${npm_version}" >> $GITHUB_OUTPUT | |
| echo "Converted npm version: ${npm_version}" | |
| - name: Validate and update package version | |
| id: patch_version | |
| if: steps.check_artifact.outputs.skip != 'true' | |
| run: | | |
| cd js/packages/ui | |
| npm_version="${{ steps.convert_version.outputs.npm_version }}" | |
| npm_tag="${{ steps.convert_version.outputs.npm_tag }}" | |
| # Check if version already exists and auto-increment post build number | |
| base_version="$npm_version" | |
| post_num=0 | |
| while true; do | |
| if [[ $post_num -eq 0 ]]; then | |
| check_version="$base_version" | |
| else | |
| check_version="${base_version}.${post_num}" | |
| fi | |
| # Check if version exists, distinguishing between "not found" (E404) and actual errors | |
| npm_view_status=0 | |
| npm_view_output=$(npm view @datarecce/ui@${check_version} version 2>&1) || npm_view_status=$? | |
| if [[ $npm_view_status -eq 0 ]]; then | |
| # Version exists | |
| existing_version="$npm_view_output" | |
| else | |
| # Check if it's a 404 (version not found) or an actual error | |
| if echo "$npm_view_output" | grep -q "E404\|npm error code E404\|is not in this registry"; then | |
| existing_version="" | |
| else | |
| echo "::error::npm view failed for version ${check_version}:" | |
| echo "$npm_view_output" | |
| exit 1 | |
| fi | |
| fi | |
| if [[ -z "$existing_version" ]]; then | |
| # Version doesn't exist, use it | |
| npm_version="$check_version" | |
| break | |
| fi | |
| echo "Version ${check_version} already exists on npm, trying next..." | |
| post_num=$((post_num + 1)) | |
| # Safety limit to prevent infinite loop | |
| if [[ $post_num -gt 100 ]]; then | |
| echo "::error::Too many post build attempts (>100)" | |
| exit 1 | |
| fi | |
| done | |
| if [[ $post_num -gt 0 ]]; then | |
| echo "::notice::Auto-incremented to post build ${post_num}: ${npm_version}" | |
| fi | |
| # Update package.json with the new version | |
| node -e " | |
| const pkg = require('./package.json'); | |
| pkg.version = '${npm_version}'; | |
| require('fs').writeFileSync('./package.json', JSON.stringify(pkg, null, 2) + '\n'); | |
| " | |
| echo "UI version: ${npm_version}" | |
| echo "version=${npm_version}" >> $GITHUB_OUTPUT | |
| echo "npm_tag=${npm_tag}" >> $GITHUB_OUTPUT | |
| - name: Build UI Package | |
| if: steps.check_artifact.outputs.skip != 'true' | |
| run: | | |
| cd js/packages/ui | |
| pnpm build | |
| # npm OIDC trusted publishing - no NPM_TOKEN required | |
| # Provenance attestation is automatic with trusted publishing | |
| - name: Publish to npm | |
| if: steps.check_artifact.outputs.skip != 'true' | |
| run: | | |
| cd js/packages/ui | |
| npm publish --tag ${{ steps.patch_version.outputs.npm_tag }} --access public | |
| - name: Summary | |
| if: steps.check_artifact.outputs.skip != 'true' | |
| run: | | |
| version="${{ steps.patch_version.outputs.version }}" | |
| tag="${{ steps.patch_version.outputs.npm_tag }}" | |
| echo "## @datarecce/ui Release Summary" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Version:** ${version}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Tag:** ${tag}" >> $GITHUB_STEP_SUMMARY | |
| echo "- **Type:** ${{ steps.release_type.outputs.type }}" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "### Install" >> $GITHUB_STEP_SUMMARY | |
| echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY | |
| if [[ "${tag}" == "nightly" ]]; then | |
| echo "npm install @datarecce/ui@nightly" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "npm install @datarecce/ui@${version}" >> $GITHUB_STEP_SUMMARY | |
| fi | |
| echo "\`\`\`" >> $GITHUB_STEP_SUMMARY | |
| - uses: actions/create-github-app-token@v2 | |
| id: app-token | |
| if: steps.check_artifact.outputs.skip != 'true' && steps.patch_version.outputs.npm_tag == 'latest' | |
| continue-on-error: true | |
| with: | |
| app-id: ${{ secrets.RECCE_ACTION_BOT_APP_ID }} | |
| private-key: ${{ secrets.RECCE_ACTION_BOT_PRIVATE_KEY }} | |
| owner: ${{ github.repository_owner }} | |
| - name: Trigger Bump @datarecce/ui in recce-cloud-infra | |
| id: dispatch | |
| if: steps.check_artifact.outputs.skip != 'true' && steps.patch_version.outputs.npm_tag == 'latest' && steps.app-token.outcome == 'success' | |
| continue-on-error: true | |
| uses: benc-uk/workflow-dispatch@v1 | |
| with: | |
| workflow: 'bump-recce-ui.yml' | |
| repo: 'DataRecce/recce-cloud-infra' | |
| ref: 'main' | |
| inputs: '{ "version": "${{ steps.patch_version.outputs.version }}" }' | |
| token: '${{ steps.app-token.outputs.token }}' | |
| - name: Handle dispatch failure | |
| if: steps.app-token.outcome == 'failure' || steps.dispatch.outcome == 'failure' | |
| run: | | |
| echo "::warning::Failed to trigger Bump @datarecce/ui workflow in recce-cloud-infra" | |
| echo "::warning::Please manually trigger the bump at: https://github.com/DataRecce/recce-cloud-infra/actions/workflows/bump-recce-ui.yml" | |
| echo "::warning::Use version: ${{ steps.patch_version.outputs.version }}" |