mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Pulling master with new dependencies left the dev server failing on unresolvable imports (motion/react, @sentry/browser) until someone manually ran pnpm install and cleared Vite's optimizer cache. The dev runner now detects a pnpm-lock.yaml newer than node_modules/.modules.yaml and runs pnpm install itself, then drops the stale Vite caches that otherwise keep serving 504 "Outdated Optimize Dep" responses. - boot (dev and watch modes): install before the migration preflight - dev mode: pnpm-lock.yaml joins the watched files; auto-restart installs first, and skips the restart instead of exiting if the install fails so the running server is never orphaned - watch mode: a lockfile watcher installs on change and nudges the server entry's mtime so tsx watch respawns onto the fresh node_modules - the manifest mtime is bumped after each install so a touched-but-unchanged lockfile cannot re-trigger installs forever Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
import { existsSync, rmSync, statSync, utimesSync } from "node:fs";
|
|
import path from "node:path";
|
|
|
|
export interface StaleInstallCheck {
|
|
stale: boolean;
|
|
reason: string | null;
|
|
}
|
|
|
|
function mtimeMs(filePath: string): number | null {
|
|
try {
|
|
return statSync(filePath).mtimeMs;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function modulesManifestPath(repoRoot: string): string {
|
|
return path.join(repoRoot, "node_modules", ".modules.yaml");
|
|
}
|
|
|
|
export function lockfilePath(repoRoot: string): string {
|
|
return path.join(repoRoot, "pnpm-lock.yaml");
|
|
}
|
|
|
|
// pnpm rewrites node_modules/.modules.yaml at the end of every install, so a
|
|
// lockfile newer than that manifest means the checkout picked up dependency
|
|
// changes (e.g. a pull) that were never installed.
|
|
export function detectStaleInstall(repoRoot: string): StaleInstallCheck {
|
|
const lockfileMtime = mtimeMs(lockfilePath(repoRoot));
|
|
if (lockfileMtime === null) {
|
|
return { stale: false, reason: null };
|
|
}
|
|
const manifestMtime = mtimeMs(modulesManifestPath(repoRoot));
|
|
if (manifestMtime === null) {
|
|
return { stale: true, reason: "dependencies have never been installed in this checkout" };
|
|
}
|
|
if (lockfileMtime > manifestMtime) {
|
|
return { stale: true, reason: "pnpm-lock.yaml changed after the last pnpm install" };
|
|
}
|
|
return { stale: false, reason: null };
|
|
}
|
|
|
|
// The manifest mtime is the staleness marker, but a no-op install may skip
|
|
// rewriting it; bump it explicitly so a touched-but-unchanged lockfile cannot
|
|
// re-trigger installs forever.
|
|
export function markInstallFresh(repoRoot: string, now = new Date()): void {
|
|
try {
|
|
utimesSync(modulesManifestPath(repoRoot), now, now);
|
|
} catch {
|
|
// Manifest missing (unusual pnpm setup): the next boot re-runs a no-op install.
|
|
}
|
|
}
|
|
|
|
export function resolveViteCachePaths(repoRoot: string): string[] {
|
|
return [
|
|
path.join(repoRoot, "ui", "node_modules", ".vite"),
|
|
path.join(repoRoot, "node_modules", ".vite"),
|
|
];
|
|
}
|
|
|
|
// Vite's optimizer keeps serving pre-bundled deps hashed against the old
|
|
// lockfile ("504 Outdated Optimize Dep") — drop the caches after an install so
|
|
// the next server boot re-bundles.
|
|
export function clearViteCaches(repoRoot: string): string[] {
|
|
const removed: string[] = [];
|
|
for (const cachePath of resolveViteCachePaths(repoRoot)) {
|
|
if (!existsSync(cachePath)) continue;
|
|
rmSync(cachePath, { recursive: true, force: true });
|
|
removed.push(cachePath);
|
|
}
|
|
return removed;
|
|
}
|