Skip to content

Releases: jakejarvis/stanza

stanza-cli@0.3.0

13 Jun 19:15
bd0a4cd

Choose a tag to compare

Minor Changes

  • #33 8d4a4ec - stanza add <category> no longer requires a module id. Omit it and, on a TTY, add opens 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

13 Jun 16:13
eee2e80

Choose a tag to compare

Minor Changes

  • #28 dc34c92 - stanza add --dry-run now 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 real add prints 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 cleartext http:// for remote registry and npm endpoints. Registry and npm payloads have no integrity check beyond TLS, so a cleartext endpoint β€” set via STANZA_REGISTRY, a third-party stanza.json#registries[*].url, or STANZA_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 remote http:// and requires https://. 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-party http:// registry is skipped with a warning while the rest of the CLI keeps working; an http:// value for STANZA_REGISTRY/STANZA_NPM_REGISTRY is a hard error. Set the new STANZA_ALLOW_INSECURE_REGISTRY=1 to opt into a trusted internal http:// mirror β€” the CLI prints a one-time stderr warning when it does.

Patch Changes

  • e776dc7 - Guard app.dir against path traversal and symlink escape. The manifest's apps[].dir is joined onto the project root for every write into an app, but was previously an unvalidated z.string() β€” a crafted/attacker-authored stanza.json (cloning an untrusted repo, CI automation) could set dir: "../../etc" and land template files outside the project. appSpecSchema now validates dir with safeRelativePath at parse time (rejecting .., absolute paths, and null bytes, so both add and remove refuse the manifest on read), and applyModule re-checks each target app's dir and 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-run no longer writes anything (previously it created the project directory, monorepo shell, and stanza.json despite 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 failed add rolls files back to their true pre-apply bytes and RegionConflictError fires before any disk change.
    • stanza remove's package-sweep guard reads the consumesPackages snapshot persisted on each installed record (falling back to a registry fetch only for legacy records), so a packages/<dir>/ still imported by an installed module is protected even offline or after an upstream rename.
    • stanza remove only 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 doctor no longer reports false drift for package-home modules installed with an --app restriction.
    • wrap-root-layout resolves 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.example generation. Module env declarations now validate name against the dotenv/shell key pattern ^[A-Za-z_][A-Za-z0-9_]*$ and reject control characters (newlines, CR, …) in example and description, both at the schema boundary (envVarSchema, applied to fetched third-party modules) and as a defense-in-depth guard inside the pure appendEnvVar helper. Previously a module with a newline in name/example/description could smuggle extra KEY=value lines into the generated .env.example.

  • e776dc7 - Guard regions file keys against path traversal and symlink escape. stanza remove deletes files using the region keys read from stanza.json, but those outer keys were previously an unvalidated z.string() β€” a crafted/attacker-authored manifest (cloning an untrusted repo, CI automation) could set a region key to "../../etc/evil" and make remove unlinkSync a path outside the project. The regions record key is now validated with safeRelativePath at parse time (rejecting .., absolute paths, and null bytes, so remove refuses 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 the app.dir guard).

create-stanza@0.1.3

13 Jun 19:15
bd0a4cd

Choose a tag to compare

Patch Changes

  • Updated dependencies [8d4a4ec]:
    • stanza-cli@0.3.0

create-stanza@0.1.2

13 Jun 16:13
eee2e80

Choose a tag to compare

Patch Changes

@withstanza/schema@0.1.1

13 Jun 16:13
eee2e80

Choose a tag to compare

Patch Changes

  • e776dc7 - Guard app.dir against path traversal and symlink escape. The manifest's apps[].dir is joined onto the project root for every write into an app, but was previously an unvalidated z.string() β€” a crafted/attacker-authored stanza.json (cloning an untrusted repo, CI automation) could set dir: "../../etc" and land template files outside the project. appSpecSchema now validates dir with safeRelativePath at parse time (rejecting .., absolute paths, and null bytes, so both add and remove refuse the manifest on read), and applyModule re-checks each target app's dir and 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.example generation. Module env declarations now validate name against the dotenv/shell key pattern ^[A-Za-z_][A-Za-z0-9_]*$ and reject control characters (newlines, CR, …) in example and description, both at the schema boundary (envVarSchema, applied to fetched third-party modules) and as a defense-in-depth guard inside the pure appendEnvVar helper. Previously a module with a newline in name/example/description could smuggle extra KEY=value lines into the generated .env.example.

  • e776dc7 - Guard regions file keys against path traversal and symlink escape. stanza remove deletes files using the region keys read from stanza.json, but those outer keys were previously an unvalidated z.string() β€” a crafted/attacker-authored manifest (cloning an untrusted repo, CI automation) could set a region key to "../../etc/evil" and make remove unlinkSync a path outside the project. The regions record key is now validated with safeRelativePath at parse time (rejecting .., absolute paths, and null bytes, so remove refuses 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 the app.dir guard).

stanza-cli@0.1.1

04 Jun 19:20
22d0461

Choose a tag to compare

Patch Changes

  • #18 04a196e - Fix stanza add monorepo turbo hard-failing on existing projects that lack a root .gitignore.

    The append-to-file codemod gains an optional createIfMissing arg: when set and the target file is absent, it creates the file containing just the wrapped marker block instead of throwing. monorepo-turbo now passes createIfMissing: true for its .turbo/ .gitignore entry, so stanza add monorepo turbo works 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's globals.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/schema package.

    @withstanza/schema now owns the stanza.json manifest schema, the registry module/index schemas, the contract types, the canonical CATEGORIES taxonomy, 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; StanzaManifestSchema backs the JSON Schema served at https://stanza.tools/schema.json. A new private @withstanza/utils package holds the shared path-safety (safeRelativePath) and env-file (appendEnvVar) helpers.

    No change to CLI behavior β€” this is an internal restructure. @withstanza/registry keeps 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 standalone scripts/compile-registry.ts (writing index.json + modules/*.json directly under its output dir, no registry/ wrapper), and the manifest JSON Schema is served by the web app's /schema.json route 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.tools via rewrites: stanza.tools/registry/index.json, stanza.tools/registry/<category>-<id>.json (latest), stanza.tools/registry/<category>-<id>@<version>.json (immutable pin), and stanza.tools/schema.json / schema@<version>.json. The HTML browse pages (/registry, /registry/<category>, /registry/<category>/<id>) are unchanged. DEFAULT_REGISTRY_URL and MANIFEST_SCHEMA_URL keep their values, so the CLI and every manifest's $schema are unaffected.

    @withstanza/schema gains compileManifestJsonSchema() (the shared schema compiler) and REGISTRY_BASE_URL (https://stanza.tools/registry). scripts/publish-registry.ts compiles the registry and uploads it to Blob on every push to main that touches registry/** or the schema source β€” so a module change goes live without a release. Latest files overwrite each run; @version pins are written once and immutable. A pull_request CI guard (scripts/check-module-versions.ts) fails when a changed module's content differs from its published pin without a version bump.

    compile-registry now emits a flat layout (<category>-<id>.json + index.json, no modules/ subdir) that maps 1:1 onto the Blob store, and a module's package.json version is the single source of truth (stamped into the compiled module; module.ts's version field 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 upcoming swap/update verbs.

create-stanza@0.1.1

04 Jun 19:20
22d0461

Choose a tag to compare

Patch Changes

@withstanza/schema@0.1.0

04 Jun 19:20
22d0461

Choose a tag to compare

Minor Changes

  • #16 ea2d8c4 - Extract the schema/contract layer into a standalone, npm-published @withstanza/schema package.

    @withstanza/schema now owns the stanza.json manifest schema, the registry module/index schemas, the contract types, the canonical CATEGORIES taxonomy, 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; StanzaManifestSchema backs the JSON Schema served at https://stanza.tools/schema.json. A new private @withstanza/utils package holds the shared path-safety (safeRelativePath) and env-file (appendEnvVar) helpers.

    No change to CLI behavior β€” this is an internal restructure. @withstanza/registry keeps 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 standalone scripts/compile-registry.ts (writing index.json + modules/*.json directly under its output dir, no registry/ wrapper), and the manifest JSON Schema is served by the web app's /schema.json route 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.tools via rewrites: stanza.tools/registry/index.json, stanza.tools/registry/<category>-<id>.json (latest), stanza.tools/registry/<category>-<id>@<version>.json (immutable pin), and stanza.tools/schema.json / schema@<version>.json. The HTML browse pages (/registry, /registry/<category>, /registry/<category>/<id>) are unchanged. DEFAULT_REGISTRY_URL and MANIFEST_SCHEMA_URL keep their values, so the CLI and every manifest's $schema are unaffected.

    @withstanza/schema gains compileManifestJsonSchema() (the shared schema compiler) and REGISTRY_BASE_URL (https://stanza.tools/registry). scripts/publish-registry.ts compiles the registry and uploads it to Blob on every push to main that touches registry/** or the schema source β€” so a module change goes live without a release. Latest files overwrite each run; @version pins are written once and immutable. A pull_request CI guard (scripts/check-module-versions.ts) fails when a changed module's content differs from its published pin without a version bump.

    compile-registry now emits a flat layout (<category>-<id>.json + index.json, no modules/ subdir) that maps 1:1 onto the Blob store, and a module's package.json version is the single source of truth (stamped into the compiled module; module.ts's version field 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 upcoming swap/update verbs.

stanza-cli@0.1.0

30 May 18:06

Choose a tag to compare

Minor Changes

  • 19b5e51 - 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.

    Ships the first two add-ons: testing-vitest and testing-playwright. They coexist in one project (stanza add testing vitest, stanza add testing playwright), expose --testing vitest,playwright on stanza init --yes, and surface as multi-select cards in the web builder. Existing stanza.json files are unaffected (the addons field defaults to empty).

  • 7349b53 - Add the api category 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 a trpc/orpc module lands β€” the wizard skips it and the web builder hides it while empty. This is purely additive: stanza.json's schema is unchanged (the modules record already keys on arbitrary categories).

    Both published packages (stanza-cli, create-stanza) now ship npm READMEs. stanza-cli also exposes a stanza-cli binary alongside the primary stanza command, so npx stanza-cli … (and bunx / pnpm dlx / yarn dlx) resolve predictably regardless of how each runner handles a single differently-named bin.

  • 71f4c9d - Unify the module taxonomy into one Category concept. The old slot/add-on split conflated two orthogonal properties; categories now carry them explicitly: cardinality ("one" single-choice / "many" coexisting) and home (app / repo / package, a tagged union replacing the packageDir + repoScoped pair). A Module is no longer a discriminated union β€” it carries a single category field.

    The manifest unifies to one modules record keyed by category, holding arrays (cardinality: "one" categories are kept to ≀ 1 record at install time). This bumps stanza.json's version to 0.2 β€” a clean break with no migration (Stanza is pre-1.0 and unpublished). Constraint-bearing is now emergent: the resolver treats only cardinality: "one" categories as peers, so a multi-choice category like testing can never accidentally become a peer. Install routing lives in one categoryHome lookup shared by the CLI runner and the web preview.

  • e89fb63 - Unify registry loading behind a single main file, add transactional apply rollback, and add stanza 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 path on every module entry; the loader resolves each module relative to the main file over file:// and http(s):// identically. The modules/<category>-<id>.json naming convention, the .ts source-tree loader, the in-repo auto-detect, and the inline-template disk fallback are all gone β€” one loader, no conventions. STANZA_REGISTRY must be the full path/URL to a main JSON file (not a directory). Breaking (pre-release, clean break): the registry index is now schemaVersion: 2, and a third-party registries object entry is { url, headers?, params? } where url is the full main-file URL β€” indexUrl and {category}/{id} URL templating are removed.
    • Auto-rollback. stanza add (and each module in stanza init) now wraps its file writes in a transaction: if any step throws β€” including mid-codemod β€” the touched files and stanza.json are restored to their pre-apply state instead of leaving a partial change.
    • stanza doctor. New read-only command that checks stanza.json against the filesystem (claimed files/deps/scripts/env vars still present, internal packages wired) and reports drift, exiting non-zero when found.
  • 8c5433a - Promote template substitution into @stanza/registry and switch the syntax to dotted Mustache-style paths. renderTemplate and buildRenderContext (previously private to the CLI / @stanza/codemods) now live next to synthesizeManifest / synthesizeEnvExample / synthesizePackageJsons, joined by a new synthesizeTemplates that 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 what stanza init writes β€” 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, 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.

    Introduces repo-scoped slots (repoScoped: true on a Slot): their config files land at the monorepo root and their scripts/devDependencies merge into the root package.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 the framework-next module β€” next lint was removed in Next 16 β€” so a tooling pick owns the root lint/format scripts cleanly. The CLI gains --tooling <id> on stanza init --yes and the web builder renders a single-select "Tooling" card, both automatically from the slot taxonomy.

create-stanza@0.1.0

30 May 18:06

Choose a tag to compare

Minor Changes

  • 7349b53 - Add the api category 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 a trpc/orpc module lands β€” the wizard skips it and the web builder hides it while empty. This is purely additive: stanza.json's schema is unchanged (the modules record already keys on arbitrary categories).

    Both published packages (stanza-cli, create-stanza) now ship npm READMEs. stanza-cli also exposes a stanza-cli binary alongside the primary stanza command, so npx stanza-cli … (and bunx / pnpm dlx / yarn dlx) resolve predictably regardless of how each runner handles a single differently-named bin.

Patch Changes