fix(js): preserve real exit code when test reset/parent-stream closure races in-flight commands (#170) #52
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: JavaScript checks and release | |
| on: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - 'js/**' | |
| - '.github/workflows/js.yml' | |
| - 'README.md' | |
| - 'LICENSE' | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| paths: | |
| - 'js/**' | |
| - '.github/workflows/js.yml' | |
| - 'README.md' | |
| - 'LICENSE' | |
| workflow_dispatch: | |
| inputs: | |
| release_mode: | |
| description: 'Manual release mode' | |
| required: true | |
| type: choice | |
| default: 'instant' | |
| options: | |
| - instant | |
| - changeset-pr | |
| bump_type: | |
| description: 'Manual release type' | |
| required: true | |
| type: choice | |
| options: | |
| - patch | |
| - minor | |
| - major | |
| description: | |
| description: 'Manual release description (optional)' | |
| required: false | |
| type: string | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| jobs: | |
| changeset-check: | |
| name: Check for JavaScript changesets | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| working-directory: js | |
| run: bun install | |
| - name: Check for changesets | |
| working-directory: js | |
| env: | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then | |
| echo "Skipping changeset check for automated release PR" | |
| exit 0 | |
| fi | |
| bun scripts/validate-changeset.mjs | |
| lint: | |
| name: Lint and format JavaScript | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [changeset-check] | |
| if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success') | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| working-directory: js | |
| run: bun install | |
| - name: Run ESLint | |
| working-directory: js | |
| run: bun run lint | |
| - name: Check formatting | |
| working-directory: js | |
| run: bun run format:check | |
| - name: Check code duplication | |
| working-directory: js | |
| run: bun run check:duplication | |
| test: | |
| name: Test JavaScript (${{ matrix.runtime }} on ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| needs: [changeset-check] | |
| if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runtime: [bun] | |
| include: | |
| - os: ubuntu-latest | |
| runtime: node | |
| node-version: 20 | |
| - os: ubuntu-latest | |
| runtime: node | |
| node-version: 22 | |
| - os: ubuntu-latest | |
| runtime: node | |
| node-version: 24 | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Setup Bun | |
| if: matrix.runtime == 'bun' | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install system dependencies (Ubuntu) | |
| if: matrix.os == 'ubuntu-latest' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| - name: Install system dependencies (macOS) | |
| if: matrix.os == 'macos-latest' | |
| run: brew install jq | |
| - name: Install system dependencies (Windows) | |
| if: matrix.os == 'windows-latest' | |
| run: choco install jq -y | |
| - name: Install dependencies (Bun) | |
| if: matrix.runtime == 'bun' | |
| working-directory: js | |
| run: bun install | |
| - name: Run tests (Bun) | |
| if: matrix.runtime == 'bun' | |
| run: bun test js/tests/ --timeout 10000 | |
| env: | |
| COMMAND_STREAM_TRACE: ${{ vars.COMMAND_STREAM_TRACE || 'false' }} | |
| - name: Setup Node.js | |
| if: matrix.runtime == 'node' | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: ${{ matrix.node-version }} | |
| - name: Test Node.js compatibility | |
| if: matrix.runtime == 'node' | |
| run: | | |
| node -e " | |
| import('./js/src/\$.mjs') | |
| .then(() => console.log('Module loads successfully in Node.js ${{ matrix.node-version }}')) | |
| .catch((error) => { | |
| console.error('Module failed to load:', error.message); | |
| process.exit(1); | |
| }); | |
| " | |
| release: | |
| name: Release JavaScript package | |
| needs: [lint, test] | |
| # Required because lint/test depend on the pull-request-only changeset-check | |
| # job, which is skipped on push events. | |
| if: | | |
| always() && !cancelled() && | |
| github.ref == 'refs/heads/main' && | |
| github.event_name == 'push' && | |
| needs.lint.result == 'success' && | |
| needs.test.result == 'success' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| working-directory: js | |
| run: bun install | |
| - name: Update npm for OIDC trusted publishing | |
| working-directory: js | |
| run: bun scripts/setup-npm.mjs | |
| - name: Check for changesets | |
| id: check_changesets | |
| working-directory: js | |
| run: | | |
| CHANGESET_COUNT=$(find .changeset -name "*.md" ! -name "README.md" | wc -l) | |
| echo "Found $CHANGESET_COUNT JavaScript changeset file(s)" | |
| echo "has_changesets=$([[ $CHANGESET_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT | |
| echo "changeset_count=$CHANGESET_COUNT" >> $GITHUB_OUTPUT | |
| - name: Check if release is needed | |
| id: check_release | |
| working-directory: js | |
| env: | |
| HAS_CHANGESETS: ${{ steps.check_changesets.outputs.has_changesets }} | |
| run: bun scripts/check-release-needed.mjs | |
| - name: Merge multiple changesets | |
| if: steps.check_changesets.outputs.has_changesets == 'true' && fromJSON(steps.check_changesets.outputs.changeset_count) > 1 | |
| working-directory: js | |
| run: bun scripts/merge-changesets.mjs | |
| - name: Version package and commit to main | |
| if: steps.check_changesets.outputs.has_changesets == 'true' | |
| id: version | |
| working-directory: js | |
| run: bun scripts/version-and-commit.mjs --mode changeset | |
| - name: Publish to npm | |
| # Run if a changeset bumped+committed the version, if a previous attempt | |
| # already committed it (re-run safety), or if check-release-needed found | |
| # the current package.json version is not on npm. `current_unpublished` | |
| # is the authoritative self-heal trigger: it fires whether or not a | |
| # changeset is present, so it also covers the #166 "failed to do any | |
| # deploy" restart, where a changeset existed locally but the bump had | |
| # already been consumed on origin/main, leaving v0.10.2 stranded. | |
| if: >- | |
| steps.version.outputs.version_committed == 'true' || | |
| steps.version.outputs.already_released == 'true' || | |
| steps.check_release.outputs.current_unpublished == 'true' | |
| id: publish | |
| working-directory: js | |
| run: bun scripts/publish-to-npm.mjs --should-pull | |
| - name: Create JavaScript GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bun scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --tag-prefix js-v | |
| - name: Format JavaScript GitHub release notes | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bun scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" --tag-prefix js-v | |
| - name: Verify npm availability | |
| # Guards against the #166 false positive: a release/tag must correspond | |
| # to a version that is actually installable from npm. | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| run: bun scripts/wait-for-npm.mjs --release-version "${{ steps.publish.outputs.published_version }}" | |
| instant-release: | |
| name: Instant JavaScript release | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| registry-url: 'https://registry.npmjs.org' | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| working-directory: js | |
| run: bun install | |
| - name: Update npm for OIDC trusted publishing | |
| working-directory: js | |
| run: bun scripts/setup-npm.mjs | |
| - name: Version package and commit to main | |
| id: version | |
| working-directory: js | |
| run: bun scripts/version-and-commit.mjs --mode instant --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Publish to npm | |
| if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' | |
| id: publish | |
| working-directory: js | |
| run: bun scripts/publish-to-npm.mjs | |
| - name: Create JavaScript GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bun scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --tag-prefix js-v | |
| - name: Format JavaScript GitHub release notes | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: bun scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" --tag-prefix js-v | |
| - name: Verify npm availability | |
| # Guards against the #166 false positive: a release/tag must correspond | |
| # to a version that is actually installable from npm. | |
| if: steps.publish.outputs.published == 'true' | |
| working-directory: js | |
| run: bun scripts/wait-for-npm.mjs --release-version "${{ steps.publish.outputs.published_version }}" | |
| changeset-pr: | |
| name: Create JavaScript changeset PR | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changeset-pr' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install dependencies | |
| working-directory: js | |
| run: bun install | |
| - name: Create changeset file | |
| working-directory: js | |
| run: bun scripts/create-manual-changeset.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Format changeset with Prettier | |
| working-directory: js | |
| run: bunx prettier --write ".changeset/*.md" | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: 'chore: add changeset for manual JavaScript ${{ github.event.inputs.bump_type }} release' | |
| branch: changeset-js-manual-release-${{ github.run_id }} | |
| delete-branch: true | |
| title: 'chore: manual JavaScript ${{ github.event.inputs.bump_type }} release' | |
| body: | | |
| ## Manual JavaScript Release Request | |
| This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** npm release. | |
| ### Release Details | |
| - Type: ${{ github.event.inputs.bump_type }} | |
| - Description: ${{ github.event.inputs.description || 'Manual JavaScript release' }} | |
| - Triggered by: @${{ github.actor }} |