Skip to content

Commit e9fe150

Browse files
authored
feat: publish the table as an npm package (#118)
* feat: build the table as an installable npm package * feat: import the table through its package name, resolved to source * ci: release through release-please and publish to npm with provenance * docs: record publishing the table as an npm package * test: pin the English default labels * feat: ship English default labels with the table * feat: route the demo through the package entry and type-check every public type * feat: prefix every design token with yart * feat: ship native light-dark() and the forced-colors remap with the table * feat: typecheck the package under nodenext resolution * ci: lint the packed tarball with publint and attw * docs: record the light-dark() floor and the prefixed, layered tokens * docs: document installing, styling and labeling the npm package * ci: pack the tarball in a job without publish rights * ci: require npm 11.5.1 to publish and time-box release-please * ci: find the first release's changelog heading, which has no brackets * ci: file dependency bumps under Dependencies and keep tooling changes out of releases * docs: note that every package export is client code under server components * fix: keep the forced-colors remap ahead of a host's token overrides * docs: say which npm releases enforce the Node 24 floor * test: catch a Sass import in the published declarations * feat: expose package.json through the exports map * fix: let a host share its own react-icons 5.x * test: install the lowest React the peer range admits in the package smoke * docs: fold the remap and react-icons decisions into their records * ci: keep dev dependency bumps out of releases and hold the react-icons floor * fix: run the benchmarks against source when the package entry pulls in styles * fix: share the build floor, source condition and class names across the build configs * test: match stylesheet imports in the published declarations by specifier * docs: complete the README snippet imports and narrow two overstated claims * docs: point the browser floor at vite.shared.ts * fix: keep the forced-colors remap ahead of a themed root override * chore: use US spelling in the stylelint color messages * docs: tighten the package docs and comments * docs: make the search example a complete component * test: typecheck the README examples against the packed tarball * test: drop toolchain guards that pin prose or duplicate loud failures (#119)
1 parent 52c64f1 commit e9fe150

70 files changed

Lines changed: 2498 additions & 877 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/dependabot.yml‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
version: 2
22

33
updates:
4-
# Package manifest at the repository root. Every entry in it is an exact pin, so the
5-
# versioning strategy is set explicitly: "increase" rewrites the pinned version in
6-
# place, while the default would be free to widen a pin into a range and quietly
7-
# reverse the convention the whole manifest follows.
4+
# Package manifest at the repository root. Every dev dependency is an exact pin, which
5+
# "increase-if-necessary" rewrites in place. The two ranges are floors for consumers:
6+
# the React peer, and react-icons, a caret so a host shares its own copy. A bump inside
7+
# the caret moves only the lockfile, so the floor stays where it is.
88
- package-ecosystem: npm
99
directory: "/"
1010
schedule:
1111
interval: weekly
1212
open-pull-requests-limit: 5
13-
versioning-strategy: increase
14-
# Generated subjects have to be Conventional Commits, same as hand written ones.
13+
versioning-strategy: increase-if-necessary
1514
# Hold new releases a few days so a hijacked publish is pulled before it is
1615
# proposed. Security updates bypass this.
1716
cooldown:
1817
default-days: 5
1918
semver-major-days: 14
19+
# Generated subjects have to be Conventional Commits, same as hand written ones.
20+
# release-please files the deps and deps-dev types under Dependencies.
2021
commit-message:
21-
prefix: "chore(deps)"
22-
prefix-development: "chore(deps-dev)"
22+
prefix: deps
23+
prefix-development: deps-dev
2324
# Grouped so a routine week is two pull requests to review rather than a dozen.
2425
# Each group is held to minor and patch only. A grouped major is unreviewable: nine
2526
# of them arrived as one pull request that could not install, because a single held
@@ -78,8 +79,9 @@ updates:
7879
open-pull-requests-limit: 5
7980
cooldown:
8081
default-days: 5
82+
# ci is a hidden changelog type, so an action bump proposes no release.
8183
commit-message:
82-
prefix: "chore(actions)"
84+
prefix: ci
8385
groups:
8486
github-actions:
8587
patterns:

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ jobs:
7878
- name: Build
7979
run: npm run build
8080

81+
# Packs the tarball a consumer installs and checks it. Nothing else resolves the
82+
# published branch of exports; dev, tests and the demo all read src/.
83+
- name: Package checks
84+
run: npm run test:package
85+
8186
# The browser download is the most expensive thing here, so it sits behind
8287
# every cheap gate. The jsdom half of the accessibility gate ran in Test.
8388
- name: Install Chromium

‎.github/workflows/npm-publish.yml‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Publishes each release to npm through OIDC trusted publishing. Inert until the
2+
# first version is published by hand and the trusted publisher is set on the package's access page.
3+
name: npm-publish
4+
5+
on:
6+
release:
7+
types: [published]
8+
9+
permissions:
10+
contents: read
11+
12+
# Two publishes of one tag would race the registry; the second waits and then fails cleanly.
13+
concurrency:
14+
group: npm-publish-${{ github.event.release.tag_name }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
# Installs and builds without publish rights, so dependency code never meets the OIDC token.
19+
pack:
20+
# release-please cuts full releases only; a hand-cut prerelease is not published.
21+
if: ${{ !github.event.release.prerelease }}
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 10
24+
permissions:
25+
contents: read
26+
outputs:
27+
node-version: ${{ steps.node.outputs.node-version }}
28+
steps:
29+
- name: Checkout
30+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
31+
with:
32+
persist-credentials: false
33+
34+
# No dependency cache: a poisoned one would end up in the published tarball.
35+
- name: Set up Node
36+
id: node
37+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
38+
with:
39+
node-version-file: .nvmrc
40+
41+
- name: Install
42+
run: npm ci
43+
44+
# prepack builds lib/ before the tarball is made.
45+
- name: Pack
46+
run: npm pack
47+
48+
- name: Upload tarball
49+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
50+
with:
51+
name: tarball
52+
path: "*.tgz"
53+
if-no-files-found: error
54+
retention-days: 1
55+
56+
publish:
57+
needs: pack
58+
runs-on: ubuntu-latest
59+
timeout-minutes: 10
60+
# The OIDC token npm exchanges for publish rights, granted to this job alone. It runs no
61+
# checkout and no install, only npm itself over the packed tarball.
62+
permissions:
63+
contents: read
64+
id-token: write
65+
steps:
66+
- name: Download tarball
67+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
68+
with:
69+
name: tarball
70+
71+
# registry-url writes the .npmrc the OIDC exchange depends on.
72+
- name: Set up Node
73+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
74+
with:
75+
node-version: ${{ needs.pack.outputs.node-version }}
76+
registry-url: https://registry.npmjs.org
77+
78+
# Trusted publishing needs npm 11.5.1 or later.
79+
- name: Check npm version
80+
run: |
81+
v="$(npm --version)"
82+
if [ "$(printf '%s\n' 11.5.1 "$v" | sort -V | head -n 1)" != 11.5.1 ]; then
83+
echo "::error::npm $v is older than 11.5.1, which trusted publishing needs."
84+
exit 1
85+
fi
86+
87+
- name: Publish
88+
run: npm publish ./*.tgz --provenance --access public
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: release-please
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
# Reopens the release PR when a push to main never fired the webhook.
8+
workflow_dispatch:
9+
10+
# The App token does the writing, so the workflow token stays read-only.
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: release-please-${{ github.ref }}
16+
cancel-in-progress: false
17+
18+
jobs:
19+
release-please:
20+
# Keeps a manual dispatch from running release-please against another ref.
21+
if: ${{ github.ref == 'refs/heads/main' }}
22+
runs-on: ubuntu-latest
23+
timeout-minutes: 10
24+
steps:
25+
- name: Generate App token
26+
id: app-token
27+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
28+
with:
29+
client-id: ${{ secrets.RELEASE_PLEASE_APP_ID }}
30+
private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }}
31+
permission-contents: write
32+
permission-issues: write
33+
permission-pull-requests: write
34+
35+
- name: Release please
36+
id: release
37+
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
38+
with:
39+
# Branch protection requires Verify, which never runs on a PR the
40+
# workflow token opens, so the App authors the release PR.
41+
token: ${{ steps.app-token.outputs.token }}
42+
config-file: release-please-config.json
43+
manifest-file: .release-please-manifest.json
44+
45+
- name: Checkout
46+
if: ${{ steps.release.outputs.release_created }}
47+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
48+
with:
49+
persist-credentials: false
50+
51+
- name: Sync the release body from the changelog
52+
if: ${{ steps.release.outputs.release_created }}
53+
env:
54+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
55+
TAG_NAME: ${{ steps.release.outputs.tag_name }}
56+
VERSION: ${{ steps.release.outputs.version }}
57+
run: |
58+
# CHANGELOG.md is the one source of the release notes. The first release has
59+
# no compare link, so its heading is "## 0.1.0 (" rather than "## [0.1.0]".
60+
# index(), not a regex, because the version carries literal dots.
61+
awk -v ver="$VERSION" '
62+
index($0, "## [" ver "]") == 1 || index($0, "## " ver " ") == 1 { found = 1; next }
63+
found && index($0, "## ") == 1 { exit }
64+
found { print }
65+
' CHANGELOG.md > release-notes.md
66+
67+
# A missing section fails the run rather than blanking a correct body.
68+
if ! grep -q '[^[:space:]]' release-notes.md; then
69+
echo "::error::No CHANGELOG.md section found for ${VERSION}; release body left as generated."
70+
exit 1
71+
fi
72+
73+
gh release edit "$TAG_NAME" --notes-file release-notes.md

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ yarn-error.log*
2424

2525
# build output
2626
/dist
27+
/lib
2728

2829
# local editor and tooling directories, never committed
2930
/.claude/

‎.prettierignore‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ src/data/films/films.json
99
# Build output.
1010
dist
1111

12+
# Library build output.
13+
lib
14+
1215
# Coverage output.
1316
coverage
1417

@@ -20,3 +23,6 @@ package-lock.json
2023
# every source file twice, and the report beside it is machine written.
2124
.stryker-tmp
2225
reports
26+
27+
# Written by release-please, whose formatting drifts on every release.
28+
CHANGELOG.md

‎.release-please-manifest.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

‎.stylelintrc.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"selector-disallowed-list": [
4949
["/:dir\\(/"],
5050
{
51-
"message": "The :dir() pseudo-class landed in Chrome 120 against this application's floor of 111, so it ships inert in the very browsers the floor exists to name: select the direction on the attribute, as [dir=\"rtl\"] .navButton svg does"
51+
"message": "Direction is selected on the attribute everywhere, so every direction rule reads one way: use [dir=\"rtl\"], as [dir=\"rtl\"] .navButton svg does"
5252
}
5353
]
5454
},
@@ -59,13 +59,13 @@
5959
"color-no-hex": [
6060
true,
6161
{
62-
"message": "Colour in a component stylesheet arrives through var(--color-*) and nowhere else, so a theme switch moves every value at once"
62+
"message": "Color in a component stylesheet arrives through var(--yart-color-*) and nowhere else, so a theme switch moves every value at once"
6363
}
6464
],
6565
"color-named": [
6666
"never",
6767
{
68-
"message": "Colour in a component stylesheet arrives through var(--color-*) and nowhere else; a keyword is as fixed as a hex and flips with no theme"
68+
"message": "Color in a component stylesheet arrives through var(--yart-color-*) and nowhere else; a keyword is as fixed as a hex and flips with no theme"
6969
}
7070
],
7171
"declaration-property-unit-allowed-list": [
@@ -75,7 +75,7 @@
7575
"/^(gap|row-gap|column-gap|font-size)$/": ["rem"]
7676
},
7777
{
78-
"message": "Spacing and type are authored in rem through a token, so the layout follows the reader's browser font-size setting: reach for var(--space-*) or var(--font-size-*)"
78+
"message": "Spacing and type are authored in rem through a token, so the layout follows the reader's browser font-size setting: reach for var(--yart-space-*) or var(--yart-font-size-*)"
7979
}
8080
],
8181
"function-disallowed-list": [
@@ -93,7 +93,7 @@
9393
"color"
9494
],
9595
{
96-
"message": "Colour in a component stylesheet arrives through var(--color-*) and nowhere else; a functional notation is as fixed as a hex and flips with no theme"
96+
"message": "Color in a component stylesheet arrives through var(--yart-color-*) and nowhere else; a functional notation is as fixed as a hex and flips with no theme"
9797
}
9898
]
9999
}

0 commit comments

Comments
 (0)