Skip to content

fix(cli): regression of CLI plugin resolution#2516

Merged
ymc9 merged 2 commits intodevfrom
fix/cli-plugin-resolve
Mar 25, 2026
Merged

fix(cli): regression of CLI plugin resolution#2516
ymc9 merged 2 commits intodevfrom
fix/cli-plugin-resolve

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Mar 25, 2026

Summary by CodeRabbit

Release v3.5.1

  • Bug Fixes

    • Improved plugin loading/validation to prevent invalid plugins from being used.
  • Chores

    • Released patch version 3.5.1 across the project; VS Code extension version updated to 3.5.2.

@claude
Copy link
Contributor

claude bot commented Mar 25, 2026

Claude finished @ymc9's task —— View job


PR Review: CLI Plugin Resolution Fix

Todo List:

  • Analyze the main code change in action-utils.ts
  • Review package.json version updates for consistency
  • Check for potential bugs or issues
  • Assess performance and security considerations
  • Evaluate test coverage needs
  • Provide comprehensive review feedback

🔍 Review Summary

This PR addresses a regression in CLI plugin resolution with a targeted fix to ensure plugins have valid generate functions before being considered valid. The changes are minimal and focused on the core issue.

📝 Code Quality & Best Practices

Strengths:

  • Consistent validation logic: The fix applies the same generate function validation across all 4 plugin loading paths (importAsEsm, importAsTs, jiti import, and fallback import)
  • Type safety improvement: Better runtime validation that loaded modules actually implement the CliPlugin interface
  • Clean implementation: Uses consistent ternary operator pattern for validation

Minor concerns:

  • The function loadPluginModule in action-utils.ts:232-314 is quite long (82 lines) and handles multiple loading strategies. Consider breaking it into smaller functions for better maintainability.

🐛 Bug Analysis

Issue Fixed:
The regression was that plugin modules were being accepted even if they didn't export a proper generate function. This could lead to runtime errors when the CLI attempted to invoke non-existent methods.

Fix Quality:

  • ✅ Addresses the root cause by validating generate function presence
  • ✅ Consistent across all import paths
  • ✅ Maintains backward compatibility by returning undefined for invalid plugins

⚡ Performance Considerations

Positive aspects:

  • No performance regression introduced
  • Early validation prevents later runtime failures
  • Minimal overhead (just a type check)

🔒 Security Assessment

No security concerns identified:

  • Changes are defensive in nature
  • No new external dependencies
  • Input validation is improved, not weakened

🧪 Test Coverage

Current state:

  • Existing test in packages/cli/test/plugins/custom-plugin.test.ts covers valid plugin loading
  • The fix ensures invalid plugins are properly rejected

Recommendation:
Consider adding a test case for invalid plugin modules (e.g., modules that export an object without a generate function) to ensure the regression doesn't reoccur.

Example test case:

it('rejects plugin without generate function', async () => {
    // Test loading a module that exports something but not a valid CliPlugin
    const result = await loadPluginModule('./invalid-plugin.js', '/test/path');
    expect(result).toBeUndefined();
});

📦 Package Version Updates

Analysis:

  • Version bump from 3.5.03.5.1 across all packages is appropriate for a patch release
  • Consistent versioning across the monorepo maintains dependency integrity
  • Version increase aligns with the nature of the fix (bug fix, not new features)

✅ Overall Assessment

This is a well-executed fix for a specific regression. The changes are:

  • Targeted and minimal
  • Consistent across all code paths
  • Safe with no breaking changes
  • Well-tested by existing test infrastructure

The only suggestion is to add a specific test case for the regression scenario to prevent future issues.

Recommendation: ✅ APPROVE


@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8a92061c-c065-46b5-a61f-d02de14015c1

📥 Commits

Reviewing files that changed from the base of the PR and between 3145ba6 and 9e5fd4b.

📒 Files selected for processing (1)
  • packages/cli/src/actions/action-utils.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/actions/action-utils.ts

📝 Walkthrough

Walkthrough

This patch updates many package.json versions from 3.5.0 → 3.5.1 (VSCode extension → 3.5.2) and tightens CLI plugin loading to only return imported modules that expose a generate function.

Changes

Cohort / File(s) Summary
Root & Monorepo Packages
package.json, packages/*/package.json (auth-adapters/better-auth, clients/client-helpers, clients/tanstack-query, common-helpers, config/eslint-config, config/typescript-config, config/vitest-config, create-zenstack, language, orm, plugins/policy, schema, sdk, server, testtools, zod)
Bumped package versions from 3.5.03.5.1. No other manifest fields changed.
IDE Extension
packages/ide/vscode/package.json
Bumped VSCode extension version to 3.5.2 (from 3.5.0).
Samples & Tests
samples/orm/package.json, tests/*/package.json (e2e, regression, runtimes/bun, runtimes/edge-runtime)
Bumped package versions from 3.5.03.5.1.
CLI plugin loading
packages/cli/src/actions/action-utils.ts
Changed loadPluginModule to validate imported modules by checking typeof <export>.generate === 'function' across import paths; added optional chaining on direct ESM fallback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through packages, one by one,
Bumped tiny numbers, now the work is done.
I sniffed the CLI plugins, gave each a test,
"Show me generate" — only those passed the quest. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main functional change in the changeset—fixing a regression in CLI plugin resolution via validation of the 'generate' function in loaded plugin modules.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/cli-plugin-resolve

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
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 (1)
packages/cli/src/actions/action-utils.ts (1)

244-261: ⚠️ Potential issue | 🟡 Minor

Missing optional chaining causes inconsistent error handling.

If the module exists but has no default export (or default is undefined), accessing result.generate throws a TypeError. This error is caught and re-thrown as a CliError with "Failed to load plugin module", which masks the actual issue and differs from the behavior at line 306 where optional chaining correctly returns undefined for invalid plugins.

Proposed fix to add optional chaining for consistency
     const importAsEsm = async (spec: string) => {
         try {
             const result = (await import(spec)).default as CliPlugin;
-            return typeof result.generate === 'function' ? result : undefined;
+            return typeof result?.generate === 'function' ? result : undefined;
         } catch (err) {
             throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`);
         }
     };

     const jiti = createJiti(pathToFileURL(basePath).toString());
     const importAsTs = async (spec: string) => {
         try {
             const result = (await jiti.import(spec, { default: true })) as CliPlugin;
-            return typeof result.generate === 'function' ? result : undefined;
+            return typeof result?.generate === 'function' ? result : undefined;
         } catch (err) {
             throw new CliError(`Failed to load plugin module from ${spec}: ${(err as Error).message}`);
         }
     };
🤖 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 244 - 261, The plugin
loaders importAsEsm and importAsTs can throw when a module has no default export
because they access result.generate directly; change the checks to use optional
chaining (e.g., verify typeof result?.generate === 'function') so a missing
default simply yields undefined instead of causing a TypeError and being wrapped
as a CliError; update both importAsEsm and importAsTs (which uses jiti created
via createJiti(pathToFileURL(basePath).toString())) to perform the
optional-chained property check to match the behavior used elsewhere for
CliPlugin validation.
🤖 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 294-300: The jiti fallback block currently assumes the imported
value exists and checks typeof result.generate, which can throw if result is
undefined; update the jiti.import handling in the same block to guard the access
(e.g., check result !== undefined/null or use optional chaining like typeof
result?.generate === 'function') before returning the plugin, keeping the same
return behavior (return result if it has a generate function, otherwise
undefined) and reference the result variable, jiti.import call and the
CliPlugin/generate check when making the change.

---

Outside diff comments:
In `@packages/cli/src/actions/action-utils.ts`:
- Around line 244-261: The plugin loaders importAsEsm and importAsTs can throw
when a module has no default export because they access result.generate
directly; change the checks to use optional chaining (e.g., verify typeof
result?.generate === 'function') so a missing default simply yields undefined
instead of causing a TypeError and being wrapped as a CliError; update both
importAsEsm and importAsTs (which uses jiti created via
createJiti(pathToFileURL(basePath).toString())) to perform the optional-chained
property check to match the behavior used elsewhere for CliPlugin validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a86e637f-0f10-485d-8ff1-764bd4483bbf

📥 Commits

Reviewing files that changed from the base of the PR and between 49e1fe6 and 3145ba6.

📒 Files selected for processing (25)
  • package.json
  • packages/auth-adapters/better-auth/package.json
  • packages/cli/package.json
  • packages/cli/src/actions/action-utils.ts
  • packages/clients/client-helpers/package.json
  • packages/clients/tanstack-query/package.json
  • packages/common-helpers/package.json
  • packages/config/eslint-config/package.json
  • packages/config/typescript-config/package.json
  • packages/config/vitest-config/package.json
  • packages/create-zenstack/package.json
  • packages/ide/vscode/package.json
  • packages/language/package.json
  • packages/orm/package.json
  • packages/plugins/policy/package.json
  • packages/schema/package.json
  • packages/sdk/package.json
  • packages/server/package.json
  • packages/testtools/package.json
  • packages/zod/package.json
  • samples/orm/package.json
  • tests/e2e/package.json
  • tests/regression/package.json
  • tests/runtimes/bun/package.json
  • tests/runtimes/edge-runtime/package.json

@claude
Copy link
Contributor

claude bot commented Mar 25, 2026

Claude finished @ymc9's task —— View job


Claude finished reviewing the pull request


PR Review: CLI Plugin Resolution Fix

Todo List:

  • Analyze the main code change in action-utils.ts
  • Review package.json version updates for consistency
  • Check for potential bugs or issues
  • Assess performance and security considerations
  • Evaluate test coverage needs
  • Provide comprehensive review feedback

🔍 Review Summary

This PR addresses a regression in CLI plugin resolution with a targeted fix to ensure plugins have valid generate functions before being considered valid. The changes are minimal and focused on the core issue.

📝 Code Quality & Best Practices

Strengths:

  • Consistent validation logic: The fix applies the same generate function validation across all 4 plugin loading paths (importAsEsm, importAsTs, jiti import, and fallback import)
  • Type safety improvement: Better runtime validation that loaded modules actually implement the CliPlugin interface
  • Clean implementation: Uses consistent ternary operator pattern for validation

Minor concerns:

  • The function loadPluginModule in action-utils.ts:232-314 is quite long (82 lines) and handles multiple loading strategies. Consider breaking it into smaller functions for better maintainability.

🐛 Bug Analysis

Issue Fixed:
The regression was that plugin modules were being accepted even if they didn't export a proper generate function. This could lead to runtime errors when the CLI attempted to invoke non-existent methods.

Fix Quality:

  • ✅ Addresses the root cause by validating generate function presence
  • ✅ Consistent across all import paths
  • ✅ Maintains backward compatibility by returning undefined for invalid plugins

CodeRabbit identified a valid concern: Missing optional chaining in lines 247, 257, and 297 could cause TypeError when modules export undefined/null as default. While the current code works due to try-catch blocks, using optional chaining (result?.generate) would be more defensive and consistent with the pattern used elsewhere (line 306).

⚡ Performance Considerations

Positive aspects:

  • No performance regression introduced
  • Early validation prevents later runtime failures
  • Minimal overhead (just a type check)

🔒 Security Assessment

No security concerns identified:

  • Changes are defensive in nature
  • No new external dependencies
  • Input validation is improved, not weakened

🧪 Test Coverage

Current state:

  • Existing test in packages/cli/test/plugins/custom-plugin.test.ts covers valid plugin loading
  • The fix ensures invalid plugins are properly rejected

Recommendation:
Consider adding a test case for invalid plugin modules (e.g., modules that export an object without a generate function) to ensure the regression doesn't reoccur.

Example test case:

it('rejects plugin without generate function', async () => {
    // Test loading a module that exports something but not a valid CliPlugin
    const result = await loadPluginModule('./invalid-plugin.js', '/test/path');
    expect(result).toBeUndefined();
});

📦 Package Version Updates

Analysis:

  • Version bump from 3.5.03.5.1 across all packages is appropriate for a patch release
  • VSCode extension bumped to 3.5.2 (per CLAUDE.md: "has a different version from the rest of the packages as VSCode doesn't allow pre-release versions")
  • Consistent versioning across the monorepo maintains dependency integrity
  • Version increase aligns with the nature of the fix (bug fix, not new features)

✅ Overall Assessment

This is a well-executed fix for a specific regression. The changes are:

  • Targeted and minimal
  • Consistent across all code paths
  • Safe with no breaking changes
  • Well-tested by existing test infrastructure

The only suggestions are:

  1. Optional improvement: Add optional chaining as identified by CodeRabbit for more defensive code
  2. Test enhancement: Add a specific regression test case for invalid plugins

Recommendation: ✅ APPROVE

The core fix is solid and addresses the regression effectively. The optional chaining improvement would be nice-to-have but not critical for this release.


@ymc9 ymc9 merged commit 826cc4e into dev Mar 25, 2026
8 of 11 checks passed
@ymc9 ymc9 deleted the fix/cli-plugin-resolve branch March 25, 2026 03:15
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