mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
04e4c23b95
* dx(doctor): flag a worktree whose node_modules lags the lockfile Add a doctor probe and a check:affected preflight that compare node_modules/.pnpm/lock.yaml (the exact lockfile snapshot pnpm installed from) against pnpm-lock.yaml via a content hash — no subprocess. On mismatch both surfaces report the same one-liner: "node_modules was installed from a different lockfile; run pnpm install", so a stale install names its own cause instead of surfacing as bogus format diffs on files a change never touched (the #1956 incident). Closes #1963 * fix(doctor): scope the node-modules probe to a local source checkout The probe ran unconditionally from findProjectRoot(), so it fired in two contexts it cannot diagnose: - Packaged installs. Published packages ship neither pnpm-lock.yaml (not in the package.json `files` allowlist) nor an installed snapshot, so every end user's `doctor` gained a spurious node-modules line and a degraded overall status. - `--remote`, where the daemon's own root describes the server deployment rather than the caller's worktree, so the answer could not address #1963 at all. Whether a root is a source checkout is now decided by the presence of pnpm-lock.yaml itself rather than a heuristic about install location, and 'no-source-checkout' is a distinct result rather than a warning, so the packaged case cannot be represented as a defect. The probe returns undefined there and the route appends no check, matching how doctor already models an out-of-scope question (the device family is likewise absent under --remote). The fresh-worktree catch is preserved: a lockfile with no installed snapshot is still a hard failure. The check:affected preflight is unchanged in behavior. Route-level assertions cover all three contexts (source, packaged, remote); each was verified to fail against the pre-fix wiring. * refactor(check): keep stale-install probe worktree-local
86 lines
3.6 KiB
TypeScript
86 lines
3.6 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { test } from 'node:test';
|
|
import { mkdtempForTestSync } from '../../src/__tests__/test-utils/tmp-dir.ts';
|
|
import { checkLockfileInstallSync } from './lockfile-install-sync.ts';
|
|
|
|
function writeLockfile(root: string, content: string): void {
|
|
fs.writeFileSync(path.join(root, 'pnpm-lock.yaml'), content);
|
|
}
|
|
|
|
function writeInstalledSnapshot(root: string, content: string): void {
|
|
const dir = path.join(root, 'node_modules', '.pnpm');
|
|
fs.mkdirSync(dir, { recursive: true });
|
|
fs.writeFileSync(path.join(dir, 'lock.yaml'), content);
|
|
}
|
|
|
|
test('reports in sync when the installed snapshot byte-matches the checked-out lockfile', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-match-');
|
|
writeLockfile(root, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
writeInstalledSnapshot(root, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), { status: 'in-sync' });
|
|
});
|
|
|
|
test('reports stale when the installed snapshot content differs from the checked-out lockfile', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-stale-');
|
|
writeLockfile(
|
|
root,
|
|
'lockfileVersion: 9.0\nimporters:\n .:\n dependencies:\n newDep: 1.0.0\n',
|
|
);
|
|
writeInstalledSnapshot(root, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), { status: 'out-of-sync', reason: 'stale' });
|
|
});
|
|
|
|
test('reports install-missing when a source checkout has no installed snapshot', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-no-install-');
|
|
writeLockfile(root, 'lockfileVersion: 9.0\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), {
|
|
status: 'out-of-sync',
|
|
reason: 'install-missing',
|
|
});
|
|
});
|
|
|
|
test('reports install-missing for a fresh worktree that has no node_modules directory at all', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-fresh-worktree-');
|
|
writeLockfile(root, 'lockfileVersion: 9.0\n');
|
|
assert.equal(fs.existsSync(path.join(root, 'node_modules')), false);
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), {
|
|
status: 'out-of-sync',
|
|
reason: 'install-missing',
|
|
});
|
|
});
|
|
|
|
test('reports no-source-checkout when there is no pnpm-lock.yaml', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-packaged-');
|
|
fs.writeFileSync(path.join(root, 'package.json'), '{"name":"agent-device"}\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), { status: 'no-source-checkout' });
|
|
});
|
|
|
|
test('reports no-source-checkout even when an installed snapshot exists without a lockfile', () => {
|
|
const root = mkdtempForTestSync('agent-device-lockfile-sync-no-lockfile-');
|
|
writeInstalledSnapshot(root, 'lockfileVersion: 9.0\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(root), { status: 'no-source-checkout' });
|
|
});
|
|
|
|
test('checks each worktree against its own lockfile state', () => {
|
|
const worktreeA = mkdtempForTestSync('agent-device-lockfile-sync-worktree-a-');
|
|
const worktreeB = mkdtempForTestSync('agent-device-lockfile-sync-worktree-b-');
|
|
writeLockfile(worktreeA, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
writeInstalledSnapshot(worktreeA, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
writeLockfile(
|
|
worktreeB,
|
|
'lockfileVersion: 9.0\nimporters:\n .:\n dependencies:\n newDep: 1.0.0\n',
|
|
);
|
|
writeInstalledSnapshot(worktreeB, 'lockfileVersion: 9.0\nimporters:\n .: {}\n');
|
|
|
|
assert.deepEqual(checkLockfileInstallSync(worktreeA), { status: 'in-sync' });
|
|
assert.deepEqual(checkLockfileInstallSync(worktreeB), { status: 'out-of-sync', reason: 'stale' });
|
|
});
|