fix(install): derive dependency versions from package.json, harden fixture isolation (#2822)

Addresses Greptile's review on #2994:

- missing-dependency.js no longer hardcodes a second copy of the four
  runtime dependency versions; it reads them from package.json's
  dependencies field instead, so the two can't silently drift apart.
  describeMissingDependencyError() still recognizes a tracked
  dependency even if package.json can't be read for some reason,
  just without a version-pinned install command in that case.
- The regression test now asserts no ancestor directory of its
  temp fixture has a node_modules, so a stray one wouldn't let
  Node resolve ajv/sql.js from there and mask what the test is
  actually meant to exercise. Also copies package.json into the
  fixture, matching a real plugin-marketplace git clone and what
  the version-lookup above now needs.
This commit is contained in:
wakqasahmed
2026-09-07 16:26:10 -04:00
committed by haelyra
parent ce11e8f690
commit fe3d82e280
2 changed files with 65 additions and 14 deletions
@@ -30,11 +30,31 @@ function test(name, fn) {
}
function copyRuntimeFiles(destDir) {
for (const entry of ['scripts', 'schemas', 'manifests']) {
for (const entry of ['scripts', 'schemas', 'manifests', 'package.json']) {
fs.cpSync(path.join(REPO_ROOT, entry), path.join(destDir, entry), { recursive: true });
}
}
// Node's module resolution walks up the directory tree looking for
// node_modules, so if any ancestor of pluginDir happened to have one, a
// require('ajv') from inside pluginDir could resolve there instead of
// hitting the MODULE_NOT_FOUND path this test exists to exercise. Confirm
// the fixture is actually isolated before trusting any of the results below.
function assertNoNodeModulesInAncestry(dir) {
let current = dir;
while (true) {
if (fs.existsSync(path.join(current, 'node_modules'))) {
throw new Error(
`Fixture is not isolated: ${path.join(current, 'node_modules')} exists, so this test ` +
'would resolve dependencies from there instead of exercising the missing-dependency path.'
);
}
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}
}
function run(scriptRelativePath, args, cwd) {
try {
const stdout = execFileSync('node', [path.join(cwd, scriptRelativePath), ...args], {
@@ -63,6 +83,10 @@ function runTests() {
const pluginDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-plugin-install-'));
try {
if (test('fixture has no node_modules anywhere in its ancestor chain', () => {
assertNoNodeModulesInAncestry(pluginDir);
})) passed++; else failed++;
copyRuntimeFiles(pluginDir);
if (test('install-plan.js --list-profiles runs without ajv installed', () => {