diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2330cb..830127f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,18 +34,6 @@ jobs: - run: yarn install - run: yarn lint:check - typecheck: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - run: corepack enable - - uses: actions/setup-node@v6 - with: - node-version-file: .node-version - cache: yarn - - run: yarn install - - run: yarn typecheck - test-matrix: runs-on: ubuntu-latest strategy: @@ -81,14 +69,8 @@ jobs: cache: yarn - run: yarn install - run: yarn build - - uses: actions/upload-artifact@v7 - with: - name: dist - path: dist - retention-days: 1 publint: - needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -98,10 +80,5 @@ jobs: node-version-file: .node-version cache: yarn - run: yarn install - # publint resolves the package `exports` against the packed tarball, which - # references ./dist/*. Reuse the build job's output instead of rebuilding. - - uses: actions/download-artifact@v8 - with: - name: dist - path: dist + - run: yarn build - run: yarn publint diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3a585e..4feb337 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,15 +23,19 @@ jobs: cache: yarn - run: yarn install - run: yarn lint:check - - run: yarn typecheck - run: yarn build - run: yarn publint - - name: Verify tag matches package.json + - name: Verify tag matches package versions run: | - PKG_VERSION=$(node -p "require('./package.json').version") TAG_VERSION="${GITHUB_REF_NAME#v}" - if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then - echo "Version mismatch: package.json=$PKG_VERSION, tag=$TAG_VERSION" - exit 1 - fi - - run: npm publish --provenance --access public + for pkg in common server; do + PKG_VERSION=$(node -p "require('./packages/${pkg}/package.json').version") + if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then + echo "Version mismatch in ${pkg}: package.json=$PKG_VERSION, tag=$TAG_VERSION" + exit 1 + fi + done + - name: Publish @proof.com/proof-vc-common + run: npm publish -w packages/common --provenance --access public --no-package-lock + - name: Publish @proof.com/proof-vc-server + run: npm publish -w packages/server --provenance --access public --no-package-lock diff --git a/.gitignore b/.gitignore index 80184fa..33d54c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build artifacts dist/ +*.tsbuildinfo # Dependency artifacts node_modules/ diff --git a/.prettierignore b/.prettierignore index 60239af..1aa9374 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ .yarn/ +**/dist/ diff --git a/CLAUDE.md b/CLAUDE.md index 177cbf6..f99c352 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,64 +1,85 @@ -# Proof VC Common - AI Assistant Guide +# Proof VC - AI Assistant Guide -Dual-target ESM TypeScript library `@proof.com/proof-vc-common`. Browser entry exposes `init` + `getAuthorizationRequestURL` (zero runtime deps). Node entry adds `verify` + `verifyVPToken` (SD-JWT verification, X.509 chain validation against an embedded trust root). +A Yarn 4 monorepo publishing two ESM TypeScript packages: + +- **`@proof.com/proof-vc-common`** (`packages/common`) — public/frontend, runs in the browser **and** Node, **zero runtime deps**. Builds OID4VP Authorization Request URLs (`createClient` / `buildAuthorizationUrl`) and reads the `vp_token` from the redirect (`parseAuthorizationResponse`). No secrets, no `nonce` generation, no transaction data. +- **`@proof.com/proof-vc-server`** (`packages/server`) — privileged/backend, **Node only**. Adds `verify` / `verifyVPToken` (SD-JWT-VC verification, X.509 chain validation against an embedded trust root), Pushed Authorization Requests, transaction data, and DC API requests. Depends on `proof-vc-common` and **re-exports it**, so backend integrators need one import. Yarn links it locally via transparent workspaces. + +`server` reuses `common`'s URL/param builders via the `@proof.com/proof-vc-common/internal` subpath export (server-only; not public frontend surface). ## Hard Rules -1. **No `node:*`, `@sd-jwt/*`, `@owf/*`, or other runtime imports from anything reachable from `src/index.browser.ts`.** Type-only imports (`import type` / `export type *`) are safe — `verbatimModuleSyntax: true` erases them. +1. **`packages/common` must stay pure.** No `node:*`, `@sd-jwt/*`, `@owf/*`, or other runtime imports from anything reachable from `packages/common/src/index.ts` or `packages/common/src/internal.ts`. Type-only imports (`import type` / `export type *`) are safe — `verbatimModuleSyntax: true` erases them. Verify after build (see Package Boundaries). 2. **Prompt before publishing.** Never bump version, push tags, create a Release, or trigger the publish workflow without explicit confirmation. Publishes are permanent. 3. **Run `yarn check-all` before any commit or push.** Composes format, lint, typecheck, publint. 4. **Keep `yarn publint` on `--pack npm`.** `--pack auto` picks yarn-1 mode and reports false-positive errors. 5. **Keep `engines.node` at `>=22.0.0` and keep the CI `test-matrix` covering that floor.** Node 22 is the oldest maintained LTS (Node 20 is EOL; `@sd-jwt/*` needs 20+). `>=22` is a lower bound, so it still allows 24 and newer. The floor is checked at runtime by the `test-matrix` job (`yarn test` on Node 22 and 24); `@types/node` tracks the dev runtime (24, from `.node-version`), not the floor. Invariant: the `test-matrix` low entry must equal the `engines.node` floor, so raise the floor by bumping both together. If you drop the matrix, pin `@types/node` to the floor major so typecheck guards it instead. 6. **Never use `eslint-disable` as a workaround.** If a lint rule fires, fix the underlying code or surface the rule to the user for a config decision — do not silence it inline. Same applies to `@ts-ignore` / `@ts-expect-error` and other suppression comments. -## Browser Graph +## Package Boundaries + +Versioning is **lockstep**: both packages always share one version, published together from one GitHub Release. -Browser-runtime files (reachable from `src/index.browser.ts`): +`packages/common/src` (browser + Node, zero deps): -- `src/index.browser.ts` -- `src/vc_presentation.browser.ts` -- `src/presentation/base_client.ts` -- `src/transaction_data.ts` -- `src/dcql.ts` -- `src/constants.ts` -- `src/types.ts` (type-only, erased at emit) +- `index.ts` (public entry), `internal.ts` (server-only helpers) +- `client.ts` — `createClient`, `buildAuthorizationUrl`, `parseAuthorizationResponse`, and the shared param/URL builders +- `dcql.ts`, `constants.ts`, `types.ts` (shared wire types — the single source of truth; server imports & re-exports them) -Everything else under `src/` is Node-side. Verify after build: +`packages/server/src` (Node only): + +- `index.ts` — `export * from "@proof.com/proof-vc-common"` then the server surface (its `createClient` intentionally shadows the frontend one) +- `client.ts` — server `createClient` (PAR, `transactionData`, `dcApiRequest`); `authorizationUrl` is `async` (PAR fetches) +- `verifier.ts` — `createVerifier` → `verify` / `verifyVPToken` +- `transaction_data.ts`, `proof_credentials.ts`, `proof_credential_factory.ts`, `utils.ts`, `types.ts` (`ProofCredential`/`VPToken`/`TrustRoot`), `certificates/**` + +Verify `common` stays free of runtime leaks after build: ```bash -grep -lE '(jose|@sd-jwt|@owf|node:)' \ - dist/index.browser.js dist/vc_presentation.browser.js \ - dist/presentation/base_client.js dist/transaction_data.js \ - dist/dcql.js dist/constants.js dist/types.js +grep -lE '(jose|@sd-jwt|@owf|node:)' packages/common/dist/*.js # Must match nothing. ``` ## Essential Commands -| Command | Purpose | -| ------------------- | -------------------------------------------- | -| `yarn check-all` | Full check: format, lint, typecheck, publint | -| `yarn build` | `tsc` emit to `dist/` | -| `yarn typecheck` | `tsc --noEmit` | -| `yarn lint:check` | eslint, no fix | -| `yarn lint` | `eslint --fix` | -| `yarn format:check` | `prettier --check` | -| `yarn format` | `prettier --write` | -| `yarn publint` | `publint --pack npm` (keep the flag) | +Run from the repo root. Use `corepack yarn …` (or just `yarn`, with Corepack enabled). + +| Command | Purpose | +| ------------------- | ---------------------------------------------------------------------------------- | +| `yarn check-all` | Full check: format, lint, build, publint | +| `yarn build` | `tsc -b` (project references; builds common then server; errors on any type error) | +| `yarn test` | `yarn workspaces foreach` run tests (each self-builds) | +| `yarn lint:check` | eslint, no fix | +| `yarn lint` | `eslint --fix` | +| `yarn format:check` | `prettier --check` | +| `yarn format` | `prettier --write` | +| `yarn publint` | publint over both publishable packages (`--pack npm`) | + +Installs are immutable by default (`.yarnrc.yml`). When changing dependencies or workspaces, run `yarn install --no-immutable` to update `yarn.lock`, then commit it. + +## Package manager (Yarn for dev, npm only for release) + +- **Yarn 4 (Corepack)** runs everything day-to-day: `install`, `build`, `test`, `lint`, `format`, `publint`, `check-all`. `yarn.lock` is the only lockfile. +- **npm is used only in the release path** — `npm version … --workspaces` (bump) and `npm publish -w … --provenance` (publish) — for OIDC provenance / trusted publishing and the `-w` workspace publish. Always pass `--no-package-lock` so npm doesn't drop a `package-lock.json` into this Yarn-only repo. +- **Don't run `npm install` / `npm ci` (or bare `npm version` / `npm publish`) locally outside the release flow.** npm's reify rewrites `node_modules` in a way that breaks Yarn's per-workspace `PATH` (symptom: workspace scripts fail with `command not found: tsc` / `publint`); recover with a fresh `yarn install`. +- Each publishable package declares its script tools (`typescript`, `publint`) as devDependencies so `yarn workspace