diff --git a/src/index.ts b/src/index.ts index 5731adb..8be395f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -52,9 +52,16 @@ const cli = async () => { default: 'text', type:'string' }) + .option('strict', { + alias: 's', + describe: 'Strict mode: only update packages with version ranges (~, ^, >=) if they are incompatible', + type: 'boolean', + default: false, + }) .example('$0 --check-versions', 'Show only version differences') .example('$0 --check-missing', 'Show missing dependencies') .example('$0 --update --dry-run', 'Show what would be updated') + .example('$0 --update --strict', 'Update only incompatible versions within ranges') .example('$0 --packages ./packages,./other-packages', 'Check multiple package directories') .example('$0 --packages ./pkg1/package.json,./pkg2/package.json', 'Check specific package.json files') .example('$0 --packages ./packages,./other/package.json', 'Mix of directories and files') @@ -135,6 +142,7 @@ const cli = async () => { format: argv.format as any, checkVersions: argv.checkVersions, checkMissing: argv.checkMissing, + strict: argv.strict, }); } catch (error:any) { console.error(chalk.red('Error during execution:')); diff --git a/src/lib.ts b/src/lib.ts index dbf7681..b6abcd1 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -16,6 +16,7 @@ interface RunOptions { format?: "text" | "json"; checkVersions?: boolean; checkMissing?: boolean; + strict?: boolean; } interface VersionInfo { @@ -75,6 +76,7 @@ class DependencyChecker { private packageJsonFiles: string[]; private dependencyMap: Map; private workspacePackages: Set; + private strictMode: boolean; constructor(appPackageJsonPath: string, packagesInput: string) { this.appPackageJsonPath = appPackageJsonPath; @@ -82,6 +84,7 @@ class DependencyChecker { this.packageJsonFiles = []; this.dependencyMap = new Map(); this.workspacePackages = new Set(); + this.strictMode = false; } private findWorkspacePackages(): void { @@ -147,6 +150,21 @@ class DependencyChecker { return this.workspacePackages.has(packageName); } + private hasVersionRange(version: string): boolean { + return /^[~^<>=]+/.test(version); + } + + private isVersionCompatible(rangeVersion: string, targetVersion: string): boolean { + try { + const cleanTargetVersion = this.getSemverVersion(targetVersion); + if (!cleanTargetVersion) return false; + + return semver.satisfies(cleanTargetVersion, rangeVersion); + } catch { + return false; + } + } + private shouldIncludeDependency( depName: string, version: string, @@ -590,30 +608,52 @@ class DependencyChecker { if (appDependencies[dep]) { const appVersion = appDependencies[dep]; - // Extract just the version numbers for comparison - const cleanCurrentVersion = version.replace(/^[~^<>=]+\s*/g, ""); - const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, ""); - - if (cleanCurrentVersion !== cleanAppVersion) { - // Extract the version prefix (operators and spaces) - const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || ''; - - // Create new version string with original prefix but updated version number - const newVersion = versionPrefix + cleanAppVersion; - - if (!dryRun) { - packageJson[section]![dep] = newVersion; + + // In strict mode, check if versions are compatible within range constraints + if (this.strictMode && this.hasVersionRange(version)) { + if (!this.isVersionCompatible(version, appVersion)) { + // Only update if the app version doesn't satisfy the range + const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, ""); + const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || ''; + const newVersion = versionPrefix + cleanAppVersion; + + if (!dryRun) { + packageJson[section]![dep] = newVersion; + } + + updates.push({ + package: packageJson.name, + dependency: dep, + from: version, + to: newVersion, + type: section, + }); + + hasUpdates = true; + } + } else { + // Original behavior for non-strict mode + const cleanCurrentVersion = version.replace(/^[~^<>=]+\s*/g, ""); + const cleanAppVersion = appVersion.replace(/^[~^<>=]+\s*/g, ""); + + if (cleanCurrentVersion !== cleanAppVersion) { + const versionPrefix = version.match(/^[~^<>=]+\s*/)?.[0] || ''; + const newVersion = versionPrefix + cleanAppVersion; + + if (!dryRun) { + packageJson[section]![dep] = newVersion; + } + + updates.push({ + package: packageJson.name, + dependency: dep, + from: version, + to: newVersion, + type: section, + }); + + hasUpdates = true; } - - updates.push({ - package: packageJson.name, - dependency: dep, - from: version, - to: newVersion, - type: section, - }); - - hasUpdates = true; } } }); @@ -768,8 +808,11 @@ class DependencyChecker { format = "text", checkVersions = false, checkMissing = false, + strict = false, } = options; + this.strictMode = strict; + if (checkMissing) { this.findPackageJsonFiles(); this.checkMissingDependencies();