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
22 changes: 15 additions & 7 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
name: pages

# Deploys the gallery to https://engineio.github.io/ui — the surface the brand
# team reviews. Every primitive, every variant, every state, rendered by the
# Deploys the gallery — the surface the brand team reviews.
#
# With access control on, the URL is a generated *.pages.github.io subdomain
# rather than engineio.github.io/ui; `gh api repos/engineio/ui/pages --jq
# .html_url` prints the current one. Every primitive, every variant, every state, rendered by the
# package itself rather than by a copy of it.
#
# SETUP IS DELIBERATELY MANUAL, AND THIS WORKFLOW FAILS UNTIL IT IS DONE.
Expand Down Expand Up @@ -57,17 +60,22 @@ jobs:

- run: bun install --frozen-lockfile

# PAGES_BASE: a project Pages site is served from /<repo>, so the base
# path has to be baked in at build time or every internal link resolves
# off-site. Assets are emitted as relative URLs by the static adapter, so
# they survive either way — links do not.
# No PAGES_BASE. This site has ACCESS CONTROL enabled, and a private
# Pages site is served from its own generated subdomain at the root
# (https://<generated>.pages.github.io/) rather than as a project site
# under /<repo>. Setting a base of /ui here was harmless only by luck —
# the static adapter emits relative asset URLs and the gallery is a
# single page whose only links are same-page anchors — but it would break
# every internal link the moment the gallery gains a second route.
#
# If access control is ever turned off, the URL becomes
# engineio.github.io/ui and this needs `PAGES_BASE: /ui` back.
#
# DS_VERSION labels the gallery with the release it was built from, which
# is why the checkout above is unshallow: `git describe` needs the tags.
- name: Build gallery
run: bun run build:gallery
env:
PAGES_BASE: /ui
DS_VERSION: ${{ steps.tag.outputs.version }}

# No `enablement: true` — see the note at the top of this file. This step
Expand Down
25 changes: 19 additions & 6 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
# Publish target for the @engineio scope. The token is injected by CI
# (release.yml sets NODE_AUTH_TOKEN from the workflow's GITHUB_TOKEN); this
# file deliberately contains no credential.
# Maps the @engineio scope to GitHub Packages, and nothing else.
#
# There is deliberately no `//npm.pkg.github.com/:_authToken=` line here.
# Putting one in — even as a `${NODE_AUTH_TOKEN}` placeholder — breaks local
# use: npm expands the variable, it is unset outside CI, and the empty result
# OVERRIDES whatever real token the developer has in ~/.npmrc. The failure is
# `E401 unauthenticated`, which reads like a bad token rather than a missing
# environment variable.
#
# Auth comes from the right place in each context instead:
#
# CI actions/setup-node writes an authenticated ~/.npmrc from
# NODE_AUTH_TOKEN (see .github/workflows/release.yml).
# local your own ~/.npmrc:
# //npm.pkg.github.com/:_authToken=<classic PAT, read:packages>
# It must be a CLASSIC token. GitHub Packages' npm registry does not
# accept fine-grained PATs, which is the usual reason this keeps
# failing after a token has been added.
@engineio:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}

# Bun ignores lifecycle scripts of dependencies by default and we rely on
# none, but be explicit so a `bun install` here never runs third-party code.
# Be explicit that installs here never run dependency lifecycle scripts.
ignore-scripts=false
30 changes: 26 additions & 4 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,42 @@ so the shape is the same for each:
`@engineio/ui` is private and org-scoped, so every place that runs
`bun install` needs a credential. Four places do.

**Local dev.** Each developer needs a PAT with `read:packages`, in
**Local dev.** Each developer needs a **classic** PAT with `read:packages`, in
`~/.npmrc` (not the repo — never commit a token):

```
@engineio:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=ghp_xxxxxxxx
```

Add a repo-level `.npmrc` with just the registry line, so resolution works
without each person rediscovering it:
It has to be a classic token. GitHub Packages' npm registry does not accept
fine-grained PATs, and the failure looks identical to a wrong token, so this is
the usual reason access stays broken after someone has "added a token".

Add a repo-level `.npmrc` with **only** the registry line:

```
@engineio:registry=https://npm.pkg.github.com
```

Do not put an auth line in the repo `.npmrc`, not even as a
`${NODE_AUTH_TOKEN}` placeholder. npm expands it, it is unset outside CI, and
the empty result overrides the real token in `~/.npmrc` — so every developer
gets `E401 unauthenticated` while holding a perfectly good credential. (This
happened in the design system repo; that is why its `.npmrc` carries the
registry line and nothing else.) CI does not need it either: `setup-node`
writes its own authenticated user config from `NODE_AUTH_TOKEN`.

**Check access before going further**, because everything below assumes it:

```
npm view @engineio/ui --registry=https://npm.pkg.github.com
```

`E401` means no token reached the registry — check `~/.npmrc` and that no
project `.npmrc` is shadowing it. `E403 does not match expected scopes` means
the token arrived but lacks `read:packages`, or is fine-grained.

**CI.** `GITHUB_TOKEN` can read packages in the same org — add the permission to
any job that installs:

Expand Down Expand Up @@ -135,7 +156,8 @@ check it before wondering why the tokens did not land.
**Two visible changes** you should expect from the token layer, both deliberate:
Proxima Nova Semibold now maps to weight 600 rather than 500, so
`font-semibold` labels will render in the correct cut for the first time; and
`--color-destructive` becomes magenta rather than the inherited oklch red.
`--color-destructive` becomes the real danger red `#FF3B30` rather than the
oklch value inherited from shadcn, which had no brand owner.

## 3. Migrate imports, one component at a time

Expand Down