Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 77 additions & 27 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
# To publish a new version, run these commands:
# vp run version <prerelease|patch|minor|major|x.y.z>
# git push --atomic --no-follow-tags origin main "v$(npm pkg get version)"
#
# The `version` task in `vite.config.ts` switches to main and matches it and the local tags to GitHub's,
# then runs `npm version` with the arguments, which commits the new version and tags it.
# Naming the tag makes the push fail if the tag is missing, and `--no-follow-tags` leaves other
# local tags out, even with `push.followTags` set. `--atomic` pushes main and the tag together, or neither.
#
# Until it's pushed, any fetch that prunes tags, like an editor's auto-fetch, deletes the new tag.
# If the push can't find it, recreate it and push again:
# git tag --annotate "v$(npm pkg get version)" --message "$(npm pkg get version)"
#
# The new tag runs this workflow, which checks the tagged commit,
# publishes it to npm, and creates the GitHub release.

name: Publish

on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g., 0.1.1, 0.2.0, 1.0.0)'
required: true
type: string
push:
tags:
- 'v*'

permissions: {}

Expand All @@ -15,55 +28,92 @@ cache-mode: none
env:
FORCE_COLOR: 1

# never run two publishes at once, and never cancel one midway
# One release at a time, never canceled midway
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

jobs:
publish:
if: github.ref == 'refs/heads/main'
# Dependencies only run in this read-only job, so they cannot mint an npm publishing token
build:
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: write # push the version tag and create the GitHub release
id-token: write # npm trusted publishing

env:
VERSION: ${{ inputs.version }}
contents: read # check out the repository

steps:
# credentials are persisted to push the version tag
# the full history includes `main`, to check that the tag is on it
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- uses: voidzero-dev/setup-vp@68f39c78aaf39be468cabd2afb09c162574c84af # v1.21.2
with:
run-install: false
registry-url: 'https://registry.npmjs.org'

- run: vp install --frozen-lockfile
- name: Check that the tag is on main
run: git merge-base --is-ancestor HEAD origin/main

# before `vp run ci`, so `vp pack` prefixes classes with the new version
- name: Update version
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR_ID+$GITHUB_ACTOR@users.noreply.github.com"
npm version "$VERSION"
- name: Check that the tag matches the package version
run: test "$GITHUB_REF_NAME" = "v$(npm pkg get version)"

- run: vp install --frozen-lockfile

- run: vp run ci
timeout-minutes: 6

- name: Upload build
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: lib
path: lib/
if-no-files-found: error
retention-days: 1

publish:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
contents: read # check out the repository
id-token: write # npm trusted publishing

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: voidzero-dev/setup-vp@68f39c78aaf39be468cabd2afb09c162574c84af # v1.21.2
with:
run-install: false
registry-url: 'https://registry.npmjs.org'

- name: Download build
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: lib
path: lib

# https://docs.npmjs.com/trusted-publishers
# `--ignore-scripts` skips the `prepare` script, so no project code runs either
- name: Publish to npm
run: npm publish --tag latest
run: npm publish --tag latest --ignore-scripts

- name: Push tag
run: git push origin "v$VERSION"
# A separate job, so the job that can publish to npm has no write token,
# and a failed release can be re-run once the version is already on npm
release:
needs: publish
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
contents: write # create the GitHub release

steps:
- name: Create GitHub Release
run: gh release create "v$VERSION" --title "v$VERSION" --generate-notes
run: gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes --verify-tag
env:
GH_TOKEN: ${{ github.token }}
97 changes: 82 additions & 15 deletions .github/workflows/update-screenshots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,116 @@ cache-mode: none

env:
FORCE_COLOR: 1
# as a git pathspec and an upload-artifact glob, it matches screenshots at any depth,
# and only images, so the job that pushes can only commit images
SCREENSHOTS: 'test/**/screenshots/**/*.png'

# dependencies only run in the read-only `screenshots` job,
# the jobs with write permissions only run gh and git
jobs:
update-screenshots:
remove-label:
if: github.event.label.name == 'Update Screenshots'
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
pull-requests: write # remove the label

steps:
- name: Remove label
run: gh pr edit "$PR_URL" --remove-label 'Update Screenshots'
env:
GH_TOKEN: ${{ github.token }}
PR_URL: ${{ github.event.pull_request.html_url }}

screenshots:
if: ${{ github.event.label.name == 'Update Screenshots' || github.event_name == 'workflow_dispatch' }}
runs-on: ubuntu-latest
timeout-minutes: 10

permissions:
contents: write # push the new screenshots
pull-requests: write # remove the label
contents: read # check out the repository

# one at a time per branch
concurrency:
group: update-screenshots@${{ github.head_ref || github.ref }}
cancel-in-progress: true

outputs:
commit: ${{ steps.checkout.outputs.commit }}
changed: ${{ steps.changes.outputs.changed }}

steps:
# credentials are persisted to push the new screenshots
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.head_ref }}
persist-credentials: false

- uses: voidzero-dev/setup-vp@68f39c78aaf39be468cabd2afb09c162574c84af # v1.21.2
with:
run-install: false

- name: Remove label
if: github.event_name == 'pull_request'
run: gh pr edit --remove-label 'Update Screenshots'
env:
GH_TOKEN: ${{ github.token }}

- run: vp install --frozen-lockfile

- run: rm -r test/**/screenshots/**
# so that removed screenshots are detected
- name: Delete screenshots
run: git ls-files -z -- "$SCREENSHOTS" | xargs -0 --no-run-if-empty rm

- run: vp run test test/visual --update
timeout-minutes: 4

- name: Check for changes
id: changes
run: |
if [ -n "$(git status --porcelain -- "$SCREENSHOTS")" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
fi

- name: Upload screenshots
if: steps.changes.outputs.changed == 'true'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: screenshots
path: ${{ env.SCREENSHOTS }}
if-no-files-found: error
retention-days: 1

push:
needs: screenshots
if: needs.screenshots.outputs.changed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5

permissions:
contents: write # push the new screenshots

steps:
# the commit the screenshots were taken on, so the push fails if the branch has moved since
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.screenshots.outputs.commit }}
persist-credentials: false

# so that removed screenshots are deleted too
- name: Delete screenshots
run: git ls-files -z -- "$SCREENSHOTS" | xargs -0 --no-run-if-empty rm

# the artifact keeps the paths after `test/`, where the upload's wildcard starts
- name: Download screenshots
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: screenshots
path: test

- name: Push new screenshots
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR_ID+$GITHUB_ACTOR@users.noreply.github.com"
git add "test/**/screenshots/**"
git diff-index --quiet HEAD || git commit -m "Update screenshots"
git push
git add -- "$SCREENSHOTS"
git commit -m "Update screenshots"
gh auth setup-git
git push origin "HEAD:refs/heads/$BRANCH"
env:
BRANCH: ${{ github.head_ref || github.ref_name }}
GH_TOKEN: ${{ github.token }}
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ website/ # demo site (Vite + TanStack Router)
- **Dual classnames** — components apply both a semantic class (`rdg-cell`) and a generated hash. Preserve both.
- **Light/dark mode** — handled via CSS `light-dark()` + `color-scheme`, not JS.
- **Accessibility first** — ARIA attributes (e.g. `aria-colindex`, `aria-rowindex`, `aria-selected`, roles) are required. Tests query by role.
- **Build** — `vp pack` bundles the library to `lib/`; `ecij` plugin prefixes classes with `rdg-{version}-` (dots→dashes) to avoid cross-version conflicts.
- **Build** — `vp pack` bundles the library to `lib/`, then lints the packed package with publint; `ecij` plugin prefixes classes with `rdg-{version}-` (dots→dashes) to avoid cross-version conflicts.
- **Workflows** — pin every action to a full commit SHA with its version in a comment (Dependabot updates both), pass expressions to `run` steps through `env` instead of inlining `${{ }}`, grant each job only the `permissions` it uses, and check out with `persist-credentials: false`. Jobs that install dependencies only get read permissions: publishing, pushing, and editing PRs happen in separate jobs that receive build output as artifacts and never install the project's dependencies.
- **Releases** — the header of `.github/workflows/publish.yml` explains how to publish. Its read-only `build` job hands `lib/` to the `publish` job, so the project's dependencies never run with the `id-token: write` permission that npm's trusted publishing relies on, and a separate `release` job creates the GitHub release.

## Testing

Expand Down
69 changes: 69 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading