Skip to content

fix(cli): improve multi-platform command execution (Windows scaffold fix) - #4

Merged
CarlosZiegler merged 3 commits into
mainfrom
fix/cli-windows-multiplatform-exec
Feb 23, 2026
Merged

fix(cli): improve multi-platform command execution (Windows scaffold fix)#4
CarlosZiegler merged 3 commits into
mainfrom
fix/cli-windows-multiplatform-exec

Conversation

@CarlosZiegler

@CarlosZiegler CarlosZiegler commented Feb 23, 2026

Copy link
Copy Markdown
Owner

Summary

This PR fixes Windows scaffold/setup failures by removing shell-dependent command execution in the CLI and switching to explicit argv + cwd process execution.

What changed

  • Refactored exec helper to use Bun.spawn(commandArgs, { cwd }) instead of sh -c
  • Converted shell-string commands to argv arrays in CLI phases:
    • scaffold: git init, bun install
    • database: bunx get-db --yes --env .env --key DATABASE_URL, bun --env-file=.env drizzle-kit push --force
    • infra: docker compose up -d <service>
  • Replaced shell file deletion (rm -f) with Node API (rmSync(..., { force: true }))
  • Switched target directory composition to path.resolve(...)
  • Added unit tests for command helper behavior:
    • executes commands successfully
    • respects cwd
    • returns non-zero exit codes without throwing

Why

The previous implementation relied on POSIX shell behavior (sh -c, cd && ..., rm -f) which is brittle on Windows and caused create-start-kit-dev to fail during project creation/install.

Validation

  • bun test packages/cli/src
  • cd packages/cli && bun run build

Impact

  • Better cross-platform reliability for CLI project creation and setup
  • No behavior change intended for successful Linux/macOS paths beyond execution robustness

Summary by CodeRabbit

  • Improvements

    • More reliable CLI command execution with structured error results and consistent exit codes.
    • Support for running commands in custom working directories and improved path handling.
    • Safer, more robust scaffolding and service start flows with clearer user messages on failure.
  • Tests

    • Added tests validating command execution behavior, working-dir handling, and non-zero exit code handling.

@vercel

vercel Bot commented Feb 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
start-template Ready Ready Preview, Comment Feb 23, 2026 8:48am

Request Review

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@CarlosZiegler has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between c6f8096 and feae71c.

📒 Files selected for processing (1)
  • packages/cli/src/lib/helpers.ts
📝 Walkthrough

Walkthrough

The exec helper was refactored to accept a command array plus optional cwd, with input validation and try/catch error handling; call sites in CLI phases were updated to invoke exec with argv-style arrays and explicit cwd where needed. A new test suite verifies stdout capture, cwd behavior, and non-zero exit codes.

Changes

Cohort / File(s) Summary
Exec Helper & Tests
packages/cli/src/lib/helpers.ts, packages/cli/src/lib/__tests__/helpers.exec.test.ts
Changed exec signature to exec(command: string[], options?: { cwd?: string }). Added empty-command validation, try/catch that maps spawn errors to exitCode 127, and direct spawning with argv arrays. Added tests for stdout capture, cwd respect, and non-zero exit code handling.
CLI Phase Invocations
packages/cli/src/phases/database.ts, packages/cli/src/phases/infra.ts, packages/cli/src/phases/scaffold.ts
Replaced shell-string exec calls with argv arrays; added { cwd } where appropriate. Scaffold: added rmSync and path.resolve usage, replaced shell file removal and string-based cwd usage with path utilities and structured exec calls. Database/Infra: commands converted to array form (bunx, drizzle-kit, docker compose).
Path handling updates
packages/cli/src/lib/state.ts, packages/cli/src/theme/theme-apply.ts
Switched string interpolation to path.join/path.resolve for platform-aware path construction when computing state and writing theme files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop through arrays of commands with cheer,
No shells to hide, just args front and clear,
I tidy paths, resolve each lane,
Catch stray errors, return their name,
A tiny rabbit test and dance of cheer!

🚥 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 title accurately describes the main change: refactoring the exec helper and command calls to use direct process execution instead of shell invocation for Windows compatibility.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cli-windows-multiplatform-exec

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.

@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

🧹 Nitpick comments (1)
packages/cli/src/lib/__tests__/helpers.exec.test.ts (1)

16-42: Consider adding coverage for the two new guard/catch paths in exec.

The new implementation added two code paths that the existing three tests don't exercise:

  1. Empty-command guard (returns exitCode: 1, stderr: "No command provided").
  2. catch block when Bun.spawn throws (returns exitCode: 127).
♻️ Suggested additional test cases
+  it("returns exitCode 1 with a descriptive error for an empty command", async () => {
+    const result = await exec([]);
+
+    expect(result.exitCode).toBe(1);
+    expect(result.stderr).toBe("No command provided");
+  });
+
+  it("returns exitCode 127 when the executable does not exist", async () => {
+    const result = await exec(["__nonexistent_binary_xyz__"]);
+
+    expect(result.exitCode).toBe(127);
+    expect(result.stderr.length).toBeGreaterThan(0);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/lib/__tests__/helpers.exec.test.ts` around lines 16 - 42,
Add two tests for exec to cover the new guard/catch paths: one test should call
exec with an empty command (e.g., [] or []) and assert it returns exitCode: 1
and stderr contains "No command provided"; another should simulate Bun.spawn
throwing (mock/spy Bun.spawn to throw) and assert exec returns exitCode: 127
(and appropriate stderr) rather than throwing. Locate the exec helper referenced
in the tests and add these assertions in
packages/cli/src/lib/__tests__/helpers.exec.test.ts alongside the existing
"exec" tests so both guard and catch branches are exercised.
🤖 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/lib/__tests__/helpers.exec.test.ts`:
- Around line 24-35: The cwd assertion can be flaky on macOS due to /tmp being a
symlink; update the test in the "respects cwd" case to compare canonical paths
by resolving the temp dir before asserting: call fs.realpathSync(cwd) (or
path.resolve + fs.realpathSync) and compare that resolved path to result.stdout
(trimmed if needed) instead of comparing to the original cwd; modify the test
that calls exec(...) and uses expect(result.stdout).toBe(cwd) to use the
resolved path from fs.realpathSync(cwd).

In `@packages/cli/src/lib/helpers.ts`:
- Around line 97-99: The current sequential reads await new
Response(proc.stdout).text() then await new Response(proc.stderr).text(), which
can deadlock if the child fills the OS pipe buffer; change to drain both streams
concurrently by starting both text() reads at once (e.g., Promise.all for the
two Response(...).text() promises) and await proc.exited separately or together
as appropriate so stdout and stderr are read in parallel; update references
around proc, proc.stdout, proc.stderr, stdout, stderr, and proc.exited in the
helpers.ts block to use Promise.all to avoid the pipe-fill deadlock.

---

Nitpick comments:
In `@packages/cli/src/lib/__tests__/helpers.exec.test.ts`:
- Around line 16-42: Add two tests for exec to cover the new guard/catch paths:
one test should call exec with an empty command (e.g., [] or []) and assert it
returns exitCode: 1 and stderr contains "No command provided"; another should
simulate Bun.spawn throwing (mock/spy Bun.spawn to throw) and assert exec
returns exitCode: 127 (and appropriate stderr) rather than throwing. Locate the
exec helper referenced in the tests and add these assertions in
packages/cli/src/lib/__tests__/helpers.exec.test.ts alongside the existing
"exec" tests so both guard and catch branches are exercised.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da291e4 and ec4af1d.

📒 Files selected for processing (5)
  • packages/cli/src/lib/__tests__/helpers.exec.test.ts
  • packages/cli/src/lib/helpers.ts
  • packages/cli/src/phases/database.ts
  • packages/cli/src/phases/infra.ts
  • packages/cli/src/phases/scaffold.ts

Comment thread packages/cli/src/lib/__tests__/helpers.exec.test.ts
Comment thread packages/cli/src/lib/helpers.ts Outdated
Replace string-concatenated paths with path.join in state.ts and
theme-apply.ts for Windows compatibility. Fix exec cwd test to resolve
symlinks (macOS /var -> /private/var).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

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

🧹 Nitpick comments (1)
packages/cli/src/theme/theme-apply.ts (1)

81-91: Spinner left dangling if writeFileSync throws.

If the src subdirectory doesn't exist under targetDir (or a permissions error occurs), writeFileSync throws before s.stop() is reached, leaving the terminal spinner running indefinitely.

♻️ Proposed fix: wrap the write in try/finally
 export function applyTheme(targetDir: string, config: ThemeConfig): void {
   const s = spinner();
   s.start("Applying theme...");

-  const css = generateFullAppCss(config);
-  writeFileSync(join(targetDir, "src", "app.css"), css, "utf-8");
-
-  s.stop(
-    `Theme applied: ${config.theme} (base: ${config.baseColor}, radius: ${config.radius}, font: ${config.font})`
-  );
+  try {
+    const css = generateFullAppCss(config);
+    writeFileSync(join(targetDir, "src", "app.css"), css, "utf-8");
+    s.stop(
+      `Theme applied: ${config.theme} (base: ${config.baseColor}, radius: ${config.radius}, font: ${config.font})`
+    );
+  } catch (err) {
+    s.stop("Failed to apply theme");
+    throw err;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/theme/theme-apply.ts` around lines 81 - 91, The spinner
started in applyTheme (variable s) can be left running if writeFileSync throws;
wrap the file write (generateFullAppCss(...) and writeFileSync(...)) in a
try/finally so s.stop() (or s.fail(...) on error) is always called;
specifically, move the css generation/write into a try block and place
s.stop(...) in the finally block (optionally call s.fail with the caught error
before rethrowing) to ensure the spinner is cleaned up even on writeFileSync or
permission errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/cli/src/theme/theme-apply.ts`:
- Around line 81-91: The spinner started in applyTheme (variable s) can be left
running if writeFileSync throws; wrap the file write (generateFullAppCss(...)
and writeFileSync(...)) in a try/finally so s.stop() (or s.fail(...) on error)
is always called; specifically, move the css generation/write into a try block
and place s.stop(...) in the finally block (optionally call s.fail with the
caught error before rethrowing) to ensure the spinner is cleaned up even on
writeFileSync or permission errors.
ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec4af1d and c6f8096.

📒 Files selected for processing (3)
  • packages/cli/src/lib/__tests__/helpers.exec.test.ts
  • packages/cli/src/lib/state.ts
  • packages/cli/src/theme/theme-apply.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/cli/src/lib/tests/helpers.exec.test.ts

Sequential reads can deadlock when child process fills stderr pipe
buffer (~64KB) before stdout is consumed. Use Promise.all to drain
both streams in parallel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@CarlosZiegler
CarlosZiegler merged commit 14f9128 into main Feb 23, 2026
4 checks passed
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