Shared Libraries — @progressivesurface/* SDK packages

How to consume PSI’s shared frontend packages, and the policy for versioning them. These are the Layer-1 building blocks of the PSI Web App Platform. Source repo: psi-platform on GHE.


What these are

A set of small, single-purpose npm packages published to GHE Packages (the private npm registry on our GitHub Enterprise instance) under the @progressivesurface scope. They replace per-app duplication: instead of every web app vendoring ps.css and re-deriving its MSAL config, apps install the package.

Why @progressivesurface and not @psi? GHE/GitHub Packages require the npm scope to equal the owning org login, and the org is ProgressiveSurface. There is no psi org, so @psi/* cannot be published there. See psi-platform/docs/decisions/0002-package-scope.md.

PackageOwnsStatus
@progressivesurface/uiDesign-system tokens (ps.css), React component wrappers (PSIButton, PSITile, PSIKpi), typed icon registry (PSIcon)0.1.0
@progressivesurface/authMSAL/Entra bootstrap (createMsalConfig, initializeMsal), useAuth, <AuthGuard>0.1.0
@progressivesurface/accessFeature flags + channel/RBAC gates0.1.0
@progressivesurface/broadcastCross-tab / cross-app broadcast messaging SDK0.2.0
@progressivesurface/dataTyped clients routed through the Portal gatewayplanned
@progressivesurface/{telemetry,layout,dev,cli}App Insights, PSIPortalShell, Vite plugin, scaffoldingplanned

Owner: Adam Devereaux (adevereaux@progressivesurface.com). Breaking (major) changes require owner sign-off.


Consuming the packages

These are private packages — every consumer authenticates; there is no anonymous read. The setup is identical for every repo and every package, and you do it once per repo (committed) and once per developer machine. A single token covers all current and future @progressivesurface/* packages across all repos, because auth is keyed by the registry host, not per package.

1. Per repo — commit an .npmrc (no secret)

@progressivesurface:registry=https://npm.progressivesurface.ghe.com
//npm.progressivesurface.ghe.com/:_authToken=${NODE_AUTH_TOKEN}

The token is supplied by the NODE_AUTH_TOKEN env var at install time — this file is safe to commit.

2. CI — zero developer tokens

The workflow’s built-in GITHUB_TOKEN is accepted by the registry. Grant it package read and pass it as NODE_AUTH_TOKEN:

permissions:
  contents: read
  packages: read          # lets npm read @progressivesurface/* from GHE Packages
jobs:
  build:
    steps:
      - uses: actions/checkout@v4
      - run: npm ci         # or pnpm install
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

No PAT, no per-repo secret. (PSI Portal’s deploy.yml is the reference.)

⚠️ Critical caveat — the packages (and their source repo) MUST be internal. A repo’s job GITHUB_TOKEN is scoped to that repo only. These npm packages are repository-scoped — they inherit access from their source repo (psi-platform). If psi-platform (and therefore the packages) is private, a different repo’s GITHUB_TOKEN cannot read themnpm ci fails with 403 ... permission_denied: read_package, even with packages: read. This silently broke every psi-portal deploy for ~4 weeks (2026-06-01 → 06-29); the earlier “fix” PRs were only verified locally, where a classic PAT (with repo scope) masked the gap.

The fix: set the psi-platform repo → internal, then each package → internal (a package can’t go internal while its source repo is private). With the packages internal, any org repo’s GITHUB_TOKEN reads them and the snippet above works as written.

New packages: there is no API to change package visibility (REST PATCH 404s; no GraphQL mutation) — it’s a UI-only toggle. So don’t script it; instead publish from the now-internal psi-platform and new packages inherit internal from birth. Confirm on first publish.

3. Local dev — once per machine

⚠️ gh auth token does NOT work here. The GitHub Packages npm registry rejects the gh CLI’s OAuth token (403 … does not match expected scopes), even when it has read:packages. You need a classic PAT.

  1. Create the token in your personal settings — not the org’s Settings tab (looking at the org is why people can’t find it). Go directly to https://progressivesurface.ghe.com/settings/tokens (or avatar → Settings → Developer settings) → Tokens (classic) → Generate. Then:
    • Select both repo and read:packages — the packages are private and tied to the private psi-platform repo, so read:packages alone returns 403.
    • Configure SSO → Authorize → ProgressiveSurface. This org enforces SAML SSO; an un-authorized token 403s even with the right scopes. (Most-missed step.)
  2. Store it once. The easiest way is the helper script in the psi-platform repo, which writes both the scope→registry mapping and the auth token to your user ~/.npmrc (idempotent, UTF-8 no BOM) and verifies read access:
    ./scripts/setup-npm-auth.ps1        # PowerShell  (or setup-npm-auth.sh)
    Or do it by hand — this covers every @progressivesurface/* package in every repo, forever:
    npm config set @progressivesurface:registry "https://npm.progressivesurface.ghe.com" --location=user
    npm config set //npm.progressivesurface.ghe.com/:_authToken "<ghp_token>" --location=user
  3. npm install / pnpm install now resolves the packages.

See the step-by-step guide in psi-platform/docs/guides/installing-psi-packages.md.

4. Use

// app entry — load the design system once (stop vendoring ps.css)
import '@progressivesurface/ui/css';
 
import { PSIButton, PSIKpi } from '@progressivesurface/ui';
import { createMsalConfig, initializeMsal, AuthGuard, useAuth } from '@progressivesurface/auth';

@progressivesurface/auth’s defaults encode the Web App Compliance Standard: redirect flow, localStorage token cache, silent-acquire-then-redirect recovery, and a 5-minute ID-token refresh guard. See each package’s README.md for the full API.

New apps created with psi-cli new (Phase 5) will scaffold the .npmrc and the CI block automatically — this manual setup is only for existing repos.


Versioning policy

  • SemVer, managed with Changesets. Every change adds a changeset (pnpm changeset) describing the bump.
  • Patch — bug fixes, no API change. Adopt freely.
  • Minor — additive (new component, new option). Backward compatible.
  • Major — breaking. Requires owner sign-off and a coordinated migration note; consuming apps upgrade deliberately, not automatically.
  • Apps should pin a caret range (^0.1.0) so they get patches/minors but not majors.
  • @progressivesurface/ui tracks the psi-design-system repo: when that releases a new ps.css, the package re-vendors it and bumps accordingly (currently mirrors design-system v1.2.1).

Publishing (maintainers)

pnpm changeset            # describe the change
pnpm version-packages     # changeset version → bumps + CHANGELOG
# merge to main → GH Actions publishes to GHE Packages and tags @progressivesurface/<pkg>@x.y.z

The publish workflow is identity-based (packages: write on the job GITHUB_TOKEN); no personal token is used to publish.


Migration guidance

Adopt opportunistically — the next time an app touches CSS or auth, swap to the package and delete the duplicated code. PSI Portal is the reference migration (it replaced its vendored ps.css and ~250 lines of MSAL logic). App-specific configuration (Graph scopes, Entra group IDs) stays in the app; only the duplicated logic moves to the package.