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

quillkit

v0.17.0

Published

The quill author's toolchain: gate a quiver, pack it, look at it, ship it. Carries the studio client it serves.

Downloads

2,061

Readme

quillkit

The quill author's toolchain: one command over the whole loop. Gate a quiver, pack it, look at it, ship it.

Install

npm install --save-dev quillkit @quillmark/quiver @quillmark/wasm

quillkit carries neither of the other two. It resolves both out of your collection's own node_modules, so the versions you pin are the format your quiver is packed in and the wasm your gate renders through. The studio client is the one thing it does carry: studio and site serve it out of the tool's own dist/client, so there is nothing to install for it and nothing to keep in step.

That client compiles in its own copies of both libraries and the engine, which your install does not resolve and your lockfile does not record. dist/client/carried.json names all three, and each release's notes say the same in one line.

The verbs

| Verb | What it does | | ----------------- | ------------------------------------------------------------------ | | quillkit test | the gate: every quill's seed and example document render | | quillkit build | pack the source layout into a servable artifact | | quillkit studio | the local loop: pack, serve, repack on save | | quillkit site | the deploy layout: the client at a root, a built quiver beneath it |

Every verb takes --quiver <dir>, the collection root where Quiver.yaml lives, defaulting to the working directory. build, studio and site take --out <dir>, studio takes --port <n> and --host <addr>, and site takes --drafts.

Gating

test is what you are blocked on: it runs in CI, against your own wasm, and installs no app.

// package.json
{
	"scripts": { "test": "quillkit test" }
}

It loads the source layout with fromDir, then renders every quill's seed, the blank document its schema opens on, and its root example.md where it ships one. An example that will not parse, or whose $quill is not the quill's bare name, fails the quill. It finds the engine itself: a named engine export from quillkit.config.js at the collection root, else @quillmark/wasm from your own node_modules. A config that is there and throws fails the gate, naming the file and what threw: only a config you have not written falls through to the artifact.

On vitest, jest or node:test, spawn the bin rather than rebuilding the loop against the library. The gate stays one implementation, and the case gates what CI gates:

import { execFileSync } from 'node:child_process';

it('gates the quiver', () => {
	execFileSync('quillkit', ['test'], { stdio: 'inherit' });
});

Looking at it

studio is what you look at: it packs your source, serves the studio client over it, and repacks whenever you save. Nothing fails a build on its verdict.

npx quillkit studio

Pick a quill, edit, watch it paint, read the errors. quillkit test answers does it work; studio answers what is it like to use. The document it holds is the blueprint's own, so what the gate renders is what you judge. Reload the page to pick up a repack.

The address bar names what is on screen — [email protected] — so a link goes to a quill rather than to the quiver's first. ?quill=showcase and ?quill=showcase@1 are links too, resolved the way getQuill resolves them; a ref the quiver does not hold opens the first quill and the address bar says so. The document is not in the URL: a reload keeps the quill and reopens its example, or its seed with Examples off.

The document has two doors, both in the head. Edit source is its canonical markdown, out and back in: what comes out opens in the CLI or a quiver repo unchanged, and what goes in is parsed and conformed against the quill in hand, so a file that names a quill this quiver holds lands in it and anything the schema will not take is named on the control it is about. Download PDF is the page itself — [email protected], rendered from the compile the preview is painting, so the file and the screen agree. It is drawn where the quill's backend writes a PDF.

--templates <dir> offers a collection's starter documents. The directory holds a templates.json listing each as { "name", "file", "description"? }, with file relative to it; other keys are ignored. The head then draws a Templates… select, and a pick opens that document in the quill its $quill names — [email protected] resolves like a link does. studio serves the directory where it stands, so an edited template is picked up on its next pick, and refuses a manifest naming a file the directory does not hold.

npx quillkit studio --templates templates

It shows a quill rather than editing one: no plate editing, no schema editing, no auth, and nothing it holds outlives the tab.

The client renders through the @quillmark/wasm it was built against, and the head names it; your quillkit test runs whatever your own tree holds, and nothing at runtime reconciles the two. The gate is authoritative, studio is advisory.

Shipping it

site writes the arrangement a deploy serves (the client at the root, a built quiver at quiver/ beneath it, which is where the client looks) and asserts both halves of it.

npx quillkit test && npx quillkit site --out ./site

The gate runs first, here and in every recipe below. site packs files and opens none of them — it stats each Quill.yaml as a sentinel and never parses it — so a quill that does not compile packs cleanly and reports itself in the client. That is what the local loop wants and what a deploy does not, and nothing in site supplies it.

--templates <dir> lays the starter documents at templates/ beside the quiver, so a deployed studio offers them as the local one does.

--drafts packs the versions below 0.1.0 too. Without it site takes quiver's draft floor, as a deployment should; with it the site is a preview of the collection as it stands, prototypes included, which is what studio serves locally.

The client resolves its quiver against document.baseURI and its assets relatively, so one build serves a root, a subpath and a preview URL with no rebuild. A ?quill= link needs no rewrite rule either: a query participates in no file resolution, and relative resolution drops it. The arrangement itself is two rules: the client's files at some base with a built quiver at quiver/ under that same base, and no quiver inside the client, since one packed there would occupy the URL the built one is served from.

What a host owes it

Four rules, the same on every host:

| Rule | Why | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | serve assets/* immutable | hash-named, so revalidating one can only return 304: a wasted round-trip per asset per visit | | do not cache quiver/quiver.json at the edge | the one name the loader reads that carries no digest, and a stale one pins readers to old quills | | a missing path is a 404 | an SPA fallback — the commonest default there is — answers 200 with the client's HTML, which the loader refuses as not the file it named | | serve over https | nothing checks the artifact's bytes against their names, so the channel is what answers a corrupted or substituted one |

The second rule covers the edge alone. The browser layer is the loader's own — quiver.json fetched no-cache, every digest-carrying name force-cache — so a host that sets no cache header at all is already correct there. An edge serving one generation's quiver.json after the next has shipped is the one staleness the addressing cannot catch.

On Vercel, a vercel.json at the repository root is the whole of it — a missing path is already a 404 and everything but assets/* already revalidates:

{
	"$schema": "https://openapi.vercel.sh/vercel.json",
	"buildCommand": "quillkit test && quillkit site --out site",
	"outputDirectory": "site",
	"headers": [
		{
			"source": "/assets/(.*)",
			"headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
		}
	]
}

Netlify takes the same shape in a root netlify.toml: [build] for the command and the publish directory, one [[headers]] block for assets/*. On a host reading _headers or _redirects out of the served directory itself, write them after the build rather than committing them — site clears what it writes, so a file placed there beforehand is gone before the deploy uploads.

For GitHub Pages, the build is the gate, quillkit site and an artifact upload:

# .github/workflows/studio.yml
name: Studio
on:
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '24'
          cache: npm
      - run: npm ci
      - run: npx quillkit test
      - run: npx quillkit site --out site
      - uses: actions/upload-pages-artifact@v3
        with:
          path: site
  deploy:
    needs: build
    runs-on: ubuntu-latest
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

Keep the deploy in your own repository, as above: nothing outside it then holds pages: write.

If your quiver is not an npm project (a Quiver.yaml and quills/ with no package.json), there is no tree for the packer to be resolved from, so install it for the run and drop the npm ci:

- run: npm install --no-save @quillmark/quiver @quillmark/wasm quillkit
- run: npx quillkit test
- run: npx quillkit site --out site

That takes whatever @quillmark/quiver is current, where a package.json would pin the format your quiver is packed in. The gate renders, so @quillmark/wasm joins the install here. Dropping it and the quillkit test line with it is the trade: a deploy that installs no wasm, and nothing that fails on a quill that does not compile.

A deployed quiver is frozen at a commit, so the repack loop is the local one, over a working tree.

Refusals

build and site clear what they write, so an --out that is, or contains, your collection or the working directory is refused rather than deleted. build assembles each generation beside its output and moves it in whole, so a client reading mid-pack sees the previous one and a failed pack leaves it serving.