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

@corrbo/plop-kit

v1.0.0

Published

A structured plop framework — defineCommand, auto-discovery, dry-run, meta generators

Readme

plop-kit

A structured plop framework for projects that need multiple generators organized by category. Adds auto-discovery, dry-run preview, command banners, bypass args, and meta generators on top of raw plop.

Requirements

  • Node.js ≥ 18
  • plop ≥ 4 (peer dependency)

Installation

npm install plop-kit plop
# or copy the package into packages/plop-kit and add NODE_PATH=packages to your scripts

Bootstrap a new project

If you're setting up from scratch, run the gen-cli meta generator once — it scaffolds the entire CLI folder with auto-discovery, constants, and tsconfig:

# no plop-kit.config.js needed for this first run
NODE_PATH=packages npx plop --plopfile packages/plop-kit/plopfile.js
# → select "gen-cli", answer three questions

This creates packages/configs/cli/ and adds gen + gen:meta to package.json.


Core API

defineCommand(plop, config)

Registers a plop generator with a category prefix in the menu, a help banner shown before prompts, and optional dry-run wrapping.

import { defineCommand } from 'plop-kit'
import type { NodePlopAPI } from 'node-plop'

export = (plop: NodePlopAPI) => defineCommand(plop, {
  name: 'feature',          // generator name shown in menu and used as CLI arg
  category: 'blm',          // must match a key registered via configure()
  description: 'create a new feature',
  help: 'Creates actions.ts, types.ts, index.ts in src/blm/{module}/features/{name}/',
  prompts: featurePrompts,
  actions: getFeatureActions, // ActionType[] or (data) => ActionType[]
})

Banner behavior: in interactive mode, plop-kit prints the description + help text before the first prompt, plus a tip about -n when not already in dry-run mode. When bypass args are present (e.g. --name auth) the banner is suppressed entirely — safe for CI and scripts.

configure(plop, categories)

Registers the category menu labels. Call once in your root plopfile before any defineCommand.

configure(plop, {
  blm:   '⚙️  BLM',
  ui:    '🖼  UI',
  tools: '🔧 Tools',
})

Action helpers

All helpers validate template files at call time and return plain ActionType[] arrays that plop can consume.

createAddActions(items)

Creates type: 'add' actions. Each item needs dest (absolute path), template (absolute path to .hbs file), and optionally data and force.

import { createAddActions, withTemplates } from 'plop-kit'

const T = withTemplates(__dirname) // resolves to __dirname/templates/<name>

createAddActions([
  { dest: path.join(dir, 'index.ts'),   template: T('index.hbs'),   data: { name } },
  { dest: path.join(dir, 'actions.ts'), template: T('actions.hbs'), data: { name } },
  withData && { dest: path.join(dir, 'data.ts'), template: T('data.hbs') },
])
// falsy items (false | null | undefined) are filtered out automatically

createModifyActions(items)

Creates type: 'modify' actions (regex replace inside an existing file).

createModifyActions([
  {
    path: path.join(dir, 'store/index.ts'),
    pattern: /^/,
    template: "export * from './{{name}}'\n",
  },
])

createAppendActions(items)

Creates type: 'append' actions (insert after a matched pattern).

createAppendActions([
  {
    path: path.join(dir, 'index.ts'),
    pattern: /\/\/ exports/,
    template: "export { {{name}} } from './{{name}}'",
  },
])

withTemplates(dir)

Returns a helper that resolves template names to dir/templates/<name>.

const T = withTemplates(__dirname)
T('index.hbs') // → /absolute/path/to/templates/index.hbs

Name utilities

Pure functions for transforming names — also registered as plop handlebars helpers via registerHelpers.

| Function | Input | Output | |---|---|---| | toPascal(name, suffix?) | 'auth-list' | 'AuthList' | | toCamel(name, suffix?) | 'auth-list' | 'authList' | | toKebab(name) | 'AuthList' | 'auth-list' | | toSnake(name) | 'AuthList' | 'auth_list' | | parseList(str) | 'a, b, c' | ['a', 'b', 'c'] |

Optional suffix strips the suffix before casing and re-appends it:

toPascal('auth', 'Store') // → 'AuthStore'
toPascal('authStore', 'Store') // → 'AuthStore' (idempotent)

registerHelpers(plop)

Registers the name utils as handlebars helpers plus two extras:

  • {{pascalCase name}}, {{camelCase name}}, {{kebabCase name}}, {{snakeCase name}}
  • {{toJS obj}} — serializes an object as JS literal (unquoted keys)
  • {{toProps obj}} — serializes as unquoted keys + unquoted string values

Safe to call multiple times — registers only once per plop instance.


registerMeta(plop, options?)

Registers the meta generators (gen-cli, gen-command) under the 🔧 Meta category. Call once in your root plopfile.

// plop-kit.config.js
const path = require('path')
module.exports = {
  commandsPath: path.join(__dirname, 'packages/configs/cli/commands'), // where category folders live
  configsPath:  path.join(__dirname, 'packages/configs/cli'),          // root of the CLI folder (used by gen-cli)
  rootPath:     __dirname,                                              // project root (where package.json is)
  nodePath:     'packages',                                             // NODE_PATH value (for require('plop-kit'))
  anchorScript: 'gen:meta',   // new gen:{category} scripts are inserted after this key in package.json
}
// packages/plop-kit/plopfile.js (or your custom plopfile)
const options = require('./plop-kit.config.js')
module.exports = plop => {
  registerHelpers(plop)
  registerMeta(plop, options)
}

isDryRun()

Returns true when the process was started with --dry-run, -n, or --preview.

import { isDryRun } from 'plop-kit'

if (isDryRun()) {
  // skip expensive side effects
}

Dry-run mode

Every generator registered via defineCommand automatically supports dry-run. Pass the flag after --:

npm run gen -- --dry-run    # preview all files
npm run gen:blm -- -n       # short form
npm run gen -- --preview    # alias

Output:

📋 Dry run — no files will be written

  + src/blm/auth/features/login/actions.ts
  + src/blm/auth/features/login/index.ts
  ~ src/blm/auth/store/index.ts

No files are written. Prompts are still interactive so you see real resolved paths.


Bypass args (non-interactive)

Pass prompt answers directly on the CLI to skip interactive input — useful for scripts and CI:

npm run gen:blm -- feature --blmName auth --features "login,logout"

--name value syntax, or --name=value. Boolean prompts: --withForm true. Checkbox prompts: --features "a,b,c".

Unrecognised answers fall back to interactive mode for that question only.


Auto-discovery conventions

plop-kit assumes this folder layout. Adding a new category or command is just creating a folder — no manual registration needed.

packages/configs/cli/
├── bootstrap.cjs           ← require('tsx/cjs') + require('./index.ts')
├── index.ts                ← scans commands/*/plopfile.ts
└── commands/
    └── {category}/
        ├── plopfile.ts     ← exports { key, label, register }
        └── {command}/
            └── index.ts    ← export = (plop) => defineCommand(plop, {...})

Category plopfile (commands/blm/plopfile.ts):

import fs from 'fs'
import path from 'path'
import { registerHelpers } from 'plop-kit'
import type { NodePlopAPI } from 'node-plop'

export const key   = 'blm'
export const label = '⚙️  BLM'

export function register(plop: NodePlopAPI) {
  registerHelpers(plop)
  for (const entry of fs.readdirSync(__dirname, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue
    const idx = path.join(__dirname, entry.name, 'index.ts')
    if (fs.existsSync(idx)) require(idx)(plop)
  }
}

Meta generators

Run via npm run gen:meta.

gen-cli

Bootstraps a full CLI folder in a new project (one-time setup). Creates bootstrap.cjs, index.ts, tsconfig.json, constants/, plop-kit.config.js, and adds gen + gen:meta to package.json.

gen-command

Scaffolds a new command inside an existing category. Detects whether the category uses TypeScript or JavaScript and generates index.ts/js, actions.ts/js, prompts.ts/js, and starter .hbs template files accordingly.

When creating a new category, also generates plopfile.ts, bootstrap.cjs, and inserts a gen:{category} npm script into package.json after anchorScript.


Custom commands (beyond scaffolding)

defineCommand isn't limited to file generation. Any logic that belongs in the developer toolbox — running scripts, parsing existing files, orchestrating multiple generators — can be registered as a command and appear in the same unified menu.

Shell command wrapper

Run an existing npm script (or any shell command) as a plop action:

export function iconsActions(): ActionType[] {
  return [
    {
      type: 'custom',
      description: 'Generate icons via SVGR',
      async action() {
        const { execSync } = require('child_process')
        execSync('npm run gen:icons', { stdio: 'inherit' })
        return 'done'
      },
    },
  ]
}

// index.ts
export = (plop: NodePlopAPI) => defineCommand(plop, {
  name: 'icons',
  category: 'tools',
  description: 'Regenerate icon components from SVG sources',
  help: 'Runs SVGR on svg-icons/files/ → src/components/icons/.',
  prompts: [],
  actions: iconsActions,
})

The developer runs npm run gen:tools, picks icons from the menu, and the script executes — no need to remember the underlying command.

Orchestrator (compose multiple generators)

A command can read existing files and call other commands' action functions directly, assembling a composite action list at runtime:

import { getBlockCreateActions }     from '../ui/screen-block/actions'
import { getScreenComponentActions } from '../ui/screen-component/actions'

export function componentParserActions(data: { file: string }): ActionType[] {
  const content = fs.readFileSync(screenPath(data.file), 'utf8')
  const parsed  = extractComponentsFromComments(content)

  return parsed.flatMap(item => {
    if (item.type === 'block')            return getBlockCreateActions(item)
    if (item.type === 'screen-component') return getScreenComponentActions(item)
    return []
  })
}

This lets a single command parse a file annotated with special comments and scaffold everything it finds — blocks, components, stores — in one run, reusing the same action functions used by individual generators.


Testing

npm test          # vitest run (single pass)
npm run test:watch  # watch mode

Tests live in tests/ and cover name-utils, dry-run, bypass args, action helpers, and defineCommand / configure behavior. All tests use vitest with a mock plop object — no disk writes except create-actions.test.js which writes to os.tmpdir().


CHANGELOG

See CHANGELOG.md.