Skip to content

json build output and usage docs#18

Merged
alexphelps merged 2 commits into
mainfrom
json-build-output
Jun 11, 2026
Merged

json build output and usage docs#18
alexphelps merged 2 commits into
mainfrom
json-build-output

Conversation

@alexphelps

Copy link
Copy Markdown
Member

No description provided.

@alexphelps alexphelps linked an issue Jun 11, 2026 that may be closed by this pull request
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread lib/engine/build.js Outdated
built++;
} catch (e) {
page.status = 'error';
page.errors.push({ code: 'RENDER_ERROR', message: e.message });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: RENDER_ERROR is used as the catch-all for every failure in the page loop, but the block also catches readFileSync (line 82), gray-matter parse errors (line 83), and mkdirSync/writeFileSync (lines 121-122). A file that fails to read or a malformed frontmatter that throws inside matter() will be reported as a RENDER_ERROR, which is misleading for callers scripting around the JSON summary. Consider using distinct codes (READ_ERROR, FRONTMATTER_ERROR, WRITE_ERROR) or at least capturing the actual exception name in the message.

Comment thread lib/actions/build.js
const summary = await build();

if (json) {
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: process.stdout.write(...) is asynchronous, but the very next conditional can call process.exit(1) (line 27) if any page errored. On a non-POSIX stdout (or any stdout backed by a pipe that hasn't been drained yet) the buffered JSON can be discarded before it reaches the consumer. The new test campaign-build --json: exits 1 and still emits JSON when a page errors only passes because execFile drains the pipe after the child closes; a CI script that reads stdout incrementally could see an empty/partial document. Pass a callback to write and exit inside it, or write via fs.writeSync(1, ...).

Comment thread lib/engine/build.js

if (!campaign) {
page.status = 'skipped';
page.warnings.push({ code: 'NO_CAMPAIGN', message: `no campaign found for slug "${campaignSlug}"` });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: The NO_CAMPAIGN branch pushes the warning entry directly and then calls logger.warn(...) with a different message text ("Skipping X — no campaign found for slug Y" vs "X: no campaign found for slug Y"). Every other warning in this function goes through the pageWarn helper defined just above (line 76), which keeps the JSON message and the stderr message in lockstep. Using pageWarn('NO_CAMPAIGN', ...) here too would prevent the two formats from drifting if the message ever changes.

Comment thread lib/engine/build.js

const { url, outputFile } = resolveOutput(relFile, frontmatter, outputPath);
page.url = url;
page.outputFile = path.relative(process.cwd(), outputFile);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: page.outputFile is documented (README, line ~210) as "relative to the project root", but it's computed via path.relative(process.cwd(), outputFile). If a caller invokes build() from any directory other than the project root (CI, programmatic embedding, a future watcher running from node_modules), the returned path will be wrong — possibly escaping the project root entirely with .. segments. Same problem on page.inputFile (line 66) and the debug log on line 124. Anchor to projectConfig.getProjectRoot() (or pass an explicit cwd option) for stable, documented paths.

Comment thread lib/logger.js
const debug = (msg) => console.log(`${TAG} \x1b[90mDEBUG\x1b[0m ${msg}`);
// Diagnostics go to stderr (like warn/error) so stdout stays reserved for
// primary output — required for `campaign-build --json | jq` to work.
const debug = (msg) => console.error(`${TAG} \x1b[90mDEBUG\x1b[0m ${msg}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Routing debug through console.error is the right call for --json, but this is a silent breaking change for any caller that previously captured debug output from stdout (e.g. node ... 2>/dev/null | grep ... or a wrapper that tees stdout). Worth a one-line note in the changelog / commit message, or — if you want to be conservative — gate it: keep console.log when stdout is a TTY or pipe, and only switch to console.error when --json is active. At minimum, the commit message should call out the behavior change explicitly.

@kilo-code-bot

kilo-code-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2

Incremental Changes Since Last Review

The new commits (18e1d2c docs improvements, cbfaf37 json build output) introduce two meaningful changes:

  1. lib/engine/build.js: Adds a stage tracker so failures are now reported as READ_ERROR, FRONTMATTER_ERROR, RESOLVE_ERROR, RENDER_ERROR, or WRITE_ERROR instead of the old catch-all RENDER_ERROR. This is a partial fix for the previous catch-all code concern, but is a breaking change to the JSON summary's public error-code contract.
  2. lib/actions/build.js: Switches from process.exit(1) to process.exitCode = 1. This is a partial fix for the previous async-write race concern, but the underlying process.stdout.write(...) is still fire-and-forget without a callback.

WARNING (new)

File Line Issue
lib/actions/build.js 16 process.stdout.write(...) is still fire-and-forget; pipe errors (EPIPE/ENOSPC) are silently swallowed and the process still exits 0.

SUGGESTION (new)

File Line Issue
test/build.test.js 657 Test name "error code names the failed step" overpromises — only FRONTMATTER is covered; READ/RESOLVE/WRITE have no regression coverage.
lib/engine/build.js 137 ${stage}_ERROR is a breaking change to the public JSON summary's code field. Needs a CHANGELOG/README note documenting the full list.
Issue Details (click to expand)

WARNING

File Line Issue
lib/actions/build.js 16 process.stdout.write(...) is fire-and-forget without a callback. Setting process.exitCode = 1 (line 29) lets the event loop drain, but a write error (EPIPE on closed pipe, ENOSPC on full disk) is silently swallowed and the process exits 0 because the summary.errors > 0 branch never runs.
lib/engine/build.js 137 ${stage}_ERROR replaces the public RENDER_ERROR catch-all across 4 of 5 stages. Any script consuming campaign-build --json and branching on errors[].code will silently misclassify. The old test at test/build.test.js:650 still asserts the literal RENDER_ERROR, masking this for render-stage failures. (Carried forward from previous review; partially addressed.)
lib/engine/build.js 106 page.outputFile / page.inputFile are documented as "relative to the project root" but computed via path.relative(process.cwd(), ...). Callers not running from the project root (CI, programmatic embedding) get wrong or ..-escaped paths. (Carried forward — unchanged.)

SUGGESTION

File Line Issue
test/build.test.js 657 The new test name promises coverage of the full stage taxonomy, but only one stage (FRONTMATTER) is exercised. A future refactor that drops a stage = '...' assignment would silently flatten errors back to the last-set stage, and this test wouldn't catch it. Either rename or add cases for READ/RESOLVE/WRITE.
lib/engine/build.js 137 Breaking change to the JSON summary's error code contract. Should be documented in CHANGELOG and the README's --json section should list the full set of codes.
lib/engine/build.js 97 The NO_CAMPAIGN branch still bypasses the pageWarn helper and uses a different message format than every other warning. (Carried forward — unchanged.)
lib/logger.js 9 Routing debug from stdout to stderr is a silent breaking change for any caller that previously captured debug output from stdout. (Carried forward — unchanged.)
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
package.json 28-30 The scripts block only defines test. README documents npm run build and npm run dev, which currently have no npm-script entry. (Carried forward — pre-existing.)
test/build.test.js ~825 New campaign-build --json integration tests assert outputFile === path.join('_site', ...), which produces backslashes on Windows. The build emits path.relative(...) (forward slashes on POSIX, backslashes on Windows). (Carried forward — pre-existing.)
Files Reviewed (4 files)
  • README.md - 0 new issues (cosmetic rename only)
  • lib/actions/build.js - 1 new issue (stdout.write still no callback)
  • lib/engine/build.js - 2 new issues (breaking change to error codes; stage test coverage gap)
  • test/build.test.js - 1 new issue (test name overpromises)

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · 208,567 tokens

Comment thread lib/actions/build.js
const summary = await build();

if (json) {
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: process.stdout.write(...) is still fire-and-forget without a callback. The previous review flagged that the write races process.exit(1); the new process.exitCode = 1 (line 29) lets the event loop drain, but if the write throws (EPIPE on a closed pipe, ENOSPC on a full disk) the error is silently swallowed and the process still exits 0 via the if (summary.errors > 0) branch not running. Pass a callback and bubble the error to exitCode:

Suggested change
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
process.stdout.write(JSON.stringify(summary, null, 2) + '\n', (err) => { if (err) process.exitCode = 1; });

Comment thread test/build.test.js
});
});

test('build: error code names the failed step — malformed frontmatter is FRONTMATTER_ERROR', async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: The test name promises "error code names the failed step", but only one step (FRONTMATTER) is exercised. The new stage taxonomy in lib/engine/build.js:81-137 introduces five codes — READ_ERROR, FRONTMATTER_ERROR, RESOLVE_ERROR, RENDER_ERROR, WRITE_ERROR — and there's no regression coverage for the other four. A future refactor that drops a stage = '...' assignment (e.g., removes the stage = 'WRITE' before the mkdirSync/writeFileSync) would silently flatten all errors back to whatever stage was last set, and this test wouldn't catch it. Either rename the test to be specific to frontmatter, or add parallel cases for the other four stages.

Comment thread lib/engine/build.js
built++;
} catch (e) {
page.status = 'error';
page.errors.push({ code: `${stage}_ERROR`, message: e.message });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: The new ${stage}_ERROR codes change the public surface of the JSON summary (consumers may have been filtering on code === 'RENDER_ERROR'). This is a breaking change for any existing script consuming campaign-build --json. Worth a CHANGELOG entry or a note in the README's --json section that documents the full code list (READ_ERROR, FRONTMATTER_ERROR, RESOLVE_ERROR, RENDER_ERROR, WRITE_ERROR). The previous test at test/build.test.js:650 still asserts the literal RENDER_ERROR, which masks this for render-stage failures, but any code that special-cased the old catch-all across all stages will silently misclassify.

@alexphelps alexphelps merged commit b339baa into main Jun 11, 2026
6 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.

Feature: expose Page Kit build route summary and shape warnings

1 participant