Files
ECC/scripts/lib/missing-dependency.js
T
wakqasahmedandhaelyra fe3d82e280 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.
2026-09-07 16:26:10 -04:00

66 lines
2.3 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
// Runtime dependencies that can be missing when ECC is installed via the
// Claude Code plugin marketplace (a plain git clone, so `npm install` never
// runs), even though the code that needs them is fine. Versions are read
// straight from package.json's "dependencies" field instead of a second
// hardcoded copy, so this can't silently drift out of sync with what's
// actually declared there.
const TRACKED_DEPENDENCIES = ['ajv', 'sql.js', 'js-yaml', '@iarna/toml'];
function loadRuntimeDependencyVersions() {
try {
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json');
const declared = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).dependencies || {};
const versions = {};
for (const name of TRACKED_DEPENDENCIES) {
const declaredVersion = declared[name];
if (declaredVersion) {
versions[name] = declaredVersion.replace(/^[\^~]/, '');
}
}
return versions;
} catch {
// package.json isn't reachable from here for some reason. Fall back to
// an empty map rather than crash — describeMissingDependencyError()
// just won't be able to suggest a pinned version in that case.
return {};
}
}
const RUNTIME_DEPENDENCY_VERSIONS = loadRuntimeDependencyVersions();
function describeMissingDependencyError(error) {
if (!error || error.code !== 'MODULE_NOT_FOUND') {
return null;
}
const match = /Cannot find module '([^']+)'/.exec(error.message || '');
const moduleName = match && match[1];
if (!moduleName || !TRACKED_DEPENDENCIES.includes(moduleName)) {
return null;
}
const pinnedVersion = RUNTIME_DEPENDENCY_VERSIONS[moduleName];
const installCommand = pinnedVersion
? `npm install --no-save ${moduleName}@${pinnedVersion}`
: `npm install --no-save ${moduleName}`;
return (
`Missing dependency '${moduleName}'. ECC's production dependencies aren't installed ` +
'(this happens when ECC was installed via the Claude Code plugin marketplace, which ' +
'clones the repo but never runs npm install). Run "npm install" from the ECC repo ' +
`root, or install just this package with "${installCommand}".`
);
}
module.exports = {
RUNTIME_DEPENDENCY_VERSIONS,
describeMissingDependencyError,
};