Fix workspace peer-dep, strict-mode, and workspace-range bugs

- checkMissingDependencies: stop flagging workspace-protocol peer
  dependencies as missing from the main app; the redundant OR clause
  bypassed the workspace exclusion filter.
- updateDependencies: strict mode now leaves pinned (non-range)
  versions untouched instead of force-updating them, matching the
  documented behavior.
- getSemverVersion: strip leftover range operators (^, ~, etc.) from
  workspace:-prefixed versions so workspace:^x.y.z and workspace:~x.y.z
  resolve correctly instead of being treated as invalid.
- Remove unreachable duplicate JSON-building branch in displayResults.
- bin/dep-check.js: exit with a non-zero status on unhandled errors
  instead of silently exiting 0.
- index.ts: replace the fragile file-URL string slicing used to detect
  direct execution with a proper pathToFileURL comparison.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LQedTpSTfxtbYUYo3gdT7v
This commit is contained in:
Claude committed 2026-09-23 22:05:58 +00:00
1 parent 9054713385
commit ce37f7990b
3 files changed
+26 -71

No files matched your search

+4 -1
View File
@@ -12,4 +12,7 @@ const require = createRequire(import.meta.url);
// Convert the file path to a proper file:// URL // Convert the file path to a proper file:// URL
const modulePath = pathToFileURL(join(__dirname, '../dist/index.js')).href; const modulePath = pathToFileURL(join(__dirname, '../dist/index.js')).href;
const { default: run } = await import(modulePath); const { default: run } = await import(modulePath);
run().catch(console.error); run().catch((error) => {
console.error(error);
process.exit(1);
});
+2 -1
View File
@@ -1,5 +1,6 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { pathToFileURL } from 'url';
import chalk from 'chalk'; import chalk from 'chalk';
import yargs from 'yargs'; import yargs from 'yargs';
import { hideBin } from 'yargs/helpers'; import { hideBin } from 'yargs/helpers';
@@ -157,7 +158,7 @@ const cli = async () => {
// Export both the CLI function and run it if this is the main module // Export both the CLI function and run it if this is the main module
export { cli as default }; export { cli as default };
if (process.argv[1] === import.meta.url.slice(7)) { if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
cli().catch(error => { cli().catch(error => {
console.error(chalk.red('Unexpected error:')); console.error(chalk.red('Unexpected error:'));
console.error(error); console.error(error);
+20 -69
View File
@@ -188,7 +188,8 @@ class DependencyChecker {
if (version.startsWith("workspace:")) { if (version.startsWith("workspace:")) {
const workspaceVersion = version.replace("workspace:", ""); const workspaceVersion = version.replace("workspace:", "");
if (workspaceVersion === "*") return null; if (workspaceVersion === "*") return null;
if (semver.valid(workspaceVersion)) return workspaceVersion; const cleanWorkspaceVersion = workspaceVersion.replace(/^[~^<>=]+\s*/g, "");
if (semver.valid(cleanWorkspaceVersion)) return cleanWorkspaceVersion;
return null; return null;
} }
@@ -241,9 +242,8 @@ class DependencyChecker {
Object.entries(packageDeps).forEach(([dep, version]) => { Object.entries(packageDeps).forEach(([dep, version]) => {
if ( if (
(!appDeps[dep] && !appDeps[dep] &&
this.shouldIncludeDependency(dep, version, packageJson)) || this.shouldIncludeDependency(dep, version, packageJson)
(packageJson.peerDependencies?.[dep] && !appDeps[dep])
) { ) {
missing.push({ name: dep, version }); missing.push({ name: dep, version });
uniqueMissingDeps.add(dep); uniqueMissingDeps.add(dep);
@@ -608,19 +608,22 @@ class DependencyChecker {
if (appDependencies[dep]) { if (appDependencies[dep]) {
const appVersion = appDependencies[dep]; const appVersion = appDependencies[dep];
// In strict mode, check if versions are compatible within range constraints if (this.strictMode) {
if (this.strictMode && this.hasVersionRange(version)) { // Strict mode: only touch dependencies with a version range,
if (!this.isVersionCompatible(version, appVersion)) { // and only when the app version falls outside that range.
// Only update if the app version doesn't satisfy the range if (
this.hasVersionRange(version) &&
!this.isVersionCompatible(version, appVersion)
) {
const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, ""); const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, "");
const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || ''; const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || '';
const newVersion = versionPrefix + cleanAppVersion; const newVersion = versionPrefix + cleanAppVersion;
if (!dryRun) { if (!dryRun) {
packageJson[section]![dep] = newVersion; packageJson[section]![dep] = newVersion;
} }
updates.push({ updates.push({
package: packageJson.name, package: packageJson.name,
dependency: dep, dependency: dep,
@@ -628,22 +631,22 @@ class DependencyChecker {
to: newVersion, to: newVersion,
type: section, type: section,
}); });
hasUpdates = true; hasUpdates = true;
} }
} else { } else {
// Original behavior for non-strict mode // Non-strict mode: always align to the app's version.
const cleanCurrentVersion = version.replace(/^[~^<>=]+\s*/g, ""); const cleanCurrentVersion = version.replace(/^[~^<>=]+\s*/g, "");
const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, ""); const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, "");
if (cleanCurrentVersion !== cleanAppVersion) { if (cleanCurrentVersion !== cleanAppVersion) {
const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || ''; const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || '';
const newVersion = versionPrefix + cleanAppVersion; const newVersion = versionPrefix + cleanAppVersion;
if (!dryRun) { if (!dryRun) {
packageJson[section]![dep] = newVersion; packageJson[section]![dep] = newVersion;
} }
updates.push({ updates.push({
package: packageJson.name, package: packageJson.name,
dependency: dep, dependency: dep,
@@ -651,7 +654,7 @@ class DependencyChecker {
to: newVersion, to: newVersion,
type: section, type: section,
}); });
hasUpdates = true; hasUpdates = true;
} }
} }
@@ -746,59 +749,7 @@ class DependencyChecker {
return; return;
} }
if (format === "text") { this.displayVersionDifferences();
this.displayVersionDifferences();
return;
}
const output = {
summary: {
conflicts: Array.from(this.dependencyMap.entries())
.filter(([, depInfo]) => depInfo.versions.size > 1)
.map(([dep, depInfo]) => ({
name: dep,
versions: Array.from(depInfo.versions.entries()).map(
([version, info]) => ({
version,
packages: Array.from(info.packages),
paths: Array.from(info.packages).map((pkg) => {
const filePath = this.packageJsonFiles.find((file) => {
const json = JSON.parse(
fs.readFileSync(file, "utf8")
) as PackageJson;
return json.name === pkg;
});
return {
package: pkg,
path: path.relative(process.cwd(), filePath || ""),
};
}),
usages: Array.from(info.usages),
})
),
})),
},
fullAnalysis: Object.fromEntries(
Array.from(this.dependencyMap.entries()).map(([dep, depInfo]) => [
dep,
{
versions: Object.fromEntries(
Array.from(depInfo.versions.entries()).map(([version, info]) => [
version,
{
packages: Array.from(info.packages),
usages: Array.from(info.usages),
},
])
),
usedAsNormal: depInfo.usedAsNormal,
usedAsPeer: depInfo.usedAsPeer,
},
])
),
};
console.log(JSON.stringify(output, null, 2));
} }
public async run(options: RunOptions = {}): Promise<void> { public async run(options: RunOptions = {}): Promise<void> {