Skip to content

feat(core,cli): add kidd run command and configurable strict mode#118

Merged
zrosenbauer merged 7 commits intomainfrom
feat/run-cmd
Mar 27, 2026
Merged

feat(core,cli): add kidd run command and configurable strict mode#118
zrosenbauer merged 7 commits intomainfrom
feat/run-cmd

Conversation

@zrosenbauer
Copy link
Copy Markdown
Member

Summary

  • Add configurable strict mode to CliOptions, CommandDef, and ScreenDef — controls whether yargs rejects unknown flags. Defaults to true (existing behavior), per-command strict: false overrides the CLI-level setting.
  • Add kidd run command with three engine modes:
    • node (default) — builds first, then runs node dist/index.mjs
    • tsx — runs the source entry file directly via tsx
    • binary — builds and compiles, then executes the compiled binary
  • Supports --inspect, --inspect-brk, --inspect-wait, and --inspect-port for debugging (node and tsx engines only — errors if used with binary)
  • All unknown flags are forwarded to the executed CLI (no -- separator needed)

Test plan

  • kidd run greet --name=world builds and runs with node engine
  • kidd run --engine=tsx greet --name=world runs from source via tsx
  • kidd run --engine=binary --target=darwin-arm64 greet compiles and runs binary
  • kidd run --engine=binary --inspect greet fails with clear error message
  • kidd run --inspect-brk greet starts with debugger breakpoint
  • kidd run --engine=binary greet without targets configured fails with clear error
  • Existing commands still reject unknown flags (strict default preserved)
  • Command with strict: false accepts unknown flags
  • All existing tests pass (pnpm test)

Add `strict` option to CliOptions, CommandDef, and ScreenDef to control
whether yargs rejects unknown flags. Defaults to true (preserving
existing behavior). Per-command `strict: false` overrides the CLI-level
setting.

Add `kidd run` command with three engine modes:
- node (default): builds then runs node dist/index.mjs
- tsx: runs source entry directly via tsx
- binary: builds, compiles, then executes the compiled binary

Supports --inspect, --inspect-brk, --inspect-wait, --inspect-port for
debugging (node and tsx only). Unknown flags are forwarded to the CLI.

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
oss-kidd Ignored Ignored Preview Mar 27, 2026 8:24pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 6fe8c11

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@kidd-cli/core Minor
@kidd-cli/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

…n-cmd

# Conflicts:
#	packages/core/src/cli.ts
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new kidd run CLI command (packages/cli/src/commands/run.ts) supporting engines node, tsx, and binary with Zod-validated options (engine, inspector flags, and --target for binary). Loads and normalizes config, rejects inspector flags with --engine=binary, extracts passthrough args, and dispatches execution per engine: node and tsx run entry files with optional inspector flags (build before node), binary validates/overrides targets, compiles, resolves a platform/target-specific executable (with Linux x64 musl preference), and spawns it. Child exit codes are propagated. Also surfaces configurable strict parsing: strict?: boolean added to CliOptions, CommandDef/Command, and ScreenDef, and yargs .strict() usage made conditional in CLI and runtime registration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the two main changes: adding a configurable strict mode to the core framework and introducing the new kidd run command with CLI support.
Description check ✅ Passed The description comprehensively documents the strict mode configuration mechanism, all three engine modes (node, tsx, binary), inspector flag support, and flag forwarding behavior aligned with the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/run-cmd

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

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq bot commented Mar 27, 2026

Merging this PR will not alter performance

✅ 2 untouched benchmarks


Comparing feat/run-cmd (6fe8c11) with main (f51a6b1)

Open in CodSpeed

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

🤖 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/commands/run.ts`:
- Around line 342-352: extractPassthroughArgs currently filters passthrough
tokens by value, which wrongly drops legitimate command arguments (e.g., the
value following a known flag); instead, rewrite the function to iterate argv
starting at the 'run' token index and parse sequentially: when you encounter a
known flag (use isKnownFlag with params.knownArgs), append the flag to the
passthrough list and if that flag accepts a value, consume and append the next
argv token (do not drop it by value-based filtering); otherwise append
standalone tokens as command args. Apply the same sequential-consume fix to the
other passthrough parsing block later in the file that uses similar value-based
filtering.
- Around line 257-263: The code currently returns the first item from
params.compileOutput.binaries (const [firstBinary] =
params.compileOutput.binaries) which may be for a different OS/arch; change the
selection logic to resolve the host target and pick the binary whose target
matches the host before falling back. Specifically, determine the current host
target (e.g., via existing host target resolver or platform detection), iterate
params.compileOutput.binaries to find a binary whose target/metadata matches
that host, and return that binary; if none match, keep the current
params.ctx.fail or fallback behavior but do not execute binaries[0] blindly.
- Around line 435-456: hasInspectFlag and buildInspectFlags ignore a port-only
invocation of --inspect-port, letting port-only runs bypass checks and emit no
flags; update hasInspectFlag to also return true when args['inspect-port'] is
set, and change buildInspectFlags to, after calling resolveInspectMode(args), if
no mode flag is returned but args['inspect-port'] exists then emit a port-only
Node flag (e.g. `--inspect-port=${args['inspect-port']}`) instead of returning
[]; validate that args['inspect-port'] is a valid number/string port before
emitting; touch the functions hasInspectFlag, buildInspectFlags and referenced
helpers resolveInspectMode/formatInspectFlag and the RunArgs handling of
'inspect-port'.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 767e3541-d56d-4e3f-be5d-18ef440ee150

📥 Commits

Reviewing files that changed from the base of the PR and between f51a6b1 and 16725fb.

⛔ Files ignored due to path filters (1)
  • .changeset/feat-run-command.md is excluded by !.changeset/**
📒 Files selected for processing (6)
  • packages/cli/src/commands/run.ts
  • packages/core/src/cli.ts
  • packages/core/src/runtime/register.ts
  • packages/core/src/types/cli.ts
  • packages/core/src/types/command.ts
  • packages/core/src/ui/screen.tsx

- Detect host platform/arch in resolveBinary instead of blindly picking
  first binary when --target is not provided
- Rewrite extractPassthroughArgs with sequential token parsing to avoid
  false positives from value-based filtering
- Include --inspect-port in hasInspectFlag so binary engine guard catches
  it, and default to --inspect when only --inspect-port is provided

Co-Authored-By: Claude <noreply@anthropic.com>
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: 4

🤖 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/commands/run.ts`:
- Around line 137-139: The failure path after checking
hasCompileTargets(configWithTarget) calls params.ctx.fail(...) but does not stop
execution; update the conditional to return immediately after calling
params.ctx.fail so the function exits and does not proceed to buildProject or
later logic (refer to hasCompileTargets, configWithTarget, params.ctx.fail and
buildProject to locate the flow).
- Around line 57-58: The code currently ignores the error element from
loadConfig by destructuring as [, configResult]; update the run command to
capture both elements (e.g., const [configErr, configResult] = await
loadConfig({ cwd })) and check configErr before calling extractConfig; if
configErr is present, log or throw the error (using the project error handling
conventions) and return/exit early to avoid using a null/undefined configResult.
Ensure references to loadConfig, extractConfig, configResult (and cwd) are
updated so the error path is handled per
contributing/standards/typescript/errors.md.
- Around line 545-559: The spawnProcess promise doesn't handle the child's
'error' event so failures (e.g., missing binary) leave the promise pending;
update spawnProcess to listen for child.on('error', ...) and in that handler
either call the promise resolver with a non-zero code (e.g., _resolve(1)) or
reject the promise so callers get notified of the failure; ensure you reference
the same child object created inside spawnProcess and cleanly resolve/reject the
promise in both 'close' and 'error' handlers to avoid leaks or double
resolution.
- Around line 330-334: The code casts params.target directly to CompileTarget
(e.g., when building compile: { ...existingCompile, targets: [params.target as
CompileTarget] }) without validation; define a Zod TargetSchema
(z.enum([...allowed strings...])) and in applyTargetOverride validate
params.target with TargetSchema.safeParse, returning ctx.fail with a clear
message listing TargetSchema.enum values if parsing fails, and only then assign
the validated value into compile.targets (use the parsed value rather than
casting).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4f47e83e-f9b4-4326-b02b-3eb3ab721d8f

📥 Commits

Reviewing files that changed from the base of the PR and between 16725fb and 60a1fe8.

📒 Files selected for processing (1)
  • packages/cli/src/commands/run.ts

- Check configError from loadConfig instead of silently discarding it
- Add missing return before ctx.fail in runWithBinary compile targets check
- Validate --target against CompileTarget enum with TargetSchema instead
  of unsafe `as CompileTarget` cast
- Handle spawn error event to prevent promise from hanging indefinitely

Co-Authored-By: Claude <noreply@anthropic.com>
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

♻️ Duplicate comments (1)
packages/cli/src/commands/run.ts (1)

419-430: ⚠️ Potential issue | 🟠 Major

Passthrough parsing still loses valid child arguments.

The sequential skip logic fixes the old value-based filtering bug, but this still anchors on the first literal run, so kidd --cwd run run --foo will treat the --cwd value as the command boundary and forward an extra run. collectPassthroughTokens() also keeps stripping wrapper flags after --, so there is still no escape hatch for child args like --target or --inspect. Anchor parsing to the matched command position and stop consuming wrapper flags once -- is seen.

Also applies to: 461-483

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

In `@packages/cli/src/commands/run.ts` around lines 419 - 430, The current
extractPassthroughArgs implementation anchors on the first literal 'run' and
then calls collectPassthroughTokens which still strips wrapper flags after
encountering '--', causing valid child args (e.g. --target, --inspect or a later
'run' value) to be lost; change extractPassthroughArgs to locate the exact
matched command position in process.argv (use the index of the actual matched
'run' token rather than the first literal) and pass the slice from that exact
position into collectPassthroughTokens, and modify collectPassthroughTokens so
that once it encounters a '--' token it stops consuming or stripping wrapper
flags and instead forwards all subsequent tokens verbatim; apply the same
anchored parsing and '--' stop logic to the other duplicated section (the block
around lines 461-483 / the other extract code).
🤖 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/commands/run.ts`:
- Around line 261-267: resolveBinary's auto-target logic uses
resolveHostTarget() but doesn't account for musl variants, so on Alpine-like
systems it can miss or choose the wrong binary; update resolveBinary (and the
similar logic around lines 305-308) to: derive hostTarget = resolveHostTarget(),
then attempt to find a binary by exact match and also try the musl variant (e.g.
`${hostTarget}-musl`) and the inverse (strip `-musl`) so both `linux-x64` and
`linux-x64-musl` are considered; if the runtime is musl-detected (or a musl
artifact exists while the non-musl also exists), prefer the musl artifact. Use
the existing params.compileOutput.binaries.find logic but check both variants
and prefer the one matching the actual host libc variant.

---

Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 419-430: The current extractPassthroughArgs implementation anchors
on the first literal 'run' and then calls collectPassthroughTokens which still
strips wrapper flags after encountering '--', causing valid child args (e.g.
--target, --inspect or a later 'run' value) to be lost; change
extractPassthroughArgs to locate the exact matched command position in
process.argv (use the index of the actual matched 'run' token rather than the
first literal) and pass the slice from that exact position into
collectPassthroughTokens, and modify collectPassthroughTokens so that once it
encounters a '--' token it stops consuming or stripping wrapper flags and
instead forwards all subsequent tokens verbatim; apply the same anchored parsing
and '--' stop logic to the other duplicated section (the block around lines
461-483 / the other extract code).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 52b04105-3ca8-4356-b6da-efec90756ddb

📥 Commits

Reviewing files that changed from the base of the PR and between 60a1fe8 and 5862d49.

📒 Files selected for processing (1)
  • packages/cli/src/commands/run.ts

Prefer linux-x64-musl over linux-x64 on x64 Linux hosts so Alpine-like
systems find the correct binary when --target is not provided.

Co-Authored-By: Claude <noreply@anthropic.com>
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 the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 642-661: The spawnProcess function swallows spawn errors in the
child.on('error') handler and only resolves with 1, losing diagnostic context;
modify spawnProcess to accept an optional onError callback (e.g., add to params:
onError?: (err: Error) => void) and invoke it with the error inside the
child.on('error') handler before resolving, or alternatively include the error
message when resolving/rejecting so callers using spawnProcess (refer to
function spawnProcess, params, and child.on('error')) can log or surface the
underlying spawn error (like ENOENT) for better diagnostics.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a77ecdee-99b8-4390-8e84-9cd32cbfecd2

📥 Commits

Reviewing files that changed from the base of the PR and between 5862d49 and b9b579e.

📒 Files selected for processing (1)
  • packages/cli/src/commands/run.ts

Log the error message to stderr when spawn fails (e.g. ENOENT for
missing tsx binary) instead of silently resolving with exit code 1.

Co-Authored-By: Claude <noreply@anthropic.com>
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: 2

♻️ Duplicate comments (1)
packages/cli/src/commands/run.ts (1)

467-478: ⚠️ Potential issue | 🟠 Major

Passthrough parsing can still lock onto the wrong run token.

Line 470 uses the first raw run token from process.argv as the command boundary. kidd --cwd run run --foo will treat the --cwd value as the boundary and forward an extra run to the child process. Use the parser’s matched command position instead of argv.indexOf('run').

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

In `@packages/cli/src/commands/run.ts` around lines 467 - 478, The current
extractPassthroughArgs uses argv.indexOf('run') which can pick the wrong literal
token; instead derive the run command boundary from the parser's matched command
position available on params.knownArgs and slice process.argv using that
position (so use the parser's matched command index from params.knownArgs to
compute runIndex, fallback to -1 if not present), then call
collectPassthroughTokens on the tokens after that position; update
extractPassthroughArgs to stop referencing argv.indexOf('run') and use the
parser-provided index from params.knownArgs (keeping collectPassthroughTokens
and the rest of the logic intact).
🤖 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/commands/run.ts`:
- Line 15: DEFAULT_ENTRY is set to './src/index.ts' but KiddConfig.entry
defaults to './index.ts', causing tsx runs to pick the wrong file; update
DEFAULT_ENTRY to './index.ts' (or derive it from KiddConfig.entry) and ensure
the code path that selects entry (where config.entry is read and fallback to
DEFAULT_ENTRY in the run command) uses this updated value so tsx falls back to
the same default as node/binary.
- Around line 154-158: The hasCompileTargets helper currently only returns true
for a compile config with an explicit non-empty targets array, which rejects
valid configs like compile: true or object configs that omit targets (both
should default to current platform); update hasCompileTargets to return true
when config.compile === true, or when config.compile is an object and either
targets is undefined (meaning use defaults) or targets is a non-empty array, and
return false only when compile is an object with an explicitly empty targets
array; reference the hasCompileTargets function used in run.ts and adjust its
conditional logic accordingly.

---

Duplicate comments:
In `@packages/cli/src/commands/run.ts`:
- Around line 467-478: The current extractPassthroughArgs uses
argv.indexOf('run') which can pick the wrong literal token; instead derive the
run command boundary from the parser's matched command position available on
params.knownArgs and slice process.argv using that position (so use the parser's
matched command index from params.knownArgs to compute runIndex, fallback to -1
if not present), then call collectPassthroughTokens on the tokens after that
position; update extractPassthroughArgs to stop referencing argv.indexOf('run')
and use the parser-provided index from params.knownArgs (keeping
collectPassthroughTokens and the rest of the logic intact).
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 86fd926f-9377-4838-8520-f991fe318f58

📥 Commits

Reviewing files that changed from the base of the PR and between b9b579e and d995ffe.

📒 Files selected for processing (1)
  • packages/cli/src/commands/run.ts

Update hasCompileTargets to accept compile: true (boolean shorthand)
and object configs that omit targets (bundler fills in defaults).

Co-Authored-By: Claude <noreply@anthropic.com>
@zrosenbauer zrosenbauer merged commit f0198b2 into main Mar 27, 2026
7 checks passed
@zrosenbauer zrosenbauer deleted the feat/run-cmd branch March 27, 2026 20:43
zrosenbauer added a commit that referenced this pull request Mar 27, 2026
The `kidd run` command (PR #118) merged after the DisplayConfig PR and
still referenced `ctx.spinner` instead of `ctx.status.spinner`. Also
adds an empty changeset for the refactor-only change.

Co-Authored-By: Claude <noreply@anthropic.com>
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