Skip to content

refactor!: drop shadcn-init, make shadcn-apply a plain forwarder - #182

Merged
abernier merged 2 commits into
mainfrom
refactor/shadcn-apply-forwarder
Aug 18, 2026
Merged

refactor!: drop shadcn-init, make shadcn-apply a plain forwarder#182
abernier merged 2 commits into
mainfrom
refactor/shadcn-apply-forwarder

Conversation

@abernier

Copy link
Copy Markdown
Owner

An épure pass over the two subcommands released yesterday in 3.3.0. shadcn-init goes; shadcn-apply stays and stops second-guessing you.

−856 lines net. cli.shadcn.ts 598 → 112, its tests 550 → 211, cli.options.ts 197 → 156.

shadcn-init is removed

It did three jobs and only the middle one was ours. Scaffolding is what shadcn init already does — better, with its own eighteen options — and starting a dev server is not a theming tool's business.

# before
$ npx material-theme-builder shadcn-init "#6750A4"

# now
$ npx shadcn@latest init --preset b0 --template vite
$ cd material-theme-app && npx material-theme-builder shadcn-apply "#6750A4"

Everything that existed to hold those three jobs together goes with it: the --preset b0 --template vite defaults, mergeDefaults()/mentions() (which injected them only where you had written none yourself), and lastName() — reading the project name back out of the merged argv, scanning from the end to mirror commander's last-one-wins, so the chain knew where to cd.

shadcn-apply is a plain forwarder

Same mechanism, same output. What changed is the rule about the --, which is now true without an asterisk: everything after the separator goes to shadcn add untouched.

  • --print is gone, and with it the Plan/Step/renderChain/quote model (~90 lines) that existed so the printed chain and the run chain could not disagree. On a single shadcn add there is nothing left to disagree about. It also took specifiedThemeArgs(), THEME_OPTIONS and Theme.args in cli.options.ts, which existed only to re-spell the given options into that printed line.
  • An option of ours after the -- is no longer refused. refuseOwnOptions()/ownOptionIn() gone. shadcn-apply '#x' -- --scheme vibrant now gets error: unknown option '--scheme' from shadcn. A worse message, and one rule instead of one rule plus an exception.
  • --shadcn-cli no longer validates its argument. parseShadcnSpec() gone; whatever npx resolves, it runs.

What stayed, and why

  • --yes on shadcn add — and it owes nothing to shadcn-init. Checked against the installed shadcn 4.18.0: add defaults its own --yes to false (unlike init, where it is true) and prompts on exactly our item type:
    if (!r.yes && !o && (m === "registry:style" || m === "registry:theme")) { /* confirm */ }
    Without it the command hangs in CI. Ours goes first, so a forwarded --no-yes still wins on commander's last-one-wins.
  • The mtb.json guard and the process.on("exit") cleanup — this runs in a directory full of someone else's files, and that one is both written and deleted. Not intelligence about options; not leaving a scratch file behind.
  • The theme options (--scheme, --contrast, the overrides, --prefix, --no-fallback) — the reason the command exists rather than the published impersonal item.
  • The name shadcn-applyapply is now a verb borrowed from a command we do not call, but renaming would make the same migration cost twice in one major. The 30-line comment defending it is gone.

Verified against the real CLI

Not just unit tests — the built binary against shadcn 4.18.0 in this repo:

invocation result
shadcn-apply '#6750A4' -- --dry-run 62 CSS variables added to src/styles/shadcn.css, exit 0
-- --view src/styles/shadcn.css forwarded verbatim, shadcn renders the diff
-- --scheme vibrant unknown option '--scheme' — forwarded, as intended
a pre-existing mtb.json refused, file untouched
after a failing shadcn add no mtb.json left behind

pnpm run lgtm green: build, figma build, lint, format, exports, typecheck, 213 tests.

Semver

Breaking on a surface published yesterday, so: changeset major4.0.0. Someone typing shadcn-init gets commander's unknown command — no migration stub, that being exactly the complexity this PR removes. The 3.3.0 CHANGELOG entry stays as written; it is history.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FtANJj1cbBZpj12mEXjgSN

abernier and others added 2 commits August 18, 2026 08:00
`shadcn-init` did three jobs and only the middle one was ours: scaffolding is
what `shadcn init` already does, with its own eighteen options, and starting a
dev server is not a theming tool's business. Removing it takes with it
everything that existed to hold those together -- the `--preset b0
--template vite` defaults, the merge that injected them only where the user had
not, and reading the project name back out of the merged argv to know where to
`cd`.

`shadcn-apply` keeps what it does and drops how it second-guessed you.
Everything after a `--` now reaches `shadcn add` untouched, with no exception:
`--print` and the `Plan`/`Step` model it required are gone, an option of ours
written after the separator is forwarded rather than refused with an
explanation, and `--shadcn-cli` no longer validates its argument.

`--print` also took `specifiedThemeArgs()` and `Theme.args` with it, those
having existed only to re-spell the given options into a printed line.

The `--yes` on `shadcn add` stays, and owes nothing to `shadcn-init`: shadcn
defaults its own to `false` and prompts on a `registry:theme` item, so without
it the command hangs in CI. Ours goes first, so a forwarded `--no-yes` still
wins on commander's last-one-wins.

cli.shadcn.ts 598 -> 112, cli.options.ts 197 -> 156, its tests 550 -> 211.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtANJj1cbBZpj12mEXjgSN
…ts choices

Two leftovers from the épure pass, both in `cli.ts`.

`addChainOptions()` declared a single option and, since `shadcn-init` went, had
a single call site -- where `addThemeOptions()` and `addSourceArgument()` each
still have two, which is what earns them a function. It cost an export, a
cross-file import and a third level of nesting to say `.option()` once. Inlined
where the subcommand is declared, next to the `--shadcn` it is named around.

`--format` maintained its list of values by hand in its own description, and
answered anything else through a `default:` branch: `--format bananas` printed
JSON without a word, where `--scheme banana` has always been refused by name.
The list is now one `FORMATS` array behind `.choices()` and a `Format` union, so
the help text is generated, the typo is refused, and the switch is exhaustive
over what the option accepts -- a format can no longer be offered without being
written, or written without being offered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FtANJj1cbBZpj12mEXjgSN
@abernier

Copy link
Copy Markdown
Owner Author

Deux nettoyages de plus, révélés par le premier passage — 77a1a7d.

addChainOptions() inliné. Il déclarait une seule option et, depuis le départ de shadcn-init, n'avait plus qu'un seul appelant — là où addThemeOptions() et addSourceArgument() en ont toujours deux, ce qui est précisément ce qui justifie d'en faire une fonction. Il coûtait un export, un import inter-fichiers et un troisième niveau d'imbrication pour dire .option() une fois. Déclaré maintenant à côté de la sous-commande, juste sous le --shadcn autour duquel il est nommé.

--format a ses .choices(). Il maintenait sa liste de valeurs à la main dans sa propre description, et répondait à tout le reste par une branche default: :

# avant
$ material-theme-builder "#6750A4" --format bananas
{ "seed": "#6750A4", ...        # du JSON, sans un mot

# maintenant
error: option '--format <type>' argument 'bananas' is invalid.
Allowed choices are json, css, figma, tailwind, shadcn, registry-item, flutter.

--scheme banana était refusé par son nom depuis toujours ; --format ne l'était pas. La liste est désormais un seul tableau FORMATS derrière .choices() et une union Format, donc le texte d'aide est généré, la faute de frappe est refusée, et le switch est exhaustif sur ce que l'option accepte — un format ne peut plus être proposé sans être écrit, ni écrit sans être proposé. Ajouté au changeset, étant cassant lui aussi.

pnpm run lgtm vert, 214 tests. Chaîne bout-en-bout re-vérifiée contre shadcn 4.18.0 : -- --dry-run passe, aucun mtb.json résiduel.

@abernier
abernier merged commit 19757d4 into main Aug 18, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant