Releases: jakejarvis/stanza
stanza-cli@0.3.0
Minor Changes
-
#33
8d4a4ec-stanza add <category>no longer requires a module id. Omit it and, on a TTY,addopens an interactive picker of that category's modules β modules that aren't compatible yet (a peer isn't installed) or are already installed render disabled with the reason shown inline. Off a TTY (CI, piped input) it lists the available ids and exits instead of hanging. Typing an id that doesn't exist now drops a TTY user into the same picker rather than failing with an opaque "module not found".stanza remove <category>mirrors this for multi-choice categories: omitting the id on a TTY opens a picker over the installed records (it still errors with the installed list off a TTY).
stanza-cli@0.2.0
Minor Changes
-
#28
dc34c92-stanza add --dry-runnow prints a grouped plan of every file it would create, modify, or skip β including the source files its codemods would edit and the reason for any skip (e.g. a dependency you already pin higher) β instead of just "no files were written". A realaddprints the same created/modified/skipped tally as a one-line summary when it finishes.To enumerate codemod edits accurately, a dry run now reads your source files, so it can surface blockers (like a missing root layout) before a real apply. It still writes nothing.
-
e776dc7- Refuse cleartexthttp://for remote registry and npm endpoints. Registry and npm payloads have no integrity check beyond TLS, so a cleartext endpoint β set viaSTANZA_REGISTRY, a third-partystanza.json#registries[*].url, orSTANZA_NPM_REGISTRYβ let an on-path attacker swap module content or steer dependency versions, reaching code execution when the user installs/builds the vendored output. The loader now rejects remotehttp://and requireshttps://.file://URLs, bare filesystem paths, and loopback hosts (localhost/127.0.0.1/::1) stay allowed untouched, so local-dev, air-gapped, and CI-fixture workflows (including a local npm proxy) are unaffected. A third-partyhttp://registry is skipped with a warning while the rest of the CLI keeps working; anhttp://value forSTANZA_REGISTRY/STANZA_NPM_REGISTRYis a hard error. Set the newSTANZA_ALLOW_INSECURE_REGISTRY=1to opt into a trusted internalhttp://mirror β the CLI prints a one-time stderr warning when it does.
Patch Changes
-
e776dc7- Guardapp.diragainst path traversal and symlink escape. The manifest'sapps[].diris joined onto the project root for every write into an app, but was previously an unvalidatedz.string()β a crafted/attacker-authoredstanza.json(cloning an untrusted repo, CI automation) could setdir: "../../etc"and land template files outside the project.appSpecSchemanow validatesdirwithsafeRelativePathat parse time (rejecting.., absolute paths, and null bytes, so bothaddandremoverefuse the manifest on read), andapplyModulere-checks each target app'sdirand asserts every resolved write destination stays within the project root after symlink resolution β closing a symlinked-app-dir escape that a lexical check alone misses. -
6ada5c0- Fix a batch of correctness bugs found in a critical-bug sweep:stanza init --dry-runno longer writes anything (previously it created the project directory, monorepo shell, andstanza.jsondespite saying "no files will be written").- Direct-fs codemods (
append-to-file,add-package-dep,set-tsconfig-paths) claim their region before writing, so a failedaddrolls files back to their true pre-apply bytes andRegionConflictErrorfires before any disk change. stanza remove's package-sweep guard reads theconsumesPackagessnapshot persisted on each installed record (falling back to a registry fetch only for legacy records), so apackages/<dir>/still imported by an installed module is protected even offline or after an upstream rename.stanza removeonly applies the legacy bare-id owner fallback when no sibling install of the same module remains, so removing one app's install on a pre-composite-owner manifest can no longer sweep another app's files.stanza doctorno longer reports false drift for package-home modules installed with an--apprestriction.wrap-root-layoutresolves the framework per dispatched app, fixing multi-app projects with differing frameworks.- The template/codemod render context carries the project's
packageManager, so{{pm}}/{{run β¦}}render bun/npm commands instead of always pnpm.
-
e776dc7- Reject env var line-injection in.env.examplegeneration. Module env declarations now validatenameagainst the dotenv/shell key pattern^[A-Za-z_][A-Za-z0-9_]*$and reject control characters (newlines, CR, β¦) inexampleanddescription, both at the schema boundary (envVarSchema, applied to fetched third-party modules) and as a defense-in-depth guard inside the pureappendEnvVarhelper. Previously a module with a newline inname/example/descriptioncould smuggle extraKEY=valuelines into the generated.env.example. -
e776dc7- Guardregionsfile keys against path traversal and symlink escape.stanza removedeletes files using the region keys read fromstanza.json, but those outer keys were previously an unvalidatedz.string()β a crafted/attacker-authored manifest (cloning an untrusted repo, CI automation) could set a region key to"../../etc/evil"and makeremoveunlinkSynca path outside the project. Theregionsrecord key is now validated withsafeRelativePathat parse time (rejecting.., absolute paths, and null bytes, soremoverefuses the manifest on read), and the remove command re-checks each region key and asserts the resolved real path stays within the project root after symlink resolution before any delete sink β closing a symlinked-directory escape that a lexical check alone misses (sibling to theapp.dirguard).
create-stanza@0.1.3
Patch Changes
- Updated dependencies [
8d4a4ec]:- stanza-cli@0.3.0
create-stanza@0.1.2
@withstanza/schema@0.1.1
Patch Changes
-
e776dc7- Guardapp.diragainst path traversal and symlink escape. The manifest'sapps[].diris joined onto the project root for every write into an app, but was previously an unvalidatedz.string()β a crafted/attacker-authoredstanza.json(cloning an untrusted repo, CI automation) could setdir: "../../etc"and land template files outside the project.appSpecSchemanow validatesdirwithsafeRelativePathat parse time (rejecting.., absolute paths, and null bytes, so bothaddandremoverefuse the manifest on read), andapplyModulere-checks each target app'sdirand asserts every resolved write destination stays within the project root after symlink resolution β closing a symlinked-app-dir escape that a lexical check alone misses. -
e776dc7- Reject env var line-injection in.env.examplegeneration. Module env declarations now validatenameagainst the dotenv/shell key pattern^[A-Za-z_][A-Za-z0-9_]*$and reject control characters (newlines, CR, β¦) inexampleanddescription, both at the schema boundary (envVarSchema, applied to fetched third-party modules) and as a defense-in-depth guard inside the pureappendEnvVarhelper. Previously a module with a newline inname/example/descriptioncould smuggle extraKEY=valuelines into the generated.env.example. -
e776dc7- Guardregionsfile keys against path traversal and symlink escape.stanza removedeletes files using the region keys read fromstanza.json, but those outer keys were previously an unvalidatedz.string()β a crafted/attacker-authored manifest (cloning an untrusted repo, CI automation) could set a region key to"../../etc/evil"and makeremoveunlinkSynca path outside the project. Theregionsrecord key is now validated withsafeRelativePathat parse time (rejecting.., absolute paths, and null bytes, soremoverefuses the manifest on read), and the remove command re-checks each region key and asserts the resolved real path stays within the project root after symlink resolution before any delete sink β closing a symlinked-directory escape that a lexical check alone misses (sibling to theapp.dirguard).
stanza-cli@0.1.1
Patch Changes
-
#18
04a196e- Fixstanza add monorepo turbohard-failing on existing projects that lack a root.gitignore.The
append-to-filecodemod gains an optionalcreateIfMissingarg: when set and the target file is absent, it creates the file containing just the wrapped marker block instead of throwing.monorepo-turbonow passescreateIfMissing: truefor its.turbo/.gitignoreentry, sostanza add monorepo turboworks on a pre-existing project with no.gitignore(previously the whole add rolled back). The flag is additive and off by default β modules appending to peer-owned files (a Prisma schema, a framework'sglobals.css) keep the original "missing file is a peer-ordering bug" throw. -
#16
ea2d8c4- Extract the schema/contract layer into a standalone, npm-published@withstanza/schemapackage.@withstanza/schemanow owns thestanza.jsonmanifest schema, the registry module/index schemas, the contract types, the canonicalCATEGORIEStaxonomy, and the package-manager + registry-config schemas β everything previously bundled into the private@withstanza/registry. It's published so third-party registry authors and editor tooling can validate against the exact same Zod source of truth the CLI uses;StanzaManifestSchemabacks the JSON Schema served at https://stanza.tools/schema.json. A new private@withstanza/utilspackage holds the shared path-safety (safeRelativePath) and env-file (appendEnvVar) helpers.No change to CLI behavior β this is an internal restructure.
@withstanza/registrykeeps only the resolver, install-field synthesis, and template rendering, depending on@withstanza/schema. The static registry build moves out of the registry package to the standalonescripts/compile-registry.ts(writingindex.json+modules/*.jsondirectly under its output dir, noregistry/wrapper), and the manifest JSON Schema is served by the web app's/schema.jsonroute rather than emitted as a build artifact. -
#18
04a196e- Serve the first-party registry and manifest schema from Vercel Blob as the single origin.The registry index, per-module manifests, and the manifest JSON Schema are now hosted on Vercel Blob and served path-transparently from
stanza.toolsvia rewrites:stanza.tools/registry/index.json,stanza.tools/registry/<category>-<id>.json(latest),stanza.tools/registry/<category>-<id>@<version>.json(immutable pin), andstanza.tools/schema.json/schema@<version>.json. The HTML browse pages (/registry,/registry/<category>,/registry/<category>/<id>) are unchanged.DEFAULT_REGISTRY_URLandMANIFEST_SCHEMA_URLkeep their values, so the CLI and every manifest's$schemaare unaffected.@withstanza/schemagainscompileManifestJsonSchema()(the shared schema compiler) andREGISTRY_BASE_URL(https://stanza.tools/registry).scripts/publish-registry.tscompiles the registry and uploads it to Blob on every push tomainthat touchesregistry/**or the schema source β so a module change goes live without a release. Latest files overwrite each run;@versionpins are written once and immutable. Apull_requestCI guard (scripts/check-module-versions.ts) fails when a changed module's content differs from its published pin without a version bump.compile-registrynow emits a flat layout (<category>-<id>.json+index.json, nomodules/subdir) that maps 1:1 onto the Blob store, and a module'spackage.jsonversion is the single source of truth (stamped into the compiled module;module.ts'sversionfield is no longer authoritative). The web app reads its own build-time compiled copy (apps/web/.registry/, gitignored) for prerendering and SSR; the schema is no longer served by an app route. No CLI behavior change β the read path for pinned versions is deferred to the upcomingswap/updateverbs.
create-stanza@0.1.1
@withstanza/schema@0.1.0
Minor Changes
-
#16
ea2d8c4- Extract the schema/contract layer into a standalone, npm-published@withstanza/schemapackage.@withstanza/schemanow owns thestanza.jsonmanifest schema, the registry module/index schemas, the contract types, the canonicalCATEGORIEStaxonomy, and the package-manager + registry-config schemas β everything previously bundled into the private@withstanza/registry. It's published so third-party registry authors and editor tooling can validate against the exact same Zod source of truth the CLI uses;StanzaManifestSchemabacks the JSON Schema served at https://stanza.tools/schema.json. A new private@withstanza/utilspackage holds the shared path-safety (safeRelativePath) and env-file (appendEnvVar) helpers.No change to CLI behavior β this is an internal restructure.
@withstanza/registrykeeps only the resolver, install-field synthesis, and template rendering, depending on@withstanza/schema. The static registry build moves out of the registry package to the standalonescripts/compile-registry.ts(writingindex.json+modules/*.jsondirectly under its output dir, noregistry/wrapper), and the manifest JSON Schema is served by the web app's/schema.jsonroute rather than emitted as a build artifact. -
#18
04a196e- Serve the first-party registry and manifest schema from Vercel Blob as the single origin.The registry index, per-module manifests, and the manifest JSON Schema are now hosted on Vercel Blob and served path-transparently from
stanza.toolsvia rewrites:stanza.tools/registry/index.json,stanza.tools/registry/<category>-<id>.json(latest),stanza.tools/registry/<category>-<id>@<version>.json(immutable pin), andstanza.tools/schema.json/schema@<version>.json. The HTML browse pages (/registry,/registry/<category>,/registry/<category>/<id>) are unchanged.DEFAULT_REGISTRY_URLandMANIFEST_SCHEMA_URLkeep their values, so the CLI and every manifest's$schemaare unaffected.@withstanza/schemagainscompileManifestJsonSchema()(the shared schema compiler) andREGISTRY_BASE_URL(https://stanza.tools/registry).scripts/publish-registry.tscompiles the registry and uploads it to Blob on every push tomainthat touchesregistry/**or the schema source β so a module change goes live without a release. Latest files overwrite each run;@versionpins are written once and immutable. Apull_requestCI guard (scripts/check-module-versions.ts) fails when a changed module's content differs from its published pin without a version bump.compile-registrynow emits a flat layout (<category>-<id>.json+index.json, nomodules/subdir) that maps 1:1 onto the Blob store, and a module'spackage.jsonversion is the single source of truth (stamped into the compiled module;module.ts'sversionfield is no longer authoritative). The web app reads its own build-time compiled copy (apps/web/.registry/, gitignored) for prerendering and SSR; the schema is no longer served by an app route. No CLI behavior change β the read path for pinned versions is deferred to the upcomingswap/updateverbs.
stanza-cli@0.1.0
Minor Changes
-
19b5e51- Add multi-choice add-on modules. AModuleis now a discriminated union onkind("slot"default, or"addon"carrying acategory), andstanza.jsonrecords add-ons in a newaddonsfield 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 viapeers+ per-framework adapters.Ships the first two add-ons:
testing-vitestandtesting-playwright. They coexist in one project (stanza add testing vitest,stanza add testing playwright), expose--testing vitest,playwrightonstanza init --yes, and surface as multi-select cards in the web builder. Existingstanza.jsonfiles are unaffected (theaddonsfield defaults to empty). -
7349b53- Add theapicategory to the taxonomy (single-choice,home: packageβpackages/api/), matching the documented roadmap for a typed RPC layer between the framework and your services. No first-party modules ship for it yet, so it's inert until atrpc/orpcmodule lands β the wizard skips it and the web builder hides it while empty. This is purely additive:stanza.json's schema is unchanged (themodulesrecord already keys on arbitrary categories).Both published packages (
stanza-cli,create-stanza) now ship npm READMEs.stanza-clialso exposes astanza-clibinary alongside the primarystanzacommand, sonpx stanza-cli β¦(andbunx/pnpm dlx/yarn dlx) resolve predictably regardless of how each runner handles a single differently-named bin. -
71f4c9d- Unify the module taxonomy into oneCategoryconcept. The old slot/add-on split conflated two orthogonal properties; categories now carry them explicitly:cardinality("one"single-choice /"many"coexisting) andhome(app/repo/package, a tagged union replacing thepackageDir+repoScopedpair). AModuleis no longer a discriminated union β it carries a singlecategoryfield.The manifest unifies to one
modulesrecord keyed by category, holding arrays (cardinality: "one"categories are kept to β€ 1 record at install time). This bumpsstanza.json's version to0.2β a clean break with no migration (Stanza is pre-1.0 and unpublished). Constraint-bearing is now emergent: the resolver treats onlycardinality: "one"categories as peers, so a multi-choice category liketestingcan never accidentally become a peer. Install routing lives in onecategoryHomelookup shared by the CLI runner and the web preview. -
e89fb63- Unify registry loading behind a single main file, add transactional apply rollback, and addstanza doctor.- Registry main file. A registry is now addressed by the full URL/path to its main JSON file (the index), which carries a required
pathon every module entry; the loader resolves each module relative to the main file overfile://andhttp(s)://identically. Themodules/<category>-<id>.jsonnaming convention, the.tssource-tree loader, the in-repo auto-detect, and the inline-template disk fallback are all gone β one loader, no conventions.STANZA_REGISTRYmust be the full path/URL to a main JSON file (not a directory). Breaking (pre-release, clean break): the registry index is nowschemaVersion: 2, and a third-partyregistriesobject entry is{ url, headers?, params? }whereurlis the full main-file URL βindexUrland{category}/{id}URL templating are removed. - Auto-rollback.
stanza add(and each module instanza init) now wraps its file writes in a transaction: if any step throws β including mid-codemod β the touched files andstanza.jsonare restored to their pre-apply state instead of leaving a partial change. stanza doctor. New read-only command that checksstanza.jsonagainst the filesystem (claimed files/deps/scripts/env vars still present, internal packages wired) and reports drift, exiting non-zero when found.
- Registry main file. A registry is now addressed by the full URL/path to its main JSON file (the index), which carries a required
-
8c5433a- Promote template substitution into@stanza/registryand switch the syntax to dotted Mustache-style paths.renderTemplateandbuildRenderContext(previously private to the CLI /@stanza/codemods) now live next tosynthesizeManifest/synthesizeEnvExample/synthesizePackageJsons, joined by a newsynthesizeTemplatesthat returns the fully-substituted file list for a resolved selection. The web builder's preview now calls the same code path the CLI does at apply time, so file previews stay byte-identical to whatstanza initwrites β fixes a regression where{{dbPackageName}}(and friends) showed up unsubstituted in the preview.The template DSL itself moves to dotted paths:
{{project.name}}(was{{projectName}}),{{project.appDir}}(was{{appDir}}),{{package.name}}(was{{packageName}}, the active module's own package), and{{packages.<dir>.name}}(was{{<dir>PackageName}}, e.g.{{packages.db.name}}for cross-package imports). Self-documenting, composes for future per-package fields ({{packages.db.version}},{{packages.db.path}}) without inventing new flat keys, and aligns with how Mustache resolves nested contexts. Existing first-party modules (auth-better-auth,auth-clerk) migrated; third-party modules referencing the old flat keys will need a one-line rename. -
65c02d4- Add a single-choice tooling slot for the lint/format toolchain, with three modules:eslint-prettier(ESLint flat config + Prettier, per-framework adapters),biome, andoxlint-oxfmt(both framework-agnostic). Modeled as a slot rather than a multi-choice add-on because the three toolchains are mutually exclusive substitutes.Introduces repo-scoped slots (
repoScoped: trueon aSlot): their config files land at the monorepo root and their scripts/devDependencies merge into the rootpackage.json, since one lint/format config governs every workspace. This is a third install home alongside app-scoped and package-scoped slots.Also drops the stale
lint: "next lint"script from theframework-nextmodule βnext lintwas removed in Next 16 β so a tooling pick owns the rootlint/formatscripts cleanly. The CLI gains--tooling <id>onstanza init --yesand the web builder renders a single-select "Tooling" card, both automatically from the slot taxonomy.
create-stanza@0.1.0
Minor Changes
-
7349b53- Add theapicategory to the taxonomy (single-choice,home: packageβpackages/api/), matching the documented roadmap for a typed RPC layer between the framework and your services. No first-party modules ship for it yet, so it's inert until atrpc/orpcmodule lands β the wizard skips it and the web builder hides it while empty. This is purely additive:stanza.json's schema is unchanged (themodulesrecord already keys on arbitrary categories).Both published packages (
stanza-cli,create-stanza) now ship npm READMEs.stanza-clialso exposes astanza-clibinary alongside the primarystanzacommand, sonpx stanza-cli β¦(andbunx/pnpm dlx/yarn dlx) resolve predictably regardless of how each runner handles a single differently-named bin.