Skip to content

Migrate leadtype CLI to Hexbus#52

Open
BurnedChris wants to merge 1 commit into
mainfrom
hexbus-migration
Open

Migrate leadtype CLI to Hexbus#52
BurnedChris wants to merge 1 commit into
mainfrom
hexbus-migration

Conversation

@BurnedChris
Copy link
Copy Markdown

No description provided.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Refactor
    • The CLI command dispatch system has been redesigned using an improved routing model for better command execution and global flag handling.
    • Global flag processing now properly preserves and restores command-specific options like --version and -v.
    • Configuration module resolution has been enhanced with improved path handling for more reliable configuration file loading.

Walkthrough

The 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.

Changes

Hexbus CLI Architecture

Layer / File(s) Summary
Dependency and CLI context setup
packages/leadtype/package.json, packages/leadtype/src/cli.ts (imports, types)
Hexbus dependency ^0.6.0 is added. Module imports expand to include readFile from node:fs/promises and Hexbus CLI utilities. LeadtypeCliContext is introduced, extending Hexbus CliContext with injected io and tracked state.exitCode.
CLI commands and flags definition
packages/leadtype/src/cli.ts (commands, flags)
Global flags array defines a --version flag. Sentinel mappings for command-local --version/-v are introduced. Commands array declares async actions for generate, lint, and help, each writing usage/output and setting context.state.exitCode.
CLI execution wiring
packages/leadtype/src/cli.ts (version, context, runCli)
Version is read from package.json with readPackageVersion(), falling back to "unknown". Context is constructed via createLeadtypeContext() with stubbed config/fs/framework/logger/package-manager fields and disabled telemetry. runCli is refactored to dispatch via dispatchCommand with explicit noCommand and unknownCommand handlers, printing version/help/usage conditionally and returning exit codes (2 for unknown, otherwise context.state.exitCode).
Config module alias for CLI compatibility
packages/leadtype/src/cli/generate.ts
TypeScript config loading in importConfigModule initializes jiti with an alias mapping that resolves the leadtype module to ../index.ts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • inthhq/leadtype#28: Touches jiti-based config initialization in packages/leadtype/src/cli/generate.ts; this PR adds a module alias to support config loading within the new Hexbus CLI architecture.

Poem

🐰 From argv fog to Hexbus gleam,
The CLI finds a clearer dream—
Commands declared, dispatch flows bright,
Config aliases set things right!
A refactor well, a structure sound. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess if it relates to the changeset. Add a pull request description explaining the purpose and scope of the Hexbus migration, including any relevant context or migration rationale.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: migrating the leadtype CLI from manual argv parsing to a Hexbus-based command dispatch model.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 hexbus-migration

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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

Copy link
Copy Markdown

@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

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between e923e9f and 532efc8.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • packages/leadtype/package.json
  • packages/leadtype/src/cli.ts
  • packages/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
Prefer unknown over any when 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.ts
  • packages/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
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax 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
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks 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 use eval() or assign directly to document.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.ts
  • packages/leadtype/src/cli.ts

Comment on lines +389 to +394
const jiti = createJiti(import.meta.url, {
alias: {
leadtype: path.resolve(import.meta.dirname, "../index.ts"),
},
moduleCache: false,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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())
PY

Repository: 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 -20

Repository: 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.

Suggested change
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.

Copy link
Copy Markdown

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

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 dispatchrunCli now builds a LeadtypeCliContext (stubbed config/fs/logger/framework/packageManager + createDisabledTelemetry()), parses argv with parseCliArgs, and routes through dispatchCommand with generate / lint / help registered as CliCommands.
  • Add --version as a leadtype-owned global flag — reads version from package.json via new URL("../package.json", import.meta.url); falls back to "unknown" on read/parse failure.
  • Sentinel-rewrite -v / --version after a command name — keeps hexbus's built-in -v from being eaten globally so leadtype generate -v etc. can keep their own version semantics.
  • Alias "leadtype" inside the jiti loader — intended so workspace docs.config.ts files resolve import "leadtype" against the source tree.

Summary | 4 files | 1 commit | base: mainhexbus-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's alias overrides 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.

generate.ts · cli.ts


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's globalFlags (--logger, --config, --color, --no-color, -y, --yes, --no-telemetry, --telemetry-debug, --force) are claimed at the top level and removed from commandArgs before they reach runGenerateCommand / runLintCommand.

No collisions today — neither generate nor lint uses any of those names — but any future subcommand flag reusing one will be swallowed silently.

cli.ts

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using 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"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

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