Skip to content

fix(cli): report error when plugin module cannot be resolved#2447

Merged
ymc9 merged 4 commits into
devfrom
fix/report-unresolvable-plugin-error
Mar 5, 2026
Merged

fix(cli): report error when plugin module cannot be resolved#2447
ymc9 merged 4 commits into
devfrom
fix/report-unresolvable-plugin-error

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Mar 5, 2026

Summary

  • When a plugin module can't be found (e.g., package not installed), the CLI now reports a clear error instead of silently skipping the plugin
  • Distinguishes between missing modules (ERR_MODULE_NOT_FOUND/MODULE_NOT_FOUND) and other loading failures
  • Plugins that exist but don't export a CLI generator (e.g., only provide a plugin.zmodel for custom attributes) continue to work as before

Closes #2393

Test plan

  • Test that referencing a non-existent plugin module throws Cannot find plugin module error
  • Test that a plugin module without a default export is gracefully skipped
  • Test that a plugin providing only plugin.zmodel for custom attributes works correctly
  • Verify all existing generate tests still pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • CLI commands (generate, check) now resolve and load external plugins more robustly, including plugins referenced by alternate entry files.
  • Bug Fixes

    • Clearer error reporting when plugin modules cannot be found or fail to load.
  • Tests

    • Added tests for unresolvable plugins, resolvable plugins without CLI generators, and plugins exposing alternate entry points.
  • Chores

    • Updated sample schema plugin references to point to explicit plugin files.

Previously, when a plugin module couldn't be found (e.g., package not
installed), the error was silently swallowed and the plugin was skipped.
Now the CLI reports a clear error message distinguishing between missing
modules and other loading failures.

Closes #2393

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 5, 2026

Claude finished @ymc9's task —— View job


PR Review: Fix CLI plugin module resolution error reporting

Review Checklist

  • Read and analyze the changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Assess security concerns
  • Validate test coverage
  • Provide comprehensive feedback

🎯 Overall Assessment: Excellent Implementation

This is a well-crafted fix that properly addresses issue #2393 with good error handling, clear messaging, and comprehensive test coverage. The implementation follows existing patterns and maintains backward compatibility.

Strengths

1. Error Handling Excellence (generate.ts:347-352)

const errorCode = (err as NodeJS.ErrnoException)?.code;
if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND') {
    throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`);
}
  • Properly distinguishes between missing modules and other import failures
  • Clear, actionable error messages for users
  • Uses proper Node.js error code checking

2. Backward Compatibility (generate.ts:344-345)

// plugin may not export a generator, return undefined in that case
return mod.default as CliPlugin | undefined;
  • Gracefully handles plugins that only provide plugin.zmodel files
  • Maintains existing behavior for valid use cases

3. Comprehensive Test Coverage

  • Line 202-214: Tests error case for non-existent plugins
  • Line 216-232: Tests plugins without CLI generators
  • Line 234-254: Tests plugins with only custom attributes
  • All three key scenarios are properly validated

Code Quality

1. Consistent Error Handling Pattern

  • Uses existing CliError class consistently
  • Follows the same pattern as other error cases in the function (lines 296, 306, 351)

2. Type Safety

  • Proper TypeScript casting with NodeJS.ErrnoException
  • Maintains existing return type signatures

3. Documentation

  • Inline comment explains the rationale for returning undefined
  • Test descriptions clearly indicate expected behavior

Performance Considerations

  • Minimal Impact: Changes only affect error paths and final fallback import
  • No Regression: Normal plugin loading paths unchanged
  • Early Exit: Error conditions are checked before continuing processing

Security Assessment

  • No Security Concerns: Changes only improve error reporting
  • Safe Error Handling: No sensitive information leaked in error messages
  • Consistent with Existing Code: Uses same patterns as existing error handling

🔍 Minor Suggestions

1. Error Code Coverage (generate.ts:348)
Consider checking for additional related error codes:

if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND' || errorCode === 'ENOENT') {

ENOENT can also occur for missing files in some environments, though current coverage should handle most cases.

2. Test Robustness
The tests are excellent, but you might consider adding a test for the error message content:

expect(() => runCli('generate', workDir)).toThrow('Cannot find plugin module "@zenstackhq/nonexistent-plugin"');

📋 Test Coverage: Exceptional

The test suite covers all three critical scenarios:

  1. ✅ Missing plugin modules throw clear errors
  2. ✅ Plugins without generators work correctly
  3. ✅ Plugins with only custom attributes work correctly

🚀 Recommendation: APPROVE

This PR is ready for merge. It:

  • ✅ Solves the reported issue cleanly
  • ✅ Maintains backward compatibility
  • ✅ Includes comprehensive tests
  • ✅ Follows existing code patterns
  • ✅ Has clear, user-friendly error messages

The implementation correctly distinguishes between legitimate missing modules (which should error) and plugins that intentionally don't export CLI generators (which should continue working). This is exactly the behavior needed to fix issue #2393 without breaking existing workflows.


@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Replaced local plugin provider extraction and dynamic loading in the CLI with shared utilities in action-utils; updated generate and check to use getPluginProvider and loadPluginModule; adjusted getPluginOptions signature/behavior; added tests for resolvable/unresolvable plugin scenarios (some duplicated).

Changes

Cohort / File(s) Summary
Generate action
packages/cli/src/actions/generate.ts
Removed local plugin provider/module-loading and loader implementations; import getPluginProvider and loadPluginModule from action-utils; updated getPluginOptions parameter type and option extraction to use shared provider shape and literal helpers.
Plugin utilities
packages/cli/src/actions/action-utils.ts
Added getPluginProvider(plugin) and loadPluginModule(provider, basePath) to resolve/load plugins (handles .zmodel, relative paths, ESM/TS, index fallbacks) and new CliError messaging; added schema-file existence checks.
Check action
packages/cli/src/actions/check.ts
Added plugin resolution step that iterates model declarations, uses getPluginProvider, and loads non-core plugins via loadPluginModule (base path = schema directory).
Tests — generate
packages/cli/test/generate.test.ts
Added tests covering unresolvable plugin module error, module without CLI generator, plugin providing only plugin.zmodel, and provider as .zmodel; similar scenarios are duplicated within the file.
Tests — check
packages/cli/test/check.test.ts
Added tests asserting success for resolvable plugin module and error for unresolvable provider ("Cannot find plugin module"); these cases appear duplicated in the file.
Runtime schemas
tests/runtimes/*/schemas/schema.zmodel
Updated plugin provider paths to point to explicit plugin.zmodel files via deeper relative paths in two runtime schema files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through code with nimble paws,

Replaced my old loaders with shared laws,
I checked each plugin — found some astray,
Wrote tiny tests to show the way,
A carrot for every module that plays.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% 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 title clearly and specifically describes the main change: implementing error reporting for unresolvable plugin modules in the CLI.
Linked Issues check ✅ Passed The code changes comprehensively address issue #2393 by implementing error detection and reporting for missing plugin modules in both generate and check commands.
Out of Scope Changes check ✅ Passed All changes directly support the objective of reporting unresolvable plugin errors. Schema file updates align with the refactored plugin path handling.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/report-unresolvable-plugin-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 and usage tips.

Extract `loadPluginModule` and `getPluginProvider` to shared action-utils
so both `generate` and `check` commands validate plugin resolution.
The `zen check` command now reports an error when a plugin module cannot
be found, matching the behavior of `zen generate`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 5, 2026

Claude finished @ymc9's task —— View job


PR Code Review

Review Checklist

  • Read and analyze the changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Assess security concerns
  • Validate test coverage
  • Provide comprehensive feedback

🎯 Overall Assessment: Excellent Implementation

This PR effectively addresses issue #2393 by improving plugin module error handling with clear, actionable error messages while maintaining backward compatibility. The implementation is well-structured, follows existing patterns, and includes comprehensive test coverage.

Strengths

1. Robust Error Handling (action-utils.ts:290-300)

try {
    const mod = await import(moduleSpec);
    return mod.default as CliPlugin | undefined;
} catch (err) {
    const errorCode = (err as NodeJS.ErrnoException)?.code;
    if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND') {
        throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`);
    }
    throw new CliError(`Failed to load plugin module "${provider}": ${(err as Error).message}`);
}

Excellent: Properly distinguishes between missing modules and other import failures with clear, actionable error messages.

2. Backward Compatibility (action-utils.ts:292-293)

// plugin may not export a generator, return undefined in that case
return mod.default as CliPlugin | undefined;

Smart Design: Gracefully handles plugins that only provide plugin.zmodel files without CLI generators.

3. Code Organization & Refactoring

  • DRY Principle: Extracted loadPluginModule from generate.ts to action-utils.ts (lines 232-301)
  • Shared Logic: Both check.ts and generate.ts now use the same plugin loading logic
  • Clean Separation: Error handling centralized in one location

4. Comprehensive Test Coverage

  • Line 202-214: Tests error case for non-existent plugins ✅
  • Line 216-232: Tests plugins without CLI generators ✅
  • Line 234-254: Tests plugins with only custom attributes ✅
  • Line 84-96: Tests error case in check command ✅

Code Quality

1. Consistent Error Handling Pattern

  • Uses existing CliError class throughout
  • Follows same pattern as other CLI errors
  • Proper TypeScript error type casting

2. Type Safety

const errorCode = (err as NodeJS.ErrnoException)?.code;

Proper handling of Node.js error codes with safe type casting.

3. Documentation

Clear inline comments explaining behavior, especially for edge cases like plugins without generators.

Performance Considerations

  • Minimal Impact: Changes only affect error paths and final fallback import
  • No Regression: Normal plugin loading paths unchanged
  • Early Exit: Error conditions checked before continuing processing
  • Efficient Fallback: Uses existing import strategies before final attempt

Security Assessment

  • No Security Concerns: Changes only improve error reporting
  • Safe Error Handling: No sensitive information exposed in error messages
  • Consistent Patterns: Uses same security approach as existing code

🔍 Minor Suggestions

1. Error Code Coverage Enhancement

Consider adding ENOENT to the error code check for broader environment compatibility:

if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND' || errorCode === 'ENOENT') {

2. Test Message Validation

While current tests verify error throwing, consider validating the specific error message:

expect(() => runCli('generate', workDir)).toThrow('Cannot find plugin module "@zenstackhq/nonexistent-plugin"');

📋 Test Coverage: Exceptional

The test suite thoroughly covers all scenarios:

  1. ✅ Missing plugin modules throw clear errors (both check and generate)
  2. ✅ Plugins without generators work correctly
  3. ✅ Plugins with only custom attributes work correctly
  4. ✅ Existing functionality remains unaffected

🚀 Recommendation: APPROVE

This PR is ready for merge. It:

  • ✅ Solves the reported issue (zen check doen't report missing imported plugin #2393) cleanly
  • ✅ Maintains backward compatibility perfectly
  • ✅ Includes comprehensive test coverage
  • ✅ Follows existing architectural patterns
  • ✅ Provides clear, user-friendly error messages
  • ✅ Improves code organization through refactoring

The implementation correctly distinguishes between legitimate missing modules (which should error) and plugins that intentionally don't export CLI generators (which should continue working). This is exactly the behavior needed to fix the issue without breaking existing workflows.

📁 Files Reviewed

  • action-utils.ts: Extracted and enhanced plugin loading logic ✅
  • check.ts: Uses shared plugin loading logic ✅
  • generate.ts: Refactored to use shared logic ✅
  • check.test.ts: Added error case test ✅
  • generate.test.ts: Added comprehensive plugin loading tests ✅

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 5, 2026

Claude finished @ymc9's task —— View job


PR Code Review

Reviewing PR #2447: "fix(cli): report error when plugin module cannot be resolved"

Review Checklist

  • Read and analyze the changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Assess security concerns
  • Validate test coverage
  • Provide comprehensive feedback

🎯 Overall Assessment: Excellent Implementation

This PR effectively addresses issue #2393 by improving plugin module error handling with clear, actionable error messages while maintaining backward compatibility. The implementation is well-structured, follows existing patterns, and includes comprehensive test coverage.

Strengths

1. Robust Error Handling (action-utils.ts:289-301)

try {
    const mod = await import(moduleSpec);
    // plugin may not export a generator, return undefined in that case
    return mod.default as CliPlugin | undefined;
} catch (err) {
    const errorCode = (err as NodeJS.ErrnoException)?.code;
    if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND') {
        throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`);
    }
    throw new CliError(`Failed to load plugin module "${provider}": ${(err as Error).message}`);
}

Excellent: Properly distinguishes between missing modules and other import failures with clear, actionable error messages.

2. Code Organization & DRY Principle

  • Smart Refactoring: Extracted loadPluginModule function from generate.ts to action-utils.ts (lines 232-301)
  • Shared Logic: Both check.ts and generate.ts now use the same plugin loading logic
  • Maintainability: Centralized error handling reduces code duplication

3. Backward Compatibility (action-utils.ts:292-293)

// plugin may not export a generator, return undefined in that case
return mod.default as CliPlugin | undefined;

Smart Design: Gracefully handles plugins that only provide plugin.zmodel files without CLI generators.

4. Comprehensive Test Coverage

  • Missing Plugins: Lines 202-214 (generate) + lines 103-115 (check) ✅
  • Plugins Without Generators: Lines 216-232 ✅
  • Plugins with Custom Attributes Only: Lines 234-254 ✅
  • Edge Cases Covered: All three critical scenarios thoroughly tested

Code Quality

1. Consistent Error Handling Pattern

  • Uses existing CliError class throughout
  • Follows same pattern as other CLI errors
  • Proper TypeScript error type casting with NodeJS.ErrnoException

2. Type Safety

const errorCode = (err as NodeJS.ErrnoException)?.code;

Proper handling of Node.js error codes with safe type casting and optional chaining.

3. Clear Documentation

Inline comments explain behavior for edge cases, especially plugins without generators.

Performance & Security

Performance

  • Minimal Impact: Changes only affect error paths and final fallback import
  • No Regression: Normal plugin loading paths unchanged
  • Efficient Fallback: Uses existing import strategies before final attempt

Security

  • No Security Concerns: Changes only improve error reporting
  • Safe Error Handling: No sensitive information exposed in error messages
  • Consistent Approach: Uses same security patterns as existing code

🔍 Minor Suggestions

1. Error Code Coverage Enhancement

Consider adding ENOENT to the error code check for broader environment compatibility:

if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND' || errorCode === 'ENOENT') {

2. Test Message Validation

While current tests verify error throwing, consider validating the specific error message:

expect(() => runCli('generate', workDir)).toThrow('Cannot find plugin module "@zenstackhq/nonexistent-plugin"');

📋 Architecture Analysis

Before (generate.ts had duplicate logic)

  • Plugin loading logic duplicated across commands
  • Inconsistent error handling between check and generate
  • Missing error differentiation

After (Centralized in action-utils.ts)

  • ✅ Single source of truth for plugin loading
  • ✅ Consistent error handling across commands
  • ✅ Clear separation between missing modules and other failures
  • ✅ Maintained backward compatibility

🚀 Recommendation: APPROVE

This PR is ready for merge. It:

  • ✅ Solves the reported issue (zen check doen't report missing imported plugin #2393) cleanly
  • ✅ Maintains backward compatibility perfectly
  • ✅ Includes comprehensive test coverage (4 new test cases)
  • ✅ Follows existing architectural patterns
  • ✅ Provides clear, user-friendly error messages
  • ✅ Improves code organization through thoughtful refactoring

The implementation correctly distinguishes between legitimate missing modules (which should error) and plugins that intentionally don't export CLI generators (which should continue working). This is exactly the behavior needed to fix the issue without breaking existing workflows.

📁 Files Reviewed

  • action-utils.ts: Extracted and enhanced plugin loading logic ✅
  • check.ts: Uses shared plugin loading logic ✅
  • generate.ts: Refactored to use shared logic ✅
  • check.test.ts: Added error case test ✅
  • generate.test.ts: Added comprehensive plugin loading tests ✅

Summary: This is a textbook example of how to fix an issue while improving code quality. The error handling is robust, the refactoring is clean, and the test coverage is thorough.


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.

🧹 Nitpick comments (1)
packages/cli/src/actions/action-utils.ts (1)

232-301: Consider adding explicit return type annotation.

The function handles multiple return paths correctly but lacks an explicit return type. Adding Promise<CliPlugin | undefined> would improve code clarity and help catch any future regressions.

Suggested change
-export async function loadPluginModule(provider: string, basePath: string) {
+export async function loadPluginModule(provider: string, basePath: string): Promise<CliPlugin | undefined> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/actions/action-utils.ts` around lines 232 - 301, The
function loadPluginModule lacks an explicit return type; update its signature to
include Promise<CliPlugin | undefined> (i.e., export async function
loadPluginModule(...): Promise<CliPlugin | undefined>) so all code paths are
properly typed; ensure the CliPlugin type is imported/available in this module
and that helper functions (importAsEsm, importAsTs) continue to return values
compatible with CliPlugin | undefined.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/cli/src/actions/action-utils.ts`:
- Around line 232-301: The function loadPluginModule lacks an explicit return
type; update its signature to include Promise<CliPlugin | undefined> (i.e.,
export async function loadPluginModule(...): Promise<CliPlugin | undefined>) so
all code paths are properly typed; ensure the CliPlugin type is
imported/available in this module and that helper functions (importAsEsm,
importAsTs) continue to return values compatible with CliPlugin | undefined.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d8dffed1-63c2-48d3-b592-92be77db4be2

📥 Commits

Reviewing files that changed from the base of the PR and between b51b74c and f64236d.

📒 Files selected for processing (4)
  • packages/cli/src/actions/action-utils.ts
  • packages/cli/src/actions/check.ts
  • packages/cli/src/actions/generate.ts
  • packages/cli/test/check.test.ts

Validates that generate succeeds when a plugin provider points to a
.zmodel file that defines custom attributes (no code module needed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 5, 2026

Claude finished @ymc9's task —— View job


PR Code Review

Reviewing PR #2447: "fix(cli): report error when plugin module cannot be resolved"

Review Checklist

  • Read and analyze the changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Evaluate performance considerations
  • Assess security concerns
  • Validate test coverage
  • Provide comprehensive feedback

🎯 Overall Assessment: Excellent Implementation

This PR effectively addresses issue #2393 by improving plugin module error handling with clear, actionable error messages while maintaining backward compatibility. The implementation is well-structured, follows existing patterns, and includes comprehensive test coverage.

Strengths

1. Robust Error Handling (action-utils.ts:295-305)

try {
    const mod = await import(moduleSpec);
    // plugin may not export a generator, return undefined in that case
    return mod.default as CliPlugin | undefined;
} catch (err) {
    const errorCode = (err as NodeJS.ErrnoException)?.code;
    if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND') {
        throw new CliError(`Cannot find plugin module "${provider}". Please make sure the package exists.`);
    }
    throw new CliError(`Failed to load plugin module "${provider}": ${(err as Error).message}`);
}

Excellent: Properly distinguishes between missing modules and other import failures with clear, actionable error messages.

2. Code Organization & DRY Principle

  • Smart Refactoring: Extracted loadPluginModule function from generate.ts to action-utils.ts (lines 232-306)
  • Shared Logic: Both check.ts and generate.ts now use the same plugin loading logic
  • Maintainability: Centralized error handling reduces code duplication

3. Backward Compatibility (action-utils.ts:297-298)

// plugin may not export a generator, return undefined in that case
return mod.default as CliPlugin | undefined;

Smart Design: Gracefully handles plugins that only provide plugin.zmodel files without CLI generators.

4. Comprehensive Test Coverage

  • Missing Plugins: Lines 202-214 (generate) + lines 103-115 (check) ✅
  • Plugins Without Generators: Lines 216-232 ✅
  • Plugins with Custom Attributes Only: Lines 234-254 ✅
  • Edge Cases Covered: All three critical scenarios thoroughly tested

Code Quality

1. Consistent Error Handling Pattern

  • Uses existing CliError class throughout
  • Follows same pattern as other CLI errors
  • Proper TypeScript error type casting with NodeJS.ErrnoException

2. Type Safety

const errorCode = (err as NodeJS.ErrnoException)?.code;

Proper handling of Node.js error codes with safe type casting and optional chaining.

3. Clear Documentation

Inline comments explain behavior for edge cases, especially plugins without generators.

Performance & Security

Performance

  • Minimal Impact: Changes only affect error paths and final fallback import
  • No Regression: Normal plugin loading paths unchanged
  • Efficient Fallback: Uses existing import strategies before final attempt

Security

  • No Security Concerns: Changes only improve error reporting
  • Safe Error Handling: No sensitive information exposed in error messages
  • Consistent Approach: Uses same security patterns as existing code

🔍 Minor Suggestions

1. Return Type Annotation (action-utils.ts:232)

The loadPluginModule function would benefit from an explicit return type:

export async function loadPluginModule(provider: string, basePath: string): Promise<CliPlugin | undefined>

2. Error Code Coverage Enhancement

Consider adding ENOENT to the error code check for broader environment compatibility:

if (errorCode === 'ERR_MODULE_NOT_FOUND' || errorCode === 'MODULE_NOT_FOUND' || errorCode === 'ENOENT') {

3. Test Message Validation

While current tests verify error throwing, consider validating the specific error message:

expect(() => runCli('generate', workDir)).toThrow('Cannot find plugin module "@zenstackhq/nonexistent-plugin"');

📋 Architecture Analysis

Before (generate.ts had duplicate logic)

  • Plugin loading logic duplicated across commands
  • Inconsistent error handling between check and generate
  • Missing error differentiation

After (Centralized in action-utils.ts)

  • ✅ Single source of truth for plugin loading
  • ✅ Consistent error handling across commands
  • ✅ Clear separation between missing modules and other failures
  • ✅ Maintained backward compatibility

📋 Test Coverage: Exceptional

The test suite thoroughly covers all scenarios:

  1. ✅ Missing plugin modules throw clear errors (both check and generate)
  2. ✅ Plugins without generators work correctly
  3. ✅ Plugins with only custom attributes work correctly
  4. ✅ Existing functionality remains unaffected

🚀 Recommendation: APPROVE

This PR is ready for merge. It:

  • ✅ Solves the reported issue (zen check doen't report missing imported plugin #2393) cleanly
  • ✅ Maintains backward compatibility perfectly
  • ✅ Includes comprehensive test coverage (5 new test cases)
  • ✅ Follows existing architectural patterns
  • ✅ Provides clear, user-friendly error messages
  • ✅ Improves code organization through thoughtful refactoring

The implementation correctly distinguishes between legitimate missing modules (which should error) and plugins that intentionally don't export CLI generators (which should continue working). This is exactly the behavior needed to fix the issue without breaking existing workflows.

📁 Files Reviewed

  • action-utils.ts: Extracted and enhanced plugin loading logic ✅
  • check.ts: Uses shared plugin loading logic ✅
  • generate.ts: Refactored to use shared logic ✅
  • check.test.ts: Added error case test ✅
  • generate.test.ts: Added comprehensive plugin loading tests ✅

Summary: This is a textbook example of how to fix an issue while improving code quality. The error handling is robust, the refactoring is clean, and the test coverage is thorough.


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: 2

🧹 Nitpick comments (1)
packages/cli/test/generate.test.ts (1)

216-273: Consider adding one .ts no-default plugin test for parity.

This block covers the ESM/no-default path well; adding a .ts no-default case would also cover the TS loading branch and prevent regressions there.

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

In `@packages/cli/test/generate.test.ts` around lines 216 - 273, Add a new test
mirroring "should succeed when plugin module exists but has no CLI generator"
that uses a .ts plugin file (no default export) to exercise the TypeScript
loader branch; in the new test createProject with a model that references
provider = './my-plugin.ts', write a minimal TS module to
workDir/zenstack/my-plugin.ts exporting a named symbol (e.g., export const name
= 'no-generator'), call runCli('generate', workDir), and assert the default
output (fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))) is true,
similar to the existing tests so the TS loading path is covered.
🤖 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/actions/action-utils.ts`:
- Around line 253-257: importAsTs currently uses jiti.import(spec, { default:
true }), which lets jiti fall back to the module object when no default export
exists and allows non-plugin modules to be treated as CliPlugin; update
importAsTs to require and validate a true default export (or set { default:
false } and then check for result.default) and throw a descriptive error if no
default export or if the default export does not match the expected CliPlugin
shape (e.g., required function or properties). Locate importAsTs and jiti.import
in action-utils.ts and add an explicit existence/type check for the default
export (or validate the plugin interface) before casting to CliPlugin and
returning, so invalid modules are rejected with a clear error.
- Around line 300-303: The error handling for dynamic import in action-utils.ts
is too broad: when import(moduleSpec) throws and errorCode is
'ERR_MODULE_NOT_FOUND' or 'MODULE_NOT_FOUND', parse the error message to extract
the missing package name and compare it to the requested module
(provider/moduleSpec) before converting it into a CliError that says the plugin
module was not found; if the missing package is a transitive dependency (name
differs from provider/moduleSpec) then rethrow the original error (or wrap with
the original message) instead of claiming the plugin package is missing. Locate
the import call and the current check using errorCode and provider, implement
message parsing to identify the actual missing module, and only throw the
“Cannot find plugin module \"${provider}\"” CliError when the extracted name
matches the target module.

---

Nitpick comments:
In `@packages/cli/test/generate.test.ts`:
- Around line 216-273: Add a new test mirroring "should succeed when plugin
module exists but has no CLI generator" that uses a .ts plugin file (no default
export) to exercise the TypeScript loader branch; in the new test createProject
with a model that references provider = './my-plugin.ts', write a minimal TS
module to workDir/zenstack/my-plugin.ts exporting a named symbol (e.g., export
const name = 'no-generator'), call runCli('generate', workDir), and assert the
default output (fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))) is
true, similar to the existing tests so the TS loading path is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d2bd923-e12a-4f78-bfa7-9ff85754035b

📥 Commits

Reviewing files that changed from the base of the PR and between cfff4bd and 650c76c.

📒 Files selected for processing (4)
  • packages/cli/src/actions/action-utils.ts
  • packages/cli/test/generate.test.ts
  • tests/runtimes/bun/schemas/schema.zmodel
  • tests/runtimes/edge-runtime/schemas/schema.zmodel

Comment thread packages/cli/src/actions/action-utils.ts
Comment thread packages/cli/src/actions/action-utils.ts
@ymc9 ymc9 merged commit 3c181f6 into dev Mar 5, 2026
11 checks passed
@ymc9 ymc9 deleted the fix/report-unresolvable-plugin-error branch March 5, 2026 02:14
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.

zen check doen't report missing imported plugin

1 participant