CLI Reference

Every command shipped with @saastro/forms — init and install-deps.

CLI Reference

@saastro/forms ships a small CLI that runs through npx. It scaffolds example forms and installs the shadcn/ui components those forms need.

npx @saastro/forms <command> [options]

Run with no arguments to print the inline help.

Command / optionWhat it does
initScaffold zero-config example forms
install-depsInstall required shadcn components + peer deps, skipping ones that already exist
--forceWith install-deps: reinstall and overwrite components that already exist

init

Scaffold two zero-config example forms in src/components/.

npx @saastro/forms init

init requires a components.json (the shadcn config) in the directory you run it from — it exits with an error otherwise:

❌ No components.json found in the current directory.
   Make sure you run this command from your project root and that shadcn/ui is initialized (npx shadcn@latest init).

If you don’t have one yet, run npx shadcn@latest init first.

Creates:

  • src/components/ExampleForm.tsx — programmatic form built with FormBuilder
  • src/components/ExampleFormJson.tsx — same form expressed as JSON config (useful as a starting point for storing forms in a database or CMS)

If either file already exists, it is left untouched and the CLI reports exists <path> instead of create <path>.

After running init, you’ll be reminded to call:

npx @saastro/forms install-deps

This installs the shadcn components @saastro/forms needs (plus any missing peer dependencies), skipping ones already in your project.

Use the generated form

import { ExampleForm } from '@/components/ExampleForm';

export default function ContactPage() {
  return <ExampleForm />;
}

The example uses Vite’s import.meta.glob so every .tsx file in @/components/ui/ is automatically wired into the form. Add new components to that folder and they’ll be available without any registration step.

Vite only: import.meta.glob is a Vite feature — it does not exist in Next.js, webpack, or other bundlers. On non-Vite setups, replace the glob in the generated files with explicit imports passed via the components prop — see the Components guide.


install-deps

Read your project’s components.json (the shadcn config) and install every shadcn component @saastro/forms field types use — the full set (input, button, calendar, command, …), not just what the example forms reference — skipping any that are already present.

npx @saastro/forms install-deps

What it does:

  1. Verifies you’re running it inside your own project (not inside the @saastro/forms package itself).
  2. Reads components.json to discover the project’s path aliases and component directory (see Alias resolution below).
  3. Ensures lib/utils.ts exists (the shadcn cn() helper). Creates it if not.
  4. Checks which components already exist in your ui directory and skips them, logging ✓ <component> skipped (exists) for each one. Existing files are never overwritten unless you pass --force.
  5. Runs shadcn@latest add <missing-components> --yes to install whichever components are missing, then rewrites their imports to match the aliases configured in your components.json.
  6. If the batch install fails (rare), retries one component at a time so a single broken package doesn’t block the rest.

When to run it:

  • After the first init.
  • Any time you add a field type whose required components you haven’t installed yet — the runtime fallback also tells you to run this command.

--force

By default, components that already exist in your ui directory are skipped so your local customizations are safe. Pass --force to reinstall everything and let shadcn overwrite the existing files:

npx @saastro/forms install-deps --force

When everything is already installed and you didn’t pass --force, the CLI tells you so:

✅ All required components are already installed.
   Pass --force to reinstall and overwrite them.

Alias resolution

The CLI reads the aliases field of your components.json (the shadcn schema: aliases.ui, aliases.components, aliases.utils, …) and resolves each alias to a real filesystem path:

  1. The alias root (e.g. @ in @/components/ui) is looked up in your tsconfig-style path mappings — compilerOptions.paths in tsconfig.json, tsconfig.app.json, or jsconfig.json (JSONC comments and trailing commas are handled). A mapping like "@/*": ["./app/*"] resolves @/components/ui to app/components/ui.
  2. If no mapping is found, the shadcn/Vite convention applies: the alias root maps to src (so @/components/uisrc/components/ui).
  3. If components.json has no usable aliases at all, the CLI falls back to the legacy @/src/ replacement and warns once:
⚠ components.json has no usable "aliases" entries. Falling back to the default "@/" → "src/" mapping.

Imports inside freshly installed components are rewritten to your configured aliases (e.g. ../../lib/utils becomes whatever aliases.utils says — @/lib/utils, ~/lib/utils, …).

What if I don’t have a components.json?

install-deps skips gracefully (exit code 0) instead of failing — it doubles as a postinstall hook, so a monorepo root or server-only package shouldn’t error out:

ℹ️  No components.json found. Skipping automatic component installation.
   Initialize shadcn/ui first (npx shadcn@latest init), then run:
   npx @saastro/forms install-deps

Run npx shadcn@latest init, answer the prompts, then re-run install-deps.


add (removed)

Earlier versions documented an add <name> command that copied custom components bundled inside the package. The package bundles no custom components, so the command could never succeed and has been removed. Running it now prints a pointer to the right tools:

This package has no bundled components. Use `npx shadcn@latest add <name>` for shadcn components, or `npx @saastro/forms install-deps` to install everything the form needs.

To install a single shadcn component, use the shadcn CLI directly:

npx shadcn@latest add <component-name>

To install everything @saastro/forms needs, use install-deps.


Troubleshooting

The literal CLI output is quoted below so you can match it to what you see in your terminal.

”You are inside the @saastro/forms package itself.”

You ran install-deps from inside the @saastro/forms package source instead of your own project. Switch to your project root (the directory where your package.json lives) and run the command from there.

”No components.json found”

shadcn isn’t initialised in this project yet (this affects both init and install-deps — they both read components.json). init exits with an error; install-deps skips gracefully. Either way, run:

npx shadcn@latest init

…answer the prompts, then re-run npx @saastro/forms install-deps.

”Could not parse components.json”

Your components.json exists but isn’t valid JSON. Fix the syntax error mentioned in the message and re-run the command.

A specific component fails to install

install-deps falls back to one-at-a-time installation when the batch run fails (“⚠️ Batch install failed, retrying one component at a time…”). If a particular component still fails, install it manually:

npx shadcn@latest add <component-name>

…and re-run install-deps to verify everything else is in place.

A component was skipped but I want a fresh copy

That’s the overwrite guard: ✓ <component> skipped (exists) means the file is already in your ui directory and was left untouched. Re-run with --force to overwrite it (this discards local edits to that file).


Peer dependencies (installed automatically since 0.2.1)

install-deps also verifies that the peer dependencies are declared in your package.json and installs any that are missing:

  • zod — installs ^4 if missing (the published peer range also accepts ^3.25 if you already have it)
  • react-hook-form (^7.67)
  • react-day-picker (^9.11.3)
  • date-fns (^4.1)

The check is by presence in your package.json, not by version — already declared dependencies are never touched.

The package manager is detected from your lockfile in this priority order:

  1. pnpm-lock.yamlpnpm add ...
  2. bun.lock or bun.lockbbun add ...
  3. yarn.lockyarn add ...
  4. package-lock.json (or none) → npm install ...

react and react-dom are intentionally not auto-installed — every React project already has them, and choosing between React 18 and 19 is a project decision.