15 Commits
Author SHA1 Message Date
jake 71b55d8bc8 docs: rename CLAUDE.md to AGENTS.md for broader agent compatibility 2026-05-21 17:27:07 -04:00
jake 19b5e51075 feat: add-on module kind, testing-vitest + testing-playwright, and multi-choice CLI/web support
- Introduce a `kind: "slot" | "addon"` discriminated union on `Module` — slot modules carry `slot`, add-on modules carry `category` (one of `KNOWN_ADDONS`). `isAddon()` narrows the union; `moduleGroup()` returns the slot-or-category key. Add-on categories (`testing`, `tooling`, `deploy`, `email`, `monorepo`) live in `KNOWN_ADDONS`/`ADDON_CATEGORIES`, deliberately disjoint from `KNOWN_SLOTS` so the resolver never lets add-ons participate in peer dispatch (they can still declare a one-way `peers` + per-framework adapters)
- Ship the first two add-ons: `testing-vitest` (jsdom + RTL, `test`/`test:watch` scripts, per-framework adapters) and `testing-playwright` (per-framework `webServer`, `test:e2e`/`test:e2e:ui` scripts — disjoint from vitest's so region claims don't collide); both under `registry/modules/testing-*/`
- Manifest gains `addons: Partial<Record<AddonCategoryId, StanzaAddonRecord[]>>` (`.default({})` keeps existing `stanza.json` valid); resolver gains `addonOrder` for deterministic codemod sequencing
- CLI: `stanza add <slot|category> <module>` accepts both slots and add-on categories; duplicate-id guard for add-ons instead of the single-fill slot guard; `stanza remove <slot|category> [id]` removes a specific add-on by id (or the whole slot); `stanza list` shows add-ons alongside slots; `stanza init --yes` gains `--testing vitest,playwright` flag; wizard prompts multi-select for add-on categories
- Web builder: add-on slots render as checkbox multi-select cards (`SlotSection` detects `isAddon` and switches to `<CheckboxCard>`), `?testing=vitest,playwright` comma-joined URL param, `builder-state.functions.ts` parses/serializes per-category add-on lists, OG and sitemap routes updated for add-on module paths
- Codemod runner branches on `isAddon` to resolve add-on records from `manifest.addons[category]` rather than `manifest.modules[slot]`; region ownership keys on `module.id` so co-existing add-ons writing the same file at disjoint dot-paths never conflict
- Add `commands.test.ts`, `selection.test.ts`, `manifest.test.ts`, and `resolver.test.ts` coverage for add-on add/remove/list flows and schema validation
2026-05-21 16:34:09 -04:00
jake 7464bf3346 fix: read registry files from disk in dev mode, add predev hook, and rename prepare script
- `registry-base.server.ts` now short-circuits in `import.meta.env.DEV` with a direct `fs/promises.readFile` from `public/registry/` — Nitro only mounts the real `assets:registry` storage in its own `nitro` environment at build time; in Vite's `ssr` environment `useStorage` resolves to an empty stub that returns null for every key, which was silently breaking all registry reads during `pnpm dev`
- Add `predev` script in `apps/web/package.json` pointing at the same `prepare-registry.sh` so the registry is populated before the dev server starts (previously only `prebuild` ran it, meaning a fresh checkout would 404 on all registry data in dev)
- Rename `copy-registry.sh` → `prepare-registry.sh` to better reflect that it may also build the registry before copying; update all references in `package.json` and CLAUDE.md
2026-05-21 14:55:24 -04:00
jake 70a41f67e8 feat: mobile/responsive pass, reactive resolvedTheme, Nitro serverAssets registry, and inline disabled-card reasons
- Builder layout: `CommandBar` is rendered twice in `builder/index.tsx` — a `lg:hidden` instance above the slot cards so phones see the copy-able command first, and the original inside the `hidden lg:block` right column; it's stateless so duplication is cheap
- File-preview height: tree pane is a fixed `h-[180px]` below `sm` (the virtualized `@pierre/trees` tree collapses to 0 without a definite height) and `sm:h-full` on wider screens; code pane is capped `max-h-[360px]` / `sm:max-h-[480px]`
- Command overflow: `command-bar.tsx` and detail `try-it.tsx` both use `whitespace-pre-wrap break-words` + `text-[11px] sm:text-xs` so the full `pnpm create stanza …` command wraps cleanly on narrow viewports
- Header density: search trigger in `site-search.tsx` collapses to an icon-only button below `sm` (label + `⌘K` hint `hidden sm:inline`, `min-w` / wider padding gated to `sm:`)
- `ThemeProvider` now exposes a reactive `resolvedTheme` computed post-mount — the old per-component `useResolvedTheme` returned `"light"` during SSR and never re-rendered, so for `system`/dark users the file tree and Shiki blocks rendered `github-light` on a dark page; `FileTree` is also re-keyed on `resolvedTheme` so its shadow-DOM palette resets on theme change; same fix applied to `templates-list.tsx`
- Disabled slot cards render an inline why-disabled reason via `describeError` in `slot-cards.tsx`: `missing-peer` → "Pick a {Slot} module first.", `incompatible-peer` → "Doesn't pair with {peer} (your {Slot} pick)." — inline (not just a `title` tooltip) so it works on touch
- Registry SSR: `public/registry/` is now registered as a Nitro `serverAssets` dir in `vite.config.ts`; `registry-base.server.ts` reads via `useStorage("assets:registry")` instead of direct `fs` calls — JSON is embedded in the server bundle at build time so SSR works on Vercel where `public/` is CDN-only, and the loopback-fetch deadlock is still avoided
- Mark all "UI polish / responsiveness" TODO items done; update CLAUDE.md to document the serverAssets pattern; clean up REGISTRY.md `ui` slot section
2026-05-21 14:20:56 -04:00
jake f0a65906fa fix: auto-build registry in copy-registry.sh when dist/registry/ is missing and update docs
- `copy-registry.sh` previously exited with an error if `dist/registry/` wasn't present (e.g. Vercel's clean checkout); now it builds the registry on-demand — preferring `bun` locally and falling back to `pnpm exec tsx` on node-only deploy targets
- Update CLAUDE.md dev-command description to reflect the new self-healing behavior and drop the "run `pnpm registry:build` first" prerequisite
- Add a "UI polish / responsiveness" section to TODO.md covering mobile layout gaps in the builder, file-preview, command bar, header, slot cards, devtools overlay, and module detail page
2026-05-21 14:00:09 -04:00
jake ad622c8579 feat: wire Changesets publish pipeline, mark internal packages private, and fix add-vite-plugin multiline indent
- Initialize Changesets: `.changeset/config.json` with `@changesets/changelog-github` for PR-linked changelog entries, `access: "public"`, `baseBranch: "main"`, `updateInternalDependencies: "patch"`
- Add `.github/workflows/release.yml` — on push to main, `changesets/action@v1` opens a "Version Packages" PR when changesets are queued; merging that PR re-runs the workflow and calls `pnpm release` (build CLI + create-stanza, then `changeset publish`); provenance attestations enabled via `id-token: write` + `NPM_CONFIG_PROVENANCE=true`; requires `NPM_TOKEN` in repo secrets
- Add root `pnpm changeset` and `pnpm release` scripts; add `@changesets/cli` + `@changesets/changelog-github` to root devDependencies
- `@stanza/codemods` and `@stanza/registry` marked `private: true` — they are inlined into the CLI bundle by tsdown and never published independently; drop the now-redundant `files` field from codemods `package.json`
- `apps/cli/package.json` and `packages/create-stanza/package.json` gain `keywords`, `homepage`, `bugs`, `author`, `repository`, and `publishConfig.access: "public"` — npm metadata required for a clean first publish
- Fix `add-vite-plugin` multiline-array indent regression — when inserting into a `plugins: [...]` array that already spans multiple lines, ts-morph was reformatting the inserted and following elements to 8-space indent while leaving the first element at 4; fix preserves the existing element indent; add a dedicated test case covering the regression
- Drop stale bun shebang comment from CI workflow; remove `packages/internal` from README; mark npm publish TODO done; update CLAUDE.md with Changesets workflow, `private: true` invariant, and `pnpm release` entry
2026-05-20 18:25:50 -04:00
jake 9460bceffc feat: migrate CLI to Node/tsx/tsdown, add CI pipeline, and wire root-level test + typecheck turbo tasks
- Replace Bun runtime with Node.js + `tsx` for the CLI dev path (`#!/usr/bin/env node` shebang, `tsx watch ./src/bin.ts` for `pnpm dev`) and `tsdown` for publishing — emits `dist/bin.mjs` as plain ESM; `create-stanza` gets the same treatment with its own `tsdown.config.ts`
- Add `.github/workflows/ci.yml` running lint, fmt-check, registry build, web build, typecheck, vitest, CLI bundle build, and a smoke-test (`node dist/bin.mjs --version` / `--help`) on push/PR with concurrency cancel-in-progress
- Add `apps/cli/src/commands/commands.test.ts` (196 lines, vitest) with unit tests for `add`, `init`, `list`, `remove`, and `search` command handlers; add `apps/cli/vitest.config.ts`
- Replace `apps/web/scripts/copy-registry.ts` with a pure-bash `copy-registry.sh` — no Node/tsx prerequisite on the deploy target; drop the TS version and its `predev` auto-build hook (`pnpm registry:build` must be run before the first web build)
- Add root `pnpm test` and `pnpm check-types` scripts that fan out to all workspaces via turbo; CI uses these as canonical entry points
- Update `tsconfig.json` files for `packages/registry`, `packages/codemods`, and `packages/create-stanza` to stay source-only (`noEmit: true`); add `publishConfig` pointing at `dist/` in publishable `package.json` files so workspace dev resolution uses `./src/` while published tarballs resolve `./dist/`
- Update CLAUDE.md toolchain invariants: document Node-only runtime, tsdown build pipeline, and per-workspace `dist/` paths; remove stale `bun apps/cli/src/bin.ts` dev command and all "Bun runs the CLI directly" references
2026-05-20 18:04:11 -04:00
jake 3a30c30d62 refactor: consolidate slot metadata into canonical SLOTS array, hoist module-level install fields, and rename peerPackagesconsumesPackages
- Replace parallel `KNOWN_SLOTS` tuple, `slotOrder` array, and `SLOT_PACKAGE_DIR` map with a single `SLOTS: Slot[]` constant — each entry carries `{ id, label, description, packageDir }`; `KNOWN_SLOTS` is re-derived as `SLOTS.map(s => s.id) as const` for Zod literal inference; `slotLabel` and the package-dir lookup both become simple lookups over the same array; adding a slot is now a two-line edit
- Hoist `dependencies`, `devDependencies`, `env`, and `scripts` to the module level on `ModuleAdapter`'s parent `Module` type — adapter-level values still override per-key; avoids re-declaring the same dep/env block in every adapter (e.g. Better Auth's `better-auth` dep and two env vars are now declared once at module level while per-framework templates and codemods stay in their adapter blocks)
- Rename `peerPackages` → `consumesPackages` and lift it from adapter scope to module scope — cross-package imports are shared infrastructure that doesn't vary per adapter; runner wires `@<project>/<dir>: workspace:*` once per module install rather than once per adapter
- Update all first-party modules (`auth-better-auth`, `orm-drizzle`, `orm-prisma`, `framework-next`, `framework-tanstack-start`, `styling-tailwind`) to the new schema
- Update CLI (`codemod-runner`, `registry-loader`, `wizard`, `add`, `init`) and `scripts/registry-build.ts` to consume the new `SLOTS` array and hoisted module fields
- Update `apps/web/scripts/copy-registry.ts` and docs (`CLAUDE.md`, `REGISTRY.md`, `README.md`, `TODO.md`) to reflect the consolidated model and clarify the slots-vs-add-ons taxonomy
2026-05-20 17:00:56 -04:00
jake 2651daf573 refactor: dissolve packages/internal into scripts/, reorganize CLI into src/lib/, and wire stanza remove codemod revert
- Delete `packages/internal` workspace package — `registry-build.ts` and `module-new.ts` move to top-level `scripts/` (no `package.json`, not shipped); `pnpm registry:build` and `pnpm module:new` scripts updated in root `package.json` and `turbo.json`; CLAUDE.md / TODO.md references updated
- Relocate CLI internals from `apps/cli/src/` → `apps/cli/src/lib/` — `codemod-runner.ts`, `manifest.ts`, `region-tracker.ts`, `registry-loader.ts`, `wizard.ts`; all intra-CLI imports updated to `@/lib/*` path alias via tsconfig `paths`; add `@types/bun` to CLI devDependencies
- `stanza remove` now dispatches `revertCodemods()` as step 1 (before declarative cleanup) — re-loads the module adapter from the registry, calls imperative reverts while framework files are still intact, then sweeps remaining region claims; fixes `wrap-root-layout` / `re-export` regions previously left as "needs manual cleanup" despite shipping working reverts
2026-05-20 16:10:17 -04:00
jake f83e84eb85 feat: slot-package extraction — auth/db/orm install into their own workspace packages
- Add `SLOT_PACKAGE_DIR` map to `packages/registry/src/module.ts`: `auth → "auth"`, `db`+`orm → "db"`, `framework`+`styling → null` (app-scoped); drives where package-scoped templates and deps land
- Runner gains `ensureSlotPackage` — bootstraps `packages/<dir>/package.json` + `tsconfig.json` on first install and merges adapter `dependencies`/`devDeps`/`scripts` there; wires the app to the slot package via a `workspace:*` dep
- Add `scope: "package"` to `TemplateRef` — resolves dest against `packages/<SLOT_PACKAGE_DIR[slot]>/` instead of `appDir` or repo root; runner writes package-scoped files to the right home
- Add `peerPackages` to `ModuleAdapter` — declares cross-package deps (e.g. `auth` needing `db`); runner injects `@<project>/<dir>: workspace:*` into the current package's `package.json`
- Add `{{<dir>PackageName}}` mustache substitution (e.g. `{{dbPackageName}}` → `@my-app/db`) — runs over template bodies (`template: true`) and codemod `args` string values; enables auth templates to import from the db package without hardcoding names
- Add `base: "package:<dir>"` support to `re-export` and `append-to-file` builtins — codemods can target files inside another slot's package (e.g. extending the orm schema barrel from the auth module)
- Update `auth-better-auth`, `auth-clerk`, `orm-drizzle`, `orm-prisma` modules to use `scope: "package"` templates and `peerPackages` cross-wiring; rename `layout-wrapper.tsx` → `provider.tsx` in auth-clerk
- `stanza remove` updated to sweep `packages/<dir>/` when no region claims remain under it
- Update `selection.ts` in apps/web to account for package-scoped file paths
- Document extraction model, `SLOT_PACKAGE_DIR`, authoring rules, and `base:` codemod targeting in `CLAUDE.md`, `README.md`, and `REGISTRY.md`
2026-05-20 15:05:11 -04:00
jake 57f43fd846 refactor: replace bundled module codemods with a static CLI-side catalog
- Modules no longer ship codemod code; adapters now declare `codemods: [{ id, args }]` referencing catalog entries by id — the registry stays pure data
- Add `apps/cli/src/codemods/` catalog: `wrap-root-layout` codemod (parameterized by provider import, component name, etc.) + `CODEMOD_CATALOG` map statically imported into the CLI binary so distribution shape (single binary, npx, bun compile) doesn't matter
- Remove `loadCodemods`, `getBundleCacheDir`, and all `codemodBundle` write/import machinery from `codemod-runner.ts`; runner now dispatches via `CODEMOD_CATALOG[invocation.id]` and forwards `invocation.args`
- Drop `registry-build.ts` codemod bundling step and `mod.codemodBundle` field from the registry package; remove per-module `codemods/index.ts` (auth-clerk replaced by catalog invocation with args)
- Change `ModuleAdapter.codemods` type from `string[]` to `CodemodInvocation[]` (`{ id: string; args?: Record<string, unknown> }`)
- Update `CLAUDE.md` to document "Registry is data; CLI is the runtime" principle and the process for adding/invoking generic codemods
2026-05-20 14:23:34 -04:00
jake 5e83f07b83 feat: add module logos, bundle codemods into registry, and build out the web UI
- **Module logos**: `registry-build.ts` now auto-detects `logo.svg` / `logo-light.svg` + `logo-dark.svg` in each module directory and inlines them as `mod.logo` (string or `{ light, dark }`) — no `module.ts` declarations needed; first-party SVGs added for all current modules
- **Codemod bundling**: `registry-build.ts` bundles each module's `codemods/index.ts` via `Bun.build` (ESM, externals: `@stanza/codemods`, `ts-morph`, `node:*`) and embeds the output as `mod.codemodBundle`; the CLI runner materializes it to `node_modules/.cache/stanza-codemods/` so externals resolve normally, replacing the old "first-party only" approach
- **Web builder UI**: split monolithic `builder.tsx` into `builder/{index,slot-cards,command-bar,file-preview}.tsx`; add `Header`, `Footer`, `ModeToggle`, `ThemeProvider`; scaffold shadcn `Badge`, `Card`, `DropdownMenu`, `Input`, `Separator`, `Sonner`, `Tabs`, `Tooltip` components
- **Server-side Shiki**: `apps/web/src/server/highlighter.ts` keeps a warm Shiki instance; `builder-state.ts` (`createServerFn`) computes selections from URL params and pre-renders `{ light, dark }` HTML per file — Shiki is never imported client-side
- **Selection logic**: new `apps/web/src/lib/selection.ts` with full unit tests
- **copy-registry script**: `predev`/`prebuild` hook copies `dist/registry/` → `apps/web/public/registry/` so the same-domain registry path resolves without a separate CDN
- Add `shiki`, `sonner`, `@pierre/trees`, `@fontsource-variable/geist-mono` to `apps/web`; update oxfmt ignore patterns and swap `react-hooks` for `react-perf` + `promise` in oxlint web config
2026-05-20 13:20:01 -04:00
jake f03bdd0f49 fix: inline template contents into registry build output for self-contained HTTP manifests
- add `content?: string` to `TemplateRef` and export `TemplateRef`/`EnvVar` from the registry package
- `registry-build.ts` now reads each template file and bakes it into the per-module JSON so CDN-fetched manifests need no follow-up requests
- `codemod-runner.ts` prefers `tpl.content` when present and falls back to reading from disk (local dev); replaces `writeTemplateFile` with `renderTemplate` + manual `writeFileSync` for finer control
- document the template distribution model in `CLAUDE.md`
2026-05-20 12:20:04 -04:00
jake 0fcc225d85 feat: consolidate maintenance scripts into packages/internal and add module:new scaffolder
- rename `packages/registry-build` → `packages/internal` to serve as a single home for internal tooling
- add `module-new.ts`: interactive CLI (powered by `@clack/prompts`) that scaffolds a new first-party module under `registry/modules/<slot>-<id>/` with `module.ts`, `package.json`, `tsconfig.json`, `templates/.gitkeep`, and an optional `codemods/index.ts` stub
- expose `pnpm module:new [slot] [id]` at the repo root and update `registry:build` turbo filter to `@stanza/internal`
2026-05-20 12:12:59 -04:00
jake b74ffb9d1a feat: initial commit 2026-05-20 11:52:53 -04:00