Skip to content

ci: guard the last two generated bundles against drift - #3355

Merged
kojiwakayama merged 3 commits into
mainfrom
ci/guard-generated-bundles
Aug 4, 2026
Merged

kojiwakayama merged 3 commits into
mainfrom
ci/guard-generated-bundles

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Why

generate:manifests:check already runs inside typecheck, which is a required check, so stale generated output fails CI today. But it only covered four of the six generators in deno task generate.

prebundle-bridge.ts and prebundle-rsc-scripts.ts had no --check counterpart at all, and both write committed artifacts:

  • src/studio/bridge/bridge-bundle.generated.ts
  • src/server/services/rsc/endpoints/rsc-bundles.generated.ts

Edit the Studio bridge or the RSC endpoint sources, forget deno task generate, and those bundles go stale with nothing failing.

What changed

Both scripts now take --check, using the pattern already in prebundle-hydration-runtime.ts, and both are wired into generate:manifests:check.

One wrinkle: the RSC bundle is formatted after it is written, so its check formats the candidate through deno fmt and compares post-format on both sides. Comparing against the raw template would report stale on every run.

Verification

Case Result
prebundle-bridge --check, clean tree passes
prebundle-rsc-scripts --check, clean tree passes
Both against a deliberately stale bundle exit 1, with the regenerate instruction
deno task generate:manifests:check exit 0, all six generators
deno task typecheck (the gate that invokes it) exit 0
deno task lint / fmt:check exit 0

Parity check: every generator invoked by deno task generate now has a --check counterpart, with none missing.

Note

scripts/build/prebundle-*.ts sit outside the repo's fmt/lint scope by config, so deno fmt/deno lint report no target files for them. The repo-level lint and fmt:check tasks both pass.

Summary by CodeRabbit

  • Chores
    • Added validation checks for bridge and RSC prebundles alongside existing generated asset checks.
    • Build verification now detects stale generated bundles and reports failures without modifying committed files.
    • Generated RSC bundles are automatically formatted for consistent output.
    • CI now runs automated checks to confirm generated artifact validation remains complete and correctly configured.

`generate:manifests:check` runs inside `typecheck`, so stale generated
output already fails a required check, but it only covered four of the six
generators in `deno task generate`.

prebundle-bridge.ts and prebundle-rsc-scripts.ts had no --check counterpart
at all, and both write committed artifacts:

  src/studio/bridge/bridge-bundle.generated.ts
  src/server/services/rsc/endpoints/rsc-bundles.generated.ts

Editing the Studio bridge or the RSC endpoint sources and forgetting to
regenerate left those bundles stale with nothing failing.

Both now take --check using the pattern from prebundle-hydration-runtime.
The RSC bundle is formatted after it is written, so its check compares
post-format on both sides rather than against the raw template.

Verified by running each with --check on a clean tree (passes) and against
a deliberately stale bundle (exits 1 with the regenerate instruction).
Every generator in `generate` now has a check counterpart.
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner August 4, 2026 08:22
Copilot AI review requested due to automatic review settings August 4, 2026 08:22
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 38 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b865a77-ed57-4fb5-bb62-24d32bf06019

📥 Commits

Reviewing files that changed from the base of the PR and between 1446615 and 76a7812.

📒 Files selected for processing (1)
  • scripts/build/generated-artifact-checks.test.ts
📝 Walkthrough

Walkthrough

The prebundle generators now support --check mode. The RSC generator formats generated TypeScript before comparison. Manifest tasks and CI now validate generated-artifact checks through a dedicated contract test.

Changes

Generated artifact validation

Layer / File(s) Summary
Prebundle check modes
scripts/build/prebundle-bridge.ts, scripts/build/prebundle-rsc-scripts.ts
The bridge and RSC generators compare generated output with committed bundles in --check mode. The RSC generator formats output before comparison and awaits esbuild.stop() during generation.
Manifest and test task wiring
deno.json
generate:manifests:check runs both prebundle checks. test:scripts includes the generated-artifact contract test.
Generated-artifact contract validation
scripts/build/generated-artifact-checks.test.ts, .github/workflows/cicd.yml
The contract test validates generator parity and required --check flags. CI runs the test with the lint checks.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: kwakayama, copilot

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the CI change that detects drift in the two generated bundles.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 ci/guard-generated-bundles

Comment @coderabbitai help to get the list of available commands.

@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: 1

🤖 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 `@scripts/build/prebundle-bridge.ts`:
- Around line 52-68: Add focused regression tests for the --check behavior in
scripts/build/prebundle-bridge.ts (lines 52-68), covering current and stale or
missing committed bundles; the implementation site requires no direct change.
Add corresponding success and failure tests in
scripts/build/prebundle-rsc-scripts.ts (lines 167-205), ensuring comparison uses
the formatted generated output; the implementation site requires no direct
change.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e914f3df-917c-4dc1-989b-61bb59765d81

📥 Commits

Reviewing files that changed from the base of the PR and between 98aa659 and d948826.

📒 Files selected for processing (3)
  • deno.json
  • scripts/build/prebundle-bridge.ts
  • scripts/build/prebundle-rsc-scripts.ts

Comment thread scripts/build/prebundle-bridge.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Veryfront’s existing generated-artifact drift checks to cover the last two generators invoked by deno task generate, ensuring committed Studio bridge and RSC client bundles fail CI when stale.

Changes:

  • Added --check mode to scripts/build/prebundle-bridge.ts to compare the committed bridge bundle against freshly generated output.
  • Added --check mode to scripts/build/prebundle-rsc-scripts.ts, formatting candidate output via deno fmt before comparing to the committed file.
  • Wired both --check scripts into deno task generate:manifests:check so the required typecheck gate catches drift.

Reviewed changes

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

File Description
scripts/build/prebundle-rsc-scripts.ts Adds --check that formats generated output and compares it to the committed RSC bundles.
scripts/build/prebundle-bridge.ts Adds --check that compares freshly bundled Studio bridge output to the committed generated file.
deno.json Extends generate:manifests:check to include the two newly checkable generators.

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

Comment thread scripts/build/prebundle-rsc-scripts.ts
Review follow-ups on the generated-bundle guard.

esbuild.stop() was called without await in prebundle-rsc-scripts, both in
the new --check path (right before an early Deno.exit, where it matters
most) and in the pre-existing write path one line below. Every other
caller awaits it, so both now do.

For tests, the realistic regression is not that the comparison is wrong,
it is that a seventh generator gets added to `deno task generate` and its
--check counterpart is forgotten. That is exactly how bridge and rsc
drifted out of the guard in the first place. The new test expands both
task chains and asserts they name the same scripts, that the check task
passes --check to each, and that it checks nothing `generate` does not
run. Verified by dropping the rsc check from the task: the test fails and
names the offending script.

Spawning the generators for real would cost an esbuild pass per case and
prove little that `generate:manifests:check` does not already prove on
every PR, so this checks the wiring instead.

Wired into the CI lint job rather than only `test:scripts`, because no
workflow invokes that task. (`test:scripts` also has a pre-existing
failure in generate-api-reference, unrelated to this change.)
Copilot AI review requested due to automatic review settings August 4, 2026 08:37

@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: 1

🤖 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 `@scripts/build/generated-artifact-checks.test.ts`:
- Line 16: Update generated-artifact tests to import assertions from
`#veryfront/testing/assert.ts` and use describe() and it() from
`#veryfront/testing/bdd.ts` instead of `#std/assert` and Deno.test. Apply this
consistently throughout the test cases covered by the generated-artifact test
suite, preserving their existing assertions and behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 86f66004-43cb-4e20-b4e4-bc6f4065a898

📥 Commits

Reviewing files that changed from the base of the PR and between d948826 and 1446615.

📒 Files selected for processing (4)
  • .github/workflows/cicd.yml
  • deno.json
  • scripts/build/generated-artifact-checks.test.ts
  • scripts/build/prebundle-rsc-scripts.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/build/prebundle-rsc-scripts.ts

Comment thread scripts/build/generated-artifact-checks.test.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (3)

scripts/build/prebundle-rsc-scripts.ts:220

  • In the write path, await esbuild.stop() won’t run if writeTextFile() or the deno fmt command throws (for example due to filesystem permissions). Wrapping the write/format block in try/finally makes cleanup deterministic and matches the pattern used elsewhere (e.g. build-npm-extension-packages.ts uses a finally for esbuild cleanup).
await Deno.writeTextFile(outputPath, output);

const fmtResult = await new Deno.Command("deno", {
  args: ["fmt", outputPath],
  stdout: "null",
  stderr: "piped",
}).output();

if (!fmtResult.success) {
  const err = new TextDecoder().decode(fmtResult.stderr).trim();
  console.warn(`[prebundle-rsc-scripts] Warning: could not format output: ${err}`);
}

await esbuild.stop();

scripts/build/generated-artifact-checks.test.ts:45

  • scriptInvocations() stores one boolean per script path, but Map.set() overwrites prior entries. If a task ever invokes the same script twice (for example once with --check and once without), this test could miss the missing-flag case depending on ordering. Consider aggregating so the stored value becomes false if any invocation of that script lacks --check.
  for (const segment of command.split("&&")) {
    const script = segment.match(/([\w./-]+\.ts)/)?.[1];
    if (!script) continue;
    found.set(script, segment.includes("--check"));
  }

scripts/build/prebundle-rsc-scripts.ts:205

  • In --check mode, await esbuild.stop() runs only after formatTypeScript(output) succeeds. If deno fmt fails and formatTypeScript throws, the esbuild service is never stopped (and because Deno.exit(...) bypasses finally, it is safest to stop explicitly before any early exit). Consider stopping esbuild in a finally around the formatting step so it runs even when formatting errors.
if (Deno.args.includes("--check")) {
  const formatted = await formatTypeScript(output);
  const committed = await Deno.readTextFile(outputPath).catch(() => null);
  await esbuild.stop();

AGENTS.md requires describe()/it() from #veryfront/testing/bdd.ts and
assertions from #veryfront/testing/assert.ts for test files. I had
followed scripts/build/dnt-polyfill.test.ts instead, which uses
#std/assert and Deno.test, but that is the older minority pattern.

Both specifiers do resolve under scripts/test.deno.json, which maps
#veryfront/ to ../src/, so there was no technical reason to deviate.

Re-verified after the conversion: dropping the bridge check from the task
still fails the test and names the offending script.
Copilot AI review requested due to automatic review settings August 4, 2026 08:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 5 out of 5 changed files in this pull request and generated no new comments.

Suppressed comments (2)

scripts/build/prebundle-bridge.ts:68

  • prebundle-bridge.ts uses esbuild via npm:esbuild, but the new --check path (now wired into generate:manifests:check in CI) does not call esbuild.stop(). Other scripts that use esbuild consistently stop it (for example scripts/build/prebundle-hydration-runtime.ts:127-129 and scripts/build/build-npm-extension-packages.ts:499-530), which avoids leaving the esbuild child process alive and potentially keeping the Deno process open longer than intended.

Consider stopping esbuild in both the --check and write paths, and ensure it happens before any Deno.exit(1) on stale output.

// --check makes a stale committed bundle fail CI instead of relying on someone
// noticing it missing from a PR diff.
if (Deno.args.includes("--check")) {
  const committed = await Deno.readTextFile(outputPath).catch(() => null);
  if (committed !== output) {
    console.error(
      `[prebundle-bridge] ${outputPath} is stale.\n` +
        `  The committed bundle does not match src/studio/bridge/.\n` +
        `  Run \`deno task generate\` and commit the result.`,
    );
    Deno.exit(1);
  }
  console.log("[prebundle-bridge] Committed bundle is up to date");
} else {
  await Deno.writeTextFile(outputPath, output);
  console.log(`[prebundle-bridge] Written to ${outputPath} (${js.length} bytes)`);
}

scripts/build/prebundle-rsc-scripts.ts:205

  • In the --check path, formatTypeScript(output) runs before await esbuild.stop(). If deno fmt fails (invalid TS output, missing formatter, etc.), the thrown error skips the stop call and can leave the esbuild service running. Since this is an early-exit/CI path, it is worth ensuring cleanup happens even on formatting failures.
// --check makes a stale committed bundle fail CI instead of relying on someone
// noticing it missing from a PR diff.
if (Deno.args.includes("--check")) {
  const formatted = await formatTypeScript(output);
  const committed = await Deno.readTextFile(outputPath).catch(() => null);
  await esbuild.stop();

  if (committed !== formatted) {
    console.error(
      `[prebundle-rsc-scripts] ${outputPath} is stale.\n` +
        `  The committed bundle does not match src/rendering/rsc/.\n` +
        `  Run \`deno task generate\` and commit the result.`,
    );
    Deno.exit(1);
  }
  console.log("[prebundle-rsc-scripts] Committed bundle is up to date");
  Deno.exit(0);
}

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit bd72d8b Aug 4, 2026
34 checks passed
@kojiwakayama
kojiwakayama deleted the ci/guard-generated-bundles branch August 4, 2026 10:35
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