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

@environmentvariable/create-keel

v0.1.2

Published

Scaffold production-ready Keel full-stack monorepos

Readme

create-keel

Scaffolds the Keel three-plane webapp architecture — a production-ready monorepo with a React UI, Fastify API, and background Worker, wired together with Docker Compose, Postgres, MinIO, and Caddy.

┌─────────┐      ┌───────────┐      ┌──────────┐
│   UI    │─────▶│   Caddy   │─────▶│   API    │
│  React  │      │  reverse  │      │ Fastify  │
│  Vite   │      │   proxy   │      │ Drizzle  │
└─────────┘      └───────────┘      └────┬─────┘
                                         │
                              ┌──────────┼──────────┐
                              │          │          │
                         ┌────▼───┐ ┌────▼───┐ ┌───▼────┐
                         │Postgres│ │ MinIO  │ │ Worker │
                         │  16    │ │  S3    │ │  jobs  │
                         └────────┘ └────────┘ └────────┘

Quick start

npx @environmentvariable/create-keel my-app
cd my-app
./scripts/dev.sh

This will prompt you for configuration choices, generate the project, install dependencies, and initialise a git repo. The dev script starts all services via Docker Compose.

To skip prompts and use sensible defaults:

npx @environmentvariable/create-keel my-app --defaults

Publish + run with npx

npx by package name resolves from the npm registry, so you should publish this CLI package first.

This repo is configured to publish as a public scoped package by default (publishConfig.access=public).

If you need npm auth locally, configure it with:

npm config set //registry.npmjs.org/:_authToken=<NPM_TOKEN>

Manual publish checklist (fallback):

# 1) Verify build and tests, and inspect publish payload
pnpm publish:check

# 2) Publish
pnpm publish --access public

After publish:

npx @environmentvariable/create-keel@latest my-app --defaults

Automated release workflow

GitHub Actions workflows:

  • .github/workflows/changesets.yml runs on every push to master and creates/updates a release PR from pending changesets.
  • .github/workflows/publish.yml runs on every push to master and publishes automatically when the package version changes.
  • If the version in package.json is unchanged, publish exits early with a skip message.
  • .github/workflows/publish.yml also supports manual dispatch with:
    • access: restricted or public
    • dry_run: true/false

Required repository secret:

  • NPM_TOKEN (publish-capable npm token)

Optional repository variable:

  • NPM_PUBLISH_ACCESS (restricted or public) to set default publish access.

Changesets-based release flow

For managed versioning/changelogs, add a changeset in each release-worthy PR:

# add a release note entry
pnpm changeset

# merge your feature PR into master
# changesets workflow opens/updates release PR automatically
# after release PR is merged, publish workflow publishes automatically

Manual commands are still available if needed:

pnpm version-packages
pnpm release

If you fork this project, replace @environmentvariable with your own npm scope in package.json and docs.

CLI options

| Flag | Values | Default | Description | |---|---|---|---| | --defaults | — | — | Skip interactive prompts, use all defaults | | --auth <provider> | clerk supabase oidc none | clerk | Authentication provider | | --storage <provider> | minio r2 s3 | minio | Object storage backend | | --redis | — | false | Include a Redis service | | --jobs <list> | comma-separated | none | Example job handlers to include (send-email, process-image, generate-pdf) | | --styling <styling> | tailwind plain none | tailwind | UI styling approach |

Options can be combined with --defaults to override individual choices:

npx @environmentvariable/create-keel my-app --defaults --auth supabase --redis

Additional command:

  • npx @environmentvariable/create-keel update-plan [projectDir] — analyze drift between a generated project and the latest scaffold.
  • npx @environmentvariable/create-keel update-bundle [projectDir] — generate an apply-ready patch bundle (files overlay + remove list + script).

Updating existing generated projects

Generated projects now include a metadata file at .keel/scaffold-state.json with the options and scaffold reference used at generation time.

Use the new update planner to compare an existing project against the latest scaffold:

# Human-readable report
npx @environmentvariable/create-keel update-plan ./my-app

# JSON report for automation
npx @environmentvariable/create-keel update-plan ./my-app --json

Generate an apply-ready bundle (replacement files + remove list + apply script):

# Human-readable summary
npx @environmentvariable/create-keel update-bundle ./my-app

# JSON output for automation
npx @environmentvariable/create-keel update-bundle ./my-app --json

Bundle output (default):

./my-app/.keel/update-bundles/<timestamp>-<scaffold-ref>/
├── files/            # Add/changed files to copy into project
├── remove-files.txt  # Files to delete from project
├── plan.txt          # Same summary as update-plan
├── manifest.json     # Machine-readable metadata
├── apply.sh          # Applies files/ + removals
└── APPLY.md          # Step-by-step instructions

Safety behavior:

  • update-plan reports project-only files (files present in your repo but not in scaffold baseline).
  • update-bundle keeps those project-only files untouched by default.
  • Only files tracked as scaffold-managed and removed upstream are added to remove-files.txt.

For older projects that do not have .keel/scaffold-state.json, provide the baseline options explicitly:

npx @environmentvariable/create-keel update-plan ./legacy-app \
  --project-name legacy-app \
  --auth clerk \
  --storage minio \
  --styling tailwind \
  --redis \
  --jobs send-email,process-image

The same legacy flags work for update-bundle.

Batch planning across many repos

If you manage many generated projects, you can produce update reports in a loop:

for project in /path/to/projects/*; do
  [ -d "$project" ] || continue
  npx @environmentvariable/create-keel update-plan "$project" --json > "$project/.keel-update-plan.json"
done

Then triage by priority (for example: projects with large changed counts first), update each project on a branch, and run that project's typecheck/tests before merging.

Batch bundle generation across many repos

for project in /path/to/projects/*; do
  [ -d "$project" ] || continue
  npx @environmentvariable/create-keel update-bundle "$project" --json > "$project/.keel-update-bundle.json"
done

Then apply each bundle inside that project repo:

bash "$project/.keel/update-bundles/<bundle-id>/apply.sh" "$project"

What you get

The generated project is a pnpm monorepo with this structure:

my-app/
├── apps/
│   ├── api/          # Fastify REST API
│   ├── worker/       # Background job processor
│   └── ui/           # React + Vite + TanStack Router
├── packages/
│   └── shared/       # Shared types, schemas, constants
├── docker-compose.yml
├── docker-compose.dev.yml
├── Caddyfile
├── .env
└── scripts/
    ├── dev.sh        # Start dev environment
    └── migrate.sh    # Run database migrations

UI plane — apps/ui

React SPA with shadcn/ui components, ready to extend:

  • Tailwind CSS v4 via @tailwindcss/vite — no PostCSS config needed
  • shadcn/ui — Button, Card included; add more with pnpm dlx shadcn@latest add <component>
  • TanStack Router — type-safe file-based routing
  • TanStack Query — server state management
  • Clerk auth (configurable) — <SignedIn> / <SignedOut> gates
  • @/ path alias configured in tsconfig + Vite

API plane — apps/api

Fastify server with batteries included:

| Feature | Details | |---|---| | Database | Drizzle ORM + Postgres, schema + migrations included | | Auth | JWT verification via JWKS, dev mode bypass | | File uploads | Presigned S3 URLs (MinIO / R2 / AWS S3) | | Job queue | Enqueue jobs for the Worker via POST /jobs | | Webhooks | Ingestion with deduplication via POST /webhooks/:source | | Observability | Pino structured logging, request IDs, health check | | Validation | Zod schemas on all inputs | | Rate limiting | 100 req/min per user/IP | | CORS | Enabled with credentials |

API routes:

| Method | Path | Auth | Description | |---|---|---|---| | GET | /health | Public | DB connectivity check + uptime | | POST | /jobs | Protected | Enqueue a background job | | GET | /jobs/:id | Protected | Get job status | | POST | /files/upload-url | Protected | Generate presigned upload URL | | POST | /files/:fileId/confirm | Protected | Confirm file upload completion | | POST | /webhooks/:source | Public | Ingest webhook events (deduped) |

Worker plane — apps/worker

Polling-based background job processor:

  • Claims jobs atomically with FOR UPDATE SKIP LOCKED — no Redis required
  • Configurable concurrency, poll interval, and stale job reclamation
  • Retry with dead-letter after max attempts
  • Graceful shutdown on SIGTERM/SIGINT
  • Handler registry pattern — add new job types in src/handlers/

Shared — packages/shared

Zod schemas, TypeScript types, error codes, and job type constants shared between API and Worker.

Infrastructure

| Service | Image | Dev ports | Purpose | |---|---|---|---| | Postgres 16 | postgres:16-alpine | 5432 | Primary database | | MinIO | minio/minio | 9000 9001 | S3-compatible object storage | | Caddy 2 | caddy:2-alpine | 80 443 | Reverse proxy (/api/* → API, /* → UI) |

Generated project commands

# Start all services in dev mode (with hot reload)
./scripts/dev.sh

# Run database migrations
./scripts/migrate.sh

# Run all tests
pnpm -r --parallel test

# Typecheck all packages
pnpm -r typecheck

# Build everything
pnpm -r build

# Run just the UI dev server
pnpm --filter ui dev

# Add a shadcn component
cd apps/ui && pnpm dlx shadcn@latest add dialog

Environment variables

Documented in the generated .env.example:

| Variable | Default | Description | |---|---|---| | NODE_ENV | development | Runtime environment | | PORT | 3001 | API server port | | LOG_LEVEL | info | Pino log level (debug, info, warn, error) | | DATABASE_URL | postgresql://keel:keel@localhost:5432/keel | Postgres connection string | | DB_POOL_MIN | 2 | Minimum database pool connections | | DB_POOL_MAX | 10 | Maximum database pool connections | | REDIS_URL | — | Redis connection (only when --redis flag is used) | | S3_ENDPOINT | http://localhost:9000 | S3/MinIO endpoint | | S3_REGION | auto | S3 region | | S3_BUCKET | keel-uploads | Upload bucket name | | S3_ACCESS_KEY_ID | minioadmin | S3 access key | | S3_SECRET_ACCESS_KEY | minioadmin | S3 secret key | | S3_FORCE_PATH_STYLE | true | Required for MinIO; disable for AWS S3 | | AUTH_PROVIDER | clerk | Auth provider (clerk, supabase, oidc, none) | | AUTH_JWKS_URL | — | JWKS endpoint for JWT verification | | AUTH_ISSUER | — | Expected JWT issuer claim | | AUTH_AUDIENCE | — | Expected JWT audience claim | | WORKER_POLL_INTERVAL_MS | 1000 | Worker polling delay when idle (ms) | | WORKER_CONCURRENCY | 3 | Max concurrent job processing | | WORKER_STALE_AFTER_MS | 300000 | Reclaim jobs older than this (5 min) | | WEBHOOK_SIGNING_SECRET | — | Secret for webhook signature verification | | ENCRYPTION_KEY | — | Application-level encryption key |


Development (contributing to create-keel itself)

pnpm install
pnpm dev my-app --defaults    # scaffold a project into ./tmp/my-app
pnpm test                     # run all tests (unit + e2e)
pnpm typecheck                # type-check the CLI source
pnpm build                    # build CLI with tsup

Cursor Cloud environment setup

If you use Cursor Cloud agents often on this repo, use the env setup prompt in:

How it works

  1. The scaffold/ directory is the golden copy of a generated project — a real, working monorepo.
  2. templates/root/ mirrors scaffold/ and may contain .hbs (Handlebars) files for conditional rendering.
  3. On generation, syncAndGenerateProject() (a lock-protected wrapper) syncs scaffold/templates/root/, then renders templates into the target project.

To add features to the generated project, edit files in scaffold/ first, then add .hbs variants in templates/root/ if the feature needs conditional logic.

Project structure

create-keel/
├── bin/create-keel.ts       # CLI entry point
├── src/
│   ├── index.ts             # Commander setup, orchestration
│   ├── cli.ts               # Interactive prompts (@clack/prompts)
│   ├── generator.ts         # Template sync + rendering
│   ├── update.ts            # Drift analysis against latest scaffold
│   ├── meta.ts              # Generator/scaffold metadata resolution
│   ├── actions.ts           # Post-gen: install deps, init git, create .env
│   ├── types.ts             # GeneratorOptions type
│   └── utils/template.ts    # Handlebars rendering
├── scaffold/                # Golden copy of generated project
├── templates/root/          # Templated copy (auto-synced from scaffold)
└── test/
    ├── unit/                # Unit tests
    └── e2e/                 # End-to-end generation tests