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

create-hr-plugin

v0.8.1

Published

Scaffold a HomeRunner widget plugin (vm.Script SSR + IIFE client) on @homerunner-next/widget-core

Readme

create-hr-plugin

Scaffold a HomeRunner widget plugin in seconds. Produces a working project (vm.Script SSR + IIFE client) wired against @homerunner-next/widget-core — the same SDK that backs first-party HomeRunner widgets.

📖 Full developer guide: https://homerunner-plugin-docs.vercel.app — manifest contracts, layouts, SSR, publishing, and an honest index of what the platform does not support yet. This README orients you; the guide is the reference.

Usage

npx create-hr-plugin my-widget                    # one widget (default)
npx create-hr-plugin my-suite --template suite    # several widgets + a layout

Then:

cd my-widget
npm run dev          # http://localhost:3001 — dev sandbox, MSW-mocked data, no live feed
npm run build        # schema inject → hr-widget-build → dist/ + a stamped manifest.json

⚠️ --template is the one irreversible decision

It picks your manifest shape, and the shape is frozen at your first publish. A single-widget plugin can never become a suite, or the reverse. Central refuses the publish and the rollback with MANIFEST_SHAPE_CHANGED; the only remedy is a brand-new plugin slug, a new marketplace listing, and every customer re-creating their widgets.

The trap is when it fires. Neither the upload audit nor the submit API nor the human review checks the shape — a flip passes all three and dies at publish, days later.

Converting is free before you publish and impossible after. When in doubt, --template suite: a suite of one is legal and publishes normally.

Requirements

| | | | --- | --- | | Node | 20.19+ or 22.12+ (Vite 7's engines; the scaffold pins vite@^7.3.1). npm run dev:ssr also needs process.loadEnvFile, which landed in 20.12 — on older Node it fails silently and loads no env at all | | @homerunner-next/widget-core | On the registry: ≥ 0.12.1 for --template single, ≥ 0.12.3 for --template suite. Those are the pins this version's templates carry (^0.12.1 and ^0.12.3) |

npx create-hr-plugin runs npm install in the generated project. If the registry's newest widget-core is older than the pin, that install fails with npm error code ETARGET No matching version found for @homerunner-next/widget-core@^0.12.3 — and the scaffolder treats that as non-fatal: it prints npm install failed — you can run it manually. and still exits 0, leaving you a project with no node_modules. If that happens, re-scaffold with --no-install and install a widget-core tarball by hand. That fallback, and the current npm-vs-repo version table, are in Install and versions.

Behind those numbers: hr-widget-build — the binary npm run build invokes — ships as widget-core's bin from 0.11.0; a suite's npm run dev:ssr -- --widget <slug> needs 0.12.2; and the SSR preview only falls back to the manifest's declared assets.css when the bundle exports no getStaticAssets from 0.12.3, which is the shape the suite scaffold writes — before that, its SSR preview rendered unstyled.

Options

npx create-hr-plugin <plugin-slug> [options]

| Option | Default | Notes | | --- | --- | --- | | --template <single\|suite> | single | Project shape — see below. Frozen at your first publish. --template=<t> works too; anything else (including --template with nothing usable after it) exits 1 | | --author <name> | git config user.name | Baked into package.json and manifest.json. If git is absent or user.name is unset, the author becomes the literal string Plugin Author | | --no-install | (off) | Skip npm install — useful in CI / sandboxes, or when the widget-core pin is not on the registry yet | | --help, -h | | Print usage and exit 0. Passing no arguments at all does the same |

The slug is always the first argument. Put flags after it — a first argument starting with - is refused (Error: expected a plugin slug first — got "--template".) rather than turned into a directory name. The CLI is otherwise entirely non-interactive: it prompts for nothing, and an existing target directory is a hard error.

The slug becomes the plugin id and the first half of every keyword the platform stores against a customer's widget row. It has to match /^[a-z0-9][a-z0-9-]*$/ — the same rule the publish audit applies to manifest.id — and the scaffolder additionally refuses dist, widgets and media, the three directory names a published version prefix owns (the audit reserves those for widget slugs). Both checks run before a single file is written, rather than at submission with the project already built and committed.

Which template

| | --template single | --template suite | | --- | --- | --- | | Manifest shape | v1: root widgetType / ssr / assets / configSchema | root widgets[] + presets[], one sub-manifest per widget | | Keyword | plugin:<slug> | plugin:<slug>:<widget> | | Widgets | exactly one | up to 24, one version and one review for all of them | | Layout widgets | not possible | yes — named slots, prefill, page assignment | | CSR-only widgets | not possible (v1 always server-renders) | yes — "ssr": false + expectedHeight | | Source layout | flat src/ | src/widgets/{slug}/ | | Ships | Tailwind v4 + a shadcn-style src/components/ui/ kit | no UI kit, no CSS framework (a layout's stylesheet is unisolated on a server-rendered page) | | Dev sandbox | npm run dev — :3001, MSW-mocked | npm run dev — :3001, all 3 widgets, the layout composing the other two | | Local SSR preview | npm run dev:ssr — :3003 | npm run dev:ssr -- --widget <slug> — :3003, one widget at a time | | Build | npm run buildhr-widget-build (iife + SSR pass) | npm run buildhr-widget-build (one iife pass per widget, plus SSR for every widget that isn't "ssr": false) |

The guide's recommendation is start as a suite, even for one widget: a suite of one publishes normally and can grow a second widget, a layout or a preset in any later version. Choose single only when all of these hold — the plugin is one widget, it always will be, it server-renders, and you want the Tailwind + src/components/ui/ kit more than you want room to grow.

What you get — --template single

my-widget/
├── manifest.json          # Plugin manifest — build:manifest injects configSchema,
│                          #   the build stamps runtime.widgetCore. Commit it.
├── package.json · tsconfig.json · .env.example
├── vite.config.ts         # viteHomerunnerWidget({ slug, command }) — externals, dev CSS, /w/ proxy
├── index.html             # Dev page; also the `npm run preview` page (built bundle, no mocks)
├── serve.js               # LOCAL PREVIEW ONLY — static server for the built output
├── vercel.json · api/     # LOCAL PREVIEW ONLY — self-hosted demo shim; deletable
├── mocks/fixtures.ts      # Dev mock data overrides
├── public/                # mockServiceWorker.js — dev only, excluded from builds
├── scripts/
│   ├── inject-schema.mjs  # Pre-build: zod → JSON Schema into manifest.json
│   └── dev-ssr.mts        # npm run dev:ssr
└── src/
    ├── index.tsx          # Client IIFE — registerPlugin() + mount()
    ├── widget.tsx         # Your React component + getInitialData()
    ├── ssr.ts             # dehydrateState + getStaticAssets (loaded server-side via vm.Script)
    ├── ssr-entry.ts       # SSR UMD entry barrel
    ├── config.ts          # widgetSchema.extend() + zodToManifestSchema()
    ├── data.ts            # Sample public-api fetcher
    ├── dev/               # Dev sandbox: mock worker + control bar — never published
    ├── widget.css
    └── components/ui/     # shadcn-style UI (Dialog with elevateDom, Button, Tooltip,
                           # Carousel, WidgetSection, WebFont, ClientOnly, ResponsiveDialogContent)

The UI components live in your plugin source — they're shadcn-style (you own them, edit / restyle / replace freely). Runtime hooks (useTopLevelPortal, usePortalContainer, useShadowHost) come from @homerunner-next/widget-core/runtime and shared utils (WidgetProvider, useWidget, useTranslation, useLocaleDetection, getProxiedImageUrl, getFeedQuery, getWidgetQuery) come from @homerunner-next/widget-core/utils — both are re-exported through src/components/ui/index.ts so you only have one import path.

Walkthrough: Your first widget.

What you get — --template suite

my-suite/
├── manifest.json                  # widgets[] summaries + presets[] — the suite root
├── package.json · tsconfig.json · .env.example
├── vite.config.ts                 # viteHomerunnerWidget() — no entry names anywhere
├── index.html · src/dev/          # Dev sandbox: all 3 widgets, mocked (never published)
├── serve.js · vercel.json         # LOCAL PREVIEW ONLY — static hosting for the built output
├── scripts/
│   ├── inject-schema.mjs          # Pre-build: zod → widgets/{slug}.manifest.json
│   └── dev-ssr.mts                # npm run dev:ssr -- --widget {slug}
├── widgets/                       # GENERATED by build:manifest — commit it
├── media/                         # Marketplace icon + READMEs (authored, committed)
├── mocks/fixtures.ts              # Dev mock data; its `widget` factory keys by widget id
└── src/
    ├── shared/property.ts         # Code >1 widget uses, imported relatively
    └── widgets/
        ├── page-frame/            # category "layout": slots, prefill, pages, presets
        │   └── ssr-entry.ts       #   mandatory — a layout must server-render
        ├── intro-card/            # server-rendered content widget
        │   └── ssr-entry.ts
        └── quick-facts/           # CSR-only: "ssr": false, expectedHeight,
                                   #   and deliberately NO ssr-entry.ts

Three widgets, one per surface: page-frame teaches the layout contract (the mandatory slots zod field whose keys mirror the manifest slot names, renderedSlots via LayoutWidgetProps, { category: "layout" } in both registerPluginWidget and mount, and light-DOM-safe CSS); intro-card is the ordinary server-rendered content widget; quick-facts is the CSR-only one, and the missing ssr-entry.ts is the point. A root presets[] entry composes all three plus the platform's own widgets in one click.

The generated README.md documents what the dev sandbox can and cannot cover — chiefly that the mock worker is a page singleton (one scenario, one settings object), and that npm run dev:ssr previews one named widget in isolation, so a layout's server-side slot composition still needs a real feed.

Walkthroughs: Build a suite and Add a layout widget.

Two things the scaffold assumes you know

Settings arrive raw. props.options is the customer's stored settings object exactly as saved — on the server and on the client, never zod-parsed for you. Call parseWidgetConfig(configZod, props.options ?? {}) before reading a single field; it applies your defaults, strips the dashboard's null / "" save artefacts and lifts legacy width values into the spacing model. Every scaffolded component already does. → Settings and options

Your bundle does not ship mount. @homerunner-next/widget-core/runtime is externalised against the host page's runtime IIFE, so the widget lifecycle (shadow DOM, SSR hydration, theming) always runs at the platform's current version rather than a copy frozen into your build. The build records which SDK it used in manifest.runtime.widgetCore; publishing requires ≥ 0.10.0. Never hand-write that field, and expect manifest.json dirty in git after every build. → Runtime, mount and the DOM contract

Shipping it

  1. npm run build — must succeed from a clean checkout of what you committed, which is exactly what the reviewer runs.
  2. Zip the source (not dist/, not node_modules/): git archive --format=zip -o ../my-plugin-1.0.0-source.zip HEAD.
  3. Upload it in the HomeRunner dashboard under Plugins ▸ My plugins ▸ Upload plugin. The audit runs in your browser before any bytes move; a failure lists exactly what is wrong.
  4. Add review notes, submit, and wait. On approval the reviewer builds your source and the built output is published to an immutable CDN version prefix. Customers then install it per feed from the marketplace.

You host nothing. Third-party plugins cannot be self-hosted — the zip pipeline is the only path open to you, and serve.js, vercel.json and api/ in a generated project are local preview scaffolding you may delete. manifest.json ships path-only URLs (e.g. dist/<slug>.iife.js) which HomeRunner resolves against the published manifest's own URL, so nothing in your build ever carries an environment-specific origin.

The full preflight checklist — including a script that reproduces the publish-time gates the reviewer never forwards to you — is in Preflight and submit, and version rules are in Publishing, versions and rollback.

Where to read next

| | | | --- | --- | | Install and versions | Node floors, the React pins, and what npm actually serves today | | The two manifest shapes | The --template decision, in full | | Layout widgets | Slots, renderedSlots, presets, page assignment | | Config schema and UI schema | What the customer's settings form can render | | Dev sandbox and mocking · Previewing SSR locally | npm run dev and npm run dev:ssr, and their limits | | SDK reference | Every export of every @homerunner-next/widget-core subpath | | Limits and error index · Troubleshooting | Every message this CLI, the build and the publish gates can emit |

SDK reference

See @homerunner-next/widget-core for the full SDK surface — subpath exports for runtime, utils, schema, plugin-schema, spacing, contracts, vite, urls, globals, manifest, plus the dev-only mock and testing subpaths — and the hr-widget-build binary this scaffold's npm run build calls.

License

MIT