Skip to content

Fix page-kit fresh repo build friction#22

Closed
next-devin wants to merge 7 commits into
mainfrom
devex-pagekit-friction-batch-1
Closed

Fix page-kit fresh repo build friction#22
next-devin wants to merge 7 commits into
mainfrom
devex-pagekit-friction-batch-1

Conversation

@next-devin

@next-devin next-devin commented Jun 22, 2026

Copy link
Copy Markdown

Purpose

This PR focuses on small, global friction points in the public page-kit build CLI: commands should be discoverable in a fresh repo, and machine-readable build output should be safe to pipe without filtering log noise.

Summary

  • document the public package name and build commands as next-campaign-page-kit
  • make campaign-build --help exit before loading project data, so help works in an empty or partially initialized repo
  • keep campaign-build --json stdout as a single JSON document by making debug diagnostics opt-in with --verbose

Deliberately out of scope

  • no parser/dependency changes; gray-matter stays in place
  • no changes or documented guarantees around cleaning or preserving _site/
  • no Campaigns OS or internal workflow assumptions

Validation

  • npm test — 249/249 passing
  • git diff --check — clean
  • focused CLI fixtures cover --help, --json, and --json --verbose

Note: npm audit --audit-level=moderate still reports the existing gray-matter -> js-yaml moderate advisory. I left that unchanged because replacing the standard frontmatter parser is not part of this focused PR.

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@next-devin next-devin marked this pull request as ready for review June 22, 2026 15:51
Comment thread lib/engine/build.js Outdated
function cleanOutputPath(outputPath) {
const resolved = path.resolve(outputPath);
const root = path.resolve(process.cwd());
const volumeRoot = path.parse(resolved).root;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: cleanOutputPath safety guard is bypassable.

The check only refuses paths that resolve to cwd or the volume root. It does not prevent:

  • Relative escapes: path.resolve(cwd, '..') returns the parent directory, which is neither cwd nor /, so fs.rmSync(..., { recursive: true, force: true }) will recursively delete it.
  • Arbitrary absolute paths: a programmatic caller passing outputPath: '/home/user/important' (or /tmp/somewhere) passes both checks and is recursively deleted.

Today the CLI never passes an outputPath, but build({ clean: true, outputPath }) is part of the public engine API (see export at line 229), so any plugin or future caller can trigger this. Recommend requiring outputPath to be a child of cwd, e.g. if (!resolved.startsWith(root + path.sep)) throw ....

Comment thread lib/frontmatter.js Outdated
return { data: {}, content: text };
}

const closingIndex = lines.findIndex((line, index) => index > 0 && line.trim() === '---');

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: Silently swallows unclosed frontmatter.

When a page starts with --- but has no closing ---, this returns { data: {}, content: text } with no warning. The previous gray-matter parser threw an error here, which surfaced as a per-page FRONTMATTER_ERROR in the build summary (and stderr). Pages with a stray opening fence now render with empty frontmatter (missing title/page_type/etc.) and only get caught downstream as MISSING_FRONTMATTER warnings, which are non-fatal — broken pages ship to _site/. Consider throwing here, or at minimum logging a FRONTMATTER_ERROR via logger.warn.

Comment thread lib/frontmatter.js Outdated

return {
data,
content: lines.slice(closingIndex + 1).join('\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: Subtle behavior change vs. gray-matter — leading newline after the closing --- is dropped.

gray-matter returns content as the substring after --- with its leading \n preserved. This implementation uses lines.slice(closingIndex + 1) which throws away the closing-fence line and the newline that followed it, so a file like ---\ntitle: x\n---\n<p>body</p> now produces content: '<p>body</p>' instead of '\n<p>body</p>'. Most Liquid templates won't care, but templates that rely on a leading blank line (e.g. ones wrapped in {% raw %}\n...) will render differently. Easy fix: lines.slice(closingIndex).join('\n').replace(/^---\n/, '---\n') or similar to preserve the boundary.

Comment thread lib/logger.js Outdated
const info = (msg) => console.log(`${TAG} ${BRAND}INFO\x1b[0m ${msg}`);
const warn = (msg) => console.warn(`${TAG} \x1b[33mWARN\x1b[0m ${msg}`);
const error = (msg) => console.error(`${TAG} \x1b[31mERROR\x1b[0m ${msg}`);
let debugEnabled = true;

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: Default debugEnabled = true contradicts the PR's "quiet by default" intent.

This module exports debug for use by other commands (dev.js, future CLIs) and tests. They never call setDebugEnabled(false), so they keep the noisy DEBUG-on-stderr behavior the PR is trying to remove from campaign-build. The behavior is only correct here because lib/actions/build.js:37 happens to flip it before any debug call. Recommend defaulting to false and explicitly opting in to debug at any call site that needs it.

Comment thread lib/actions/build.js Outdated
const clean = !args.includes('--no-clean');
const verbose = args.includes('--verbose');

logger.setDebugEnabled(verbose);

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: setDebugEnabled mutates module-scoped state shared by every consumer of lib/logger.js.

If anything in the same process — a plugin, an embedded build() call, a test, or a future dev/hot-reload command — loads the logger after campaign-build runs, it inherits whatever verbosity the last command picked. Worse, since the module default is true, the leak is one-directional (cannot accidentally silence a future caller that wanted debug). Consider scoping the flag to the build call (pass verbose into build(), gate logger.debug on a per-call flag) or instantiating a fresh logger inside lib/actions/build.js.

Comment thread lib/actions/build.js
const args = process.argv.slice(2);

if (args.includes('--help') || args.includes('-h')) {
process.stdout.write(HELP_TEXT);

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: HELP_TEXT has no trailing newline.

Most CLI tools (e.g. node --help, man) emit a final \n so the next shell prompt doesn't glue onto the last line of help. Append \n before the closing backtick.

@kilo-code-bot

kilo-code-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0

Incremental Review (8d336ff..7c0fb01)

The PR's actual diff against main (5 files, ~232 lines) is a focused incremental change: extracting HELP_TEXT, adding --help/-h short-circuit before project data load, adding --verbose plumbing through build(), and gating the one per-page logger.debug call on that flag. The previously-merged --no-clean / cleanOutputPath cleanup is intact in the base branch.

New findings in this PR's diff:

File Line Issue
lib/logger.js 10 debug() defaults enabled to true; only the build pipeline's single debug call is gated on --verbose. clone.js and compress.js debug calls still print unconditionally and have no --verbose flag, contradicting the README's "quiet by default" promise.
README.md 253 "Debug lines are quiet by default and are printed to stderr only when --verbose is passed" overpromises: it holds for campaign-build's one debug line, but other binaries' debug output is unconditional.

Verified resolved in this PR's diff (no action needed):

  • lib/actions/build.js HELP_TEXT now ends with a trailing newline (was a previous SUGGESTION).
  • --help exits before loading any project data; covered by new test campaign-build --help: exits before loading project data.
  • --json --verbose keeps JSON on stdout and DEBUG on stderr; covered by new test campaign-build --json --verbose: keeps JSON on stdout and debug on stderr.
  • logger.debug retains backward-compatible single-arg call shape so clone.js:41 and compress.js:219 still compile and print as before (covered by logger.debug: emits by default for existing call sites).

Not in this PR's diff (carried forward — moot for merge)

The previous review summary listed carried-forward issues on lib/frontmatter.js, lib/logger.js debugEnabled default, and lib/actions/build.js setDebugEnabled. Inspection of the PR's actual base (main @ eed0679) shows:

  • lib/frontmatter.js no longer exists — the custom parser was swapped for gray-matter in main before this PR opened. The four carried-forward frontmatter.js findings (silent unclosed fence, leading-newline drop, incomplete null-object check, "why replace gray-matter" question) are therefore resolved at the codebase level, not by anything in this PR's diff. No further action needed from this PR.
  • lib/logger.js still defaults enabled: true — see the warning above. The PR partially mitigates this for campaign-build only.
  • lib/actions/build.js setDebugEnabled is unchanged; not touched by this PR.
Files Reviewed (5 files in PR diff)
  • README.md — 1 issue (overpromising "quiet by default" wording)
  • lib/actions/build.js — no issues (HELP_TEXT newline OK, --help/--verbose handling clean, no leftover references)
  • lib/engine/build.js — no issues (verbose plumbed correctly, JSDoc added)
  • lib/logger.js — 1 issue (enabled default contradicts README promise; per-call override works)
  • test/build.test.js — no issues (3 new tests cover new behavior; removed parseFrontmatter tests are appropriate since lib/frontmatter.js is gone in main)

Fix these issues in Kilo Cloud

Previous Review Summaries (5 snapshots, latest commit 8d336ff)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8d336ff)

Status: No New Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Incremental Review (68ca066..HEAD)

The two follow-up commits (e048799 Make build cleaning opt-in, 8d336ff Remove build clean option) fully resolve every previously flagged issue that lived on changed lines:

Previous finding Status
lib/engine/build.js:184pathsOverlap symlink bypass (CRITICAL) ResolvedpathsOverlap and cleanOutputPath removed entirely. fs.rmSync is no longer called on the output path.
lib/engine/build.js:184pathsOverlap case-insensitive FS bypass (CRITICAL) Resolved — function removed.
lib/engine/build.js:52 — guard only protects srcPath (WARNING) ResolvedcleanOutputPath no longer called from build(). Programmatic callers can no longer trigger recursive deletion through the build API.
lib/engine/build.jscleanOutputPath safety guard bypassable (CRITICAL, carried) Resolved — function deleted and dropped from module.exports.
lib/actions/build.js:24 — HELP_TEXT no trailing newline (SUGGESTION, carried) ResolvedHELP_TEXT now ends with a trailing blank line before the closing backtick.

New behavior matches the renamed tests: by default _site/ is preserved, no pre-build cleaning occurs, and --no-clean is gone.

Carried Forward (on unchanged files — not in this PR's diff)

These remain open from earlier reviews but live outside the current incremental diff; flagging them again here for visibility only:

File Issue
lib/frontmatter.js Silently swallows unclosed frontmatter (returns { data: {}, content: raw } rather than erroring).
lib/frontmatter.js Subtle behavior change vs. gray-matter — leading newline after the closing --- is dropped.
lib/logger.js Default debugEnabled = true contradicts the PR's "quiet by default" intent.
lib/actions/build.js setDebugEnabled mutates module-scoped state shared by every consumer of lib/logger.js.
Files Reviewed (4 files in incremental diff)
  • lib/engine/build.js — previous CRITICAL/WARNING issues resolved by removing cleanOutputPath/pathsOverlap and dropping opts.clean.
  • lib/actions/build.js--no-clean flag removed; HELP_TEXT now ends with trailing newline.
  • README.md — docs updated to match new "preserves existing files" default.
  • test/build.test.js — stale cleanOutputPath/--no-clean/"cleans stale output" tests removed; remaining test rewritten and passes.

Previous review (commit 68ca066)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 2
WARNING 1
SUGGESTION 0

Incremental Review (4bcd623..68ca066)

The latest commit adds a protectedPaths opt to cleanOutputPath and a new pathsOverlap helper, plus reorders the call so the guard runs before file discovery. The guard correctly rejects the cases the new tests cover (outputPath === srcPath and outputPath nested inside srcPath), but the lexical-only path comparison leaves two real-world bypasses that defeat the safety guarantee — both can recursively delete the source tree, which is the exact failure the guard was added to prevent.

CRITICAL

File Line Issue
lib/engine/build.js 184 pathsOverlap uses path.relative, which does not resolve symlinks. A symlink at outputPath pointing inside srcPath passes the guard and fs.rmSync follows it to delete the source tree.
lib/engine/build.js 184 pathsOverlap is case-sensitive, but macOS/Windows filesystems are case-insensitive by default. srcPath = /project/src vs outputPath = /project/SRC/_site compare as disjoint siblings while the OS treats them as the same directory — guard passes, source tree deleted.

WARNING

File Line Issue
lib/engine/build.js 52 The new guard only protects srcPath. Programmatic callers of the exported build() can still pass arbitrary outputPath values (siblings of any directory) and trigger recursive deletion. The original CRITICAL about arbitrary absolute paths is only partially mitigated.

Carried Forward (from previous reviews on unchanged files)

These remain on files outside the incremental diff (lib/frontmatter.js, lib/logger.js, lib/actions/build.js):

File Issue
lib/frontmatter.js Silently swallows unclosed frontmatter.
lib/frontmatter.js Subtle behavior change vs. gray-matter — leading newline after the closing --- is dropped.
lib/logger.js Default debugEnabled = true contradicts the PR's "quiet by default" intent.
lib/actions/build.js setDebugEnabled mutates module-scoped state shared by every consumer of lib/logger.js.
lib/actions/build.js HELP_TEXT has no trailing newline.
lib/frontmatter.js The null-object type check is incomplete (resolved in current lib/frontmatter.js but flag retained for visibility).
Files Reviewed (2 files in incremental diff)
  • lib/engine/build.js - 3 issues (2 CRITICAL, 1 WARNING)
  • test/build.test.js - 0 issues (good coverage for the new guard: same-path, nested-path, and build({ outputPath: srcPath }))

Fix these issues in Kilo Cloud

Previous review (commit 4bcd623)

Status: No Issues Found | Recommendation: Merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Both previously flagged incremental issues are now resolved in commit 4bcd623:

  • lib/frontmatter.js:29data === null is now explicitly rejected before the typeof check, so ---\nnull\n--- throws frontmatter must be a YAML object instead of crashing later in the renderer. Covered by new test parseFrontmatter: throws when YAML frontmatter resolves to null.
  • lib/logger.js:9-11enabled now defaults to true when the key is absent (Object.prototype.hasOwnProperty.call(opts, 'enabled') ? opts.enabled : true), restoring the legacy behavior. Existing callers in lib/actions/compress.js:219 and lib/actions/clone.js:41 now print again without modification. Covered by logger.debug: emits by default for existing call sites.

The two test additions also exercise the per-call opt-out ({ enabled: false }) and the default-on path, locking the new contract.

Files Reviewed (3 files in incremental diff)
  • lib/frontmatter.js - 0 issues (WARNING fixed)
  • lib/logger.js - 0 issues (WARNING fixed; existing call sites in compress.js / clone.js no longer regressed)
  • test/build.test.js - 0 issues (good coverage for both fixes)

Fix these issues in Kilo Cloud

Previous review (commit 8c1838b)

Status: 1 Issue Found (Incremental) | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Previously Reported Issues — Status

All 6 issues from the prior review at 73e9da8 have been resolved in the incremental commits:

File Previous Status Notes
lib/engine/build.js CRITICAL — cleanOutputPath guard bypassable Fixed Now uses path.relative + checks for .. traversal and absolute paths.
lib/frontmatter.js WARNING — silently swallowed unclosed frontmatter Fixed Now throws frontmatter missing closing ---.
lib/frontmatter.js WARNING — leading \n after closing --- dropped Fixed content: text.slice(closing.next) preserves the newline.
lib/logger.js WARNING — debugEnabled = true default Fixed Module-scoped state removed; debug is opt-in per call.
lib/actions/build.js WARNING — setDebugEnabled mutated module state Fixed Function removed; verbose passed as a build option instead.
lib/actions/build.js SUGGESTION — HELP_TEXT had no trailing \n Fixed Trailing blank line in template literal now produces a final \n.

Incremental Findings (new commits since 73e9da8)

WARNING

File Line Issue
lib/frontmatter.js 29 null slips past the object type check. yaml.load('null') returns null (typeof object, not an array), so ---\nnull\n---\n<body> returns data: null and crashes the renderer on frontmatter.title / frontmatter.page_layout access in lib/engine/build.js.

Additional Issues Observed in Incremental Diff (summary-only — affected lines not in this PR's diff)

File Line Issue
lib/actions/compress.js 219 logger.debug(\${skippedCount} image… already fully compressed, skipped`)is now permanently silenced. The newlogger.debug(msg, { enabled })API requires the caller to pass{ enabled: true }, but compresshas no--verboseplumbing. The change inlib/logger.js:9-11` silently broke this existing call site — users no longer see skip diagnostics.
lib/actions/clone.js 41 Same regression: logger.debug(\updated permalinks in ${entry.name}`)no longer prints.clonealso has no--verbose` flag, so this message is dead.
lib/logger.js 9-11 API design concern: the new debug signature silently breaks every existing caller. Either default opts.enabled to a module-level flag (e.g. from CPK_DEBUG env or a new setDebugEnabled) for backwards compatibility, or update every existing call site in the same PR.
Files Reviewed (7 files)
  • README.md - 0 issues (cleaned up; no new findings)
  • lib/actions/build.js - 0 issues (previous issues resolved)
  • lib/engine/build.js - 0 issues in diff (previous CRITICAL resolved; new verbose plumbing correct)
  • lib/frontmatter.js - 1 issue (new WARNING: null not rejected by object-type check)
  • lib/logger.js - 0 issues in diff (previous WARNING resolved), but API change regresses 2 other call sites (see summary)
  • lib/actions/compress.js - 1 issue (unchanged line; regression from logger API change)
  • lib/actions/clone.js - 1 issue (unchanged line; regression from logger API change)
  • test/build.test.js - 0 issues (good coverage for new frontmatter and cleanOutputPath behavior)

Fix these issues in Kilo Cloud

Previous review (commit 73e9da8)

Status: 6 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 4
SUGGESTION 1
Issue Details (click to expand)

CRITICAL

File Line Issue
lib/engine/build.js 165 cleanOutputPath safety guard only blocks cwd and volume root; bypassable via .. traversal or arbitrary absolute path passed through the public build({ outputPath, clean }) API.

WARNING

File Line Issue
lib/frontmatter.js 11 Unclosed frontmatter (--- with no closing fence) silently returns empty data instead of erroring like gray-matter did — broken pages ship to _site/ as warnings only.
lib/frontmatter.js 25 Drops the leading \n after the closing ---, a subtle behavior change vs. gray-matter that may shift whitespace in Liquid templates.
lib/logger.js 7 debugEnabled = true default contradicts the PR's "quiet by default" goal; only correct because lib/actions/build.js flips it early. Library consumers and tests still get DEBUG output.
lib/actions/build.js 37 setDebugEnabled mutates module-scoped logger state shared across all consumers — leaks verbosity into plugins, dev server, and tests loaded after campaign-build.

SUGGESTION

File Line Issue
lib/actions/build.js 26 HELP_TEXT has no trailing \n; shell prompt glues onto the last line.
Files Reviewed (7 files)
  • README.md - 0 issues
  • lib/actions/build.js - 2 issues
  • lib/actions/dev.js - 0 issues (caller reviewed for behavioral impact)
  • lib/engine/build.js - 1 issue
  • lib/frontmatter.js - 2 issues
  • lib/logger.js - 1 issue
  • test/build.test.js - 0 issues
  • package.json / package-lock.json - 0 issues (lock file churn from gray-matterjs-yaml swap)

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 75.7K · Output: 17.1K · Cached: 1.8M

Comment thread lib/frontmatter.js Outdated
const source = text.slice(firstLine.next, closing.start).replace(/\r?\n$/, '');
const data = source.trim() ? yaml.load(source) : {};

if (typeof data !== 'object' || Array.isArray(data)) {

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: The null-object type check is incomplete — yaml.load('null') returns null, which passes both typeof data !== 'object' (false) and Array.isArray(data) (false), so a frontmatter block of ---\nnull\n---\n<body> returns data: null. The renderer then crashes on null.title / null.page_layout access in lib/engine/build.js:125,129,188,192,194,206.

Reject null explicitly (e.g. data === null || typeof data !== 'object' || Array.isArray(data)), or check after the parse with if (!data) throw ....


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@next-devin next-devin requested a review from alexphelps June 22, 2026 16:17
@next-devin next-devin marked this pull request as draft June 22, 2026 16:17
@next-devin next-devin marked this pull request as ready for review June 23, 2026 01:56
Comment thread lib/engine/build.js Outdated
logger.debug(`Cleaned \x1b[90m${relative}\x1b[0m`, { enabled: opts.verbose });
}

function pathsOverlap(a, b) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: pathsOverlap is bypassable via symlinks.

path.relative and path.resolve operate on the lexical path and do not resolve symlinks. If outputPath is a symlink whose target lives inside srcPath (e.g. src/_sitesrc/), the lexical comparison sees two distinct paths, the overlap check returns false, the guard passes, and fs.rmSync(resolved, { recursive: true, force: true }) follows the symlink and recursively deletes the source tree.

The whole point of this guard is to prevent exactly that — misconfigured outputPath deleting source — and the previous review flagged that scenario. The fix should compare real paths, e.g. fs.realpathSync for both sides, before the overlap test (and bail out if either path does not exist). At minimum, document this limitation.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread lib/engine/build.js Outdated
logger.debug(`Cleaned \x1b[90m${relative}\x1b[0m`, { enabled: opts.verbose });
}

function pathsOverlap(a, b) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: pathsOverlap is bypassable on case-insensitive filesystems.

path.relative is case-sensitive on every platform, but macOS (HFS+/APFS default) and Windows (NTFS) are case-insensitive. A repo with srcPath = /project/src and an outputPath like /project/SRC/_site (different casing) compares as a non-overlapping sibling under the guard, while fs.rmSync resolves both names to the same on-disk directory and deletes the source tree.

This is the same class of bug as the symlink bypass and the original CRITICAL — the safety guard passes, the destructive op still runs. Consider normalizing case (or using fs.realpathSync, which on case-insensitive volumes surfaces the canonical case) before comparing.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread lib/engine/build.js Outdated
const start = Date.now();

if (opts.clean === true && opts.files === undefined) {
cleanOutputPath(outputPath, { verbose, protectedPaths: [srcPath] });

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: Cleaning _site before file discovery assumes default outputPath/srcPath.

This call now runs unconditionally for full builds (opts.clean === true && opts.files === undefined) and protects srcPath from overlap. But the existing engine API still lets callers pass arbitrary outputPath values that are siblings of arbitrary directories outside srcPath (e.g. outputPath: '/tmp/somewhere-else'). The previous review's CRITICAL about arbitrary absolute paths is partially mitigated for srcPath but not addressed as a general API contract — programmatic callers can still recursively delete arbitrary trees. If the goal is clean to be safe to expose in the public API, this should be tightened (e.g. require outputPath to be inside a configurable safe root, not just disjoint from srcPath).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@alexphelps

Copy link
Copy Markdown
Member

@next-devin can you describe the problems this is solving? I don't quite understand the purpose of this or what problems we are solving.

@next-devin

next-devin commented Jun 23, 2026

Copy link
Copy Markdown
Author

@next-devin can you describe the problems this is solving? I don't quite understand the purpose of this or what problems we are solving.

@alexphelps This PR is a small DX/reliability pass for next-campaign-page-kit itself, scoped to issues that affect any repo using the package.
The problems it solves are:
campaign-build --help currently requires a valid project shape, so it can fail before showing help.
campaign-build --json can be polluted by debug output, which makes it harder to consume safely in scripts/CI.
The package docs should point users at the actual public npm package and CLI commands.
gray-matter is carrying a moderate audit finding, so this replaces it with a small local frontmatter parser backed by js-yaml.
I intentionally removed the _site cleaning change after thinking through multi-campaign output directories. The build continues to preserve existing _site files, so this PR should stay agnostic about how campaign repos organize or deploy their output.

@alexphelps alexphelps left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We need to clarify the issues we are solving and how this PR addresses those issues directly.

Once issues are clearly identified and understood, the PR should be focused.

Comment thread README.md Outdated

By default, `campaign-build` preserves existing files in `_site/` so repos can
serve or manage additional campaign output there. Use `--clean` only when
`_site/` is fully owned by this build and stale output should be removed first.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@next-devin this will create massive confusion. Assets are always copied over here, users should not put their own content in the _site dir, what problem is this solving?

Comment thread lib/frontmatter.js Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why are we trying to replace grey-matter? grey-matter is the defacto standard yaml parser used by all node based ssgs.

@next-devin

Copy link
Copy Markdown
Author

@alexphelps I tightened this based on your feedback. I pulled out the parser/audit change and the _site preservation/cleaning framing.

The PR now only targets package/CLI friction that should apply to any page-kit user:

  • public package/docs wording for next-campaign-page-kit
  • campaign-build --help short-circuiting before project validation
  • campaign-build --json keeping stdout as JSON, with debug diagnostics opt-in via --verbose

No dependency changes, no output-directory semantics, and no Campaigns OS/internal workflow assumptions.

Comment thread lib/logger.js
// primary output — required for `campaign-build --json | jq` to work.
const debug = (msg) => console.error(`${TAG} \x1b[90mDEBUG\x1b[0m ${msg}`);
const debug = (msg, opts = {}) => {
const enabled = Object.prototype.hasOwnProperty.call(opts, 'enabled') ? opts.enabled : true;

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: The defensive Object.prototype.hasOwnProperty.call(opts, 'enabled') ? opts.enabled : true defaulting to true is at odds with the README's new claim ("Debug lines are quiet by default…"). In practice this means:

  • The build pipeline's only debug call (lib/engine/build.js:135) does honor verbose correctly because it explicitly passes { enabled: verbose }.
  • But every other logger.debug call site (lib/actions/clone.js:41, lib/actions/compress.js:219) still prints to stderr unconditionally because they don't pass { enabled: false } and the default is true. None of those binaries expose a --verbose flag, so the "quiet by default" promise is silently violated.

This was previously flagged as carried-forward (lib/logger.js default debugEnabled = true contradicts the PR's "quiet by default" intent). The current PR narrows the disagreement to build's debug line only by threading verbose per call — it does not fix the underlying module-level default. Worth either (a) flipping the module-level default to false and requiring an explicit setDebugEnabled(true) or --verbose flag at every call site, or (b) scoping the README's "quiet by default" claim strictly to campaign-build --json so it doesn't overpromise.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread README.md
`campaign-build --json` reports, for every page in the build, which source file was rendered, which URL it resolved to, and which output file it was written to — in a form that CI jobs and scripts can consume directly.

In this mode, stdout carries exactly one JSON document and nothing else. Warnings, errors, and debug lines all go to stderr, so the summary can be piped or redirected without any filtering:
In this mode, stdout carries exactly one JSON document and nothing else. Warnings and errors go to stderr. Debug lines are quiet by default and are printed to stderr only when `--verbose` is passed, so the summary can be piped or redirected without any filtering:

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: The new sentence claims "Debug lines are quiet by default and are printed to stderr only when --verbose is passed". This is true for campaign-build's single per-page debug message (which is gated by the new verbose parameter), but the claim is broader than what the implementation actually delivers:

  • lib/logger.js's debug() defaults enabled to true.
  • lib/actions/clone.js:41 and lib/actions/compress.js:219 call logger.debug(msg) with no opts, so they always print regardless of any flag.
  • Neither campaign-clone nor campaign-compress has a --verbose flag, so users cannot opt in or out at those entry points.

A reader skimming this paragraph could reasonably conclude that all DEBUG lines across the kit are gated by --verbose, which is not the case. Either narrow the wording to "campaign-build's per-page debug output is quiet by default and prints only with --verbose", or actually wire a --verbose flag through the other binaries and flip the logger default. (See also the comment on lib/logger.js:10.)


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@next-devin

Copy link
Copy Markdown
Author

Too much churn, will resubmit this in a cleaner way

@next-devin next-devin closed this Jun 23, 2026
@next-devin next-devin deleted the devex-pagekit-friction-batch-1 branch June 23, 2026 04:04
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