diff --git a/.github/workflows/pkg-pr-new.yml b/.github/workflows/pkg-pr-new.yml new file mode 100644 index 000000000..579a50f28 --- /dev/null +++ b/.github/workflows/pkg-pr-new.yml @@ -0,0 +1,79 @@ +name: Publish Preview Packages + +on: + pull_request: + types: [labeled, synchronize, opened, reopened] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + publish-preview: + if: "contains(github.event.pull_request.labels.*.name, 'trigger: preview') || contains(github.event.pull_request.labels.*.name, 'trigger: preview-all')" + runs-on: ubuntu-latest + env: + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + TURBO_TEAM: ${{ vars.TURBO_TEAM }} + + steps: + - uses: actions/checkout@v7 + with: + # `Select packages` diffs against the base commit, which is only + # reachable with the full history. + fetch-depth: 0 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v6 + with: + cache: pnpm + node-version: lts/* + + - name: Select packages + id: select + env: + PREVIEW_ALL: "${{ contains(github.event.pull_request.labels.*.name, 'trigger: preview-all') }}" + BASE_SHA: ${{ github.event.pull_request.base.sha }} + run: | + if [[ "$PREVIEW_ALL" == "true" ]]; then + directories=$(ls -d packages/*) + else + directories=$(git diff --name-only "$BASE_SHA...HEAD" -- packages | cut -d/ -f1-2 | sort -u) + fi + + packages=$( + echo "$directories" | while read -r directory; do + if [[ -f "$directory/package.json" ]]; then echo "./$directory"; fi + done | tr '\n' ' ' + ) + + if [[ -z "${packages// /}" ]]; then + echo "::notice::No package directories changed; nothing to publish" + fi + + echo "packages=$packages" >> "$GITHUB_OUTPUT" + + - name: Install project dependencies + if: steps.select.outputs.packages != '' + run: pnpm install + + - name: Build packages + if: steps.select.outputs.packages != '' + env: + PACKAGES: ${{ steps.select.outputs.packages }} + run: | + filters=() + for package in $PACKAGES; do + filters+=(--filter "$package") + done + pnpm build "${filters[@]}" + + # `--pnpm` is required because the workspace uses `catalog:` dependency + # specifiers, which `npm pack` cannot resolve. + - name: Publish preview packages + if: steps.select.outputs.packages != '' + env: + PACKAGES: ${{ steps.select.outputs.packages }} + run: pnpm exec pkg-pr-new publish $PACKAGES --comment=update --compact --pnpm diff --git a/README.md b/README.md index ff1eab3e8..63608d8fe 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,21 @@ pnpm build # build every package Run `pnpm test` for the test suites and `pnpm check:types`, `pnpm check:lint`, and `pnpm check:format` for the checks CI runs. +### Testing with preview packages + +Pull requests can publish preview builds to [pkg.pr.new](https://pkg.pr.new), so a change can be tried in a real project before it is merged and released to npm. + +1. Add the `trigger: preview` label to the pull request to publish the packages it changes, or `trigger: preview-all` to publish every package. A pull request that changes no package publishes nothing under `trigger: preview`. +2. Wait for the "Publish Preview Packages" workflow to finish. It posts a comment with an install URL for each published package and keeps that comment updated on every subsequent commit. +3. Install a preview build by URL: + +```sh +npm install https://pkg.pr.new/@portabletext/editor@ +pnpm add https://pkg.pr.new/@portabletext/editor@ +``` + +Regular dependencies between packages point at the preview builds from the same run, but peer dependencies keep their npm ranges. A plugin preview therefore resolves `@portabletext/editor` from npm unless the editor preview is installed alongside it, which requires the pull request to publish both. Preview builds are removed once the pull request is closed. + ## License [MIT](./LICENSE) © [Sanity.io](https://www.sanity.io/) diff --git a/package.json b/package.json index b55e204f5..1d51fc9cb 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "lint-staged": "^15.5.2", "oxfmt": "catalog:tooling", "oxlint": "catalog:tooling", + "pkg-pr-new": "catalog:tooling", "prettier": "^3.9.1", "prettier-plugin-astro": "^0.14.1", "prettier-plugin-gherkin": "^3.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index acd2f0455..b5740e06d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,6 +94,9 @@ catalogs: oxlint: specifier: ^1.79.0 version: 1.79.0 + pkg-pr-new: + specifier: ^0.0.88 + version: 0.0.88 remeda: specifier: ^2.32.0 version: 2.32.0 @@ -171,6 +174,9 @@ importers: oxlint: specifier: catalog:tooling version: 1.79.0 + pkg-pr-new: + specifier: catalog:tooling + version: 0.0.88 prettier: specifier: ^3.9.1 version: 3.9.1 @@ -7943,6 +7949,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pkg-pr-new@0.0.88: + resolution: {integrity: sha512-Xc6PMJ2gher0WZP+rtjefFk26hIb7V1PTLL30bmy1Z2vsRmSvqiss7Ag1XUdyplTGYzIlFNJp4L3vMNmK44N6g==} + hasBin: true + playwright-core@1.62.1: resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} engines: {node: '>=20'} @@ -16881,6 +16891,8 @@ snapshots: pirates@4.0.7: {} + pkg-pr-new@0.0.88: {} + playwright-core@1.62.1: {} playwright@1.62.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index df0d963cd..655939f31 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -43,6 +43,7 @@ catalogs: oxc-transform-react: ^0.145.0 oxfmt: ^0.56.0 oxlint: ^1.79.0 + pkg-pr-new: ^0.0.88 remeda: ^2.32.0 tailwind-merge: ^3.3.1 tailwindcss: ^4.1.18