diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml new file mode 100644 index 0000000..9ec9c64 --- /dev/null +++ b/.github/workflows/pkg-pr-new.yml @@ -0,0 +1,107 @@ +name: Publish Any Commit + +# Publishes preview packages via pkg.pr.new. Gated so untrusted/fork PRs can't +# trigger a build+publish just by opening a PR - only: +# - push: pushing to a branch already requires repo write access +# - pull_request: only when opened/updated by an author with write access +# (e.g. a maintainer's own branch) +# - pull_request_review: for everyone else, once a maintainer approves the PR +# tags/releases are excluded so version-tag pushes don't trigger a duplicate publish. +on: + push: + branches: ['**'] + tags: ['!**'] + pull_request: + types: [opened, synchronize, reopened] + pull_request_review: + types: [submitted] + +permissions: {} + +jobs: + check: + name: Check permissions + if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event.review.state == 'approved' + runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + approved: ${{ steps.check.outputs.approved }} + steps: + - name: Push events are already gated by branch write access + if: github.event_name == 'push' + id: push-approved + run: echo "approved=true" >> "$GITHUB_OUTPUT" + - name: Check PR author permission + uses: actions-cool/check-user-permission@c21884f3dda18dafc2f8b402fe807ccc9ec1aa5e # v2.4.0 + if: github.event_name == 'pull_request' + id: checkAuthor + with: + username: ${{ github.event.pull_request.user.login }} + require: write + - name: Check reviewer permission + uses: actions-cool/check-user-permission@c21884f3dda18dafc2f8b402fe807ccc9ec1aa5e # v2.4.0 + if: github.event_name == 'pull_request_review' + id: checkPermissions + with: + require: write + - name: Combine results + id: check + # Exactly one of the three steps above runs per trigger, the others are + # skipped and their outputs are empty strings, so this picks whichever ran. + run: echo "approved=${{ steps.push-approved.outputs.approved || steps.checkAuthor.outputs.require-result || steps.checkPermissions.outputs.require-result }}" >> "$GITHUB_OUTPUT" + + build: + name: Build + needs: check + if: needs.check.outputs.approved == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + # For PR-related events this checks out the PR head commit (not a merge + # commit with main), matching the sha pkg-pr-new publishes/comments with. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm run build + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dist + path: dist/ + + publish: + name: Publish + needs: build + runs-on: ubuntu-latest + # Separate job from `build` so only this one - the one that actually needs + # to comment on the PR - carries pull-requests: write. + permissions: + pull-requests: write + steps: + # For PR-related events this checks out the PR head commit (not a merge + # commit with main), matching the sha pkg-pr-new publishes/comments with. + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile --ignore-scripts + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: dist + path: dist/ + # --commentWithSha uses the commit sha in install URLs/comments instead + # of a PR number, and pnpm exec (not npx/dlx) runs the pinned devDependency. + - run: pnpm exec pkg-pr-new publish --commentWithSha diff --git a/package.json b/package.json index 19f85aa..605b97c 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "knip": "^6.18.0", "oxfmt": "^0.60.0", "oxlint": "^1.71.0", + "pkg-pr-new": "^0.0.88", "postcss": "^8.5.15", "publint": "^0.3.21", "tailwindcss": "^2.2.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 029b3c9..9399cfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: oxlint: specifier: ^1.71.0 version: 1.75.0 + pkg-pr-new: + specifier: ^0.0.88 + version: 0.0.88 postcss: specifier: ^8.5.15 version: 8.5.23 @@ -1723,6 +1726,10 @@ packages: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} + pkg-pr-new@0.0.88: + resolution: {integrity: sha512-Xc6PMJ2gher0WZP+rtjefFk26hIb7V1PTLL30bmy1Z2vsRmSvqiss7Ag1XUdyplTGYzIlFNJp4L3vMNmK44N6g==} + hasBin: true + postcss-js@3.0.3: resolution: {integrity: sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==} engines: {node: '>=10.0'} @@ -3368,6 +3375,8 @@ snapshots: picomatch@4.0.5: {} + pkg-pr-new@0.0.88: {} + postcss-js@3.0.3: dependencies: camelcase-css: 2.0.1