Skip to content

feat(serve): add --stdio flag for NUL-delimited stdin/stdout protocol#24

Merged
zeroedin merged 4 commits into
mainfrom
feat/serve-stdio
Apr 17, 2026
Merged

feat(serve): add --stdio flag for NUL-delimited stdin/stdout protocol#24
zeroedin merged 4 commits into
mainfrom
feat/serve-stdio

Conversation

@zeroedin

Copy link
Copy Markdown
Owner

Summary

  • Adds --stdio flag to golit serve that replaces HTTP with a NUL-delimited stdin/stdout protocol
  • Same warm engine pool and rendering pipeline — only the transport layer changes
  • Mutually exclusive with --listen; defaults to 1 engine worker for sequential stdio requests
  • Clean shutdown on stdin EOF or SIGTERM (no /health endpoint needed — pipe liveness is implicit)

Protocol

stdin:  <html>...</html>\0
stdout: <rendered>...</rendered>\0

Usage

# Start stdio server
golit serve --defs bundles/ --stdio

# Example: pipe a request
printf '<my-greeting name="World"></my-greeting>\0' | golit serve --defs bundles/ --stdio

Relates to #22 — stdio mode closes the gap with lit-ssr-wasm's NUL-delimited protocol while keeping HTTP as the default.

Test plan

  • TestRunStdio_SingleRequest — basic component rendering via stdio
  • TestRunStdio_MultipleRequests — process stays alive across requests
  • TestRunStdio_EmptyInput — bare NUL returns bare NUL
  • TestRunStdio_EOF — clean shutdown on stdin close
  • TestRunStdio_PassthroughHTML — plain HTML passes through unchanged
  • TestRunStdio_IgnoredTags--ignore works in stdio mode
  • TestRunStdio_LargePayload — 100 elements in a single request
  • TestRunStdio_FullDocument — full HTML document rendering
  • TestRunStdio_BrokenStdout — returns error on broken pipe
  • TestRunStdio_PoolReuse — warm pool produces consistent output
  • TestE2E_ServeStdio — full binary integration test with multi-request session
  • TestE2E_ServeStdioMutualExclusion--stdio + --listen rejected

🤖 Generated with Claude Code

Copilot AI 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.

Pull request overview

Adds a new --stdio transport mode to golit serve, enabling a long-running NUL-delimited stdin/stdout request/response protocol while reusing the existing warm engine pool and rendering pipeline.

Changes:

  • Add --stdio flag to golit serve, with --listen mutual exclusion and stdio-default concurrency behavior.
  • Implement stdio request loop (runStdio) using NUL (\0) as the frame delimiter.
  • Add unit + E2E tests covering single/multi request sessions, EOF shutdown, ignore list behavior, and mutual exclusion.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.

File Description
cmd/golit/stdio.go Implements NUL-delimited stdin/stdout serving loop for warm SSR.
cmd/golit/stdio_test.go Adds unit/E2E coverage for stdio protocol behavior and lifecycle.
cmd/golit/serve.go Adds --stdio flag parsing, mutual exclusion with --listen, and stdio execution path.
cmd/golit/main.go Updates CLI usage/help text to document --stdio serve mode.
Comments suppressed due to low confidence (1)

cmd/golit/serve.go:84

  • The --concurrency/-j parsing increments i inside the case and again at the end of the case, while the for loop also increments i. This causes arguments to be skipped (e.g., -j 2 --stdio will likely skip --stdio). Consider refactoring to the i += 2 / i++ style used elsewhere (or switching this loop to a for i < len(args) form) so i advances exactly once per consumed token.
		case "--concurrency", "-j":
			if i+1 < len(args) {
				if n, err := strconv.Atoi(args[i+1]); err == nil {
					if n < 1 {
						return fmt.Errorf("--concurrency value must be a positive integer")
					}
					concurrency = n
					i++
				}
			}
			if concurrency == 0 {
				concurrency = runtime.NumCPU()
			}
			i++
		default:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/golit/stdio.go
Comment thread cmd/golit/stdio.go
Comment thread cmd/golit/stdio.go Outdated
Comment thread cmd/golit/stdio_test.go Outdated
Comment thread cmd/golit/stdio_test.go
Comment thread cmd/golit/stdio_test.go
Comment thread cmd/golit/stdio_test.go
Comment thread cmd/golit/stdio_test.go
zeroedin and others added 3 commits April 17, 2026 11:49
Adds an alternative transport mode to `golit serve` that uses
NUL-delimited stdin/stdout instead of HTTP. The warm engine pool
is shared — only the I/O layer changes.

  golit serve --defs bundles/ --stdio

Protocol: write HTML terminated by \0 to stdin, read rendered
HTML terminated by \0 from stdout. Process stays alive across
requests. Closes cleanly on stdin EOF or SIGTERM.

Mutually exclusive with --listen. Defaults to 1 engine worker
(sequential requests) but can be overridden with --concurrency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same fix as PR #23 — buildGolit must produce a .exe on Windows
or exec.Command cannot find the binary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
projectRoot, buildGolit, and buildTestBundles are now provided by
render_test.go (merged via PR #23). Remove the copies from
stdio_test.go to fix go vet redeclaration errors.

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

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/golit/serve.go
…arsing

- Handle render and flush errors on the EOF path in stdio mode instead
  of silently swallowing them
- Remove duplicate cmd.Wait() from E2E test defer (goroutine already
  calls Wait)
- Remove extra i++ in --concurrency/-j case that caused the next flag
  to be skipped

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@zeroedin
zeroedin merged commit 1b16c27 into main Apr 17, 2026
3 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.

2 participants