Migrate leadtype CLI to Hexbus#52
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
📝 WalkthroughSummary by CodeRabbitRelease Notes
WalkthroughThe leadtype CLI is migrated from manual argv parsing to a Hexbus-based command dispatch architecture. A hexbus runtime dependency is added, and the CLI refactors to define commands and flags declaratively, use Hexbus's dispatchCommand routing, resolve package version dynamically, and preserve command-local version flags through sentinel mapping. Config loading is updated to provide a module alias for the leadtype package. ChangesHexbus CLI Architecture
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/leadtype/src/cli/generate.ts`:
- Around line 389-394: The alias for "leadtype" passed into createJiti currently
resolves to path.resolve(import.meta.dirname, "../index.ts") which breaks in
packaged builds; update the alias resolution in the createJiti call (the alias
for "leadtype" used when invoking createJiti) to first check for the built
entrypoint (resolve import.meta.dirname + "../index.js" or the package's
published dist entry) and use that if it exists, otherwise fall back to the
source "../index.ts"; use the already-imported fs and path utilities to test for
the built file and set the alias value accordingly before calling createJiti.
🪄 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: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1d1a4a56-9d28-4601-96f8-856650c5e595
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
packages/leadtype/package.jsonpackages/leadtype/src/cli.tspackages/leadtype/src/cli/generate.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/cli/generate.tspackages/leadtype/src/cli.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/cli/generate.tspackages/leadtype/src/cli.ts
| const jiti = createJiti(import.meta.url, { | ||
| alias: { | ||
| leadtype: path.resolve(import.meta.dirname, "../index.ts"), | ||
| }, | ||
| moduleCache: false, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Configured alias block:"
sed -n '379,396p' packages/leadtype/src/cli/generate.ts
echo
echo "Published runtime entrypoints:"
python - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path("packages/leadtype/package.json").read_text())
print("bin:", pkg["bin"])
print('exports[\".\"][\"import\"]:', pkg["exports"]["."]["import"])
src_runtime_dir = Path("packages/leadtype/src/cli")
dist_runtime_dir = Path("packages/leadtype/dist/cli")
print("source-time alias target :", (src_runtime_dir / "../index.ts").resolve())
print("packaged-time alias target:", (dist_runtime_dir / "../index.ts").resolve())
print("packaged JS entrypoint :", (dist_runtime_dir / "../index.js").resolve())
PYRepository: inthhq/leadtype
Length of output: 1086
🏁 Script executed:
# Check what files exist in dist after build and in src
echo "=== Source files ==="
ls -la packages/leadtype/src/index.* 2>/dev/null || echo "No src/index files found"
echo
echo "=== Dist files (if they exist) ==="
ls -la packages/leadtype/dist/index.* 2>/dev/null || echo "No dist/index files found yet (package may not be built)"
echo
echo "=== Checking imports in generate.ts ==="
head -30 packages/leadtype/src/cli/generate.ts | grep -E "^import|^from"Repository: inthhq/leadtype
Length of output: 896
🏁 Script executed:
# Check what path utilities are available in generate.ts
grep -E "import.*path|existsSync" packages/leadtype/src/cli/generate.ts | head -20Repository: inthhq/leadtype
Length of output: 497
Resolve the alias against the published entrypoint to avoid runtime import failures in packaged builds.
When this code runs from the packaged CLI, import.meta.dirname resolves to dist/cli, making ../index.ts point to dist/index.ts. However, the package publishes dist/index.js as the ES module entrypoint, not dist/index.ts. This causes configs that import leadtype to fail when using the installed CLI. The fix checks for the built entrypoint first and falls back to the source file—both required utilities are already imported in this file.
Proposed fix
- const jiti = createJiti(import.meta.url, {
- alias: {
- leadtype: path.resolve(import.meta.dirname, "../index.ts"),
- },
- moduleCache: false,
- });
+ const sourceEntry = path.resolve(import.meta.dirname, "../index.ts");
+ const distEntry = path.resolve(import.meta.dirname, "../index.js");
+ const jiti = createJiti(import.meta.url, {
+ alias: {
+ leadtype: existsSync(distEntry) ? distEntry : sourceEntry,
+ },
+ moduleCache: false,
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const jiti = createJiti(import.meta.url, { | |
| alias: { | |
| leadtype: path.resolve(import.meta.dirname, "../index.ts"), | |
| }, | |
| moduleCache: false, | |
| }); | |
| const sourceEntry = path.resolve(import.meta.dirname, "../index.ts"); | |
| const distEntry = path.resolve(import.meta.dirname, "../index.js"); | |
| const jiti = createJiti(import.meta.url, { | |
| alias: { | |
| leadtype: existsSync(distEntry) ? distEntry : sourceEntry, | |
| }, | |
| moduleCache: false, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/cli/generate.ts` around lines 389 - 394, The alias for
"leadtype" passed into createJiti currently resolves to
path.resolve(import.meta.dirname, "../index.ts") which breaks in packaged
builds; update the alias resolution in the createJiti call (the alias for
"leadtype" used when invoking createJiti) to first check for the built
entrypoint (resolve import.meta.dirname + "../index.js" or the package's
published dist entry) and use that if it exists, otherwise fall back to the
source "../index.ts"; use the already-imported fs and path utilities to test for
the built file and set the alias value accordingly before calling createJiti.
There was a problem hiding this comment.
Caution
The jiti alias added in generate.ts resolves to a path that does not exist in the published dist/ layout — any user docs.config.ts that does import "leadtype" will fail at runtime under the built CLI. Details inline.
TL;DR — Swaps the hand-rolled leadtype CLI dispatcher for hexbus's parseCliArgs + dispatchCommand, wires a stub CliContext, and adds a --version flag plus a -v/--version sentinel layer so the version flag stays command-local. Also teaches the jiti config loader an alias for "leadtype" — but the alias path is broken once bundled.
Key changes
- Adopt hexbus for CLI parsing and dispatch —
runClinow builds aLeadtypeCliContext(stubbed config/fs/logger/framework/packageManager +createDisabledTelemetry()), parses argv withparseCliArgs, and routes throughdispatchCommandwithgenerate/lint/helpregistered asCliCommands. - Add
--versionas a leadtype-owned global flag — readsversionfrompackage.jsonvianew URL("../package.json", import.meta.url); falls back to"unknown"on read/parse failure. - Sentinel-rewrite
-v/--versionafter a command name — keeps hexbus's built-in-vfrom being eaten globally soleadtype generate -vetc. can keep their own version semantics. - Alias
"leadtype"inside the jiti loader — intended so workspacedocs.config.tsfiles resolveimport "leadtype"against the source tree.
Summary | 4 files | 1 commit | base: main ← hexbus-migration
jiti alias path is wrong in the published dist
Before: jiti used default Node resolution, finding
node_modules/leadtype/dist/index.js.
After: jiti'saliasoverrides default resolution and points at a file that doesn't exist in published packages.
rollup.config.ts bundles src/cli.ts (with generate.ts inlined) into dist/cli.js. At runtime in a consumer install, import.meta.dirname of that file is <install>/dist, so path.resolve(import.meta.dirname, "../index.ts") resolves to <install>/index.ts — which is not in the files allowlist (only dist/ is published). I confirmed jiti does not fall back to Node resolution when an alias target is missing; it throws Cannot find module '<install>/index.ts'. Any consumer with a TypeScript docs.config.ts that does import { defineDocsConfig } from "leadtype" will fail under the built CLI.
Hexbus global flag surface is now reserved
Before: the CLI only consumed
-h,--help, and explicit subcommand flags; everything else fell through to the subcommand.
After: hexbus'sglobalFlags(--logger,--config,--color,--no-color,-y,--yes,--no-telemetry,--telemetry-debug,--force) are claimed at the top level and removed fromcommandArgsbefore they reachrunGenerateCommand/runLintCommand.
No collisions today — neither generate nor lint uses any of those names — but any future subcommand flag reusing one will be swallowed silently.
Claude Opus | 𝕏
| const jiti = createJiti(import.meta.url, { moduleCache: false }); | ||
| const jiti = createJiti(import.meta.url, { | ||
| alias: { | ||
| leadtype: path.resolve(import.meta.dirname, "../index.ts"), |
There was a problem hiding this comment.
This alias is broken in the published dist/. rollup inlines generate.ts into dist/cli.js, so at runtime import.meta.dirname is <install>/dist and this resolves to <install>/index.ts — a path that isn't in the published package (only dist/ ships). jiti does not fall back to Node resolution when an alias target is missing; it throws Cannot find module .... The net effect is that any consumer docs.config.ts doing import { defineDocsConfig } from "leadtype" will fail under the built CLI. Either gate the alias on a workspace-only path that resolves (e.g. only set it when the resolved file existsSync), or point at ../dist/index.js so production resolves and workspace dev paths still hit something valid.
| if ( | ||
| context.flags.help === true && | ||
| context.commandName !== "help" && | ||
| (context.commandName || context.commandArgs.length === 0) |
There was a problem hiding this comment.
Minor: the context.commandName || context.commandArgs.length === 0 tail is hard to read because the commandName !== "help" guard above already short-circuits the commandName === "help" case. The condition really just means "--help was passed, and there's a recognizable command or nothing after it." Consider pulling the predicate into a named boolean (e.g. const wantsCommandUsage = ...) — it makes the branch obvious and avoids re-deriving the intent next time someone edits this.

No description provided.