Coalesce <br><br> hard-break runs into a paragraph break #278
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 | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| # Manual release support - consolidated here to work with npm trusted publishing | |
| # npm only allows ONE workflow file as trusted publisher, so all publishing | |
| # must go through this workflow (js.yml) | |
| 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: Only one workflow run per branch at a time | |
| # - For main branch (releases): cancel older runs to prevent blocking newer releases | |
| # - For PR branches: queue runs to avoid cancelling checks on force-pushes | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} | |
| defaults: | |
| run: | |
| working-directory: js | |
| jobs: | |
| # === DETECT CHANGES - determines which jobs should run === | |
| detect-changes: | |
| name: JS - Detect Changes | |
| runs-on: ubuntu-latest | |
| if: github.event_name != 'workflow_dispatch' | |
| outputs: | |
| js-code-changed: ${{ steps.changes.outputs.js-code-changed }} | |
| js-changed: ${{ steps.changes.outputs.js-changed }} | |
| scripts-changed: ${{ steps.changes.outputs.scripts-changed }} | |
| js-workflow-changed: ${{ steps.changes.outputs.js-workflow-changed }} | |
| any-js-code-changed: ${{ steps.changes.outputs.any-js-code-changed }} | |
| any-code-changed: ${{ steps.changes.outputs.any-code-changed }} | |
| docs-changed: ${{ steps.changes.outputs.docs-changed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Detect changes | |
| id: changes | |
| working-directory: . | |
| env: | |
| GITHUB_EVENT_NAME: ${{ github.event_name }} | |
| GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: node scripts/detect-code-changes.mjs | |
| # Changeset check - only runs on PRs with JS package source changes | |
| changeset-check: | |
| name: JS - Check for Changesets | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: github.event_name == 'pull_request' && needs.detect-changes.outputs.js-code-changed == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Check for changesets | |
| run: | | |
| # Skip changeset check for automated version PRs | |
| if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then | |
| echo "Skipping changeset check for automated release PR" | |
| exit 0 | |
| fi | |
| # Run changeset validation script | |
| node ../scripts/validate-changeset.mjs | |
| # === PUBLISHABLE CHANGES CHECK === | |
| # Warns when PR touches publishable paths without changeset | |
| # This is informational — auto-release on main handles the gap | |
| publishable-check: | |
| name: JS - Publishable Changes Check | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-js-code-changed == 'true' | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check for publishable changes without changeset | |
| working-directory: . | |
| env: | |
| GITHUB_BASE_REF: ${{ github.base_ref }} | |
| run: node scripts/check-publishable-changes.mjs | |
| # Linting and formatting - runs when JS-relevant files change | |
| lint: | |
| name: JS - Lint and Format Check | |
| runs-on: ubuntu-latest | |
| needs: [detect-changes] | |
| if: | | |
| always() && !cancelled() && ( | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' || | |
| needs.detect-changes.outputs.any-js-code-changed == 'true' || | |
| needs.detect-changes.outputs.docs-changed == 'true' | |
| ) | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Run ESLint | |
| run: npm run lint | |
| - name: Check formatting | |
| run: npm run format:check | |
| - name: Check code duplication | |
| run: npm run check:duplication | |
| # Test matrix: Node.js on Ubuntu with unit tests and e2e tests | |
| test: | |
| name: JS - Test (Node.js on ${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| needs: [detect-changes, changeset-check] | |
| # Run if: push/dispatch event, OR (changeset check passed/skipped AND JS code actually changed) | |
| if: | | |
| always() && !cancelled() && ( | |
| github.event_name == 'push' || | |
| github.event_name == 'workflow_dispatch' || | |
| ( | |
| (needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && | |
| needs.detect-changes.outputs.any-js-code-changed == 'true' | |
| ) | |
| ) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps chromium | |
| - name: Install Puppeteer browsers | |
| run: npx puppeteer browsers install chrome | |
| - name: Run unit, integration, and e2e process tests | |
| run: npm test -- --testPathIgnorePatterns="docker.test.js" | |
| - name: Run Habr article live integration tests | |
| env: | |
| HABR_INTEGRATION: 'true' | |
| run: npm test -- --testPathPattern="habr-article" --testTimeout=120000 | |
| timeout-minutes: 15 | |
| - name: Run Google Docs public-document live integration tests (issue #90) | |
| env: | |
| GDOCS_INTEGRATION: 'true' | |
| run: npm test -- --testPathPattern="gdocs-public-doc" --testTimeout=120000 | |
| timeout-minutes: 10 | |
| - name: Build Docker image for e2e tests | |
| run: docker compose build | |
| timeout-minutes: 10 | |
| - name: Run e2e Docker tests | |
| run: npm test -- tests/e2e/docker.test.js | |
| timeout-minutes: 5 | |
| # Release - only runs on main after tests pass (for push events) | |
| # Self-healing: detects unreleased commits and auto-bumps patch version | |
| release: | |
| name: JS - Release | |
| needs: [lint, test] | |
| if: | | |
| !cancelled() && | |
| github.ref == 'refs/heads/main' && | |
| github.event_name == 'push' && | |
| needs.lint.result == 'success' && | |
| needs.test.result == 'success' | |
| runs-on: ubuntu-latest | |
| # Permissions required for npm OIDC trusted publishing | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| 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: Install dependencies | |
| run: npm install | |
| - name: Update npm for OIDC trusted publishing | |
| run: node ../scripts/setup-npm.mjs | |
| - name: Check if release is needed | |
| id: release_check | |
| run: node ../scripts/check-release-needed.mjs | |
| - name: Auto-bump patch version for unreleased changes | |
| if: steps.release_check.outputs.should_release == 'true' && steps.release_check.outputs.needs_auto_bump == 'true' | |
| id: auto_bump | |
| working-directory: . | |
| # Uses scripts/safe-git-push.mjs for fetch+rebase+retry so concurrent | |
| # pushes from the Rust release workflow don't cause non-fast-forward | |
| # rejections. See docs/case-studies/issue-94. | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| cd js | |
| node ../scripts/instant-version-bump.mjs --bump-type patch --description "Auto-release unreleased changes" | |
| cd .. | |
| git add -A | |
| NEW_VERSION=$(node -p "require('./js/package.json').version") | |
| git commit -m "chore(js): auto-bump to ${NEW_VERSION} for unreleased changes" | |
| node scripts/safe-git-push.mjs --branch main | |
| echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT | |
| echo "Auto-bumped to ${NEW_VERSION}" | |
| - name: Version packages and commit to main (changeset flow) | |
| if: steps.release_check.outputs.has_changesets == 'true' && steps.release_check.outputs.needs_auto_bump != 'true' | |
| id: version | |
| run: node ../scripts/version-and-commit.mjs --mode changeset | |
| - name: Publish to npm | |
| if: | | |
| steps.release_check.outputs.should_release == 'true' && ( | |
| steps.version.outputs.version_committed == 'true' || | |
| steps.version.outputs.already_released == 'true' || | |
| steps.auto_bump.outputs.version != '' || | |
| steps.release_check.outputs.needs_auto_bump == 'false' | |
| ) | |
| id: publish | |
| run: node ../scripts/publish-to-npm.mjs --should-pull | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" | |
| - name: Format GitHub release notes | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" | |
| # Manual Instant Release - triggered via workflow_dispatch with instant mode | |
| # This job is in js.yml because npm trusted publishing | |
| # only allows one workflow file to be registered as a trusted publisher | |
| instant-release: | |
| name: JS - Instant Release | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant' | |
| runs-on: ubuntu-latest | |
| # Permissions required for npm OIDC trusted publishing | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| 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: Install dependencies | |
| run: npm install | |
| - name: Update npm for OIDC trusted publishing | |
| run: node ../scripts/setup-npm.mjs | |
| - name: Version packages and commit to main | |
| id: version | |
| run: node ../scripts/version-and-commit.mjs --mode instant --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Publish to npm | |
| # Run if version was committed OR if a previous attempt already committed (for re-runs) | |
| if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' | |
| id: publish | |
| run: node ../scripts/publish-to-npm.mjs | |
| - name: Create GitHub Release | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" | |
| - name: Format GitHub release notes | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: node ../scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" | |
| # Manual Changeset PR - creates a pull request with the changeset for review | |
| changeset-pr: | |
| name: JS - Create Changeset PR | |
| if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changeset-pr' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: '24.x' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Create changeset file | |
| run: node ../scripts/create-manual-changeset.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" | |
| - name: Format changeset with Prettier | |
| run: | | |
| # Run Prettier on the changeset file to ensure it matches project style | |
| npx prettier --write ".changeset/*.md" || true | |
| echo "Formatted changeset files" | |
| - name: Create Pull Request | |
| uses: peter-evans/create-pull-request@v8 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| commit-message: 'chore(js): add changeset for manual ${{ github.event.inputs.bump_type }} release' | |
| branch: changeset-js-manual-release-${{ github.run_id }} | |
| delete-branch: true | |
| title: 'chore(js): manual ${{ github.event.inputs.bump_type }} release' | |
| body: | | |
| ## Manual Release Request (JavaScript) | |
| This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. | |
| ### Release Details | |
| - **Type:** ${{ github.event.inputs.bump_type }} | |
| - **Description:** ${{ github.event.inputs.description || 'Manual release' }} | |
| - **Triggered by:** @${{ github.actor }} | |
| ### Next Steps | |
| 1. Review the changeset in this PR | |
| 2. Merge this PR to main | |
| 3. The automated release workflow will create a version PR | |
| 4. Merge the version PR to publish to npm and create a GitHub release |