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
91 changes: 88 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,87 @@ jobs:
# unable to tell it is behind — the same silent drift the manifest exists to remove.
run: node scripts/check-capability-version.mjs --base "origin/${{ github.base_ref || github.event.repository.default_branch }}"

# Does the PUBLISHED package work when a real consumer installs it?
#
# Everything in `validate` tests the source, or `dist/` from inside the repository. Neither can see the
# questions a consumer actually hits, because the tarball's METADATA decides them: which file an `exports`
# condition resolves to, which declarations TypeScript reads beside it, whether `files` left something
# out. A defect there leaves the source suite green while nothing can import the package.
#
# Managers resolve differently, so npm's answer does not generalise — that is the whole reason to run more
# than one. Yarn Classic and Berry differ enough that a pass under one is not a pass under the other, so
# both are here.
#
# Every version is EXACT. A floating major silently changes what was exercised, and a run that passes for
# a reason nobody chose is not evidence. The script also prints the version it resolved, so the label and
# the artifact cannot disagree.
#
# Berry does not come from the `yarn` package on npm — that line stops at 2.x — so it is installed from
# `@yarnpkg/cli-dist`, which is the distribution that carries the `yarn` binary.
consumers:
name: 📦 Consumers on ${{ matrix.label }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- { label: npm 11.6.0, manager: npm, setup: 'npm@11.6.0' }
- { label: pnpm 9.15.4, manager: pnpm, setup: 'pnpm@9.15.4' }
- { label: Yarn Classic 1.22.22, manager: yarn, setup: 'yarn@1.22.22' }
# The layout is in the label: this exercises Berry resolving the export map through
# `node_modules`, which is what the fixture pins. Plug'n'Play is a separate shape and is not
# covered, so the check must not read as "Berry works".
- { label: Yarn Berry 4.18.0 (node-modules), manager: yarn, setup: '@yarnpkg/cli-dist@4.18.0' }
- { label: Bun 1.2.21, manager: bun, setup: '' }
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22.x
cache: npm

- if: matrix.manager == 'bun'
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.2.21

- name: Install the package manager at an exact version
if: matrix.manager != 'bun'
run: |
npm install --global "${{ matrix.setup }}"
# The version actually on PATH, before any fixture runs. An install that resolved to something
# else — or to nothing — must be visible here rather than inferred from a later failure.
${{ matrix.manager }} --version

- name: Install dependencies
run: npm ci

- name: Consumer shapes against a packed tarball
run: node scripts/compat-matrix.mjs --manager ${{ matrix.manager }}

# The generated `.cmd` launcher and path handling are Windows-only code paths in npm's shim, not ours,
# and they are exactly what breaks a bin that works everywhere else. One smoke test rather than the whole
# matrix: the question is whether the launcher runs and resolves, not whether four managers agree.
windows-smoke:
name: 🪟 Windows bin and consumer smoke
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22.x
cache: npm

- name: Install dependencies
run: npm ci

- name: Consumer shapes against a packed tarball
run: node scripts/compat-matrix.mjs --manager npm

validate:
name: Validate on Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
Expand Down Expand Up @@ -71,12 +152,16 @@ jobs:
- name: Typecheck
run: npm run typecheck

- name: Test
run: npm test

# Before the tests. Several of them only run once `dist/` exists — the edge export-resolution and
# edge-safe checks among them — and in the other order they skip, which is indistinguishable from
# passing. Two dist-dependent checks are rerun explicitly below with a flag that refuses to skip;
# the rest rely on this ordering, so it is the ordering that has to be right.
- name: Build
run: npm run build

- name: Test
run: npm test

# After the build, because it drives the built bin. npm installs a bin as a SYMLINK, so this is the
# only invocation shape that catches an entry point which runs when called directly and does nothing
# when installed — silently, with exit 0.
Expand Down
8 changes: 7 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,15 @@ reads exactly like passing.

## Before you open a pull request

- `npm run typecheck`, `npm test`, `npm run build` all clean.
- `npm run typecheck`, `npm run build`, `npm test` all clean — in that order, for the reason above.
- `npm pack --dry-run` if you touched anything about packaging. `files` in `package.json` is the
authoritative allowlist of what ships.
- `npm run test:consumers` if you touched `exports`, `files`, the build config or anything about types. It
packs a real tarball, installs it into isolated consumer projects and checks every shape a consumer can
use — ESM and CommonJS, JavaScript and TypeScript, the installed bin, and that nothing outside `exports`
is reachable. Nothing else can see those: they are decided by the tarball's metadata rather than by the
code, so the source suite passes while a consumer cannot import the package at all. CI runs it across
npm, pnpm, Yarn and Bun, plus a Windows smoke test.
- Read your own diff, commit messages and pull-request description against the boundary above. The
pull-request template asks you to confirm this; it is the only control that covers prose, because secret
scanning finds credentials and nothing finds a paragraph of architecture.
Expand Down
25 changes: 19 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,31 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
},
"default": "./dist/index.js"
},
"./protect": {
"types": "./dist/protect.d.ts",
"workerd": "./dist/protect.edge.js",
"worker": "./dist/protect.edge.js",
"edge-light": "./dist/protect.edge.js",
"deno": "./dist/protect.edge.js",
"browser": "./dist/protect.edge.js",
"import": "./dist/protect.js",
"require": "./dist/protect.cjs"
"import": {
"types": "./dist/protect.d.ts",
"default": "./dist/protect.js"
},
"require": {
"types": "./dist/protect.d.cts",
"default": "./dist/protect.cjs"
},
"default": "./dist/protect.js"
}
},
"bin": {
Expand All @@ -56,6 +68,7 @@
"verify:edge": "node scripts/verify-edge-platform.mjs",
"dev": "tsup --watch",
"test": "vitest run",
"test:consumers": "node scripts/compat-matrix.mjs",
"test:manifest": "bun scripts/test-manifest.ts",
"test:watch": "vitest",
"typecheck": "tsc --noEmit && node scripts/typecheck-templates.mjs",
Expand Down
Loading
Loading