npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@coralreefventures/site-tools

v0.7.0

Published

Command-line tools for a Coral Reef public site: serve the static export as the host does, gate it with Lighthouse, and lock it behind the door.

Readme

@coralreefventures/site-tools

Three command-line tools for a Coral Reef public site, which is a Next.js static export served by Amplify Hosting. Both sites carried the first two as scripts/serve.ts and scripts/lighthouse.ts; they are bins now, and the products' scripts go. The third locks a site behind the door.

reef-serve [--root out] [--port 3140]
reef-lighthouse [--root out] [--reports lighthouse] [--port 3160] [--threshold 90] [--retries 2] [--shard 1/3] [--chrome <path>] [path ...]
reef-door-bundle --site <id> [--out out] --door-url <url> --hosts <h1,h2> --jwks-url <url> [--signout-chain id=host,...]
                 [--name <display name>] [--mark <file>] [--mark-dark <file>] [--accent #rrggbb] [--accent-dark #rrggbb]

reef-serve serves the export the way Amplify does, for Playwright and Lighthouse: a directory answers with its index.html, a directory path without its slash redirects to it, anything else is the 404 page with status 404, and text is compressed as the CDN compresses it. PORT is read when --port is not given, so a Playwright webServer command can pass either:

webServer: { command: `pnpm exec reef-serve --port ${port}`, port },

reef-lighthouse is the merge gate: Lighthouse on every page in the export's sitemap (or the paths given), with its default mobile emulation, each of performance, accessibility, best practices and SEO at the threshold or above. A page under it is measured again, up to --retries more times (2 by default), and the median of its runs decides: a performance score swings a few points between runs of the same page on a shared runner, and one low run should not fail a gate three runs would pass, while a page every run finds low still fails. A page under it then fails the run and leaves its last report in --reports. A category's own floor (--performance 85) stands in for the threshold for that category alone, where a site's shell puts every page a few points under 90 on a shared runner while the other three categories hold at 90. --shard <index>/<count> measures one share of the pages, every count-th from the index-th (1-based, as Playwright's --shard), so a site whose gate outgrows one runner splits it across a CI matrix: Lighthouse measures one page at a time, about 14 seconds each on a CI runner, and pages measured side by side on one runner would skew each other's performance scores. Lighthouse itself is not a dependency, because it bundles axe-core (MPL-2.0), which is outside the family's licence allowlist; it runs through pnpm dlx, pinned (--lighthouse-version). Chrome is Playwright's Chromium when @playwright/test is installed beside this package, or --chrome.

The door gate

reef-door-bundle turns a static export into an Amplify deployment that serves nothing without a session from the door. It writes .amplify-hosting/ with the export inside the gate's compute bundle and a deployment manifest with exactly one route, /* to Compute, and no static route, so every byte (pages, RSC payloads, _next/static chunks, images, social cards, the 404 page) is answered by the gate. The app runs on platform WEB_COMPUTE with the cache type AMPLIFY_MANAGED, so cookies are in the CDN's cache key.

reef-door-bundle --site <id> --door-url https://<door> --hosts <host> --jwks-url https://<door>/.well-known/crv-door-jwks.json \
  --signout-chain <id>=<host>,<id>=<host>

Each flag falls back to an environment variable, so a build spec can set them per app: CRV_DOOR_URL, CRV_DOOR_HOSTS, CRV_DOOR_JWKS_URL, CRV_DOOR_SIGNOUT_CHAIN and CRV_DOOR_HOST_HEADER (host, the default, or x-forwarded-host if the platform rewrites Host). The bin fetches the JWKS and fails the build if it is unreachable, empty, or anything but P-256 public keys; it refuses a static/ directory, a second route and a bundle over 200 MB.

The gate is a Node server with no dependencies (it imports Node's own modules and nothing else, which a test holds), listening on port 3000. For every request, in order:

  1. A path with .., a backslash or a control character, raw or percent-encoded, is a 400; a method other than GET, HEAD or POST is a 405.
  2. A Host not in --hosts is an empty 403.
  3. /robots.txt disallows everything; /_door/health answers ok.
  4. POST /_door takes the door's ticket from a form body: an ES256 JWS whose kid is in the JWKS, issued by the door for this exact host, unexpired and living at most an hour, bound to the __Host-crv_door_state cookie, and naming the same canonical next as the form. It becomes the __Host-crv_door cookie (HttpOnly; Secure; SameSite=Lax, for the ticket's remaining life) and a 303 to next, and sets __Host-crv_door_seen=1 (HttpOnly; Secure; SameSite=Lax, 30 days), a marker with no identity in it that this browser has had a session here. Anything wrong is a 303 back to the door with ?error=ticket. GET /_door/signin?next=<path> starts a sign-in: it runs next through Canonical next (a missing, repeated or refused one becomes /), sets a fresh 32-byte __Host-crv_door_state cookie (SameSite=None, ten minutes) and answers a 302 to the door with site, host, next and state. The coming-soon page's button links here. Only a top-level navigation starts one (a navigation, or a request without Sec-Fetch-* headers that accepts HTML, and Sec-Fetch-Dest: document when sent); an image, a frame or a fetch gets an empty 401 and no cookie, so another site cannot overwrite a visitor's state mid-sign-in.
  5. /_door/signout?then=<id> clears all three cookies (an empty value and an Expires at the epoch, with the attributes each was set with and no Max-Age, which Firefox can keep for a moment as a cookie expiring now) and goes on by a fixed map: to the gate of the site <id> in --signout-chain, naming the site after it, or, for anything else (door, the last stop), to the door's /signout/. It never redirects to a URL from the query.
  6. With a valid session the export is served as Amplify maps it, HTML and .txt as private, no-store and /_next/static/** as private, max-age=31536000, immutable.
  7. Without one, a page load (a navigation, or a request without Sec-Fetch-* headers that accepts HTML) gets the coming-soon page, below, as a 401; anything else gets an empty 401. Neither sets a cookie. The exception is a top-level page load carrying the seen marker: an invitee whose hour is up is sent straight to the door (a fresh state cookie and the 302 /_door/signin gives), which issues a new ticket at once while its own sign-in lives, so renewal stays silent. The marker opens nothing; a fetch carrying it still gets the empty 401. Both 401s carry WWW-Authenticate: Cookie realm="<id>", form-action="/_door/signin", cookie-name="__Host-crv_door".
  8. An exception is an empty 500. The gate fails closed.

Every response carries X-Robots-Tag: noindex, nofollow, HSTS, nosniff, X-Frame-Options: DENY and a Cache-Control with no-store or private. next is canonicalised by parsing it against the host, re-serialising it as path and query, and refusing it if it leaves the origin or holds a backslash or control character (canonicalNext, exported, so the door's ticket issuer can run the same steps).

The coming-soon page

What a visitor without a session sees on the product's own domain: the product's mark and name, " is coming soon.", "Open for now to invited guests.", a "Have an invitation? Sign in" button to /_door/signin carrying the request's canonical next (so sign-in happens at the door and the ticket brings the invitee back to the page they asked for), and a "Get involved" link to <door>/get-involved/?site=<id>.

It is the gate's own HTML and carries nothing of the site: one inline stylesheet, the mark as a data: URI, no script, no request to anywhere. It is built once when the gate starts and is the same bytes for every path except the next its button carries. Its headers are the gate's security headers, X-Robots-Tag: noindex, nofollow, Cache-Control: no-store, and a Content-Security-Policy that allows exactly that: default-src 'none'; style-src 'sha256-<its stylesheet>'; img-src data: (the last only with a mark), and base-uri, form-action and frame-ancestors all 'none'. HEAD gets the same headers and no body. It follows prefers-color-scheme, reads at 320px, and every color pair meets WCAG AA.

Why 401. No request without a session gets a 2xx, so the leak check and any probe can tell a locked site from an open one by status alone. A 200 would read as the site itself to a crawler or an uptime monitor, and a 503 reads as an outage. RFC 9110 requires a 401 to carry a WWW-Authenticate challenge, and no registered scheme describes a session cookie from a sign-in page, so the gate uses the shape drafted for exactly that (draft-broyer-http-cookie-auth). Browsers prompt only for schemes they implement, so for this one they render the body, which the browser suite proves in Chromium, Firefox and WebKit.

Its content comes from the bin's flags, stored in gate.config.json as page and checked when the bundle is made:

| Flag | Default | Checked | |---|---|---| | --name | the site id, capitalized | 1 to 60 characters, no control character, no space at either end | | --mark | none: the name stands alone | an .svg or .png path inside the export (a link out of it is refused), at most 64 KB, inlined as base64 | | --mark-dark | none: the dark scheme shows --mark on a light tile | as --mark; only with --mark | | --accent | #1f2933 | #rrggbb, at least 3:1 on the light page (#ffffff) | | --accent-dark | #d5dbe1 | #rrggbb, at least 3:1 on the dark page (#11161b) |

The button's label is white or black, whichever reads better on the accent; one of the two always reaches 4.5:1. The mark is a file of the site that every visitor then sees, so name only a file meant to be public: the logo, never anything from behind the door. If the Amplify app's custom headers also set a Content-Security-Policy, Amplify's wins or both apply; the products' policy allows inline styles and data: images, so the page renders either way.

The gate holds only public keys, takes all of its configuration as data and names no product. The ticket issuer, the door and the site registry are the door's own.

The library

The library entry exports serve, createStaticServer, runLighthouse and pagesFromSitemap for a script that wants them in-process, and the gate's parts: createGate, startGate, verifyTicket, parseJwks, canonicalNext, parseGateConfig, bundleDoor, and the coming-soon page's comingSoon and parsePageConfig.

This is the one package in the repository with a build: a bin has to be JavaScript, because Node refuses to strip types from a file under node_modules. tsc writes dist/ on prepack, and the door bundle copies the gate's compiled files from there.