Skip to content

fix(core): isolate Vite cache for integration test servers - #2413

Merged
ascorbic merged 5 commits into
emdash-cms:mainfrom
bferanmi806-sketch:fix/2247-vite-cache-isolation
Aug 13, 2026
Merged

ascorbic merged 5 commits into
emdash-cms:mainfrom
bferanmi806-sketch:fix/2247-vite-cache-isolation

Conversation

@bferanmi806-sketch

@bferanmi806-sketch bferanmi806-sketch commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #2247

Each integration test server already receives its own copied workDir, which isolates Astro's project lock. The fixture intentionally shares node_modules through a symlink to the workspace dependency tree so server startup remains fast and parallel.

Vite followed that symlink and inherited one physical node_modules/.vite dependency-optimizer cache. Parallel servers could therefore rewrite the same cache concurrently, producing ENOTEMPTY failures in node_modules/.vite/deps and making the integration suite flaky.

This change assigns each server a private .vite-cache directory under its own workDir, passes it through the test-only EMDASH_TEST_VITE_CACHE environment variable, and maps it to Astro/Vite's cacheDir. Shared node_modules and parallelism are preserved. The existing cleanup removes the cache with the normal per-server workDir.

The regression test requires each server's private .vite-cache directory to exist and contain actual optimizer/cache file output. This catches fallback to the shared node_modules/.vite cache without relying on cwd inequality, while retaining the concurrent server startup and seeded-content checks.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change) ? focused concurrent-server regression and integration/CLI coverage pass
  • pnpm format has been run ? the three changed files were formatted and checked directly with Oxfmt and Prettier; the repository-wide format baseline was not rewritten
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation. Do not include messages.po changes except in translation PRs ? n/a, no user-facing strings
  • I have added a changeset (if this PR changes a published package) ? n/a, test/CI-only change under the current contribution policy
  • New features link to an approved Discussion: https://github.com/emdash-cms/emdash/discussions/... ? n/a, bug fix

AI-generated code disclosure

  • This PR includes AI-generated code ? model/tool: OpenCode + GPT-5.6 Luna; Codex adversarial review + GPT-5.6 Sol

Screenshots / test output

RED -> GREEN

  • RED: before the fix, the concurrent regression observed both servers resolving to the same physical Vite cache path; the shared-cache repro also triggered the Astro concurrent-server race path.
  • GREEN: the focused race loop passed 5/5 before shipping, and the final WSL run against the synchronized validation setup passed 1 test in 56.6 seconds.
  • The prior supported cold run took about 56.6 seconds; 30s/60s were not supported by that measurement. The test now declares an explicit 90-second timeout on this slow it(...) only; 90s matches the helper startup budget while reducing the previous 120s allowance; global Vitest timeout behavior is unchanged.
  • This validates the reported cache race path but does not claim that one successful run proves all CI flakiness is impossible.

Validation

  • pnpm typecheck: passed across the workspace packages.
  • pnpm lint: passed.
  • pnpm lint:quick: passed with 0 diagnostics across 2,310 files.
  • Focused Oxfmt check on the three changed files: passed.
  • Focused Prettier check on the three changed files: passed.
  • git diff --check: passed.
  • pnpm install --frozen-lockfile: passed; the lockfile remained unchanged.
  • pnpm --filter emdash build: passed in 64 seconds.
  • Focused WSL integration run using vitest.integration.config.ts passed 1 test in 56.6 seconds; the current test timeout is 90 seconds (the recorded run used --testTimeout 120000).
  • Earlier focused integration/CLI validation: 8 files and 90 tests passed; the concurrent race loop passed 5/5.
  • The full workspace pnpm build was attempted but exceeded the local five-minute tool ceiling while building packages/core; it reported no build error before the timeout, and the narrower core build passed.
  • Native Windows official integration setup remains blocked by the pre-existing execFile("pnpm", ...) spawn pnpm ENOENT issue and Windows symlink permissions, so the real-server regression was run under WSL/Linux.
  • No changeset or unrelated generated files are included.

@changeset-bot

changeset-bot Bot commented Aug 10, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d4b4b58

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@emdashbot emdashbot Bot 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.

This PR correctly fixes the Vite cache race in concurrent integration test servers. The approach is sound: each createTestServer already gets a private workDir, so putting .vite-cache under that root and passing it to Vite's cacheDir naturally isolates the dependency optimizer without disturbing the symlinked node_modules sharing. Cleanup is handled by the existing rmSync(workDir).

I checked the three changed files and traced the new EMDASH_TEST_VITE_CACHE variable from server.ts through the fixture's astro.config.mjs to the regression assertion in concurrent-servers.test.ts. There are no other occurrences. The change is test-only, so none of the production conventions (SQL, auth, locale filtering, logged-out query counts, i18n) are affected. No changeset is needed for a test-only fix.

The new assertion verifies the isolation directly: realpathSync confirms the per-server .vite-cache directories resolve to different physical paths. The 120-second timeout on the slow integration test is appropriate for cold dev-server startup.

I found no bugs, regressions, or convention violations. Good to merge.

@bferanmi806-sketch

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

@bferanmi806-sketch

Copy link
Copy Markdown
Contributor Author

recheck

github-actions Bot added a commit that referenced this pull request Aug 10, 2026
@github-actions github-actions Bot added cla: signed review/approved Approved; no new commits since and removed cla: needed review/needs-review No maintainer or bot review yet labels Aug 10, 2026

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

Thank you for tackling this! Just a couple notes on the regression test.

// back to the same physical cache directory.
const viteCacheA = realpathSync(join(a.cwd, ".vite-cache"));
const viteCacheB = realpathSync(join(b.cwd, ".vite-cache"));
expect(viteCacheA).not.toBe(viteCacheB);

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.

This seems to be a tautology. Two lines above, the test already asserts a's cwd and b's cwd are different. What actually makes the test fail appears to be realpathSync throwing. You can instead try something like:

-import { realpathSync } from "node:fs";
+import { existsSync, readdirSync } from "node:fs";
@@
-             // The Vite dependency optimizer must not follow both node_modules symlinks
-             // back to the same physical cache directory.
-             const viteCacheA = realpathSync(join(a.cwd, ".vite-cache"));
-             const viteCacheB = realpathSync(join(b.cwd, ".vite-cache"));
-             expect(viteCacheA).not.toBe(viteCacheB);
+             // Vite must write the dependency optimizer's output into each server's own
+             // cache dir, not follow the shared node_modules symlink to one physical copy.
+             for (const cwd of [a.cwd, b.cwd]) {
+                     const cacheDir = join(cwd, ".vite-cache");
+                     expect(existsSync(cacheDir), `${cacheDir} was never created`).toBe(true);
+                     expect(readdirSync(cacheDir)).toContain("deps");
+             }

expect(slugs).toContain("pages");
}
});
}, 120_000);

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.

This might be too generous. I am not sure what's the appropriate number to set it to, but other tests seem to run comfortably with a 30s timeout.

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 11, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The outer timeout must exceed the helper's startup-only budget so accepted and failure paths can finish setup, report errors, and clean up.

expect(slugs).toContain("pages");
}
});
}, 90_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

createTestServer itself allows 90 seconds just for the server to bind, then still performs setup and seeding. With this identical 90-second Vitest limit, a server that binds near the helper deadline—or exhausts it—makes Vitest abort before setup/error reporting and cleanup can finish, potentially leaving the child alive. Set this above the helper budget (for example, restore 120 seconds), or pass shorter timeout values to these calls so the outer limit retains cleanup headroom.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Aug 12, 2026
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 12, 2026
@bferanmi806-sketch

Copy link
Copy Markdown
Contributor Author

Restored the outer timeout to 120s while keeping the helper at 90s, so cleanup has headroom. Focused test and lint/format checks pass thanks for catching this.

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks!

@ascorbic
ascorbic enabled auto-merge (squash) August 13, 2026 08:24
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 13, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2413

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2413

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2413

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2413

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2413

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2413

emdash

npm i https://pkg.pr.new/emdash@2413

create-emdash

npm i https://pkg.pr.new/create-emdash@2413

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2413

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2413

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2413

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2413

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2413

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2413

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2413

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2413

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2413

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2413

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2413

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2413

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2413

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2413

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2413

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2413

commit: d4b4b58

@ascorbic
ascorbic merged commit c4e3d34 into emdash-cms:main Aug 13, 2026
46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integration tests flaky: parallel dev servers race on the shared Vite dep cache (ENOTEMPTY on node_modules/.vite/deps)

3 participants