Skip to content

Keep the cache root out of git on the build path, and document it - #3606

Merged
kojiwakayama merged 5 commits into
mainfrom
fix/dx-20260811-r2-16
Aug 12, 2026
Merged

kojiwakayama merged 5 commits into
mainfrom
fix/dx-20260811-r2-16

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

What the round-2 verification found

Finding 16 was filed as "veryfront dev writes an undocumented .cache/ tree into the host project root and never adds a .gitignore entry" and marked partially-fixed: the git-hygiene half looked fixed, the documentation half did not.

Reproducing on the published 0.1.1229 showed the git-hygiene half is only fixed on the path the verification happened to test.

Adopt-path project (hand-built per the Installation doc, own .gitignore listing only node_modules/, dist/, .env, git init + commit), published veryfront@0.1.1229:

command git status --porcelain -uall
veryfront dev + one page load 0 lines (fixed by #3581)
veryfront build 10 untracked .cache/**.mjs files
veryfront build --dry-run 10 untracked .cache/**.mjs files

.cache/.gitignore is simply absent after a build.

Why the previous fix missed it

#3581 diagnosed the problem correctly but hooked the repair to the wrong lifecycle. It calls ensureCacheDirIgnored() from clearAllLocalCaches(), and only veryfront dev, veryfront start, and veryfront serve call that — they are the commands that boot a server. veryfront build never boots a server, so it writes veryfront-mdx-esm/ and veryfront-http-bundle/ into <project>/.cache with no marker in sight. The fix was verified through the dev path only, which is exactly the path it covered.

The fix

buildProduction() marks the cache root itself. That is the single library entry the CLI build, the MCP build tool, and a direct API call all funnel through. It is not in setupBuildDirectories(), because a dry run returns from that step early and still populates the cache root (verified above).

Documentation half

The project-structure guide gains a Generated directories section: what .cache/ holds (named after the real subdirectories, pinned by a test), that it ignores itself so an adopted project never has to edit its own .gitignore, that deleting it is safe, and VERYFRONT_CACHE_DIR for keeping generated bundles out of the project tree entirely. Every claim was checked against published-CLI behaviour, including that VERYFRONT_CACHE_DIR relocates the whole root and leaves the project clean.

Live URL that must show this after the docs sync lands: https://veryfront.com/code/guides/project-structure (section "Generated directories"). docs/code/guides/ in veryfront-docs is overwritten wholesale from this repo by .github/workflows/sync-docs.yml → update-reference.yml, so this repo is the source of truth; veryfront-docs#375 carries the same text so the live page does not wait on the sync run.

Tests

  • tests/integration/server/build/build.test.ts — new case runs buildProduction() inside runWithCacheDir() and asserts the cache root ends up with a .gitignore containing *. Confirmed red before the fix (AssertionError: Values are not equal) and green after.
  • tests/docs/guide-content.test.ts — pins the new guide section to the real cache layout via basename(getMdxEsmCacheDir()) / basename(getHttpBundleCacheDir()), so renaming a cache subdirectory fails here instead of silently stranding the guide.

Proof the original symptom is gone

Same sandbox project, same commands, this branch's build instead of 0.1.1229:

$ veryfront build         -> git status --porcelain -uall: 0 lines
$ veryfront build --dry-run -> git status --porcelain -uall: 0 lines
$ cat .cache/.gitignore
# Created by Veryfront. Holds generated bundles only, safe to delete.
*
$ git check-ignore -v .cache/
.cache/.gitignore:2:*   .cache/

Summary by CodeRabbit

  • Documentation

    • Documented generated .cache/ and dist/ directories, including their contents, regeneration, safe deletion, and cache-directory configuration.
  • Bug Fixes

    • Production dry-run builds now ensure generated cache files are ignored by version control, preventing untracked cache bundles.
  • Tests

    • Added coverage verifying cache-directory documentation and correct .gitignore creation during production builds.

@coderabbitai

coderabbitai Bot commented Aug 11, 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: 33 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: 5296221b-a47f-4e2f-96d9-6cbb32438423

📥 Commits

Reviewing files that changed from the base of the PR and between 2d2b242 and d5b2bc3.

📒 Files selected for processing (2)
  • docs/guides/project-structure.md
  • tests/docs/guide-content.test.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: beb7ce54-3d16-4558-a784-fac2b5daf56f

📥 Commits

Reviewing files that changed from the base of the PR and between 6f3242d and 2d2b242.

📒 Files selected for processing (4)
  • docs/guides/project-structure.md
  • src/build/production-build/build/build-orchestrator.ts
  • tests/docs/guide-content.test.ts
  • tests/integration/server/build/build.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • tests/integration/server/build/build.test.ts
  • docs/guides/project-structure.md
  • src/build/production-build/build/build-orchestrator.ts
  • tests/docs/guide-content.test.ts

📝 Walkthrough

Walkthrough

The change documents generated .cache/ and dist/ directories. Production builds now create .cache/.gitignore, including during dry runs. Documentation and integration tests verify the behavior.

Changes

Cache directory support

Layer / File(s) Summary
Build cache initialization
src/build/production-build/build/build-orchestrator.ts, tests/integration/server/build/build.test.ts
Production builds ensure .cache/.gitignore ignores all cache contents. The integration test verifies this during a dry-run build.
Generated directory documentation
docs/guides/project-structure.md, tests/docs/guide-content.test.ts
The guide documents .cache/, dist/, cache contents, VERYFRONT_CACHE_DIR, ignore behavior, and regeneration. The documentation test verifies these details and the runtime cache subdirectories.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: kwakayama

🚥 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 summarizes the cache Git-ignore fix and the documentation added for the generated cache directory.
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 fix/dx-20260811-r2-16

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5512b80928

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/build/production-build/build/build-orchestrator.ts

@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

🤖 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 `@docs/guides/project-structure.md`:
- Around line 194-198: Update the `.cache/.gitignore` documentation to state
that Veryfront creates the self-ignoring file only when it is absent and
preserves any existing file without adding `*` to it; clarify this behavior for
both commands described in the section.

In `@src/build/production-build/build/build-orchestrator.ts`:
- Around line 88-96: Update the build orchestration around
ensureCacheDirIgnored() and generateLocalReleaseAssetManifest() to resolve one
cache root and reuse it for both the ignore marker and generated build caches,
including when options.projectDir differs from cwd(). Preserve explicit cache
overrides, and add coverage for the default path without an explicit cache
override.
🪄 Autofix

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: 4e99f20b-62cc-4e80-b200-c766e1d9cacf

📥 Commits

Reviewing files that changed from the base of the PR and between 64d6850 and db52cbc.

📒 Files selected for processing (4)
  • docs/guides/project-structure.md
  • src/build/production-build/build/build-orchestrator.ts
  • tests/docs/guide-content.test.ts
  • tests/integration/server/build/build.test.ts

Comment thread docs/guides/project-structure.md Outdated
Comment thread src/build/production-build/build/build-orchestrator.ts
@kwakayama kwakayama added needs-human-input Maintainer action required and removed needs-human-input Maintainer action required labels Aug 12, 2026
Outside production the cache root is `<project>/.cache`, so `veryfront build`
drops generated bundles into the user's project. #3581 wrote a self-ignoring
`.cache/.gitignore` from `clearAllLocalCaches()`, which only `veryfront dev`,
`start`, and `serve` call, so the build path was left uncovered: a project that
adopted Veryfront into an existing tree still saw ten untracked `.mjs` files
after one `veryfront build`, and `git add -A` committed them.

Mark the cache root from `buildProduction()` instead of from another entry
point's startup, so the CLI build, the MCP build tool, and a direct API call
are all covered. It goes there rather than in `setupBuildDirectories()` because
a dry run skips that step and still populates the cache root.

Also document `.cache/` in the project-structure guide. Until now the only
explanation a developer got for the directory appearing in their tree was the
comment inside the generated `.gitignore`; the guide now names both cache
subdirectories, states that the directory ignores itself, and documents
`VERYFRONT_CACHE_DIR` for moving it out of the project.

Verified against the published 0.1.1229 repro: a hand-built adopt-path project
(own .gitignore listing only node_modules/, dist/, .env) is left with a clean
`git status --porcelain -uall` after `veryfront build` and after
`veryfront build --dry-run`, where 0.1.1229 leaves ten untracked bundles.
The cwd-relative-test-reads audit ratchets per file, and reading the guide
by cwd-relative path added a 30th racy read to tests/docs/guide-content.test.ts.
Review follow-up: `ensureCacheDirIgnored()` deliberately leaves an existing
`.cache/.gitignore` alone, so the guide must not promise that every run writes
a catch-all there.
@kojiwakayama
kojiwakayama force-pushed the fix/dx-20260811-r2-16 branch from 0d52ec3 to 2d2b242 Compare August 12, 2026 03:56
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@kwakayama kwakayama added needs-human-input Maintainer action required and removed needs-human-input Maintainer action required labels Aug 12, 2026

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

  • [P3] Correct the production cache-location guidance — docs/guides/project-structure.md:185 says the CLI writes generated directories into the project root, and :191-200 says both commands create project .cache/. But src/utils/cache-dir.ts:85-102 selects $HOME/.cache/veryfront when NODE_ENV or VERYFRONT_MODE is production (unless overridden). The documentation test at tests/docs/guide-content.test.ts:568-580 forces a cache context and only checks directory names, so it cannot catch this default-production mismatch. Document the production location and test both modes.
Area Score
Correctness 40/40
Tests 19/20
Reliability/security 15/15
Maintainability 15/15
Scope/docs 5/10

Review-Gate:
Reviewer: Codex
Reviewed-SHA: 2d2b242
Score: 94/100
Actionable-Findings: 1
Verdict: COMMENT

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 12, 2026
@kwakayama kwakayama added the needs-human-input Maintainer action required label Aug 12, 2026
@kwakayama
kwakayama removed this pull request from the merge queue due to a manual request Aug 12, 2026
The guide described one cache location, the project's `.cache/`. There are
two: `getDefaultCacheBaseDir()` (src/utils/cache-dir.ts) returns
`$HOME/.cache/veryfront` when `NODE_ENV` or `VERYFRONT_MODE` is `production`
and `HOME` is set, so every deployed run wrote somewhere the guide did not
name.

The documentation test could not have caught that. It forced a cache context
with `runWithCacheDir()` and then asserted on `basename()` of the subdirectory
paths, which answers with the forced root in both modes -- the default
resolution the guide describes was never exercised. It now calls the real
`getCacheBaseDir()` once per mode with the overrides cleared, asserts the two
roots actually differ before asserting anything about the prose, and reads the
trigger variables out of `getDefaultCacheBaseDir()` so a renamed trigger fails
here instead of leaving readers checking a variable the code stopped
consulting.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

[P3] cache-location guidance — fixed in 9a553d2.

Citation confirmed. getDefaultCacheBaseDir() (src/utils/cache-dir.ts:85-95) returns join(home, ".cache", "veryfront") when NODE_ENV or VERYFRONT_MODE is production and HOME is set, and join(cwd(), ".cache") otherwise; getCacheBaseDir() (:97-103) puts the async cache context, then VERYFRONT_CACHE_DIR, then VF_CACHE_DIR ahead of both. The guide described only the second branch, so it was wrong for every deployed run.

Doc. docs/guides/project-structure.md no longer opens the section with the unconditional "The CLI writes these directories into the project root" — dist/ always is, .cache/ only outside production. A new "Where the cache root lives" subsection names the production location, both triggers, and the HOME requirement, and says VERYFRONT_CACHE_DIR/VF_CACHE_DIR win in either mode. The .gitignore marker paragraph now says "cache root" rather than .cache/, since ensureCacheDirIgnored() also resolves through getCacheBaseDir() and writes the marker into whichever root is live.

Test. You were right about why the old one could not fail. It wrapped everything in runWithCacheDir("/tmp/veryfront-guide-content", ...) and then asserted on basename() of the subdirectory paths — the forced context is the first branch of getCacheBaseDir(), so the default resolution the guide describes was never executed, and the assertions reduced to "the guide contains the strings veryfront-mdx-esm and veryfront-http-bundle", which are also in the table above them. It answered identically in both modes.

The replacement drops the forced context and calls the real getCacheBaseDir() once per mode with VERYFRONT_CACHE_DIR/VF_CACHE_DIR cleared and a stub HOME, then asserts the guide names what each branch actually produced. Three things keep it honest:

  • assert(development.base !== production.base) runs before any assertion about prose, so the test cannot silently degrade into a one-mode test.
  • The expected production string is derived, not typed: production.base.slice(home.length + 1) → .cache/veryfront.
  • The trigger variables are parsed out of getDefaultCacheBaseDir()'s own body (getHostEnv("...") matches), so renaming a trigger fails the doc test instead of leaving readers checking a variable the code stopped consulting.

Evidence it distinguishes the modes — three runs of tests/docs/guide-content.test.ts:

  1. New test, old wording (doc fix not yet applied) → FAILED: Expected actual: "<project-structure.md>" to contain: ".cache/veryfront". The mode-resolution assertions above it passed first, so the production branch really did resolve to $HOME/.cache/veryfront.
  2. Source mutated to if (false && home && isProduction) (production branch dead), doc fix in place → FAILED: AssertionError: development and production must resolve to different cache roots.
  3. VERYFRONT_MODE renamed to VERYFRONT_RUNTIME_MODE in the source, doc fix in place → FAILED: to contain: "VERYFRONT_RUNTIME_MODE".

Both mutations were reverted; the commit touches only the guide and the test, and deno task docs regenerates docs/api-reference/ with no diff. Unmutated: ok | 1 passed (30 steps) | 0 failed.

Resolving. Leaving the PR out of the merge queue for you.

ensureCacheDirIgnored() resolves through getCacheBaseDir(), so it leaves an
existing marker alone in the home cache root exactly as it does in the
project's. Saying so only about `.cache/.gitignore` invited a production
reader to assume the opposite.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Addendum, d5b2bc3: while re-reading the section I found one more instance of the same species of error and fixed it rather than leaving it. The marker paragraph asserted "a .cache/.gitignore you wrote yourself is never overwritten" — true, but stated only about the project root, which invites a production reader to assume their $HOME/.cache/veryfront/.gitignore would be clobbered. ensureCacheDirIgnored() resolves through getCacheBaseDir() (src/utils/cache-dir.ts:164) and returns on exists() for whichever root is live, so the guarantee now says "in either root".

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 2b3a0e0 Aug 12, 2026
33 checks passed
@kojiwakayama
kojiwakayama deleted the fix/dx-20260811-r2-16 branch August 12, 2026 07:00
kojiwakayama added a commit that referenced this pull request Aug 12, 2026
The validator this PR adds found three em dashes that reached main while no
authoring-time check existed: two from #3606 in project-structure.md and one
from #3616 in deploy-project.md. Rewritten as sentences and parentheses with
the meaning unchanged.

The rebase also took main's 'Verify it worked' section rather than this
branch's older copy, so #3616's reviewed route-probing guidance survives.
kojiwakayama added a commit that referenced this pull request Aug 12, 2026
The backfill replayed a "Generated directories" section from the docs mirror
into project-structure.md, but this repo already had a newer one: #3606 landed
the production cache root and VF_CACHE_DIR here after the mirror copy was
written. The result was two H2 sections with the same heading giving different
answers, the later one claiming the CLI writes .cache/ into the project root
with no mention of production. Drop the stale copy; the retained section is a
strict superset of it.

Also rewords the summary line above that section: "`.cache/` is too during
development" elided its verb across a sentence boundary.

coding-agents.md still stated the MCP port as `--port + 2` in two places. The
earlier fix only corrected the verification section, and one of the two misses
was the "Port already in use" heading, which is precisely the case where the
requested port and the bound port differ. mcpPort is computed from
started.port (cli/commands/dev/command.ts:240-243), so a --port 4000 that falls
forward to 4001 serves MCP on 4003.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human-input Maintainer action required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants