chore: publish [next] pre-release #180
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
| # https://help.github.com/en/categories/automating-your-workflow-with-github-actions | |
| # See: https://github.com/JulianCataldo/gh-actions | |
| # For matrix setup: | |
| # https://github.com/withastro/astro/blob/main/.github/workflows/ci.yml | |
| # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow | |
| name: CI / Release | |
| on: | |
| workflow_dispatch: | |
| push: | |
| paths-ignore: | |
| - .github/** | |
| - '!.github/workflows/release.yaml' | |
| - '**/*.md' | |
| branches: | |
| - '([0-9])?(.{+([0-9]),x}).x' | |
| - main | |
| - next | |
| - next-major | |
| - alpha | |
| - beta | |
| - 'feat/*' | |
| - 'fix/*' | |
| # - to-integrate | |
| # - to-integrate-next | |
| permissions: | |
| contents: read # for checkout | |
| # TODO: Trusted Publishers | |
| # id-token: write # Required for OIDC | |
| jobs: | |
| release: | |
| name: CI / Release | |
| timeout-minutes: 30 | |
| permissions: | |
| contents: write # to be able to publish a GitHub release | |
| issues: write # to be able to comment on released issues | |
| pull-requests: write # to be able to comment on released pull requests | |
| id-token: write # to enable use of OIDC for npm provenance | |
| runs-on: ubuntu-latest | |
| # TODO: | |
| # runs-on: ${{ matrix.os }} | |
| # timeout-minutes: 25 | |
| # # needs: build | |
| # strategy: | |
| # matrix: | |
| # OS: [ubuntu-latest] | |
| # NODE_VERSION: [18, 20] | |
| # include: | |
| # - os: macos-14 | |
| # NODE_VERSION: 18 | |
| # - os: windows-latest | |
| # NODE_VERSION: 18 | |
| # fail-fast: false | |
| # env: | |
| # NODE_VERSION: ${{ matrix.NODE_VERSION }} | |
| steps: | |
| # MARK: Setup GH Action | |
| - name: 'Harden Runner' | |
| uses: 'step-security/harden-runner@a90bcbc6539c36a85cdfeb73f7e2f433735f215b' # v2.15.0 | |
| with: | |
| egress-policy: 'audit' | |
| - name: Git checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| # - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* | |
| # persist-credentials: false | |
| # env: | |
| # GIT_COMMITTER_NAME: "GitHub Actions Shell" | |
| # GIT_AUTHOR_NAME: "GitHub Actions Shell" | |
| # EMAIL: "github-actions[bot]@users.noreply.github.com" | |
| # MARK: Setup Node env. | |
| - name: Setup PNPM | |
| uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4.2.0 | |
| - name: Use Node.js 24 | |
| uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 | |
| with: | |
| registry-url: 'https://registry.npmjs.org' | |
| node-version: 24 | |
| cache: pnpm | |
| - name: Install packages | |
| shell: bash | |
| run: pnpm install --frozen-lockfile | |
| # TODO: More tests | |
| # - name: Syncpack Lint | |
| # shell: bash | |
| # run: node --run syncpack:lint | |
| # NOTE: Audit is for prod only because a lot of root packages (like lerna etc.) | |
| # are used old packages with intricate dependency trees, and they are | |
| # never shipped to the user. But that's not 100% optimal, as devDeps could | |
| # provoke some sec issues, too? A middleground is better than nothing and | |
| # regularly blocked releases for obscure root mono-repo tooling deps. | |
| - name: | |
| 'Verify the integrity of provenance attestations and registry | |
| signatures for installed [prod] dependencies' | |
| run: 'node --run audit' | |
| # MARK: Lint/Checks pre-build | |
| - name: Lint last commit — Commitlint | |
| shell: bash | |
| run: node --run lint:commit | |
| # - name: Lint CSS — Stylelint | |
| # shell: bash | |
| # run: node --run lint:css | |
| - name: Check all formatting — Prettier | |
| shell: bash | |
| run: node --run format | |
| # MARK: Build packages | |
| - name: Setup Turbo cache | |
| uses: dtinth/setup-github-actions-caching-for-turbo@cc723b4600e40a6b8815b65701d8614b91e2669e # v1.3.0 | |
| - name: Build libraries | |
| shell: bash | |
| run: node --run build:libs | |
| - name: Generate Typedoc | |
| shell: bash | |
| run: node --run docs:generate | |
| - name: Build docs website | |
| shell: bash | |
| env: | |
| VITE_DOCS_IS_NEXT: | |
| ${{ github.ref == 'refs/heads/next' && 'true' || 'false' }} | |
| run: node --run build:docs | |
| # MARK: Lint/Checks post-build | |
| - name: Cache ESLint results | |
| uses: actions/cache@v4 | |
| with: | |
| path: .eslintcache | |
| key: eslint-${{ hashFiles('pnpm-lock.yaml', 'eslint.config.js') }} | |
| - name: Lint JS/TS — ESLint | |
| shell: bash | |
| run: node --run lint:es | |
| # MARK: Tests | |
| - name: Tests — Units | |
| shell: bash | |
| run: node --run test:unit | |
| # MARK: Playwright — single install for all test suites | |
| # All packages use the same @playwright/test version (catalog:). | |
| # Browsers land in ~/.cache/ms-playwright regardless of working directory. | |
| - name: Cache Playwright browsers | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ hashFiles('pnpm-lock.yaml') }} | |
| - name: Install Playwright browsers | |
| shell: bash | |
| run: npx playwright install --with-deps chromium | |
| working-directory: starter-projects | |
| - name: Tests — Integration | |
| shell: bash | |
| run: node --run test:integration | |
| # MARK: Starter Projects — Smoke Test (pre-publish, workspace-linked) | |
| - name: 'Regenerate starter-project templates' | |
| run: pnpm --prefix starter-projects run generate | |
| - name: Tests — Starter projects smoke | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' | |
| shell: bash | |
| run: pnpm --prefix starter-projects run test:smoke | |
| - name: Tests — Docs smoke | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' | |
| shell: bash | |
| run: pnpm --prefix docs/website run test:smoke | |
| # MARK: Publish packages | |
| # NOTE: Auth is configured by setup-node's registry-url above. | |
| # The NPM_TOKEN secret MUST be a Granular Access Token (or classic Automation | |
| # token) to bypass 2FA/OTP requirements in CI. | |
| - name: Git user configuration | |
| run: | | |
| git config --global user.name "${{ github.actor }}" | |
| git config --global user.email "${{ github.actor }}@users.noreply.github.com" | |
| # MARK: [MAIN] | |
| # --force-git-tag: a prior interrupted CI run may have left orphan tags | |
| # (e.g. @gracile/client@0.5.0) that collide with graduation. | |
| - name: 'Lerna publish [main]' | |
| if: github.ref == 'refs/heads/main' | |
| # https://github.com/lerna/lerna/issues/2532 | |
| id: graduateRelease | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' | |
| NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}' | |
| NPM_CONFIG_PROVENANCE: 'true' | |
| run: | | |
| pnpm lerna publish --message 'chore: publish [main] release [skip ci]' --create-release=github --conventional-graduate --force-git-tag --yes | |
| # Fallback for https://github.com/lerna/lerna/issues/2532 (newly introduced | |
| # packages that fail --conventional-graduate). Fail explicitly if neither | |
| # the graduate nor the fallback actually published anything. | |
| - name: 'Lerna publish [main] — fallback (non-graduate)' | |
| if: | |
| ${{ always() && github.ref == 'refs/heads/main' && | |
| steps.graduateRelease.outcome == 'failure' }} | |
| env: | |
| GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' | |
| NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}' | |
| NPM_CONFIG_PROVENANCE: 'true' | |
| run: | | |
| echo "Graduate publish failed — attempting recovery" | |
| git stash || true | |
| # Recovery 1: from-package (handles Sigstore/Rekor TLOG_CREATE_ENTRY_ERROR 409) | |
| # Queries npm directly and publishes any package versions not yet in the registry, | |
| # regardless of git tags — safe to run even if some packages are already published. | |
| echo "==> from-package: recovering partial publish" | |
| FROM_PKG_OUT=$(pnpm lerna publish from-package --yes 2>&1) | |
| FROM_PKG_EXIT=$? | |
| echo "$FROM_PKG_OUT" | |
| # Recovery 2: non-graduate lerna publish (handles lerna#2532: newly introduced | |
| # packages that have no pre-release version to graduate from). | |
| echo "==> non-graduate: publishing new or changed packages" | |
| OUTPUT=$(pnpm lerna publish --message 'chore: publish [main] release [skip ci]' --create-release=github --yes 2>&1) || true | |
| echo "$OUTPUT" | |
| # Fail explicitly only when BOTH approaches published nothing useful. | |
| if [ "$FROM_PKG_EXIT" -ne 0 ] && echo "$OUTPUT" | grep -q 'No changed packages to publish'; then | |
| echo "::error::Neither from-package nor conventional fallback published anything" | |
| exit 1 | |
| fi | |
| # MARK: [NEXT] | |
| - name: 'Lerna publish [next]' | |
| if: github.ref == 'refs/heads/next' | |
| env: | |
| GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' | |
| NODE_AUTH_TOKEN: '${{ secrets.NPM_TOKEN }}' | |
| NPM_CONFIG_PROVENANCE: 'true' | |
| run: | | |
| pnpm lerna publish --message 'chore: publish [next] pre-release' --conventional-prerelease --pre-dist-tag=next --preid=next --yes | |
| # MARK: Commit regenerated starter-project templates | |
| # Templates were generated pre-publish (rsync + README) and keep | |
| # workspace:/catalog: refs — create-gracile resolves at scaffold time. | |
| - name: 'Commit starter-project templates' | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/next' | |
| run: | | |
| git add starter-projects/templates | |
| git commit -m 'chore: regenerate starter-project templates [skip ci]' || echo 'No template changes to commit' | |
| git push | |
| - name: Merge back main into next | |
| if: github.ref == 'refs/heads/main' | |
| continue-on-error: true | |
| env: | |
| GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}' | |
| run: | | |
| git checkout next | |
| git merge main --no-ff -m 'chore: auto-merge main into next [skip ci]' | |
| git push | |
| - name: 'Deploy docs website [main → production]' | |
| if: github.ref == 'refs/heads/main' | |
| env: | |
| NETLIFY_AUTH_TOKEN: '${{ secrets.NETLIFY_AUTH_TOKEN }}' | |
| NETLIFY_SITE_ID: '${{ secrets.NETLIFY_SITE_ID }}' | |
| shell: bash | |
| run: pnpm --prefix ./docs/website deploy:main | |
| - name: 'Deploy docs website [next → preview]' | |
| if: github.ref == 'refs/heads/next' | |
| env: | |
| NETLIFY_AUTH_TOKEN: '${{ secrets.NETLIFY_AUTH_TOKEN }}' | |
| NETLIFY_SITE_ID: '${{ secrets.NETLIFY_SITE_ID }}' | |
| shell: bash | |
| run: pnpm --prefix ./docs/website deploy:next |