diff --git a/CLAUDE.md b/CLAUDE.md index 8407a46..477bd53 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ Three things differentiate stanza from other scaffolders: - `packages/registry/` — shared schema, slot/peer resolver, Zod manifest validator - `packages/codemods/` — ts-morph helpers (idempotent + reversible) - `packages/create-stanza/` — `pnpm create stanza` shim -- `packages/internal/` — Internal maintenance scripts: `registry-build.ts` (emits static CDN JSON), `module-new.ts` (scaffolds a new module) +- `scripts/` — repo-root maintainer helpers (not shipped): `registry-build.ts` emits static CDN JSON, `module-new.ts` scaffolds a new module - `registry/modules/-/` — first-party modules: `module.ts` + `templates/` (modules don't ship codemod code; see Architecture rules) In a **generated project**, `auth`, `db`, and `orm` modules install into their own internal workspace package at `packages//` (named `@/`); the app consumes them via `workspace:*` deps. `framework` and `styling` stay app-scoped because they wire the app shell itself. The mapping is hardcoded in [`SLOT_PACKAGE_DIR`](packages/registry/src/module.ts) — `auth → "auth"`, both `db` and `orm → "db"` (they share a single `packages/db/` package). @@ -29,7 +29,7 @@ In a **generated project**, `auth`, `db`, and `orm` modules install into their o ## Commands - `bun apps/cli/src/bin.ts ` — run CLI directly without build -- `pnpm registry:build` (or `bun packages/internal/src/registry-build.ts`) — regenerate `dist/registry/{index,modules/*}.json` +- `pnpm registry:build` (or `bun scripts/registry-build.ts`) — regenerate `dist/registry/{index,modules/*}.json` - `pnpm module:new [slot] [id]` — scaffold a new module under `registry/modules/` - `pnpm --filter @stanza/web dev` — TanStack Start dev server; `predev` hook auto-copies `dist/registry/` → `apps/web/public/registry/` so the same-domain registry path resolves - `pnpm lint` / `pnpm lint:fix` — Oxlint across the whole repo (config: `.oxlintrc.json`) @@ -42,8 +42,8 @@ In a **generated project**, `auth`, `db`, and `orm` modules install into their o ## Toolchain invariants - 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.base.json`; build with `bun build`, not `tsc` -- `tsconfig.base.json` excludes `**/templates/**` globally — template files target user projects, not this repo +- TypeScript 6 — `allowImportingTsExtensions: true` + `noEmit: true` is set in `tsconfig.json`; build with `bun build`, not `tsc` +- `tsconfig.json` excludes `**/templates/**` globally — template files target user projects, not this repo - Zod 4: use `z.partialRecord(K, V)` for finite-key partial records (`z.record(z.enum, V)` requires exhaustive keys) - TanStack Start: `verbatimModuleSyntax: false` in `apps/web/tsconfig.json` (server bundles leak otherwise); `tanstackStart()` MUST precede `react()` in vite plugins - Bun runs the CLI directly from `.ts`; don't add a TS compile step @@ -51,7 +51,7 @@ In a **generated project**, `auth`, `db`, and `orm` modules install into their o ## Architecture rules - **Modules are vendored**: their templates land in the user's repo verbatim; no `@stanza/runtime` dep -- **Template distribution**: `packages/internal`'s `registry-build.ts` inlines each template file's contents into the per-module JSON's `templates[].content` field so HTTP-loaded manifests are self-contained. The runner prefers `tpl.content` and only reads from `registry/modules//templates/` when it's absent (local dev). New templates need no build wiring — they're picked up automatically +- **Template distribution**: `scripts/registry-build.ts` inlines each template file's contents into the per-module JSON's `templates[].content` field so HTTP-loaded manifests are self-contained. The runner prefers `tpl.content` and only reads from `registry/modules//templates/` when it's absent (local dev). New templates need no build wiring — they're picked up automatically - **Module logos**: drop `logo.svg` (theme-agnostic) or `logo-light.svg` + `logo-dark.svg` (theme pair) in a module's directory. The registry build auto-detects and inlines as `mod.logo` (string or `{ light, dark }`) — module authors don't declare anything in `module.ts`. First-party logos come from [svgl.app](https://svgl.app). The web builder renders inline via `dangerouslySetInnerHTML` - **Registry is data; CLI is the runtime**: the per-module JSON ships templates (text), deps (strings), env (strings), scripts (strings), logos (SVG markup), and codemod **invocations** (`{ id, args }`). It does NOT ship codemod _code_. The catalog of generic codemods lives in [packages/codemods/src/builtins/](packages/codemods/src/builtins/) and is exposed via the `@stanza/codemods/builtins` subpath export — each codemod is parameterized by `TArgs` and reusable across modules (`wrap-root-layout` serves both Clerk and any future provider-style auth/state library). The catalog is statically imported into the CLI binary at build time, so distribution shape (single binary, pnpm-isolated, npm-hoisted, `npx`, `bun --compile`) doesn't matter — implementations always travel with the runtime - **Adding a generic codemod**: drop `.ts` under [packages/codemods/src/builtins/](packages/codemods/src/builtins/), default-export a `Codemod`, and register it in [packages/codemods/src/builtins/index.ts](packages/codemods/src/builtins/index.ts). Codemods that bake in module-specific identifiers don't belong — factor them into args @@ -60,7 +60,7 @@ In a **generated project**, `auth`, `db`, and `orm` modules install into their o - **Slot taxonomy** is currently `framework | styling | db | orm | auth` (see `KNOWN_SLOTS`); adding a slot is a manifest schema bump — update `KNOWN_SLOTS`, `slotOrder`, the Zod manifest schema, and `SLOT_PACKAGE_DIR` together (decide whether the new slot extracts into its own package or stays app-scoped) - **Adapter keys** encode peer choices (e.g., `next+drizzle`); the resolver picks the most specific match - **Slot-package extraction**: `auth`/`db`/`orm` modules install into `packages//` workspace packages (named `@/`); templates `scope: "package"`, deps/devDeps/scripts route there, and the app gets a `workspace:*` dep wired by the runner. `framework`/`styling` stay app-scoped (their `SLOT_PACKAGE_DIR` entry is `null`). The bootstrap files (the package's `package.json` + `tsconfig.json` and the host app's workspace dep) are **system-owned** — not tracked in `regions`. `stanza remove`'s sweep deletes them when no claims remain under `packages//` -- **Generated projects don't share a tsconfig base**: every `apps/*/tsconfig.json` and `packages/*/tsconfig.json` is self-contained. The framework module ships the app's tsconfig; the runner's `ensureSlotPackage` writes a matching self-contained config when bootstrapping a slot package. `tsconfig.base.json` lives in the stanza repo only; do not emit it in generated trees +- **Generated projects don't share a tsconfig base**: every `apps/*/tsconfig.json` and `packages/*/tsconfig.json` is self-contained. The framework module ships the app's tsconfig; the runner's `ensureSlotPackage` writes a matching self-contained config when bootstrapping a slot package. `tsconfig.json` lives in the stanza repo only; do not emit it in generated trees - **Cross-package wiring**: when an adapter's source code imports from another internal package (e.g. `better-auth`'s `auth.ts` reads `db` from the orm package), declare `peerPackages: ["db"]` on the adapter. The runner adds `@/db: workspace:*` to the current package's `package.json`. Templates can reference other packages via `{{PackageName}}` substitution (e.g. `{{dbPackageName}}` → `@my-app/db`) — substitution runs over both template bodies (when `template: true`) and codemod-invocation `args` string values - **Region ownership** in `stanza.json` is the source of truth for `remove`/future-`swap`; two modules claiming the same region is a hard error (`RegionConflictError`) - **Declarative beats imperative**: prefer `templates`/`dependencies`/`env`/`scripts` over imperative codemods; the runner applies declarative fields generically diff --git a/TODO.md b/TODO.md index 3f5c5b2..5db9c79 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ The builder is functionally wired but visually unfinished. It's inline-styled ra - [ ] Layout: header (logo, GitHub link, docs link), footer - [ ] Dark mode (Tailwind 4 `light-dark()` + system pref) - [ ] SEO: meta tags via `head()` on routes, OG image, sitemap -- [ ] Host the registry on the same domain — wire `packages/internal`'s registry-build output to `apps/web/public/registry/` and serve statically, or via a Vercel route handler +- [ ] Host the registry on the same domain — wire the registry-build output (`scripts/registry-build.ts` → `dist/registry/`) to `apps/web/public/registry/` and serve statically, or via a Vercel route handler - [ ] Vercel deploy config — `vercel.json` if needed, env vars for `STANZA_REGISTRY` (point dev at local FS, prod at the deployed CDN path) - [ ] Docs section (could be MDX routes): overview, authoring guide, registry spec @@ -30,10 +30,9 @@ The wizard and verbs work but a few things from the plan are stubbed. - [ ] Implement opt-out PostHog telemetry — wire `posthog-node` (already a dep), prompt on first run, store a `telemetryId` in `stanza.json`, respect `--no-telemetry` and `DO_NOT_TRACK=1` - [ ] `--yes` flag for non-interactive `init` — pick defaults via flags (`--framework=next` etc.); essential for CI tests - [ ] HTTP registry loader path is implemented but unverified — smoke test against the static JSON output -- [ ] `stanza init`: today's `bootstrapShell` doesn't include `tsconfig.json` at the repo root for the generated project, and doesn't emit `turbo.json`. Decide whether stanza ships those or modules do. Also: the root `pnpm-workspace.yaml` must cover `packages/*` so the runner's bootstrapped slot packages link correctly +- [ ] `stanza init`: today's `bootstrapShell` doesn't emit `turbo.json`. Decide whether stanza ships that or a tooling module does. Also: the root `pnpm-workspace.yaml` must cover `packages/*` so the runner's bootstrapped slot packages link correctly - [ ] Better error messages for `RegionConflictError` (current message is technical; should suggest `stanza remove ` or manual cleanup) - [ ] `bun build` the CLI for publish — script exists, not yet exercised -- [ ] `stanza remove` doesn't dispatch to the inverse `revert()` on imperative codemods — regions touched by `wrap-root-layout` and `re-export` get flagged as "needs manual cleanup" even though the codemod ships a working revert. Fix: look up `CODEMOD_CATALOG[id].revert` for region keys that match a codemod-claim pattern (`imports.*`, `providers.*`, `re-exports.*`, `append.*`) and call it - [ ] Tests for command handlers (`init`, `add`, `remove`) — current coverage is just codemods + resolver ## Modules diff --git a/apps/cli/package.json b/apps/cli/package.json index 3fc2e63..c089273 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -33,6 +33,7 @@ "semver": "^7.8.0" }, "devDependencies": { + "@types/bun": "^1.3.14", "@types/node": "^25.9.1", "@types/semver": "^7.7.1", "typescript": "^6.0.3", diff --git a/apps/cli/src/commands/add.ts b/apps/cli/src/commands/add.ts index 7f4ef4c..77cbc94 100644 --- a/apps/cli/src/commands/add.ts +++ b/apps/cli/src/commands/add.ts @@ -6,9 +6,9 @@ import { resolveAdapter, type SlotId, KNOWN_SLOTS } from "@stanza/registry"; import kleur from "kleur"; import type { Argv } from "mri"; -import { applyModule } from "../codemod-runner"; -import { findProjectRoot, readManifest } from "../manifest"; -import { loadRegistry } from "../registry-loader"; +import { applyModule } from "@/lib/codemod-runner"; +import { findProjectRoot, readManifest } from "@/lib/manifest"; +import { loadRegistry } from "@/lib/registry-loader"; export async function cmdAdd(args: { slot?: string; diff --git a/apps/cli/src/commands/init.ts b/apps/cli/src/commands/init.ts index 7b99e9f..d07eb93 100644 --- a/apps/cli/src/commands/init.ts +++ b/apps/cli/src/commands/init.ts @@ -6,10 +6,10 @@ import { resolveAdapter, slotOrder } from "@stanza/registry"; import kleur from "kleur"; import type { Argv } from "mri"; -import { applyModule } from "../codemod-runner"; -import { initManifest } from "../manifest"; -import { loadRegistry } from "../registry-loader"; -import { runInitWizard } from "../wizard"; +import { applyModule } from "@/lib/codemod-runner"; +import { initManifest } from "@/lib/manifest"; +import { loadRegistry } from "@/lib/registry-loader"; +import { runInitWizard } from "@/lib/wizard"; export async function cmdInit(args: { name?: string; argv: Argv }): Promise { const registry = await loadRegistry(); diff --git a/apps/cli/src/commands/list.ts b/apps/cli/src/commands/list.ts index 84b1bd1..bd09fde 100644 --- a/apps/cli/src/commands/list.ts +++ b/apps/cli/src/commands/list.ts @@ -3,7 +3,7 @@ import { slotOrder, type SlotId } from "@stanza/registry"; import kleur from "kleur"; import type { Argv } from "mri"; -import { findProjectRoot, readManifest } from "../manifest"; +import { findProjectRoot, readManifest } from "@/lib/manifest"; export async function cmdList(_args: { argv: Argv }): Promise { const projectRoot = findProjectRoot(); diff --git a/apps/cli/src/commands/remove.ts b/apps/cli/src/commands/remove.ts index b6b3fa0..77384c5 100644 --- a/apps/cli/src/commands/remove.ts +++ b/apps/cli/src/commands/remove.ts @@ -7,8 +7,10 @@ import { KNOWN_SLOTS, SLOT_PACKAGE_DIR, type SlotId } from "@stanza/registry"; import kleur from "kleur"; import type { Argv } from "mri"; -import { findProjectRoot, readManifest, writeManifest } from "../manifest"; -import { regionsOwnedBy } from "../region-tracker"; +import { revertCodemods } from "@/lib/codemod-runner"; +import { findProjectRoot, readManifest, writeManifest } from "@/lib/manifest"; +import { regionsOwnedBy } from "@/lib/region-tracker"; +import { loadRegistry } from "@/lib/registry-loader"; export async function cmdRemove(args: { slot?: string; argv: Argv }): Promise { if (!args.slot) { @@ -30,21 +32,43 @@ export async function cmdRemove(args: { slot?: string; argv: Argv }): Promise null); + const adapter = mod?.adapters.find((a) => a.key === installed.adapter); + if (mod && adapter) { + const revertResult = await revertCodemods({ + projectRoot, + manifest, + module: mod, + adapter, + dryRun, + }); + manifest = revertResult.manifest; + for (const id of revertResult.manualCleanup) { + manualCleanup.push(`codemod:${id} (no revert or revert threw)`); + } + } else { + p.log.warn( + `Couldn't re-load ${installed.id}@${installed.adapter} from the registry — skipping imperative codemod reversal.`, + ); + } + + // Step 2: reverse the declarative side — files, deps, scripts, env vars — + // driven by whatever region claims remain after the codemod reverts. + const owned = regionsOwnedBy(manifest, installed.id); for (const { file, region } of owned) { const abs = path.join(projectRoot, file); if (file === ".env.example") { @@ -67,14 +91,19 @@ export async function cmdRemove(args: { slot?: string; argv: Argv }): Promise/, tear down the bootstrap (package.json, - // tsconfig.json, the dir itself) and drop the workspace dep from the app. - // The system-owned bootstrap files aren't tracked as regions, so they'd - // otherwise linger forever. + // Step 3: sweep any internal package whose claims have all been released. + // The bootstrap files (package.json, tsconfig.json, the workspace dep on + // the app) are system-owned — not tracked in regions, so they'd otherwise + // linger forever. const sweptPackages: string[] = []; - const appPkgRel = `${manifest.appDir}/package.json`; - const appPkgAbs = path.join(projectRoot, appPkgRel); + const appPkgAbs = path.join(projectRoot, manifest.appDir, "package.json"); for (const dir of new Set( Object.values(SLOT_PACKAGE_DIR).filter((d): d is string => d !== null), )) { - const stillUsed = Object.keys(nextRegions).some((file) => file.startsWith(`packages/${dir}/`)); + const stillUsed = Object.keys(manifest.regions).some((file) => + file.startsWith(`packages/${dir}/`), + ); if (stillUsed) continue; const pkgRoot = path.join(projectRoot, "packages", dir); if (!fs.existsSync(pkgRoot)) continue; @@ -111,9 +141,7 @@ export async function cmdRemove(args: { slot?: string; argv: Argv }): Promise 0) { @@ -121,7 +149,7 @@ export async function cmdRemove(args: { slot?: string; argv: Argv }): Promise 0) { p.log.warn( - `${manualCleanup.length} region(s) need manual cleanup:\n` + + `${manualCleanup.length} item(s) need manual cleanup:\n` + manualCleanup.map((r) => ` • ${r}`).join("\n"), ); } diff --git a/apps/cli/src/commands/search.ts b/apps/cli/src/commands/search.ts index 4a2a646..f1c98e4 100644 --- a/apps/cli/src/commands/search.ts +++ b/apps/cli/src/commands/search.ts @@ -1,7 +1,7 @@ import kleur from "kleur"; import type { Argv } from "mri"; -import { loadRegistry } from "../registry-loader"; +import { loadRegistry } from "@/lib/registry-loader"; export async function cmdSearch(args: { query?: string; argv: Argv }): Promise { const registry = await loadRegistry(); diff --git a/apps/cli/src/codemod-runner.ts b/apps/cli/src/lib/codemod-runner.ts similarity index 78% rename from apps/cli/src/codemod-runner.ts rename to apps/cli/src/lib/codemod-runner.ts index f58d8a1..1d02a36 100644 --- a/apps/cli/src/codemod-runner.ts +++ b/apps/cli/src/lib/codemod-runner.ts @@ -21,7 +21,7 @@ import type { import { SLOT_PACKAGE_DIR } from "@stanza/registry"; import { writeManifest } from "./manifest"; -import { claim, RegionConflictError } from "./region-tracker"; +import { claim, release, RegionConflictError } from "./region-tracker"; export type RunResult = { manifest: StanzaManifest; @@ -86,20 +86,7 @@ export async function applyModule(args: { if (created) bootstrappedPackage = { dir: packageDir, name: packageName }; } - // Render context provides `{{packageName}}` for the active module's own - // package, plus shorthand `{{PackageName}}` keys for every slot package - // so cross-package imports (e.g. better-auth's auth.ts importing `db` from - // `@/db`) can be templated declaratively. - const renderContext: Record = { - appDir: manifest.appDir, - projectName: manifest.name, - packageName, - }; - for (const dir of new Set( - Object.values(SLOT_PACKAGE_DIR).filter((d): d is string => d !== null), - )) { - renderContext[`${dir}PackageName`] = `@${manifest.name}/${dir}`; - } + const renderContext = buildRenderContext(manifest, packageName); // 1. Templates (claim regions per-template-file). for (const tpl of adapter.templates ?? []) { @@ -413,7 +400,8 @@ function buildContext(args: { project: () => Project; touchedFiles: Set; dryRun: boolean; - onClaim: (file: string, region: string) => void; + onClaim?: (file: string, region: string) => void; + onRelease?: (file: string, region: string) => void; }): CodemodContext { return { projectRoot: args.projectRoot, @@ -423,12 +411,118 @@ function buildContext(args: { owner: { slot: args.module.slot, module: args.module.id }, adapter: args.adapter.key, claimRegion(file, region) { - args.onClaim(file, region); + args.onClaim?.(file, region); }, - releaseRegion() { - // No-op during `add`. The remove path uses regionsOwnedBy() to reverse. + releaseRegion(file, region) { + args.onRelease?.(file, region); }, }; } +/** + * Build the render context used by both template-body substitution and + * codemod-args substitution. Provides: + * - `{{appDir}}`, `{{projectName}}` — project-level + * - `{{packageName}}` — the active module's own package (empty for slots + * without a package mapping) + * - `{{PackageName}}` (e.g. `{{dbPackageName}}`) — shorthand for every + * slot package, so cross-package imports stay declarative + */ +function buildRenderContext(manifest: StanzaManifest, packageName: string): Record { + const ctx: Record = { + appDir: manifest.appDir, + projectName: manifest.name, + packageName, + }; + for (const dir of new Set( + Object.values(SLOT_PACKAGE_DIR).filter((d): d is string => d !== null), + )) { + ctx[`${dir}PackageName`] = `@${manifest.name}/${dir}`; + } + return ctx; +} + +export type RevertResult = { + manifest: StanzaManifest; + touchedFiles: string[]; + dryRun: boolean; + /** + * Codemods that couldn't be reverted automatically — either no `revert()` + * is defined or one threw. Each entry is the codemod id; the caller + * surfaces this as "needs manual cleanup". + */ + manualCleanup: string[]; +}; + +/** + * Replay an installed module's imperative codemods in reverse via their + * `revert()` functions. Each revert releases the regions it claimed, which + * we propagate into the manifest. Declarative reversals (files, deps, env) + * stay in `commands/remove.ts` — they're region-driven and don't need the + * module to be re-loaded. + * + * Args are reconstructed by running the original `args` template strings + * through the same `renderTemplate` substitution used on apply, so reverts + * see the same concrete values (e.g. `providerImport: "@my-app/auth"`). + */ +export async function revertCodemods(args: { + projectRoot: string; + manifest: StanzaManifest; + module: Module; + adapter: ModuleAdapter; + dryRun: boolean; +}): Promise { + const { projectRoot, module, adapter, dryRun } = args; + let manifest = args.manifest; + const touchedFiles = new Set(); + const manualCleanup: string[] = []; + const appRoot = path.join(projectRoot, manifest.appDir); + + const codemods = adapter.codemods ?? []; + if (codemods.length === 0) return { manifest, touchedFiles: [], dryRun, manualCleanup }; + + const packageDir = SLOT_PACKAGE_DIR[module.slot]; + const packageName = packageDir ? `@${manifest.name}/${packageDir}` : ""; + const renderContext = buildRenderContext(manifest, packageName); + + const project = lazyProject(appRoot); + const ctx = buildContext({ + projectRoot, + appRoot, + manifest, + module, + adapter, + project, + touchedFiles, + dryRun, + onRelease: (file, region) => { + manifest = release(manifest, file, region); + }, + }); + + // Reverse order: if codemod B layered on top of A's output, undo B first. + for (const invocation of codemods.toReversed()) { + const fn = CODEMOD_CATALOG[invocation.id]; + if (!fn || !fn.revert) { + manualCleanup.push(invocation.id); + continue; + } + if (dryRun) continue; + try { + const renderedArgs = renderArgs(invocation.args ?? {}, renderContext); + const result = await fn.revert(ctx, renderedArgs); + result.touchedFiles.forEach((f) => touchedFiles.add(f)); + } catch { + // Don't surface the exception body — the caller already prints a + // "manual cleanup" warning with the codemod id; the user can re-run + // with stack traces if they need details. Keep going so other codemods + // still get a chance to revert. + manualCleanup.push(invocation.id); + } + } + if (!dryRun) await project.save?.(); + + return { manifest, touchedFiles: [...touchedFiles], dryRun, manualCleanup }; +} + export { RegionConflictError }; diff --git a/apps/cli/src/manifest.ts b/apps/cli/src/lib/manifest.ts similarity index 100% rename from apps/cli/src/manifest.ts rename to apps/cli/src/lib/manifest.ts diff --git a/apps/cli/src/region-tracker.ts b/apps/cli/src/lib/region-tracker.ts similarity index 100% rename from apps/cli/src/region-tracker.ts rename to apps/cli/src/lib/region-tracker.ts diff --git a/apps/cli/src/registry-loader.ts b/apps/cli/src/lib/registry-loader.ts similarity index 100% rename from apps/cli/src/registry-loader.ts rename to apps/cli/src/lib/registry-loader.ts diff --git a/apps/cli/src/wizard.ts b/apps/cli/src/lib/wizard.ts similarity index 100% rename from apps/cli/src/wizard.ts rename to apps/cli/src/lib/wizard.ts diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json index 374bac5..2ac72d6 100644 --- a/apps/cli/tsconfig.json +++ b/apps/cli/tsconfig.json @@ -1,7 +1,10 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../tsconfig.json", "compilerOptions": { - "types": ["node"] + "paths": { + "@/*": ["./src/*"] + }, + "types": ["node", "bun"] }, "include": ["src"] } diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 1d4898b..7cb8f21 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../tsconfig.json", "compilerOptions": { "jsx": "preserve", "jsxImportSource": "react", diff --git a/package.json b/package.json index 5b27945..bb56ede 100644 --- a/package.json +++ b/package.json @@ -14,11 +14,15 @@ "lint:fix": "oxlint --fix", "fmt": "oxfmt", "fmt:check": "oxfmt --check", - "registry:build": "turbo run registry:build --filter=@stanza/internal", - "module:new": "pnpm --filter=@stanza/internal run module:new" + "registry:build": "bun scripts/registry-build.ts", + "module:new": "bun scripts/module-new.ts" }, "devDependencies": { + "@clack/prompts": "^1.4.0", + "@stanza/registry": "workspace:*", "@types/node": "^25.9.1", + "kleur": "^4.1.5", + "mri": "^1.2.0", "oxfmt": "^0.51.0", "oxlint": "^1.66.0", "turbo": "^2.9.14", diff --git a/packages/codemods/tsconfig.json b/packages/codemods/tsconfig.json index eac65a4..425cedc 100644 --- a/packages/codemods/tsconfig.json +++ b/packages/codemods/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src", diff --git a/packages/create-stanza/src/bin.ts b/packages/create-stanza/src/bin.ts index d533fa5..29f8254 100644 --- a/packages/create-stanza/src/bin.ts +++ b/packages/create-stanza/src/bin.ts @@ -1,5 +1,7 @@ #!/usr/bin/env bun import { run } from "@stanza/cli"; +import mri from "mri"; + /** * `pnpm create stanza my-app` lands here. We forward straight to the CLI's * init command — no extra logic, since the wizard wants to live in one place. @@ -7,8 +9,6 @@ import { run } from "@stanza/cli"; * The argv shape from npm's `create-` convention is the same as a normal CLI * invocation, with the project name as the first positional arg. */ -import mri from "mri"; - const argv = mri(process.argv.slice(2), { alias: { h: "help", v: "version" }, boolean: ["help", "version", "yes", "dry-run", "no-telemetry"], diff --git a/packages/create-stanza/tsconfig.json b/packages/create-stanza/tsconfig.json index 307afcd..f95f72b 100644 --- a/packages/create-stanza/tsconfig.json +++ b/packages/create-stanza/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", "types": ["node"] diff --git a/packages/internal/package.json b/packages/internal/package.json deleted file mode 100644 index 0b1a51e..0000000 --- a/packages/internal/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@stanza/internal", - "version": "0.1.0", - "private": true, - "description": "Internal maintenance scripts for the stanza monorepo.", - "license": "MIT", - "type": "module", - "scripts": { - "registry:build": "bun run ./src/registry-build.ts", - "module:new": "bun run ./src/module-new.ts", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@clack/prompts": "^1.4.0", - "@stanza/registry": "workspace:*", - "kleur": "^4.1.5", - "mri": "^1.2.0" - }, - "devDependencies": { - "@types/bun": "^1.3.14", - "@types/node": "^25.9.1", - "typescript": "^6.0.3" - } -} diff --git a/packages/internal/tsconfig.json b/packages/internal/tsconfig.json deleted file mode 100644 index 34df389..0000000 --- a/packages/internal/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "rootDir": "./src", - "types": ["node", "bun"] - }, - "include": ["src"] -} diff --git a/packages/registry/tsconfig.json b/packages/registry/tsconfig.json index 792172f..49e05ce 100644 --- a/packages/registry/tsconfig.json +++ b/packages/registry/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.json", + "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist", "rootDir": "./src" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b8d77f7..51caa47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,21 @@ importers: .: devDependencies: + '@clack/prompts': + specifier: ^1.4.0 + version: 1.4.0 + '@stanza/registry': + specifier: workspace:* + version: link:packages/registry '@types/node': specifier: ^25.9.1 version: 25.9.1 + kleur: + specifier: ^4.1.5 + version: 4.1.5 + mri: + specifier: ^1.2.0 + version: 1.2.0 oxfmt: specifier: ^0.51.0 version: 0.51.0 @@ -48,6 +60,9 @@ importers: specifier: ^7.8.0 version: 7.8.0 devDependencies: + '@types/bun': + specifier: ^1.3.14 + version: 1.3.14 '@types/node': specifier: ^25.9.1 version: 25.9.1 @@ -208,31 +223,6 @@ importers: specifier: ^6.0.3 version: 6.0.3 - packages/internal: - dependencies: - '@clack/prompts': - specifier: ^1.4.0 - version: 1.4.0 - '@stanza/registry': - specifier: workspace:* - version: link:../registry - kleur: - specifier: ^4.1.5 - version: 4.1.5 - mri: - specifier: ^1.2.0 - version: 1.2.0 - devDependencies: - '@types/bun': - specifier: ^1.3.14 - version: 1.3.14 - '@types/node': - specifier: ^25.9.1 - version: 25.9.1 - typescript: - specifier: ^6.0.3 - version: 6.0.3 - packages/registry: dependencies: zod: diff --git a/registry/modules/auth-better-auth/tsconfig.json b/registry/modules/auth-better-auth/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/auth-better-auth/tsconfig.json +++ b/registry/modules/auth-better-auth/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/auth-clerk/tsconfig.json b/registry/modules/auth-clerk/tsconfig.json index 71940c6..26f927e 100644 --- a/registry/modules/auth-clerk/tsconfig.json +++ b/registry/modules/auth-clerk/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "compilerOptions": { "types": ["node"] }, diff --git a/registry/modules/db-postgres/tsconfig.json b/registry/modules/db-postgres/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/db-postgres/tsconfig.json +++ b/registry/modules/db-postgres/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/db-sqlite/tsconfig.json b/registry/modules/db-sqlite/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/db-sqlite/tsconfig.json +++ b/registry/modules/db-sqlite/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/framework-next/tsconfig.json b/registry/modules/framework-next/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/framework-next/tsconfig.json +++ b/registry/modules/framework-next/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/framework-tanstack-start/tsconfig.json b/registry/modules/framework-tanstack-start/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/framework-tanstack-start/tsconfig.json +++ b/registry/modules/framework-tanstack-start/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/orm-drizzle/tsconfig.json b/registry/modules/orm-drizzle/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/orm-drizzle/tsconfig.json +++ b/registry/modules/orm-drizzle/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/orm-prisma/tsconfig.json b/registry/modules/orm-prisma/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/orm-prisma/tsconfig.json +++ b/registry/modules/orm-prisma/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/registry/modules/styling-tailwind/tsconfig.json b/registry/modules/styling-tailwind/tsconfig.json index 8988b35..5a91c52 100644 --- a/registry/modules/styling-tailwind/tsconfig.json +++ b/registry/modules/styling-tailwind/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../../tsconfig.base.json", + "extends": "../../../tsconfig.json", "include": ["module.ts"], "exclude": ["templates"] } diff --git a/packages/internal/src/module-new.ts b/scripts/module-new.ts similarity index 93% rename from packages/internal/src/module-new.ts rename to scripts/module-new.ts index 4450a62..16555f1 100644 --- a/packages/internal/src/module-new.ts +++ b/scripts/module-new.ts @@ -3,9 +3,9 @@ * Scaffold a new first-party module under registry/modules/-/. * * Usage: - * bun module:new — fully interactive - * bun module:new — positional, prompts for the rest - * bun module:new [flags] — non-interactive + * bun scripts/module-new.ts — fully interactive + * bun scripts/module-new.ts — positional, prompts for the rest + * bun scripts/module-new.ts [flags] — non-interactive * * Flags: * --label "Display Name" Default: title-cased id @@ -195,7 +195,7 @@ function packageJson(a: Args): string { function tsconfig(withCodemod: boolean): string { const cfg = { - extends: "../../../tsconfig.base.json", + extends: "../../../tsconfig.json", ...(withCodemod ? { compilerOptions: { types: ["node"] } } : {}), include: withCodemod ? ["module.ts", "codemods/**/*.ts"] : ["module.ts"], exclude: ["templates"], @@ -286,7 +286,7 @@ function printHelp() { ${kleur.bold("module:new")} — scaffold a new first-party stanza module ${kleur.bold("Usage")} - bun module:new [slot] [id] [flags] + bun scripts/module-new.ts [slot] [id] [flags] ${kleur.bold("Flags")} --label "Display Name" Default: title-cased id @@ -297,9 +297,9 @@ ${kleur.bold("Flags")} -h, --help Show this help ${kleur.bold("Examples")} - bun module:new # fully interactive - bun module:new email resend # prompt only for label/description - bun module:new ui shadcn-radix --yes # no prompts - bun module:new auth workos --with-codemod # include codemods/index.ts stub + bun scripts/module-new.ts # fully interactive + bun scripts/module-new.ts email resend # prompt only for label/description + bun scripts/module-new.ts ui shadcn-radix --yes # no prompts + bun scripts/module-new.ts auth workos --with-codemod # include codemods/index.ts stub `); } diff --git a/packages/internal/src/registry-build.ts b/scripts/registry-build.ts similarity index 100% rename from packages/internal/src/registry-build.ts rename to scripts/registry-build.ts diff --git a/tsconfig.base.json b/tsconfig.json similarity index 100% rename from tsconfig.base.json rename to tsconfig.json diff --git a/turbo.json b/turbo.json index 413f6c6..ac28487 100644 --- a/turbo.json +++ b/turbo.json @@ -20,10 +20,6 @@ "typecheck": { "dependsOn": ["^build"], "outputs": [] - }, - "registry:build": { - "dependsOn": ["^build"], - "outputs": ["dist/registry/**"] } } }