Skip to content

feat(cli): add apply command#1758

Merged
zernonia merged 3 commits intodevfrom
feat-apply-preset
Apr 11, 2026
Merged

feat(cli): add apply command#1758
zernonia merged 3 commits intodevfrom
feat-apply-preset

Conversation

@zernonia
Copy link
Copy Markdown
Member

@zernonia zernonia commented Apr 8, 2026

Summary

  • Adds shadcn-vue apply [preset], mirroring shadcn apply. Swaps the project's design preset and re-installs every UI component against it in one shot.
  • Supports the same three preset shapes as the shadcn-ui CLI: a named preset (reka-nova), an encoded preset from the web UI (00M9Mun7wYkt6), or a remote preset URL.
  • The project's existing base and rtl are layered on top of the resolved preset so applying never silently switches the user's component library.
  • Snapshots components.json to components.json.apply.bak before delegating to runInit({ force: true, reinstall: true, skipPreflight: true, ... }), and restores it on failure.

What's in the diff

  • packages/cli/src/commands/apply.ts — the new command + exported pure helpers (resolveApplyPreset, resolveApplyPresetConfig, getBase, getInitCommand).
  • packages/cli/src/preflights/preflight-apply.ts — preflight that validates package.json + a parseable components.json.
  • packages/cli/src/utils/get-project-info.ts — adds getProjectComponents(cwd) which scans the resolved ui/ dir to discover what to re-install.
  • packages/cli/src/index.ts — registers the command.
  • packages/cli/src/commands/apply.test.ts — 16 unit tests covering positional vs --preset resolution, base derivation, init-command formatting, and the three preset-resolution paths (named, encoded, URL via msw mock).
  • apps/v4/content/docs/06.cli.md — adds the ## apply doc section, matching shadcn-ui's docs style.

Notes

  • apply does not require vue-metamorph. It works by re-downloading components from the registry under the new style URL, not by transforming local source. If a preset swap also changes iconLibrary or rtl, users still need to follow up with shadcn-vue migrate icons / migrate rtl to update their own (non-ui/) source files.
  • Uses a dedicated .apply.bak suffix to avoid colliding with the .bak lifecycle that init already manages internally via its process.on('exit') handler.

Test plan

  • pnpm --filter @shadcn/cli vitest run src/commands/apply.test.ts — 16 tests pass
  • Smoke test against a real project: npx shadcn-vue apply reka-nova in a cloned starter, confirm components.json.style updates and existing ui/ components are re-fetched
  • Smoke test with --preset <encoded> and a remote URL
  • Confirm the abort path (decline the confirmation prompt, or trigger an init failure mid-run) restores the original components.json from .apply.bak

Summary by CodeRabbit

  • New Features

    • Added an apply CLI command to apply presets to existing projects, preserving base/RTL, prompting when components exist, and safely running an in-place init with backups
    • Project component detection to list existing UI components
  • Chores

    • New safe file backup/restore helper and quieter cleanup handling
    • Init prompt defaults tweaked for base color and CSS variables
  • Bug Fixes

    • Prevent duplicate nested CSS at-rules
    • Expanded rules for when CSS variables are overwritten
  • Tests

    • Unit tests covering preset resolution, command formatting, URL handling, and base/RTL behavior
  • Documentation

    • CLI docs and examples for the new apply command and its options

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new apply CLI command and docs to apply presets to existing projects. Implements preflight validation, preset resolution (named/encoded/URL), component enumeration, safe components.json backup/restore, in-place runInit invocation, helper utilities, tests, and CLI registration.

Changes

Cohort / File(s) Summary
CLI Command & Helpers
packages/cli/src/commands/apply.ts
New apply subcommand with Zod-validated options, preset resolution (positional/flag/encoded/URL), confirmation flow, backup/restore of components.json, in-place runInit with forced base/RTL, error handling, and exported helpers (applyOptionsSchema, resolveApplyPreset, getInitCommand, getBase, resolveApplyInitUrl).
Tests
packages/cli/src/commands/apply.test.ts
New Vitest suite covering preset precedence/whitespace/conflict exit, getBase, getInitCommand quoting, and resolveApplyInitUrl behaviors (named, encoded, remote; param merging, RTL/base overrides, tracking).
CLI Registration
packages/cli/src/index.ts
Registers the new apply command on the top-level commander program via .addCommand(apply).
Preflight
packages/cli/src/preflights/preflight-apply.ts
New preFlightApply validating cwd, presence of package.json and components.json; returns errors/config or reports invalid config and exits on fatal issues.
Project Inspection Utility
packages/cli/src/utils/get-project-info.ts
Added getProjectComponents(cwd) to enumerate immediate UI components (directories and immediate files), deduplicate and sort names for reinstall confirmation.
File Helpers
packages/cli/src/utils/file-helper.ts
Added withFileCopyBackup helper (copy-back backup, exit-handler restore, delete-on-success/restore-on-failure) and made deleteFileBackup catch-clause silent; introduced internal backup options.
CSS Updater
packages/cli/src/utils/updaters/update-css.ts
Avoid creating duplicate child at-rules when processing nested empty-body at-rules by checking existing atrule nodes before appending.
Add Components Logic
packages/cli/src/utils/add-components.ts
Expanded shouldOverwriteCssVars predicate to treat registry:base and registry:font as conditions that require overwriting CSS variables.
Init prompt defaults
packages/cli/src/commands/init.ts
promptForMinimalConfig now initializes baseColor and cssVariables from provided opts or falls back to defaultConfig.tailwind consistently.
Docs
apps/v4/content/docs/06.cli.md
Added apply documentation section with example usage and full help/usage (preset, -y/--yes, -c/--cwd, -s/--silent, -h/--help).

Sequence Diagram

sequenceDiagram
    participant User as User
    participant CLI as CLI (commander)
    participant Apply as Apply Command
    participant Preflight as preFlightApply
    participant FS as Filesystem
    participant Preset as Preset Resolver
    participant Init as runInit

    User->>CLI: run `shadcn-vue apply [preset]`
    CLI->>Apply: dispatch handler
    Apply->>Apply: validate args (Zod)
    Apply->>Preset: resolve preset (named / encoded / URL)
    Apply->>Preflight: preFlightApply(cwd)
    Preflight->>FS: check package.json & components.json
    Preflight-->>Apply: config or errors
    Apply->>Apply: derive base & RTL from config
    Apply->>Apply: enumerate components (getProjectComponents)
    Apply->>User: prompt for confirmation (unless --yes)
    User-->>Apply: confirm
    Apply->>FS: copy components.json -> components.json.apply.bak
    Apply->>Init: runInit(inPlace=false, reinstall=true, yes=true, skipPreflight=true, preset/url, base, rtl)
    Init->>FS: write files, update components
    Init-->>Apply: success / failure
    alt success
        Apply->>FS: delete .apply.bak
        Apply->>User: log success
    else failure
        Apply->>FS: restore components.json from .apply.bak
        Apply->>User: log error
    end
    Apply->>Apply: clearRegistryContext (finally)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped in with a gentle clap,
A preset folded in my paw.
Backups kept, changes swept,
If fright appears, I restore it all.
Apply with courage, nibble bugs small.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(cli): add apply command' directly and clearly summarizes the main change: introducing a new 'apply' CLI command to the shadcn-vue project.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-apply-preset

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/cli/src/commands/apply.test.ts (1)

68-221: Add one action-level test for the failure/rollback path.

This suite only exercises helper exports. The new command flow itself—preflight, backup creation, restore on failure, and cleanup—still isn't covered. A mocked runInit() failure test would lock down the risky path in apply() much better than helper-only coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/apply.test.ts` around lines 68 - 221, Add an
action-level test that calls the top-level apply() flow and simulates a failing
runInit() to exercise preflight, backup, rollback and cleanup: mock runInit() to
throw, spy on preflight(), createBackup()/backup creation, restoreBackup() and
cleanupBackup() (or their actual names in the module) and assert that
createBackup() was called before the failure, restoreBackup() was invoked after
the failure, cleanupBackup() ran, and apply() surfaces the error (or exits) as
expected; ensure the test imports apply() from the CLI command module and
restores all spies/mocks after the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/apply.ts`:
- Around line 153-185: The rollback never runs because runInit() funnels errors
into handleError(), which exits the process instead of throwing, so apply()
never reaches the catch block to restore applyBackupPath; fix by changing
runInit()/handleError() so errors are surfaced: add an option (e.g.,
exitOnError: boolean) to runInit and thread it to handleError, make handleError
throw the error instead of calling process.exit when exitOnError is false, and
call runInit({ ..., exitOnError: false }) from the apply command so a thrown
error reaches the catch that restores componentsJsonPath from applyBackupPath;
reference runInit, handleError, applyBackupPath and componentsJsonPath when
making the change.

In `@packages/cli/src/preflights/preflight-apply.ts`:
- Around line 30-36: The code currently force-asserts config with `config!`
after `const config = await getConfig(options.cwd)` even though `getConfig()`
can return null for an empty components.json; update preflight handling in
`preflight-apply.ts` to treat `!config` the same as the invalid-config branch:
do not use the non-null assertion, and when `config` is null return the `{
errors, config: null }` response but populate `errors` (or add a specific
invalid-config error key) so callers (like the logic in `apply.ts`) receive an
explicit invalid-config error instead of a silent exit — modify the return path
around the `config` value and ensure any downstream checks rely on the error
flag rather than a non-null assertion.

---

Nitpick comments:
In `@packages/cli/src/commands/apply.test.ts`:
- Around line 68-221: Add an action-level test that calls the top-level apply()
flow and simulates a failing runInit() to exercise preflight, backup, rollback
and cleanup: mock runInit() to throw, spy on preflight(), createBackup()/backup
creation, restoreBackup() and cleanupBackup() (or their actual names in the
module) and assert that createBackup() was called before the failure,
restoreBackup() was invoked after the failure, cleanupBackup() ran, and apply()
surfaces the error (or exits) as expected; ensure the test imports apply() from
the CLI command module and restores all spies/mocks after the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed1c65af-2bba-452d-ac2e-ea8dc2759daf

📥 Commits

Reviewing files that changed from the base of the PR and between 9ae3af8 and d771bfb.

📒 Files selected for processing (6)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.test.ts
  • packages/cli/src/commands/apply.ts
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/get-project-info.ts

Comment thread packages/cli/src/commands/apply.ts Outdated
Comment thread packages/cli/src/preflights/preflight-apply.ts
@zernonia zernonia force-pushed the feat-apply-preset branch from d771bfb to 53ba6bc Compare April 11, 2026 16:15
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/cli/src/commands/apply.ts (1)

181-217: ⚠️ Potential issue | 🔴 Critical

Surface init failures back to this catch before relying on rollback.

The backup is only restored if runInit() throws into this block. If runInit() still handles its own failures via handleError() / process.exit(), Lines 212-215 never run and .apply.bak becomes the only copy of the original components.json.

Please verify that runInit() can return control here on failure. Expected result: its error path should rethrow (or support a non-exiting mode) instead of terminating the process.

#!/bin/bash
set -euo pipefail

echo '--- init/apply error flow ---'
rg -n -C4 'export async function runInit|catch \(|handleError\(|process\.exit\(' \
  packages/cli/src/commands/init.ts \
  packages/cli/src/utils/handle-error.ts \
  packages/cli/src/commands/apply.ts
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/apply.ts` around lines 181 - 217, runInit currently
may terminate the process via handleError/process.exit which prevents the apply
catch from running and restoring .apply.bak; change runInit (and/or handleError)
to surface failures instead of exiting by either: 1) adding a non-exiting mode
flag (e.g., runInit({ ..., abortOnError: false } or similar) so runInit will
throw errors instead of calling process.exit, or 2) making handleError rethrow
when invoked from runInit in non-interactive flows; then update the runInit
invocation in apply.ts to pass the non-exiting flag (or call the throwing
variant) so failures propagate to the surrounding catch and trigger the rollback
code that copies applyBackupPath back to componentsJsonPath. Ensure any helper
names referenced are runInit and handleError/process.exit and that tests or
callers expecting process exit are updated accordingly.
🧹 Nitpick comments (1)
packages/cli/src/commands/apply.test.ts (1)

24-187: Please add an action-level test for apply().

This suite only covers the exported helpers; it never exercises the command's .action() body. That leaves the missing-config preflight branch and the .apply.bak restore path unprotected even though they're the risky parts of this feature.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/apply.test.ts` around lines 24 - 187, The test
suite never invokes the command's action body, so add an action-level test that
imports the exported apply command (the exported object/function named apply)
and calls its .action(...) to exercise the command flow; set up a temporary cwd
and fixtures to simulate missing config (to trigger the preflight/missing-config
branch) and create a fake .apply.bak file to verify the restore path,
mocking/stubbing filesystem ops (fs.rename/fs.exists/fs.unlink or the helper
functions used in apply), network/URL resolution if needed
(resolveApplyInitUrl), and process.exit behavior to assert the expected
outcomes; ensure the test cleans up mocks and temp files and asserts that
restore logic was executed and that the missing-config branch exits/returns as
expected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/apply.ts`:
- Around line 18-20: The branch in the apply command is checking the wrong error
key (MISSING_CONFIG) so the friendly guidance from preFlightApply() never runs;
change the conditional(s) that compare the error key(s) to use
ERRORS.MISSING_CONFIG_FILE (the same constant used in
preflights/preflight-apply.ts) instead of MISSING_CONFIG so the components.json
missing flow triggers the guidance and avoids silently falling through to
process.exit(1); update every occurrence in the apply command (including the
block around the second occurrence referenced) to use
ERRORS.MISSING_CONFIG_FILE.
- Around line 249-258: The code uses JSON.stringify in quoteShellArg which
produces double-quoted args that allow shell variable expansion; update
quoteShellArg to produce safe POSIX single-quoted arguments (wrap the value in
single quotes and escape any internal single quotes using the '\'' pattern) so
getInitCommand returns a shell-safe string for presets; also update the tests
that expect double quotes to expect the new single-quoted/escaped output.

---

Duplicate comments:
In `@packages/cli/src/commands/apply.ts`:
- Around line 181-217: runInit currently may terminate the process via
handleError/process.exit which prevents the apply catch from running and
restoring .apply.bak; change runInit (and/or handleError) to surface failures
instead of exiting by either: 1) adding a non-exiting mode flag (e.g., runInit({
..., abortOnError: false } or similar) so runInit will throw errors instead of
calling process.exit, or 2) making handleError rethrow when invoked from runInit
in non-interactive flows; then update the runInit invocation in apply.ts to pass
the non-exiting flag (or call the throwing variant) so failures propagate to the
surrounding catch and trigger the rollback code that copies applyBackupPath back
to componentsJsonPath. Ensure any helper names referenced are runInit and
handleError/process.exit and that tests or callers expecting process exit are
updated accordingly.

---

Nitpick comments:
In `@packages/cli/src/commands/apply.test.ts`:
- Around line 24-187: The test suite never invokes the command's action body, so
add an action-level test that imports the exported apply command (the exported
object/function named apply) and calls its .action(...) to exercise the command
flow; set up a temporary cwd and fixtures to simulate missing config (to trigger
the preflight/missing-config branch) and create a fake .apply.bak file to verify
the restore path, mocking/stubbing filesystem ops (fs.rename/fs.exists/fs.unlink
or the helper functions used in apply), network/URL resolution if needed
(resolveApplyInitUrl), and process.exit behavior to assert the expected
outcomes; ensure the test cleans up mocks and temp files and asserts that
restore logic was executed and that the missing-config branch exits/returns as
expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d3a17c43-1de0-4f3e-b746-5231c0a6babc

📥 Commits

Reviewing files that changed from the base of the PR and between d771bfb and 53ba6bc.

📒 Files selected for processing (6)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.test.ts
  • packages/cli/src/commands/apply.ts
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/get-project-info.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/v4/content/docs/06.cli.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts

Comment thread packages/cli/src/commands/apply.ts
Comment thread packages/cli/src/commands/apply.ts
@zernonia zernonia force-pushed the feat-apply-preset branch from 53ba6bc to 780c2cb Compare April 11, 2026 16:31
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/apply.test.ts`:
- Around line 61-78: The test "exits when positional and flag presets disagree"
creates an exitSpy via vi.spyOn(process, 'exit') with a mockImplementation but
calls exitSpy.mockRestore() unguarded; wrap the assertions and expectations in a
try/finally (or move cleanup to an afterEach) so exitSpy.mockRestore() always
runs even if an expectation fails—locate the exitSpy created around
vi.spyOn(process, 'exit') and ensure its mockRestore is executed in finally to
guarantee test isolation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9113b00b-087b-4ecf-bb29-d061c9627fde

📥 Commits

Reviewing files that changed from the base of the PR and between 53ba6bc and 780c2cb.

📒 Files selected for processing (8)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.test.ts
  • packages/cli/src/commands/apply.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/file-helper.ts
  • packages/cli/src/utils/get-project-info.ts
✅ Files skipped from review due to trivial changes (3)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/index.ts
  • packages/cli/src/commands/apply.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/src/utils/get-project-info.ts
  • packages/cli/src/preflights/preflight-apply.ts

Comment thread packages/cli/src/commands/apply.test.ts
@zernonia zernonia force-pushed the feat-apply-preset branch 2 times, most recently from a179ee3 to 12f578f Compare April 11, 2026 16:39
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/cli/src/commands/apply.test.ts (1)

61-78: ⚠️ Potential issue | 🟡 Minor

Protect process.exit spy cleanup with finally.

At Line 77, exitSpy.mockRestore() is unguarded. If an assertion fails first, the spy can leak into later tests.

Proposed patch
   it('exits when positional and flag presets disagree', () => {
     const exitSpy = vi
       .spyOn(process, 'exit')
       .mockImplementation(((_code?: number) => {
         throw new Error('process.exit called')
       }) as never)

-    expect(() =>
-      resolveApplyPreset({
-        ...baseOptions,
-        positionalPreset: 'vega',
-        preset: 'nova',
-      }),
-    ).toThrow('process.exit called')
-
-    expect(exitSpy).toHaveBeenCalledWith(1)
-    exitSpy.mockRestore()
+    try {
+      expect(() =>
+        resolveApplyPreset({
+          ...baseOptions,
+          positionalPreset: 'vega',
+          preset: 'nova',
+        }),
+      ).toThrow('process.exit called')
+
+      expect(exitSpy).toHaveBeenCalledWith(1)
+    }
+    finally {
+      exitSpy.mockRestore()
+    }
   })
#!/bin/bash
set -euo pipefail

# Confirm the unguarded cleanup in this test block.
cat -n packages/cli/src/commands/apply.test.ts | sed -n '61,80p'

# Check whether global mock restoration is enabled in Vitest config/package settings.
fd -i 'vitest.config.*' -x sh -c 'echo "== {} =="; sed -n "1,240p" "{}"'
rg -n --iglob '*vitest*' --iglob 'package.json' '\brestoreMocks\b|\bclearMocks\b|\bmockReset\b'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/apply.test.ts` around lines 61 - 78, The test
creates a process.exit spy (exitSpy) but calls exitSpy.mockRestore()
unconditionally, which can leak the spy if an assertion throws; wrap the spy
cleanup in a finally block so mockRestore always runs. Specifically, in the test
case that calls resolveApplyPreset with positionalPreset and preset (the it
block using exitSpy and calling resolveApplyPreset), move the
exitSpy.mockRestore() into a finally that surrounds the expect(...).toThrow and
expect(exitSpy).toHaveBeenCalledWith(1) assertions so the spy on process.exit is
restored regardless of test failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/init.ts`:
- Around line 810-811: The current assignment lets opts.cssVariables (which
defaults to true) override project config and unintentionally mutate existing
tailwind.cssVariables; change the selection logic for cssVariables so you only
use opts.cssVariables when it was explicitly provided (e.g., check
opts.cssVariables !== undefined or use "cssVariables" in opts) and otherwise
fall back to defaultConfig.tailwind.cssVariables (leave baseColor behavior
unchanged), and update the caller/action layer to pass an explicit/optional
override flag from the Commander option source so only user-supplied flags
override project config.

---

Duplicate comments:
In `@packages/cli/src/commands/apply.test.ts`:
- Around line 61-78: The test creates a process.exit spy (exitSpy) but calls
exitSpy.mockRestore() unconditionally, which can leak the spy if an assertion
throws; wrap the spy cleanup in a finally block so mockRestore always runs.
Specifically, in the test case that calls resolveApplyPreset with
positionalPreset and preset (the it block using exitSpy and calling
resolveApplyPreset), move the exitSpy.mockRestore() into a finally that
surrounds the expect(...).toThrow and expect(exitSpy).toHaveBeenCalledWith(1)
assertions so the spy on process.exit is restored regardless of test failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6999bbd-00ff-44a4-b463-afbeb002e416

📥 Commits

Reviewing files that changed from the base of the PR and between 780c2cb and a179ee3.

📒 Files selected for processing (9)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.test.ts
  • packages/cli/src/commands/apply.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/file-helper.ts
  • packages/cli/src/utils/get-project-info.ts
  • packages/cli/src/utils/updaters/update-css.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/file-helper.ts

Comment thread packages/cli/src/commands/init.ts Outdated
@zernonia zernonia force-pushed the feat-apply-preset branch from 12f578f to 0b50099 Compare April 11, 2026 16:42
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
packages/cli/src/commands/apply.test.ts (1)

61-78: ⚠️ Potential issue | 🟡 Minor

Guard process.exit spy cleanup with finally.

Line 77 cleanup is unguarded; if an expectation fails first, the spy can leak and break test isolation.

Suggested fix
   it('exits when positional and flag presets disagree', () => {
     const exitSpy = vi
       .spyOn(process, 'exit')
       .mockImplementation(((_code?: number) => {
         throw new Error('process.exit called')
       }) as never)

-    expect(() =>
-      resolveApplyPreset({
-        ...baseOptions,
-        positionalPreset: 'vega',
-        preset: 'nova',
-      }),
-    ).toThrow('process.exit called')
-
-    expect(exitSpy).toHaveBeenCalledWith(1)
-    exitSpy.mockRestore()
+    try {
+      expect(() =>
+        resolveApplyPreset({
+          ...baseOptions,
+          positionalPreset: 'vega',
+          preset: 'nova',
+        }),
+      ).toThrow('process.exit called')
+
+      expect(exitSpy).toHaveBeenCalledWith(1)
+    }
+    finally {
+      exitSpy.mockRestore()
+    }
   })
#!/bin/bash
set -euo pipefail

# Verify whether global Vitest config auto-restores mocks.
fd -i 'vitest.config.*' -x sh -c 'echo "== {} =="; sed -n "1,220p" "{}"'
rg -n --iglob '*vitest*' --iglob 'package.json' '\brestoreMocks\b|\bclearMocks\b|\bmockReset\b'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/apply.test.ts` around lines 61 - 78, The test uses
a spy exitSpy on process.exit in the 'exits when positional and flag presets
disagree' case but restores it only at the end, so a failing expectation can
leak the spy; wrap the spy setup/restore in a try/finally around the expect
block (or use before/after hooks) so exitSpy.mockRestore() always runs; update
the test around resolveApplyPreset and exitSpy to ensure process.exit is mocked
and then restored in a finally block referencing exitSpy, process.exit, and
resolveApplyPreset.
packages/cli/src/commands/init.ts (1)

810-811: ⚠️ Potential issue | 🟠 Major

cssVariables still overrides project config without explicit user intent.

Line 811 still reads opts.cssVariables directly, and Line 887 reassigns from opts.cssVariables again. With Commander’s defaulted boolean option, this can still flip existing tailwind.cssVariables unexpectedly in non-interactive runs.

Suggested fix
-  let cssVariables = opts.cssVariables ?? defaultConfig.tailwind.cssVariables
+  // Preserve project value by default; only override when the CLI flag was explicitly provided.
+  let cssVariables = defaultConfig.tailwind.cssVariables
@@
-    cssVariables = opts.cssVariables
+    cssVariables = options.tailwindCssVariables ?? cssVariables

Then wire an explicit override from the action layer (based on Commander option source), so only user-provided --css-variables/--no-css-variables changes this value.

#!/bin/bash
set -euo pipefail

# Verify whether cssVariables is sourced from explicit CLI option intent or defaulted option value.
sed -n '230,330p' packages/cli/src/commands/init.ts
sed -n '800,910p' packages/cli/src/commands/init.ts
rg -n --type=ts "getOptionValueSource|cssVariables|--css-variables|opts\\.cssVariables" \
  packages/cli/src/commands/init.ts packages/cli/src/commands/apply.ts

Also applies to: 887-887

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/commands/init.ts` around lines 810 - 811, The code currently
assigns cssVariables from opts.cssVariables unconditionally which lets
Commander’s defaulted boolean flip an existing default; change the action-layer
assignment so cssVariables is only overridden when the CLI option was explicitly
provided: use Commander’s getOptionValueSource for the css-variables flag (e.g.
command.getOptionValueSource('--css-variables') or
command.getOptionValueSource('cssVariables')) and only set cssVariables =
opts.cssVariables when that source !== 'default' (otherwise keep
defaultConfig.tailwind.cssVariables or the project config); apply the same guard
where cssVariables is reassigned (referencing baseColor, cssVariables,
defaultConfig.tailwind, and opts.cssVariables) so non-interactive runs don’t
unintentionally flip the value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/utils/get-project-info.ts`:
- Around line 367-372: Replace the separate exists check and readdir with a
single atomic read guarded by try/catch: remove the fs.existsSync(uiDir)
pre-check and call await fs.readdir(uiDir, { withFileTypes: true }) inside a try
block (using the existing uiDir value), and on any error (e.g., ENOENT) return
[]; update the code around the uiDir variable and the entries usage so the
function (in get-project-info.ts where uiDir and entries are declared)
gracefully falls back to an empty array instead of throwing.
- Around line 375-385: The current loop over entries adds any immediate
directory (entry.isDirectory()) or many TS/JS files to names, causing
non-component artifacts to be included; update the logic so directories are only
added if they contain a component entry (e.g., contain
index.vue/index.tsx/index.ts or any *.vue file) and files are only added when
their basename looks like a component (e.g., PascalCase or ends with .component)
and is not in a short blacklist like ['utils','types','helpers','styles'];
modify the loop around entries, the checks using entry.isDirectory(),
entry.isFile(), and the regex for /\.(?:vue|ts|tsx|js|jsx)$/, and consult the
directory contents (fs.readdir or Dirent) to confirm an index or .vue exists
before calling names.add(entry.name) or names.add(path.basename(...)).

---

Duplicate comments:
In `@packages/cli/src/commands/apply.test.ts`:
- Around line 61-78: The test uses a spy exitSpy on process.exit in the 'exits
when positional and flag presets disagree' case but restores it only at the end,
so a failing expectation can leak the spy; wrap the spy setup/restore in a
try/finally around the expect block (or use before/after hooks) so
exitSpy.mockRestore() always runs; update the test around resolveApplyPreset and
exitSpy to ensure process.exit is mocked and then restored in a finally block
referencing exitSpy, process.exit, and resolveApplyPreset.

In `@packages/cli/src/commands/init.ts`:
- Around line 810-811: The code currently assigns cssVariables from
opts.cssVariables unconditionally which lets Commander’s defaulted boolean flip
an existing default; change the action-layer assignment so cssVariables is only
overridden when the CLI option was explicitly provided: use Commander’s
getOptionValueSource for the css-variables flag (e.g.
command.getOptionValueSource('--css-variables') or
command.getOptionValueSource('cssVariables')) and only set cssVariables =
opts.cssVariables when that source !== 'default' (otherwise keep
defaultConfig.tailwind.cssVariables or the project config); apply the same guard
where cssVariables is reassigned (referencing baseColor, cssVariables,
defaultConfig.tailwind, and opts.cssVariables) so non-interactive runs don’t
unintentionally flip the value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60673bcb-8ff3-4230-b90e-6e7e47a3df2a

📥 Commits

Reviewing files that changed from the base of the PR and between a179ee3 and 12f578f.

📒 Files selected for processing (10)
  • apps/v4/content/docs/06.cli.md
  • packages/cli/src/commands/apply.test.ts
  • packages/cli/src/commands/apply.ts
  • packages/cli/src/commands/init.ts
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/add-components.ts
  • packages/cli/src/utils/file-helper.ts
  • packages/cli/src/utils/get-project-info.ts
  • packages/cli/src/utils/updaters/update-css.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/v4/content/docs/06.cli.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/cli/src/index.ts
  • packages/cli/src/preflights/preflight-apply.ts
  • packages/cli/src/utils/file-helper.ts
  • packages/cli/src/commands/apply.ts

Comment thread packages/cli/src/utils/get-project-info.ts Outdated
Comment thread packages/cli/src/utils/get-project-info.ts
@zernonia zernonia force-pushed the feat-apply-preset branch 5 times, most recently from ae94646 to 62ada20 Compare April 11, 2026 17:07
zernonia and others added 3 commits April 12, 2026 01:18
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zernonia zernonia force-pushed the feat-apply-preset branch from 62ada20 to 18e3d02 Compare April 11, 2026 17:18
@zernonia zernonia merged commit b882700 into dev Apr 11, 2026
5 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