mirror of
https://github.com/jakejarvis/stanza.git
synced 2026-07-16 19:16:00 -04:00
docs: expand README and documentation site with concepts, getting-started, and CLI reference pages
- Add `concepts.mdx` covering categories, cardinality, home, vendoring, peers/adapters, manifest, regions, and the open registry model - Flesh out `getting-started.mdx` with prerequisites, install/run steps, package-manager variants, inspect/remove commands, and a non-interactive `--yes` example - Rewrite `cli-reference.mdx` with per-verb sections, global flags, environment variables, and dependency-versioning semantics - Expand `README.md` with a "Why Stanza?" section, quick-start snippet, docs link, and updated module list; swap `pnpm create` example for `npm init` - Add `concepts` to `meta.json` nav order
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
"@stanza/cli": minor
|
||||
"stanza-cli": minor
|
||||
---
|
||||
|
||||
Add multi-choice **add-on** modules. A `Module` is now a discriminated union on `kind` (`"slot"` default, or `"addon"` carrying a `category`), and `stanza.json` records add-ons in a new `addons` field keyed by category (each holding 0..n modules). Add-on categories (`testing`, `tooling`, `deploy`, `email`, `monorepo`) are disjoint from slots, so they never constrain another module's adapter dispatch — but they can still target a framework via `peers` + per-framework adapters.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
"@stanza/cli": minor
|
||||
"stanza-cli": minor
|
||||
---
|
||||
|
||||
Unify the module taxonomy into one `Category` concept. The old slot/add-on split
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
"@stanza/cli": minor
|
||||
"stanza-cli": minor
|
||||
---
|
||||
|
||||
Add a single-choice **tooling** slot for the lint/format toolchain, with three modules: `eslint-prettier` (ESLint flat config + Prettier, per-framework adapters), `biome`, and `oxlint-oxfmt` (both framework-agnostic). Modeled as a slot rather than a multi-choice add-on because the three toolchains are mutually exclusive substitutes.
|
||||
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
run: pnpm test
|
||||
|
||||
- name: Build CLI bundle
|
||||
run: pnpm --filter @stanza/cli build
|
||||
run: pnpm --filter stanza-cli build
|
||||
|
||||
- name: Smoke-test built CLI
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,7 @@ Three things differentiate stanza from other scaffolders:
|
||||
|
||||
## Layout
|
||||
|
||||
- `apps/cli/` — `@stanza/cli`, node entrypoint at `src/bin.ts` (run via tsx in dev, tsdown-built to `dist/bin.mjs` for publish)
|
||||
- `apps/cli/` — `stanza-cli`, node entrypoint at `src/bin.ts` (run via tsx in dev, tsdown-built to `dist/bin.mjs` for publish)
|
||||
- `apps/web/` — `@stanza/web`, TanStack Start visual builder (Vite-native, no Vinxi)
|
||||
- `packages/registry/` — shared schema, category/peer resolver, Zod manifest validator
|
||||
- `packages/codemods/` — ts-morph helpers (idempotent + reversible)
|
||||
@@ -22,8 +22,8 @@ In a **generated project**, a module's output lands per its category's `home` (i
|
||||
|
||||
## Commands
|
||||
|
||||
- `pnpm --filter @stanza/cli dev -- <verb>` — run CLI directly via `tsx watch ./src/bin.ts`; no build step. Or `tsx apps/cli/src/bin.ts <verb>` for a one-shot run
|
||||
- `pnpm --filter @stanza/cli build` — build the publishable CLI via tsdown (compiles to ESM JS at `apps/cli/dist/`, externalizes npm deps, inlines workspace packages). Same for `create-stanza`
|
||||
- `pnpm --filter stanza-cli dev -- <verb>` — run CLI directly via `tsx watch ./src/bin.ts`; no build step. Or `tsx apps/cli/src/bin.ts <verb>` for a one-shot run
|
||||
- `pnpm --filter stanza-cli build` — build the publishable CLI via tsdown (compiles to ESM JS at `apps/cli/dist/`, externalizes npm deps, inlines workspace packages). Same for `create-stanza`
|
||||
- `pnpm registry:build` — regenerate `dist/registry/{index,modules/*}.json`. Uses bun for maintainer convenience (the script body is portable; `tsx scripts/registry-build.ts` works too)
|
||||
- `pnpm --filter @stanza/web dev` — TanStack Start dev server. `prebuild` invokes [`apps/web/scripts/prepare-registry.sh`](apps/web/scripts/prepare-registry.sh) which copies the built `dist/registry/` into `apps/web/public/registry/`. Since `dist/registry/` is gitignored, the script builds it first when it's missing (e.g. on Vercel's clean checkout) — preferring `bun` locally and falling back to `pnpm exec tsx` on node-only deploy targets. The deployed site ships this directory as static assets, so CLI and web consume the same JSON. `public/registry/` is also registered as a Nitro `serverAssets` dir (vite.config.ts), so its contents are embedded into the server bundle at build time — SSR reads it via `useStorage("assets:registry")` (not the CDN), which is why it works on Vercel where `public/` is absent from the function fs
|
||||
- `pnpm lint` / `pnpm lint:fix` — Oxlint across the whole repo (config: `.oxlintrc.json`)
|
||||
@@ -54,7 +54,7 @@ Core workflow:
|
||||
- **Node-only at runtime.** The CLI source uses node APIs and is dev-run via `tsx`; the published binary is plain ESM JS (`#!/usr/bin/env node`). The only place bun appears is the shebang on root maintainer scripts (`scripts/*.ts`) for our own convenience — those scripts don't use any `Bun.*` APIs and run fine under tsx/node
|
||||
- **Build pipeline**: `tsdown` compiles each publishable package to ESM JS in `dist/`. External npm deps are _not_ bundled (users install them via the normal dep chain); workspace deps are _inlined_ (we don't publish `@stanza/registry` and `@stanza/codemods` separately). Transitive runtime deps (`ts-morph`, `zod`) MUST be declared as direct `dependencies` of the publishable package or tsdown will inline them into the bundle
|
||||
- **Per-workspace `dist/` paths**: `main`/`types` in the source `package.json` still point at `./src/` so other workspaces resolve `.ts` directly during dev. The published tarball overrides via `publishConfig` to point at `./dist/<x>.mjs`/`.d.mts`
|
||||
- **Publishing**: only `@stanza/cli` and `create-stanza` ship to npm. `@stanza/codemods` + `@stanza/registry` are marked `private: true` (inlined into the CLI bundle by tsdown); `@stanza/web` is private (deployed as a Vercel site, not an npm package); the `registry/modules/*` packages are also private (they're registry data, not npm packages). Releases go through **Changesets**: drop a markdown file via `pnpm changeset`, push to main → the [release workflow](.github/workflows/release.yml) opens a "Version Packages" PR; merging that PR triggers the same workflow to run `pnpm release` (build CLI + create-stanza, then `changeset publish` to npm). Requires `NPM_TOKEN` in repo secrets; provenance attestations are emitted via `id-token: write` + `NPM_CONFIG_PROVENANCE=true`
|
||||
- **Publishing**: only `stanza-cli` and `create-stanza` ship to npm. `@stanza/codemods` + `@stanza/registry` are marked `private: true` (inlined into the CLI bundle by tsdown); `@stanza/web` is private (deployed as a Vercel site, not an npm package); the `registry/modules/*` packages are also private (they're registry data, not npm packages). Releases go through **Changesets**: drop a markdown file via `pnpm changeset`, push to main → the [release workflow](.github/workflows/release.yml) opens a "Version Packages" PR; merging that PR triggers the same workflow to run `pnpm release` (build CLI + create-stanza, then `changeset publish` to npm). Requires `NPM_TOKEN` in repo secrets; provenance attestations are emitted via `id-token: write` + `NPM_CONFIG_PROVENANCE=true`
|
||||
- pnpm 10 + `node-linker: isolated` — each workspace MUST declare `@types/node` in its own devDeps and set `types: ["node"]` in tsconfig (auto-discovery doesn't reach into the isolated `node_modules/@types`)
|
||||
- TypeScript 6 — `allowImportingTsExtensions: true` + `noEmit: true` is set in `tsconfig.json`; the CLI/create-stanza emit JS via tsdown, the registry/codemods packages stay source-only and never emit
|
||||
- `tsconfig.json` excludes `**/templates/**` globally — template files target user projects, not this repo
|
||||
|
||||
@@ -1,32 +1,57 @@
|
||||
# Stanza
|
||||
|
||||
Modular monorepo template CLI — shadcn for full-stack TypeScript stacks.
|
||||
Modular monorepo template CLI — aka shadcn for full-stack TypeScript projects.
|
||||
|
||||
```sh
|
||||
pnpm create stanza my-app
|
||||
npm init stanza my-app
|
||||
```
|
||||
|
||||
Pick a framework, ORM, database, auth provider, styling — get a clean monorepo with idiomatic code, vendored into your repo. Add modules later with `stanza add`.
|
||||
Pick a framework, ORM, database, auth provider, and styling — get a clean monorepo with idiomatic code, vendored into your repo. Layer in more modules later with `stanza-cli add`.
|
||||
|
||||
Generated projects keep slot boundaries explicit: `auth`, `db`, and `orm` install into their own internal workspace packages (`packages/auth/`, `packages/db/`, named `@<your-app>/auth`, `@<your-app>/db`), and your app consumes them via `workspace:*` deps. Swapping an auth provider replaces the contents of `packages/auth/` without touching your app's imports.
|
||||
## Why Stanza?
|
||||
|
||||
- **`add` works after `init`.** Run `stanza add` on an existing project — it's manifest-driven and peer-aware, so it picks the right adapter for your stack and wires deps, env, and templates into the correct workspace package.
|
||||
- **Your code, vendored.** Generated files land in your repo verbatim. There's no Stanza runtime to install or carry around.
|
||||
- **Open registry.** Modules are static JSON. Point the CLI at your own host and serve custom modules.
|
||||
|
||||
## Quick start
|
||||
|
||||
```sh
|
||||
npm init stanza my-app
|
||||
cd my-app
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Add a module to an existing project at any time:
|
||||
|
||||
```sh
|
||||
npx stanza-cli add auth better-auth
|
||||
```
|
||||
|
||||
`auth`, `db`, and `orm` install into their own internal workspace packages (`packages/auth/`, `packages/db/`, named `@<your-app>/auth`, `@<your-app>/db`); your app consumes them via `workspace:*`. Swapping an auth provider replaces the contents of `packages/auth/` without touching your app's imports.
|
||||
|
||||
## Docs
|
||||
|
||||
Full guides and the CLI reference live at **[stanza.tools/docs](https://stanza.tools/docs)**. Assemble a stack visually with the builder at **[stanza.tools](https://stanza.tools)**.
|
||||
|
||||
## What's inside
|
||||
|
||||
```
|
||||
apps/
|
||||
cli/ # @stanza/cli — the CLI binary
|
||||
cli/ # stanza-cli — the CLI binary
|
||||
web/ # https://stanza.tools (TanStack Start)
|
||||
packages/
|
||||
registry/ # shared schema, slot/peer/capability resolver
|
||||
codemods/ # ts-morph helpers for region-aware patching
|
||||
create-stanza/ # `pnpm create stanza` shim
|
||||
registry/
|
||||
modules/ # first-party modules (framework, orm, db, auth, styling)
|
||||
modules/ # first-party modules (framework, orm, db, auth, styling, tooling, testing)
|
||||
```
|
||||
|
||||
## Status
|
||||
## Contributing
|
||||
|
||||
Work in progress. See [CLAUDE.md](./CLAUDE.md), [TODO.md](./TODO.md), and [REGISTRY.md](./REGISTRY.md) for current state.
|
||||
Work in progress. See [CLAUDE.md](./CLAUDE.md) for architecture and toolchain invariants, [REGISTRY.md](./REGISTRY.md) for the module roadmap, and [TODO.md](./TODO.md) for active work.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -126,13 +126,13 @@ The full first-party module roadmap lives in [REGISTRY.md](REGISTRY.md). These a
|
||||
- [ ] Golden snapshot tests per module combination (per the plan's verification section) — for each valid `(framework, orm, db, auth, styling, pm)` tuple, run `stanza init` headless, snapshot the tree, compare against fixture
|
||||
- [ ] Integration test for the canonical stack — Docker Postgres + Playwright sign-up flow
|
||||
- [ ] Registry deploy pipeline — on push to main, build `dist/registry/` and push to Vercel/CF
|
||||
- [x] npm publish workflow — Changesets configured (`.changeset/config.json`, public access, `@changesets/changelog-github` for PR links). [Release workflow](.github/workflows/release.yml) runs `changesets/action@v1` on push to main: opens a "Version Packages" PR when changesets queue up; merging publishes via `pnpm release` (build CLI + create-stanza, then `changeset publish`). Provenance attestations enabled. Tarball dry-run verified: `@stanza/cli` is 20 KB, `create-stanza` is 1.7 KB; both `npm install`-able from a clean tmpdir and `--version` / `--help` work. Repo needs `NPM_TOKEN` secret before the first publish
|
||||
- [x] npm publish workflow — Changesets configured (`.changeset/config.json`, public access, `@changesets/changelog-github` for PR links). [Release workflow](.github/workflows/release.yml) runs `changesets/action@v1` on push to main: opens a "Version Packages" PR when changesets queue up; merging publishes via `pnpm release` (build CLI + create-stanza, then `changeset publish`). Provenance attestations enabled. Tarball dry-run verified: `stanza-cli` is 20 KB, `create-stanza` is 1.7 KB; both `npm install`-able from a clean tmpdir and `--version` / `--help` work. Repo needs `NPM_TOKEN` secret before the first publish
|
||||
- [ ] `.env.example` at repo root listing `STANZA_REGISTRY`, PostHog key, etc.
|
||||
|
||||
## Open items from the plan
|
||||
|
||||
- [x] Domain — `stanza.tools` (registry served at `https://stanza.tools/registry`, web builder at `https://stanza.tools`)
|
||||
- [ ] npm scope clearance — `@stanza` scope availability (the CLI assumes `@stanza/cli`); fall back to unscoped `stanza` if taken
|
||||
- [x] npm name clearance — locked down unscoped `stanza-cli` and `create-stanza` on npm; the CLI publishes as `stanza-cli` (binary stays `stanza`)
|
||||
- [ ] Better Auth vs Clerk feature parity — Clerk wraps its own UI, Better Auth is headless; document the difference or ship shared UI stubs (`SignInForm`, callback page) that each adapter fills in
|
||||
|
||||
## Out of scope for now
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@stanza/cli",
|
||||
"name": "stanza-cli",
|
||||
"version": "0.1.0",
|
||||
"description": "Modular monorepo template CLI — shadcn for stacks.",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,21 +1,103 @@
|
||||
---
|
||||
title: CLI reference
|
||||
description: The stanza command-line verbs at a glance.
|
||||
description: The stanza command-line verbs, flags, and environment variables.
|
||||
---
|
||||
|
||||
stanza currently ships five verbs against the slot taxonomy. More (`swap`,
|
||||
`update`) are planned.
|
||||
stanza ships five verbs against the category taxonomy. Run any verb with
|
||||
`--help` for its full flag list:
|
||||
|
||||
| Verb | What it does |
|
||||
| -------- | --------------------------------------------- |
|
||||
| `init` | Scaffold a new monorepo from a stack |
|
||||
| `add` | Add a module to an existing project |
|
||||
| `remove` | Remove a module and sweep its claimed regions |
|
||||
| `list` | List installed modules |
|
||||
| `search` | Search the registry |
|
||||
|
||||
Run any verb with `--help` for its flags:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
stanza add --help
|
||||
```
|
||||
|
||||
## `init`
|
||||
|
||||
Scaffold a new monorepo. Without flags it launches an interactive wizard;
|
||||
with `--yes` it takes every pick from flags.
|
||||
|
||||
```sh
|
||||
stanza init [name] --yes --framework=next --orm=drizzle --db=postgres --pm=pnpm
|
||||
```
|
||||
|
||||
- `name` — project directory name (positional; prompted if omitted).
|
||||
- `--yes` — non-interactive; take selections from category flags.
|
||||
- `--<category>=<ids>` — pick modules for a category. One flag per category
|
||||
(`--framework`, `--styling`, `--db`, `--orm`, `--auth`, `--tooling`,
|
||||
`--testing`); single-choice categories take one id, multi-choice categories
|
||||
take a comma-separated list. Omitted categories are skipped — `--yes` chooses
|
||||
no defaults.
|
||||
- `--pm=<pnpm|bun|npm>` — package manager recorded in the manifest.
|
||||
|
||||
## `add`
|
||||
|
||||
Add one module to an existing project.
|
||||
|
||||
```sh
|
||||
stanza add <category> <module>
|
||||
```
|
||||
|
||||
Resolves peers, selects the matching adapter, and writes the module's templates,
|
||||
deps, env, and scripts to the right home. Adding a module to a single-choice
|
||||
category that's already filled fails until you remove the existing one.
|
||||
|
||||
## `remove`
|
||||
|
||||
Remove a module and sweep the files (regions) it owns.
|
||||
|
||||
```sh
|
||||
stanza remove <category> [id]
|
||||
```
|
||||
|
||||
For single-choice categories the `id` is optional; for multi-choice categories
|
||||
(like `testing`) it's required.
|
||||
|
||||
## `list`
|
||||
|
||||
Print installed modules, grouped by category, from the nearest `stanza.json`.
|
||||
|
||||
```sh
|
||||
stanza list
|
||||
```
|
||||
|
||||
## `search`
|
||||
|
||||
List registry modules and their `category/id` pairs. Pass a query to filter.
|
||||
|
||||
```sh
|
||||
stanza search [query]
|
||||
```
|
||||
|
||||
Use the printed **id** (not the display label) when passing a module to `add`.
|
||||
|
||||
<Callout type="info">
|
||||
**Planned:** `swap` (replace a module with another in the same category) and
|
||||
`update` (re-pull a module at a newer version) are on the roadmap. The manifest
|
||||
already reserves the fields they need; the verbs aren't implemented yet.
|
||||
</Callout>
|
||||
|
||||
## Global flags
|
||||
|
||||
These apply to the mutating verbs (`init`, `add`, `remove`):
|
||||
|
||||
- `--dry-run` — print the actions that would be taken and write nothing.
|
||||
- `--dangerously-allow-dirty` — allow a mutating command to run with a dirty git
|
||||
working tree. By default stanza refuses, so its edits never mix with
|
||||
uncommitted changes. Commit or stash first when you can.
|
||||
- `--no-telemetry` — disable anonymous usage events for this invocation.
|
||||
|
||||
## Environment variables
|
||||
|
||||
- `STANZA_REGISTRY=<url-or-path>` — use a custom or self-hosted registry (HTTP
|
||||
URL or filesystem path) instead of the default.
|
||||
- `STANZA_NO_NPM_LOOKUP=1` — skip npm version lookups and write dependency ranges
|
||||
verbatim.
|
||||
- `STANZA_NPM_REGISTRY=<url>` — override the npm registry used for version lookups.
|
||||
- `STANZA_TELEMETRY=0` / `DO_NOT_TRACK=1` — disable telemetry persistently.
|
||||
Telemetry is also auto-skipped in CI.
|
||||
|
||||
## Dependency versioning
|
||||
|
||||
On `init` and `add`, stanza bumps each `^`/`~` dependency range to the latest npm
|
||||
version that satisfies it, keeping the modifier. Other ranges and `workspace:*`
|
||||
specifiers are written as-is. When offline, it falls back to the range declared
|
||||
in the module.
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
title: Concepts
|
||||
description: The mental model behind stanza — categories, vendoring, peers, and the manifest.
|
||||
---
|
||||
|
||||
A handful of ideas explain how stanza decides what to write and where. Once they
|
||||
click, the CLI's behavior is predictable.
|
||||
|
||||
## Categories
|
||||
|
||||
Every module belongs to exactly one **category**. A category has two independent
|
||||
properties that govern how its modules behave:
|
||||
|
||||
- **Cardinality** — `one` (single-choice) or `many` (coexisting).
|
||||
- **Home** — where a module's output lands: `app`, `repo`, or `package`.
|
||||
|
||||
| Category | Cardinality | Home |
|
||||
| ----------- | ----------- | --------- |
|
||||
| `framework` | one | app |
|
||||
| `styling` | one | app |
|
||||
| `db` | one | package |
|
||||
| `orm` | one | package |
|
||||
| `auth` | one | package |
|
||||
| `tooling` | one | repo |
|
||||
| `testing` | many | app |
|
||||
|
||||
A `one` category holds at most one module — adding a second fails until you
|
||||
remove the first. A `many` category lets modules coexist (e.g. Vitest and
|
||||
Playwright side by side).
|
||||
|
||||
**Home** decides placement. `app` modules wire your app shell (in `apps/web/`,
|
||||
say). `repo` modules write config at the monorepo root. `package` modules install
|
||||
into their own internal workspace package under `packages/<dir>/`, named
|
||||
`@<your-app>/<dir>`, which your app consumes via `workspace:*`. `db` and `orm`
|
||||
share a single `packages/db/` so the ORM client sits next to the schema it
|
||||
queries.
|
||||
|
||||
<Callout type="info">
|
||||
**Planned:** more categories are on the roadmap — `api`, `ai`, `payments`
|
||||
(single-choice) and `email`, `deploy` (coexisting). They aren't shipping yet,
|
||||
but the taxonomy is designed to absorb them without breaking existing
|
||||
projects.
|
||||
</Callout>
|
||||
|
||||
## Vendoring
|
||||
|
||||
stanza copies a module's code into your repo verbatim. There's no shared runtime
|
||||
package — the generated files are ordinary source you can read, edit, and commit.
|
||||
That means upgrades never silently change your app's behavior, and you're never
|
||||
locked into stanza's abstractions.
|
||||
|
||||
## Peers and adapters
|
||||
|
||||
Modules declare **peers** — the other categories they care about. When you `add`
|
||||
a module, stanza reads your current selections and picks the **adapter** that
|
||||
matches. Better Auth, for instance, peers on both `framework` and `orm`, so
|
||||
adding it to a Next + Drizzle app produces different wiring than a TanStack Start
|
||||
+ Prisma app. You pick the module; stanza picks the right variant.
|
||||
|
||||
## The manifest (`stanza.json`)
|
||||
|
||||
The `stanza.json` file at your repo root is the source of truth for what's
|
||||
installed. It records your module selections per category and the **regions** —
|
||||
the files and file-fragments — each module owns. `add`, `remove`, and `list` all
|
||||
read it. Treat it as generated state: don't hand-edit it unless you're repairing
|
||||
something deliberately.
|
||||
|
||||
## Regions
|
||||
|
||||
A **region** is a claim on a file (or a section of one) by a specific module.
|
||||
This is how `stanza remove` knows exactly what to delete and what to leave alone:
|
||||
it sweeps only the regions the removed module owns. Because regions key on the
|
||||
module, two modules can safely write disjoint parts of the same file — Vitest
|
||||
owning the `test` script and Playwright owning `test:e2e`, for example, without
|
||||
conflict.
|
||||
|
||||
## Open registry
|
||||
|
||||
A registry is just static JSON: an index plus one file per module, each carrying
|
||||
its templates, dependencies, env vars, and codemod invocations. The CLI ships
|
||||
with a default registry, but you can point it anywhere — a URL or a local path —
|
||||
with `STANZA_REGISTRY`. That's the hook for self-hosting your own modules or
|
||||
pinning a fixture in CI.
|
||||
@@ -5,20 +5,94 @@ description: Create your first stanza project and add modules.
|
||||
|
||||
Scaffold a new monorepo, then layer in modules as you go.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** 20 or newer.
|
||||
- A package manager — **pnpm** (recommended), **npm**, or **bun**. stanza writes
|
||||
a workspace-based monorepo, so a workspace-aware package manager is required.
|
||||
|
||||
## Create a project
|
||||
|
||||
```bash
|
||||
The create command launches an interactive wizard that walks you through each
|
||||
category:
|
||||
|
||||
```sh
|
||||
pnpm create stanza my-app
|
||||
```
|
||||
|
||||
The same command works across package managers:
|
||||
|
||||
```sh
|
||||
npm create stanza@latest -- my-app
|
||||
bun create stanza@latest my-app
|
||||
```
|
||||
|
||||
To run it without the wizard — in CI, for example — see
|
||||
[Non-interactive setup](#non-interactive-setup) below.
|
||||
|
||||
## What you get
|
||||
|
||||
stanza generates a clean monorepo: your app under `apps/`, internal packages
|
||||
under `packages/`, and a `stanza.json` manifest at the root. The manifest records
|
||||
which modules you selected and which files each one owns — it's how `add` and
|
||||
`remove` know what's installed and what to clean up. You generally don't edit it
|
||||
by hand.
|
||||
|
||||
## Install and run
|
||||
|
||||
```sh
|
||||
cd my-app
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Add a module
|
||||
|
||||
Every slot — `framework`, `styling`, `db`, `orm`, `auth` — is one command away:
|
||||
Every category — `framework`, `styling`, `db`, `orm`, `auth`, `tooling`,
|
||||
`testing` — is one command away:
|
||||
|
||||
```bash
|
||||
```sh
|
||||
stanza add auth better-auth
|
||||
```
|
||||
|
||||
stanza resolves peer constraints, picks the right adapter for your stack, and
|
||||
stanza resolves peer constraints, picks the adapter that matches your stack, and
|
||||
writes the module's templates, dependencies, and environment variables into the
|
||||
appropriate workspace package.
|
||||
appropriate place. For `auth`, `db`, and `orm` that's an internal workspace
|
||||
package (e.g. `packages/auth/`, named `@my-app/auth`) that your app consumes via
|
||||
`workspace:*`. After adding a package-scoped module, re-run your package
|
||||
manager's install so the new workspace package links:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Inspect and remove
|
||||
|
||||
```sh
|
||||
stanza list # show installed modules, grouped by category
|
||||
stanza search drizzle # find modules in the registry by query
|
||||
stanza remove auth # remove a module and sweep the files it owns
|
||||
```
|
||||
|
||||
`stanza search` prints each module's `category/id` pair — use the **id**, not the
|
||||
display label, when passing it to `add`.
|
||||
|
||||
## Non-interactive setup
|
||||
|
||||
Pass `--yes` and select each category explicitly with a flag. Single-choice
|
||||
categories take one id; multi-choice categories (like `testing`) take a
|
||||
comma-separated list:
|
||||
|
||||
```sh
|
||||
npx -y stanza-cli@latest init my-app --yes \
|
||||
--framework=next \
|
||||
--styling=tailwind \
|
||||
--db=postgres \
|
||||
--orm=drizzle \
|
||||
--auth=better-auth \
|
||||
--testing=vitest,playwright \
|
||||
--pm=pnpm
|
||||
```
|
||||
|
||||
`--yes` never fills in defaults for categories you omit — any category without a
|
||||
flag is simply skipped.
|
||||
|
||||
@@ -5,13 +5,35 @@ description: Assemble modular full-stack TypeScript monorepos with stanza.
|
||||
|
||||
stanza is a shadcn-style CLI for assembling modular full-stack TypeScript
|
||||
monorepos. Pick a framework, styling, database, ORM, and auth — stanza vendors
|
||||
the code directly into your repo, with no runtime dependency to carry around.
|
||||
idiomatic code directly into your repo, with no runtime dependency to carry
|
||||
around.
|
||||
|
||||
## Why stanza
|
||||
|
||||
- **Post-init `add`** works on existing projects — manifest-driven and peer-aware.
|
||||
- **Vendored code** — generated files land in your repo verbatim.
|
||||
- **Open registry** — host your own static JSON and serve custom modules.
|
||||
- **`add` works after `init`.** Run `stanza add` on an existing project. It reads
|
||||
your `stanza.json` manifest, resolves peer constraints, and picks the right
|
||||
adapter for your stack — so adding auth to a Next + Drizzle app writes
|
||||
different code than adding it to a TanStack Start + Prisma app.
|
||||
- **Your code, vendored.** Generated files land in your repo verbatim. There's no
|
||||
`@stanza/runtime` package to install, and nothing to upgrade out from under
|
||||
you — the code is yours to edit.
|
||||
- **Open registry.** Modules are plain static JSON. The CLI ships with a default
|
||||
registry, but you can point it at your own host with `STANZA_REGISTRY` and
|
||||
serve custom modules.
|
||||
|
||||
These docs are a work in progress. Use the sidebar to browse, or head to the
|
||||
[builder](/) to start assembling a stack.
|
||||
## How it fits together
|
||||
|
||||
You choose modules across a small set of **categories** — `framework`, `styling`,
|
||||
`db`, `orm`, `auth`, `tooling`, `testing`. stanza scaffolds a clean monorepo,
|
||||
vendoring each module's templates, dependencies, and environment variables into
|
||||
the right place: app shell code into your app, data and auth layers into their
|
||||
own workspace packages. Later, `stanza add` and `stanza remove` layer modules in
|
||||
and out without you hand-editing the wiring.
|
||||
|
||||
## Next steps
|
||||
|
||||
- **[Getting started](/docs/getting-started)** — create your first project and add a module.
|
||||
- **[Concepts](/docs/concepts)** — the mental model: categories, vendoring, peers, and the manifest.
|
||||
- **[CLI reference](/docs/cli-reference)** — every verb, flag, and environment variable.
|
||||
|
||||
Or skip ahead and assemble a stack visually with the [builder](/).
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"pages": ["index", "getting-started", "cli-reference"]
|
||||
"pages": ["index", "getting-started", "concepts", "cli-reference"]
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { waitUntil } from "@vercel/functions";
|
||||
import { getPostHogServerClient } from "@/server/posthog.server";
|
||||
|
||||
/**
|
||||
* `POST /api/events` — analytics ingest for the `@stanza/cli`. The CLI sends
|
||||
* `POST /api/events` — analytics ingest for the `stanza-cli`. The CLI sends
|
||||
* plain `fetch` batches here so it never has to bundle `posthog-node`; this
|
||||
* route holds the PostHog project key server-side and forwards each event via
|
||||
* the `posthog-node` client.
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
"fmt:check": "oxfmt --check",
|
||||
"registry:build": "bun scripts/registry-build.ts",
|
||||
"changeset": "changeset",
|
||||
"release": "pnpm --filter @stanza/cli --filter create-stanza build && changeset publish"
|
||||
"release": "pnpm --filter stanza-cli --filter create-stanza build && changeset publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@changesets/changelog-github": "^0.7.0",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"check-types": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stanza/cli": "workspace:*"
|
||||
"stanza-cli": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.9.1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
import { run } from "@stanza/cli";
|
||||
import { run } from "stanza-cli";
|
||||
|
||||
import { version } from "../package.json" with { type: "json" };
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineConfig } from "tsdown";
|
||||
|
||||
/**
|
||||
* `create-stanza` is a thin shim over `@stanza/cli`. We compile its TS to
|
||||
* ESM but externalize EVERYTHING — including `@stanza/cli` itself, which
|
||||
* `create-stanza` is a thin shim over `stanza-cli`. We compile its TS to
|
||||
* ESM but externalize EVERYTHING — including `stanza-cli` itself, which
|
||||
* the user installs alongside `create-stanza` via npm's normal dep chain
|
||||
* (it's in `dependencies`). Inlining `@stanza/cli` here would also pull in
|
||||
* (it's in `dependencies`). Inlining `stanza-cli` here would also pull in
|
||||
* its transitive 12 MB of ts-morph and friends.
|
||||
*/
|
||||
export default defineConfig({
|
||||
|
||||
Generated
+1
-1
@@ -248,7 +248,7 @@ importers:
|
||||
|
||||
packages/create-stanza:
|
||||
dependencies:
|
||||
'@stanza/cli':
|
||||
stanza-cli:
|
||||
specifier: workspace:*
|
||||
version: link:../../apps/cli
|
||||
devDependencies:
|
||||
|
||||
@@ -9,13 +9,13 @@ Use only the published CLI surface. Do not assume the Stanza source repo, local
|
||||
|
||||
## Runtime Contract
|
||||
|
||||
- The CLI package is `@stanza/cli`; the binary is `stanza`.
|
||||
- The CLI package is `stanza-cli`; the binary is `stanza`.
|
||||
- The create package is `create-stanza`; package-manager create commands forward to `stanza init`.
|
||||
- Prefer direct npm execution in automation because it avoids package-manager argument-forwarding ambiguity:
|
||||
|
||||
```sh
|
||||
npx -y @stanza/cli@latest --help
|
||||
npx -y @stanza/cli@latest init my-app --yes --framework=next
|
||||
npx -y stanza-cli@latest --help
|
||||
npx -y stanza-cli@latest init my-app --yes --framework=next
|
||||
```
|
||||
|
||||
- If the user wants create-style commands, use the correct separator for their package manager:
|
||||
@@ -31,8 +31,8 @@ bun create stanza@latest my-app --yes --framework=next
|
||||
1. Check the CLI surface before making assumptions:
|
||||
|
||||
```sh
|
||||
npx -y @stanza/cli@latest --help
|
||||
npx -y @stanza/cli@latest search
|
||||
npx -y stanza-cli@latest --help
|
||||
npx -y stanza-cli@latest search
|
||||
```
|
||||
|
||||
2. Discover current module IDs with `stanza search [query]`. Registry contents evolve, so do not hardcode a module unless the user specified it or `search` confirms it.
|
||||
@@ -40,7 +40,7 @@ npx -y @stanza/cli@latest search
|
||||
3. Scaffold with `init --yes`, passing every category you want explicitly:
|
||||
|
||||
```sh
|
||||
npx -y @stanza/cli@latest init my-app --yes \
|
||||
npx -y stanza-cli@latest init my-app --yes \
|
||||
--framework=next \
|
||||
--styling=tailwind \
|
||||
--db=postgres \
|
||||
|
||||
Reference in New Issue
Block a user