Skip to content

feat(errors)!: rebuild errors module on @vercel/error#15

Merged
amondnet merged 2 commits into
mainfrom
amondnet/error
Jul 2, 2026
Merged

feat(errors)!: rebuild errors module on @vercel/error#15
amondnet merged 2 commits into
mainfrom
amondnet/error

Conversation

@amondnet

@amondnet amondnet commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adopts @vercel/error as the structured-error foundation for the errors module.

  • CliError now extends VercelError — beyond code, every error can carry the full structured context: reason (why), hint (what could help), fix (how to resolve), link (docs), plus metadata, attributes, and cause chaining. code still always defaults to UNKNOWN_ERROR.
  • Environment-aware rendering for freetoString() prints a colored tree on a TTY and plain indented text when piped (NO_COLOR respected), with ANSI/OSC sanitization at render boundaries.
  • createErrors() factory compatibilityCliError accepts (message, options) so it plugs into createErrors({ ErrorClass: CliError, scope, docsBaseUrl }) for per-tool scoping and code-derived docs links.
  • Re-exports core primitives (VercelError, createErrors, isVercelError, hasCode, getMessage, getRootCause, ...) from ./errors and the root entry, so consumers don't need a direct dependency.
  • exitCodeForError now uses hasCode, so any error-like value tagged VALIDATION_ERROR maps to exit code 2 — not just CliError instances.

Breaking changes

  • CliError constructor: (message, code, suggestions)(message, options); suggestions is replaced by hint/fix.
  • ErrorOutput payload: { error, code, help? }{ error, code, reason?, hint?, fix?, link? }. Legacy suggestions from older toolkit copies (cross-version boundary) still map onto hint.
  • errorOutput(message, code, details?) takes a details object instead of a suggestions array.
  • validateFormat moved the supported-formats list from the message into fix.

Notes on the dependency

@vercel/error@0.0.2 is an early-stage vercel-labs package; ^0.0.2 effectively pins it (caret on 0.0.x), it has zero runtime dependencies, and its lefthook postinstall stays blocked by Bun.

Test plan

  • bun test — 176 pass
  • tsc --noEmit, eslint . clean
  • bun run build + smoke test against dist/: structured payload, factory docsBaseUrl link derivation, validation exit code 2, TTY tree formatting all verified end-to-end

Summary by cubic

Rebuilt the errors module on @vercel/error to provide richer structured errors and consistent, environment-aware rendering. Updated the API and output shape to carry reason/hint/fix/link and improve exit-code mapping.

  • New Features

    • CliError now extends VercelError; always has code (defaults to UNKNOWN_ERROR) plus reason, hint, fix, link, metadata, and cause.
    • Environment-aware toString() output (colors on TTY, plain when piped).
    • createErrors compatibility and re-exports of VercelError, guards (isVercelError, isError, isErrorLike, hasCode), and helpers (getMessage, getRootCause) from the toolkit.
    • exitCodeForError uses hasCode so any VALIDATION_ERROR returns 2; toErrorOutput preserves context for VercelError and foreign CliError copies, and maps legacy suggestions to hint (suggestions win if both are present).
  • Migration

    • Replace new CliError(message, code, suggestions) with new CliError(message, { code, hint?, fix?, reason?, link? }).
    • Update errorOutput(message, code, details?); payload is { error, code, reason?, hint?, fix?, link? }. Read these fields in consumers.
    • Validation messages move supported-format guidance into fix.
    • Import createErrors, VercelError, and guards from @pleaseai/cli-toolkit/errors; no direct @vercel/error dependency needed.

Written for commit 9f03e73. Summary will update on new commits.

Adopt @vercel/error as the structured-error foundation. CliError now
extends VercelError, gaining reason/hint/fix/link/metadata/cause context,
environment-aware terminal formatting, and createErrors() factory
compatibility via ErrorClass.

BREAKING CHANGE: CliError's constructor is now (message, options) instead
of (message, code, suggestions); suggestions is replaced by hint/fix. The
ErrorOutput payload drops help in favor of reason/hint/fix/link (legacy
suggestions from older toolkit copies still map onto hint). errorOutput()
takes a details object instead of a suggestions array, and
validateFormat's message moved its supported-formats list into fix.
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@amondnet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 43 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 32292a28-1bc9-4d1e-9bd3-b01509735db6

📥 Commits

Reviewing files that changed from the base of the PR and between e908de0 and 9f03e73.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .gitignore
  • README.md
  • eslint.config.js
  • package.json
  • src/errors/error.ts
  • src/errors/index.ts
  • src/errors/output.ts
  • src/index.ts
  • src/output/json.ts
  • src/validation/numeric.ts
  • src/validation/text.ts
  • test/errors/error.test.ts
  • test/errors/output.test.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch amondnet/error

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.

@socket-security

socket-security Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​vercel/​error@​0.0.26910010092100

View full report

@gemini-code-assist

Copy link
Copy Markdown

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 12 files

Architecture diagram
sequenceDiagram
    participant UserCode as User Code
    participant CliError as CliError (extends VercelError)
    participant VercelError as VercelError (@vercel/error)
    participant toErrorOutput as toErrorOutput()
    participant exitCodeForError as exitCodeForError()
    participant Environment as Environment (TTY/pipe)
    participant createErrors as createErrors Factory
    participant OutputModule as Output Module (toon/json)
    participant ProcessExit as Process Exit Code

    Note over UserCode,ProcessExit: Primary throw–catch flow

    UserCode->>CliError: new CliError('msg', { code:'NOT_FOUND', reason:'', hint:'', fix:'', link:'' })
    CliError->>VercelError: super(message, options) with code default UNKNOWN_ERROR
    VercelError-->>CliError: VercelError instance (name='CliError', code, reason, hint, fix, link, cause, ...)
    UserCode->>toErrorOutput: catch(error) → toErrorOutput(error)
    toErrorOutput->>VercelError: isVercelError(error) → true (CliError extends VercelError)
    toErrorOutput->>CliError: extract code, reason, hint, fix, link (context fields)
    alt Legacy cross‑version CliError (pre‑@vercel/error shape)
        toErrorOutput->>CliError: check (error as any).suggestions array → join into hint string
    end
    toErrorOutput-->>UserCode: ErrorOutput { error, code, reason?, hint?, fix?, link? }
    UserCode->>OutputModule: outputData(ErrorOutput, 'toon'/'json')
    OutputModule-->>UserCode: Rendered structured output

    UserCode->>exitCodeForError: process.exitCode = exitCodeForError(error)
    exitCodeForError->>VercelError: hasCode(error, 'VALIDATION_ERROR')
    alt error.code === 'VALIDATION_ERROR'
        exitCodeForError-->>UserCode: return 2
    else
        exitCodeForError-->>UserCode: return 1
    end
    UserCode->>ProcessExit: process.exit(exitCode)

    Note over UserCode,ProcessExit: Environment‑aware toString() (inherited from VercelError)

    UserCode->>CliError: error.toString()
    CliError->>Environment: check isTTY and NO_COLOR
    alt TTY and color allowed
        Environment-->>CliError: Use ANSI colored tree format
    else Piped or NO_COLOR
        Environment-->>CliError: Use plain indented text (no ANSI)
    end
    CliError-->>UserCode: Formatted string

    Note over UserCode,createErrors: Factory usage (optional)

    UserCode->>createErrors: createErrors({ scope:'gh-please', ErrorClass:CliError, docsBaseUrl:'...' })
    createErrors-->>UserCode: errors object with raise() method
    UserCode->>CliError: errors.raise('msg', { code:'NOT_FOUND' })
    CliError->>VercelError: constructor sets link=docsBaseUrl+'/'+code
    VercelError-->>CliError: Structured error with derived link
    CliError-->>UserCode: thrown

    Note over UserCode,ProcessExit: Re‑exported primitives available from './errors'
    Note over UserCode: VercelError, createErrors, isVercelError, hasCode, getMessage, getRootCause, etc.
Loading

Re-trigger cubic

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown

Greptile Summary

Rebuilds the errors module on @vercel/error, elevating CliError from a plain Error subclass to a VercelError-based structured error with reason/hint/fix/link context fields and environment-aware TTY rendering. All call sites across validation and output modules are updated, and @vercel/error primitives are re-exported so consumers don't need a direct dependency.

  • CliError constructor changes from (message, code, suggestions) to (message, options), with code always defaulted to UNKNOWN_ERROR via a ?? guard in the super() call; declare readonly code: string correctly narrows the parent's string | undefined type without emitting runtime code.
  • toErrorOutput dispatch is ordered correctly — isVercelError runs first (catching real CliError instances via inheritance), and the isCliError structural fallback handles legacy foreign-bundle copies with a suggestionshint collapse plus defensive reads of new-style fields.
  • exitCodeForError now uses the broader hasCode helper, intentionally extending exit-code 2 to any error-like value tagged VALIDATION_ERROR, not just CliError instances.

Confidence Score: 5/5

The change is safe to merge; all breaking-change call sites are updated, dispatch logic is correct, and the test suite covers all dispatch paths including cross-bundle and legacy-format scenarios.

The dispatch ordering in toErrorOutput is sound — CliError extends VercelError, so real CliError instances always satisfy isVercelError first; the structural fallback only fires for legacy or foreign-bundle copies. The declare readonly code: string narrowing is TypeScript-only and correct at runtime. exitCodeForError's shift to hasCode is an intentional, tested broadening. No silent data loss, no incorrect branch reachability, no missing field propagation.

No files require special attention.

Important Files Changed

Filename Overview
src/errors/error.ts Rebuilds CliError on VercelError, adds declare readonly code: string narrowing, updates exitCodeForError to use hasCode, and updates isCliError structural check. Logic is sound — code always defaults to UNKNOWN_ERROR, and the broader hasCode-based exit-code mapping is correctly tested.
src/errors/output.ts Replaces suggestions/help with reason/hint/fix/link flat fields; adds isVercelError fast-path before isCliError fallback. Dispatch ordering is correct since CliError extends VercelError and will always satisfy isVercelError first. Legacy suggestions-to-hint mapping in the structural fallback branch is defensive and tested.
src/errors/index.ts Re-exports all @vercel/error primitives (VercelError, createErrors, guards, helpers) alongside the toolkit's own exports. Clean barrel file with no conflicts.
test/errors/error.test.ts Good coverage of the new API: VercelError inheritance, all four context fields, cause chaining, structural isCliError checks, and exitCodeForError with VercelError and legacy ForeignCliError.
test/errors/output.test.ts Comprehensive coverage of toErrorOutput dispatch paths: VercelError, CliError, ForeignCliError with legacy suggestions, mixed new/old fields, and fallback for generic Error and non-error values.
package.json Adds @vercel/error@^0.0.2 as a runtime dependency; caret on 0.0.x is a de-facto exact pin to 0.0.2 as intended.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["toErrorOutput(error)"] --> B{"isVercelError?"}
    B -->|yes| C["Extract: message, code, reason, hint, fix, link"]
    C --> D["errorOutput with full context"]
    B -->|no| E{"isCliError? structural name+code check"}
    E -->|yes| F["Read suggestions array"]
    F --> G{"suggestions present?"}
    G -->|yes| H["legacyHint = suggestions.join newline"]
    G -->|no| I["legacyHint = undefined"]
    H --> J["errorOutput with legacyHint ?? error.hint"]
    I --> J
    E -->|no| K["getMessage(error) ?? String(error)"]
    K --> L["errorOutput with UNKNOWN_ERROR"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["toErrorOutput(error)"] --> B{"isVercelError?"}
    B -->|yes| C["Extract: message, code, reason, hint, fix, link"]
    C --> D["errorOutput with full context"]
    B -->|no| E{"isCliError? structural name+code check"}
    E -->|yes| F["Read suggestions array"]
    F --> G{"suggestions present?"}
    G -->|yes| H["legacyHint = suggestions.join newline"]
    G -->|no| I["legacyHint = undefined"]
    H --> J["errorOutput with legacyHint ?? error.hint"]
    I --> J
    E -->|no| K["getMessage(error) ?? String(error)"]
    K --> L["errorOutput with UNKNOWN_ERROR"]
Loading

Reviews (2): Last reviewed commit: "chore(errors): apply AI code review sugg..." | Re-trigger Greptile

Comment thread src/errors/output.ts Outdated
Comment thread test/errors/output.test.ts
- carry reason/hint/fix/link through the structural isCliError fallback
  so a VercelError-derived copy that slips past isVercelError doesn't
  silently lose context fields (suggestions still wins for hint)
- assert all four context fields explicitly on the CliError path in
  toErrorOutput tests, plus the foreign new-style copy fallback
- ignore .impeccable local tool cache in eslint and git
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@amondnet
amondnet merged commit 2963421 into main Jul 2, 2026
9 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