Skip to content

fix(cli): stabilization batch + CLI test suite#234

Merged
pi0 merged 2 commits into
mainfrom
fix/cli-batch
Jul 14, 2026
Merged

fix(cli): stabilization batch + CLI test suite#234
pi0 merged 2 commits into
mainfrom
fix/cli-batch

Conversation

@pi0x

@pi0x pi0x commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

CLI batch from the v1 stabilization review.

  • F60main() now honors mainOpts.args ?? process.argv.slice(2) (adds an in-process path for tests).
  • F40 — parse args first, then detect mode from real positionals, so an option value (-p 8080 fetch /api) is no longer mistaken for the subcommand.
  • F41 — define -h short flag (documented in usage but previously threw ERR_PARSE_ARGS_UNKNOWN_OPTION).
  • F38 — watch mode now installs the SIGINT handler (checked runtimeArgs, not args), so the dev-server child isn't orphaned on kill -INT.
  • F39 — add duplex: "half" when the request body is a stream, so -d @- and -d @file work on Node.
  • F42 — only override the entry module's tls when CLI flags supply it; a bare --tls without --cert/--key now errors instead of silently downgrading to HTTP.
  • F43 — an explicit --static ./missing now errors; the implicit public default stays silent.
  • F44 — unknown flags, missing entry, and positional-vs---entry conflicts print a one-line message + usage hint and exit non-zero (no raw stack traces).
  • F44srvx fetch loads .env before running; schemeless host:port is treated as http (fixes localhost:3000/api).
  • F59 — escape error.stack/title before interpolating into the dev error page HTML.
  • F58NO_COLOR now disables on any non-empty value and colors are gated on a TTY (with FORCE_COLOR override).
  • loader.ts — JSX error hint now recommends --import jiti/register (was --register).
  • F46 — new test/cli.test.ts (14 tests, via execa + a source runner and one in-process main({args}) case): --version, --help/-h, serve+fetch, fetch exit codes (0/22), F40, F42, F43, F39 stdin+file, F44 unknown-flag/env/schemeless-URL. Watch-reload and SIGINT-orphan tests were left out to avoid flakiness.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved CLI argument parsing with clearer errors and --help guidance.
    • Added support for loading environment files before fetch requests.
    • Improved handling of streamed request bodies from stdin and files.
    • Schemeless host-and-port URLs are now recognized correctly.
  • Bug Fixes

    • CLI colors now respect NO_COLOR, terminal interactivity, and FORCE_COLOR.
    • Added validation for missing static directories and incomplete TLS options.
    • Escaped error-page content to prevent unsafe HTML rendering.
    • Corrected JSX compatibility guidance for jiti/register.

- F60: honor `mainOpts.args` instead of always reading process.argv
- F40: parse args first so option values aren't mistaken for the subcommand
- F41: define `-h` short flag (documented but missing)
- F38: install SIGINT cleanup in watch mode (checked runtimeArgs, not args)
- F39: add `duplex: "half"` for streamed request bodies (`-d @-` / `-d @file`)
- F42: only override entry `tls` from CLI flags; `--tls` w/o cert/key now errors
- F43: explicit missing `--static` dir errors; implicit `public` stays silent
- F44: friendly one-line errors for bad flags / missing entry + usage hint
- F44: load `.env` before `fetch`; treat schemeless host:port as http
- F59: escape error output in the dev error page HTML
- F58: NO_COLOR any non-empty value disables; gate colors on TTY
- loader: fix JSX hint to recommend `--import` (not `--register`)
- F46: add test/cli.test.ts covering the above (execa + in-process main)

Co-Authored-By: Claude Fable 5 <[email protected]>
@pi0x
pi0x requested a review from pi0 as a code owner July 14, 2026 11:41
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The CLI now has unified argument parsing, earlier environment loading, improved URL and streamed-body handling, stricter serve configuration validation, escaped error rendering, updated compatibility guidance, and expanded integration tests using a source-based runner.

CLI behavior and integration

Layer / File(s) Summary
CLI entry and argument orchestration
src/cli/main.ts, src/cli/_utils.ts, src/loader.ts
Argument parsing, environment loading, watch handling, color detection, and JSX compatibility messaging are updated.
Fetch URL and streamed body handling
src/cli/fetch.ts, test/cli.test.ts, test/fixtures/cli/*
Fetch mode normalizes schemeless URLs, supports streamed stdin/file bodies, and validates request and response behavior.
Serve configuration and safe error rendering
src/cli/serve.ts, test/cli.test.ts
Static and TLS options are validated, TLS settings are passed through, and rendered error content is HTML-escaped.
Source runner and CLI regression coverage
test/_cli-run.ts, test/cli.test.ts
CLI tests execute from source and cover informational output, injected arguments, serve startup, and concise argument errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • h3js/srvx#174: Overlaps with fetch request construction, streamed bodies, duplex handling, and URL parsing.

Suggested reviewers: pi0, oskarlebuda

Poem

I’m a rabbit with a CLI burrow,
Fetching streams through rain and snow.
TLS gates stand firm and bright,
Escaped errors keep pages right.
Tests hop forth from source today!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main theme: CLI stabilization fixes plus a new CLI test suite.
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.
✨ 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-batch

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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/srvx@234

commit: 084599b

@pi0
pi0 merged commit 24a71b8 into main Jul 14, 2026
14 of 15 checks passed
@pi0
pi0 deleted the fix/cli-batch branch July 14, 2026 12:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/cli/_utils.ts`:
- Around line 4-13: Update the noColor initializer to parse FORCE_COLOR rather
than checking its truthiness, ensuring the explicit value "0" disables colors
while preserving the existing handling for other FORCE_COLOR values and
NO_COLOR/TERM conditions.

In `@src/cli/serve.ts`:
- Around line 35-48: Update the static path validation in the serve flow to
require that staticDir exists and is a directory, using the same
statSync(...).isDirectory() pattern as parseArgs(). Preserve the implicit public
fallback behavior, while explicit --static paths that are missing or files must
throw the existing clear directory-not-found error.
🪄 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: CHILL

Plan: Pro

Run ID: ea19094d-32c6-4eaf-a3ba-ec81c66a9202

📥 Commits

Reviewing files that changed from the base of the PR and between 14f815f and 084599b.

📒 Files selected for processing (9)
  • src/cli/_utils.ts
  • src/cli/fetch.ts
  • src/cli/main.ts
  • src/cli/serve.ts
  • src/loader.ts
  • test/_cli-run.ts
  • test/cli.test.ts
  • test/fixtures/cli/data.txt
  • test/fixtures/cli/server.ts

Comment thread src/cli/_utils.ts
Comment on lines 4 to +13
const noColor = /* @__PURE__ */ (() => {
const env = globalThis.process?.env ?? {};
return env.NO_COLOR === "1" || env.TERM === "dumb";
const proc = globalThis.process;
const env = proc?.env ?? {};
if (env.FORCE_COLOR) {
return false;
}
if (env.NO_COLOR || env.TERM === "dumb") {
return true;
}
return !proc?.stdout?.isTTY;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/cli/_utils.ts =="
nl -ba src/cli/_utils.ts | sed -n '1,80p'

echo
echo "== tests mentioning FORCE_COLOR / NO_COLOR / isTTY =="
rg -n "FORCE_COLOR|NO_COLOR|isTTY|noColor" src test tests . || true

Repository: h3js/srvx

Length of output: 211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== src/cli/_utils.ts =="
cat -n src/cli/_utils.ts | sed -n '1,120p'

echo
echo "== matching tests / references =="
rg -n "FORCE_COLOR|NO_COLOR|isTTY|noColor" src test tests . || true

Repository: h3js/srvx

Length of output: 3373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== test/cli.test.ts =="
cat -n test/cli.test.ts | sed -n '1,220p'

Repository: h3js/srvx

Length of output: 8162


🌐 Web query:

supports-color FORCE_COLOR 0 disable colors documentation

💡 Result:

To disable color output in command-line applications that utilize the supports-color library or follow its conventions, you can set the environment variable FORCE_COLOR to 0 [1][2][3]. When FORCE_COLOR=0 is set, it explicitly overrides other terminal color support detection mechanisms and forces the application to disable ANSI color output [1][2][4][5]. In addition to this, the NO_COLOR standard is widely supported across the industry as a way to disable color output [6][7]. According to the NO_COLOR specification, any non-empty value for the NO_COLOR environment variable (e.g., NO_COLOR=1) should signal applications to refrain from adding ANSI color to their output [6][8][5]. While both are used to disable colors, FORCE_COLOR=0 is specific to libraries and tools that implement the supports-color convention, whereas NO_COLOR is a broader, cross-platform standard intended for any CLI software [9][10][6]. If both are present, the specific application's implementation determines priority, though FORCE_COLOR is often treated as having higher precedence in libraries that support both [9][10].

Citations:


Handle FORCE_COLOR=0 explicitly. env.FORCE_COLOR is truthy for "0", so this branch enables colors when the convention expects them to be disabled. Parse the env value instead of checking truthiness.

🤖 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 `@src/cli/_utils.ts` around lines 4 - 13, Update the noColor initializer to
parse FORCE_COLOR rather than checking its truthiness, ensuring the explicit
value "0" disables colors while preserving the existing handling for other
FORCE_COLOR values and NO_COLOR/TERM conditions.

Comment thread src/cli/serve.ts
Comment on lines +35 to +48
// F43: an explicit `--static` pointing at a missing dir must error; the
// implicit `public` default may stay silent.
const explicitStatic = !!cliOpts.static;
const staticDir = resolve(
cliOpts.dir || (loaded.url ? dirname(fileURLToPath(loaded.url)) : "."),
cliOpts.static || "public",
);
cliOpts.static = existsSync(staticDir) ? staticDir : "";
if (existsSync(staticDir)) {
cliOpts.static = staticDir;
} else if (explicitStatic) {
throw new Error(`--static directory not found: ${staticDir}`);
} else {
cliOpts.static = "";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

--static existence check doesn't verify it's a directory.

existsSync(staticDir) passes for files too. An explicit --static ./some-file would silently set cliOpts.static to a file path and only fail later inside serveStatic on each request, instead of failing fast with a clear message like the sibling "not found" branch. parseArgs() in src/cli/main.ts already uses statSync(...).isDirectory() for the analogous entry/dir check — worth mirroring here.

🐛 Suggested fix
-    if (existsSync(staticDir)) {
+    if (existsSync(staticDir) && statSync(staticDir).isDirectory()) {
       cliOpts.static = staticDir;
     } else if (explicitStatic) {
       throw new Error(`--static directory not found: ${staticDir}`);
📝 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
// F43: an explicit `--static` pointing at a missing dir must error; the
// implicit `public` default may stay silent.
const explicitStatic = !!cliOpts.static;
const staticDir = resolve(
cliOpts.dir || (loaded.url ? dirname(fileURLToPath(loaded.url)) : "."),
cliOpts.static || "public",
);
cliOpts.static = existsSync(staticDir) ? staticDir : "";
if (existsSync(staticDir)) {
cliOpts.static = staticDir;
} else if (explicitStatic) {
throw new Error(`--static directory not found: ${staticDir}`);
} else {
cliOpts.static = "";
}
// F43: an explicit `--static` pointing at a missing dir must error; the
// implicit `public` default may stay silent.
const explicitStatic = !!cliOpts.static;
const staticDir = resolve(
cliOpts.dir || (loaded.url ? dirname(fileURLToPath(loaded.url)) : "."),
cliOpts.static || "public",
);
if (existsSync(staticDir) && statSync(staticDir).isDirectory()) {
cliOpts.static = staticDir;
} else if (explicitStatic) {
throw new Error(`--static directory not found: ${staticDir}`);
} else {
cliOpts.static = "";
}
🤖 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 `@src/cli/serve.ts` around lines 35 - 48, Update the static path validation in
the serve flow to require that staticDir exists and is a directory, using the
same statSync(...).isDirectory() pattern as parseArgs(). Preserve the implicit
public fallback behavior, while explicit --static paths that are missing or
files must throw the existing clear directory-not-found error.

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.

2 participants