Skip to content

fix: CLI input validation with Zod schemas (#60)#68

Merged
let-sunny merged 3 commits intomainfrom
claude/v1-release-prep-HyMNQ
Mar 25, 2026
Merged

fix: CLI input validation with Zod schemas (#60)#68
let-sunny merged 3 commits intomainfrom
claude/v1-release-prep-HyMNQ

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 25, 2026

Summary

  • analyze: --preset validated via Zod (relaxed|dev-friendly|ai-ready|strict). Invalid values now fail immediately with clear error.
  • save-fixture: --image-scale validated before data.json save — prevents incomplete fixture directories on bad input.
  • visual-compare: Warns when --figma-url has no node-id (results may be inaccurate for full files).
  • implement: Warns for unscoped Figma URL + early --image-scale validation.
  • init: --token + --mcp together now rejected with explicit error (was silently ignoring --mcp).

All 4 public commands (analyze, save-fixture, visual-compare, implement) now use Zod.safeParse() at handler entry for runtime option validation, per CLAUDE.md convention: "Validate all external inputs with Zod schemas".

Test plan

  • pnpm lint passes
  • pnpm test:run passes (427/427)
  • Manual: canicode analyze ./fixtures/material3-kit --preset invalid → clear error
  • Manual: canicode init --token x --mcp → mutual exclusivity error
  • Manual: canicode save-fixture <url> --image-scale abc → error before any file creation

Closes #60

https://claude.ai/code/session_018Y1Y4GuLuyeUEp5vHnuUKu

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced CLI option validation with clearer, field-level error messages across all commands.
    • Added validation for --image-scale parameter to enforce valid range (1-4).
    • Prevented conflicting use of --token and --mcp flags in the init command.
  • New Features

    • Added warnings when Figma URLs lack required node-id parameter in implement and visual-compare commands.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Warning

Rate limit exceeded

@let-sunny has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 29 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d5bba49b-c8a8-4117-9413-ba253b9ee7fd

📥 Commits

Reviewing files that changed from the base of the PR and between ec28624 and 6dbbb53.

📒 Files selected for processing (5)
  • src/cli/commands/analyze.ts
  • src/cli/commands/implement.ts
  • src/cli/commands/init.ts
  • src/cli/commands/save-fixture.ts
  • src/cli/commands/visual-compare.ts
📝 Walkthrough

Walkthrough

This PR enhances CLI input validation across five command modules by replacing TypeScript interface-based typing with runtime Zod schema validation. Early validation failures now print formatted error messages and exit with status code 1. Additional validations include enforcing numeric ranges for --image-scale, detecting Figma URLs lacking node-id, and preventing simultaneous use of --token and --mcp flags.

Changes

Cohort / File(s) Summary
CLI Command Validation
src/cli/commands/analyze.ts, src/cli/commands/implement.ts, src/cli/commands/init.ts, src/cli/commands/save-fixture.ts, src/cli/commands/visual-compare.ts
Replaces TypeScript interface-based option typing with Zod schema validation. Changes all .action handlers to accept rawOptions: Record<string, unknown> and validate via safeParse. On validation failure, prints formatted "Invalid options" message with field-level errors and exits with status 1. Adds new validations: --image-scale range enforcement (1-4), Figma URL node-id presence warnings, and --token/--mcp mutual exclusivity check.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Zod schemas hop in, options now sound,
Early validation guards are around,
No silent failures, just loud clear warnings,
CLI commands dawn with safer mornings! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: adding Zod schema-based CLI input validation across multiple commands.
Linked Issues check ✅ Passed All objectives from issue #60 are met: Zod schemas added for CLI options, early validation implemented, mutual exclusivity enforced for init command, and Figma URL warnings added.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #60 requirements; no out-of-scope modifications detected across the five modified command files.

✏️ 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 claude/v1-release-prep-HyMNQ

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

Copy link
Copy Markdown
Owner Author

Manual Test Results ✅

All 3 manual test cases passed. Automated tests also pass (427/427).

1. canicode analyze ./fixtures/material3-kit --preset invalid

Invalid options:
--preset: Invalid option: expected one of "relaxed"|"dev-friendly"|"ai-ready"|"strict"

→ Exit code 1, clear error message ✅

2. canicode init --token x --mcp

Error: --token and --mcp are mutually exclusive. Choose one.

→ Exit code 1, mutual exclusivity enforced ✅

3. canicode save-fixture <figma-url> --image-scale abc

Error: --image-scale must be 1-4 (2 for PC, 3 for mobile)

→ Exit code 1, error before any file creation ✅

Automated Tests

Test Files  28 passed (28)
     Tests  427 passed (427)

https://claude.ai/code/session_0113aX578Sq8Q4RQeho8jsuV

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/cli/commands/implement.ts (1)

113-117: 🧹 Nitpick | 🔵 Trivial

Duplicate --image-scale validation is now unreachable.

This validation block at lines 113-117 duplicates the early validation at lines 44-51. Since the early check exits on invalid values, this code path can never be reached with an invalid imageScale. Consider removing this redundant check.

🧹 Remove redundant validation
           if (figmaToken) {
             const imgScale = options.imageScale !== undefined ? Number(options.imageScale) : 2;
-            if (!Number.isFinite(imgScale) || imgScale < 1 || imgScale > 4) {
-              console.error("Error: --image-scale must be 1-4 (2 for PC, 3 for mobile)");
-              process.exit(1);
-            }

             const { FigmaClient } = await import("../../core/adapters/figma-client.js");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/commands/implement.ts` around lines 113 - 117, Remove the redundant
image-scale validation block that re-parses options.imageScale into imgScale and
checks Number.isFinite/imgScale range; this is duplicative of the earlier
validation and unreachable after the initial check, so delete the secondary
check (the code using imgScale and calling console.error/process.exit) and keep
only the first validation that validates options.imageScale.
src/cli/commands/save-fixture.ts (1)

143-148: 🧹 Nitpick | 🔵 Trivial

Duplicate --image-scale validation is now unreachable.

This validation at lines 143-148 duplicates the early validation at lines 47-54. Since the early check exits on invalid values, this code path can never be reached with an invalid imageScale. Consider removing this redundant check.

🧹 Remove redundant validation
           const imageNodes = collectImageNodes(file.document);
           if (imageNodes.length > 0) {
             const imgScale = options.imageScale !== undefined ? Number(options.imageScale) : 2;
-            if (!Number.isFinite(imgScale) || imgScale < 1 || imgScale > 4) {
-              console.error("Error: --image-scale must be 1-4 (2 for PC, 3 for mobile)");
-              process.exit(1);
-            }

             const imageDir = resolve(fixtureDir, "images");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/commands/save-fixture.ts` around lines 143 - 148, Remove the
redundant image-scale validation inside the block that checks imageNodes (the
imgScale check using options.imageScale), since the same validation already runs
earlier and exits on invalid values; simply delete the inner validation and its
process.exit path in the save-fixture logic so the imageNodes branch uses the
already-validated imgScale value (references: imageNodes, imgScale,
options.imageScale).
src/cli/commands/init.ts (1)

7-22: 🧹 Nitpick | 🔵 Trivial

Inconsistent validation pattern: init command lacks Zod schema unlike other commands in this PR.

The other commands (analyze, implement, save-fixture, visual-compare) define a Zod schema and use safeParse for runtime validation. This command uses a plain TypeScript interface with a manual guard. For consistency and compliance with coding guidelines requiring Zod validation for external inputs, consider aligning this command with the established pattern.

♻️ Suggested refactor to use Zod schema
+import { z } from "zod";
+
-interface InitOptions {
-  token?: string;
-  mcp?: boolean;
-}
+const InitOptionsSchema = z.object({
+  token: z.string().optional(),
+  mcp: z.boolean().optional(),
+}).refine(
+  (opts) => !(opts.token && opts.mcp),
+  { message: "--token and --mcp are mutually exclusive. Choose one." }
+);
+
+type InitOptions = z.infer<typeof InitOptionsSchema>;

Then in the action handler:

-    .action((options: InitOptions) => {
+    .action((rawOptions: Record<string, unknown>) => {
       try {
-        if (options.token && options.mcp) {
-          console.error("Error: --token and --mcp are mutually exclusive. Choose one.");
-          process.exit(1);
-        }
+        const parseResult = InitOptionsSchema.safeParse(rawOptions);
+        if (!parseResult.success) {
+          const msg = parseResult.error.issues.map(i => i.message).join("\n");
+          console.error(`\nInvalid options:\n${msg}`);
+          process.exit(1);
+        }
+        const options = parseResult.data;

Based on learnings: "Validate all external inputs with Zod schemas" and "Infer TypeScript types from Zod schemas using z.infer<typeof Schema>".

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

In `@src/cli/commands/init.ts` around lines 7 - 22, Replace the manual TypeScript
interface and ad-hoc guard in registerInit with a Zod schema: define an
InitSchema using z.object({ token: z.string().optional(), mcp:
z.boolean().optional() }), infer the input type with z.infer<typeof InitSchema>,
and in the .action handler call InitSchema.safeParse(options); if safeParse
fails, print the validation error and exit; if it succeeds, use the parsed data
and enforce the mutual-exclusion check (token && mcp) thereafter. Ensure you
import z from "zod" and update references to InitOptions to use the inferred
type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cli/commands/visual-compare.ts`:
- Around line 8-15: VisualCompareOptionsSchema currently uses z.unknown() for
width and height which sacrifices validation; update the schema for the width
and height fields in VisualCompareOptionsSchema to accept explicit CLI-friendly
types (e.g., a union of string and number) so the parser can validate and coerce
values correctly; reference the VisualCompareOptionsSchema and the width and
height fields when making the change and consider using z.union([z.string(),
z.number()]) or a preprocess step if you want to coerce strings to numbers
before validation.

---

Outside diff comments:
In `@src/cli/commands/implement.ts`:
- Around line 113-117: Remove the redundant image-scale validation block that
re-parses options.imageScale into imgScale and checks Number.isFinite/imgScale
range; this is duplicative of the earlier validation and unreachable after the
initial check, so delete the secondary check (the code using imgScale and
calling console.error/process.exit) and keep only the first validation that
validates options.imageScale.

In `@src/cli/commands/init.ts`:
- Around line 7-22: Replace the manual TypeScript interface and ad-hoc guard in
registerInit with a Zod schema: define an InitSchema using z.object({ token:
z.string().optional(), mcp: z.boolean().optional() }), infer the input type with
z.infer<typeof InitSchema>, and in the .action handler call
InitSchema.safeParse(options); if safeParse fails, print the validation error
and exit; if it succeeds, use the parsed data and enforce the mutual-exclusion
check (token && mcp) thereafter. Ensure you import z from "zod" and update
references to InitOptions to use the inferred type.

In `@src/cli/commands/save-fixture.ts`:
- Around line 143-148: Remove the redundant image-scale validation inside the
block that checks imageNodes (the imgScale check using options.imageScale),
since the same validation already runs earlier and exits on invalid values;
simply delete the inner validation and its process.exit path in the save-fixture
logic so the imageNodes branch uses the already-validated imgScale value
(references: imageNodes, imgScale, options.imageScale).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d9c48dfe-d669-492e-95e8-c0958b98e9cc

📥 Commits

Reviewing files that changed from the base of the PR and between f8a395a and ec28624.

📒 Files selected for processing (5)
  • src/cli/commands/analyze.ts
  • src/cli/commands/implement.ts
  • src/cli/commands/init.ts
  • src/cli/commands/save-fixture.ts
  • src/cli/commands/visual-compare.ts

claude added 3 commits March 25, 2026 13:00
- analyze: validate --preset with Zod schema (rejects invalid values early)
- save-fixture: move --image-scale validation before any file I/O
- visual-compare: warn when --figma-url has no node-id
- implement: warn for unscoped Figma URL + early --image-scale validation
- init: reject --token and --mcp when both provided (mutual exclusivity)
- All 4 public commands now use Zod safeParse for runtime option validation

Closes #60

https://claude.ai/code/session_018Y1Y4GuLuyeUEp5vHnuUKu
All 3 manual test cases passed:
- --preset invalid → Zod validation error
- --token x --mcp → mutual exclusivity error
- --image-scale abc → early validation error before file I/O

https://claude.ai/code/session_0113aX578Sq8Q4RQeho8jsuV
- visual-compare: z.unknown() → z.union([z.string(), z.number()]) for width/height
- implement: remove redundant --image-scale validation (unreachable after early check)
- save-fixture: remove redundant --image-scale validation (unreachable after early check)
- init: convert to Zod schema with .refine() for --token/--mcp mutual exclusivity

https://claude.ai/code/session_018Y1Y4GuLuyeUEp5vHnuUKu
@let-sunny let-sunny force-pushed the claude/v1-release-prep-HyMNQ branch from f3b3285 to 6dbbb53 Compare March 25, 2026 13:00
@let-sunny let-sunny merged commit 8335350 into main Mar 25, 2026
3 checks passed
@let-sunny let-sunny deleted the claude/v1-release-prep-HyMNQ branch March 25, 2026 13:01
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.

fix: CLI 입력 검증 강화 (Zod 스키마, early validation, mutual exclusivity)

2 participants