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

witherspoon-course-template

v1.2.2

Published

Shared Astro template that builds a self-contained static site from any approved course directory.

Readme

course-template

One Astro project that builds a self-contained static site from any approved course directory.

Before a course has been published, run the shared template directly:

npm install                                        # first run only
npm run build -- --course ../course-<slug>         # → ../course-<slug>/dist
npm run dev   -- --course ../course-<slug>         # live preview (Tailscale + free port)
npm run verify -- ../course-<slug>/dist            # gates S1–S15
npm run test   -- ../course-<slug>/dist            # runtime behaviour in jsdom
npm run check-widgets -- --course ../course-<slug> # widget JSON, without a build
npm run typecheck
node tools/publish.mjs --course ../course-<slug>   # upload dist/ to here.now

course-publish adds thin wrappers to the course's package.json. After that, stay in the course directory and run npm run build, npm run dev, npm run verify, npm run check-widgets, npm run typecheck, npm run test, or npm run deploy. deploy is witherspoon-course publish (here.now); there is no npm publish script in this package, so npm publish still means the registry. The wrappers reuse this template and its node_modules; dependencies are not copied into every course.

Course directories hold their content, publication manifest, and command wrappers. Updating a course means editing course.json and its markdown; improving the site means editing this template, once, for every course.

What it reads

course.json is the single source of truth for structure and assessment — units, topics, objectives, flashcards, quizzes, unit tests, projects. Markdown supplies prose only:

| Source | Becomes | | --- | --- | | course.json | every page's structure, and all quiz/flashcard/test data | | <topic>/read.md | the reading on a topic page, including its ```widget blocks | | <project>/brief.md | the brief on a project page | | <project>/starter/, <project>/tests/ | starter files and grader sources | | SOURCES.md | the sources page (falls back to a table from course.json) | | <course-dir>/assets/ | images and diagrams, copied to dist/assets/ |

Four content collections (course, units, topics, projects) are defined in src/content.config.ts with zod schemas. A course that violates one fails the build naming the entry and the field — topics → unit-1/topic-1 … correctOptionIndex 7 is out of range — instead of producing a site that grades wrongly.

Why the assets are not bundled

Four hard constraints drive the whole design: no external requests, no absolute paths, works without JavaScript, and deployable at any subpath. Astro is zero-JS by default, which handles the third for free. The fourth is the one a bundler quietly breaks — Astro emits /_astro/… root-absolute URLs for anything it processes, which 404s the moment the site is served from a subdirectory.

So the runtime and the stylesheet never go through Astro's bundler:

  • src/runtime/*.ts → bundled by esbuild to one classic IIFE at assets/site.js
  • src/styles/*.css → bundled by esbuild to assets/site.css
  • both referenced with a prefix computed from the page's depth (src/lib/rel.ts)
  • build.inlineStylesheets: 'always' as a backstop, so a component <style> could never emit an external /_astro/*.css

tools/build.mjs stages all of that into .build/public/ before invoking Astro, then prunes the output to HTML plus assets/ so content-layer scratch files never ship. Gate S2 fails on any /_astro/ reference, so a regression here is caught rather than discovered on deploy.

Layout

src/
  content.config.ts     collections + zod schemas
  lib/                  course.ts (read/derive) · loaders.ts · rel.ts · search.ts · nav.ts · md.ts
                        widgets.ts (compile ```widget fences) · color.ts (per-unit OKLCH hues)
  layouts/Page.astro    shell, config block, relative asset links
  components/           Quiz · Flashcards · ProgressRing · Checklist · Rubric · Markdown
  runtime/              store · quiz · deck · progress · certificate · search · confetti ·
                        widgets · readbar · …
  styles/               tokens · base · components · widgets · print
  pages/                index · certificate · sources · 404 · [unit]/[page] · assets/search-index.js
tools/
  build.mjs             the --course wrapper
  verify.mjs            gates S1–S15
  test-runtime.mjs      jsdom behaviour tests
  check-widgets.mjs     widget JSON validation without a build
  render-views.mjs      renders quiz.md / flashcards.md / unit-test.md from course.json
  publish.mjs           upload dist/ to here.now (witherspoon-course publish)

src/pages/[unit]/[page].astro is one dynamic route dispatching on a kind prop — unit overview, topic, test, project. Sibling [topic].astro/[project].astro routes would be an Astro route conflict, and under build.format: 'file' an index.astro would emit unit-1.html rather than unit-1/index.html.

Figures and widgets

A reading can embed a static figure (photograph, unit art, SVG) as a fenced block or a markdown image pointing at assets/…. The build rewrites the path for page depth, probes dimensions, and wraps it in a light .figure card:

```figure
{ "src": "assets/img/unit-1.webp", "alt": "…", "caption": "…" }
```

Unit overview heroes are declared on units[i].hero in course.json (or dropped in as assets/img/unit-N.webp) and render on unit-N/index.html.

A reading can also embed an interactive widget as a fenced block:

```widget
{ "type": "anatomy", "title": "…", "parts": [ … ] }
```

src/lib/widgets.ts lifts each fence out before the markdown is rendered, compiles it to HTML, and puts it back afterwards — so the JSON is never at the mercy of the markdown processor's opinion about it, and no spec or renderer ever reaches the browser. src/runtime/widgets.ts then sets data-enhanced and adds interaction on top of markup that is already complete: the un-enhanced form of every widget is readable, and the runtime hides rather than removes, which is also what makes the print stylesheet able to bring it all back.

Eight types — anatomy, flow, compare, terminal, match, order, sequence, tree. The authoring catalogue is .claude/skills/course-site/references/widgets.md. A malformed widget fails the build naming the file and the field; that is deliberate, because a silently broken diagram is worse than a build that stops.

Contracts

  • .claude/skills/course-site/references/site-spec.md — the design contract this template implements
  • .claude/skills/course-site/references/state.md — the localStorage contract src/runtime/store.ts implements
  • .claude/skills/course-site/references/build-gates.md — what each gate means
  • .claude/skills/course-site/references/widgets.md — the widget catalogue, for authors
  • .claude/skills/course-builder/references/schema.md — the course.json shape

Ids are positional (u1t1, u1p1) and topics are numbered per unit in the URL while the source tree numbers them globally. Both are what saved progress is keyed on — changing either orphans every learner's stored progress.