mirror of
https://github.com/warkanum/monorepo-dep-checker.git
synced 2025-05-18 18:57:29 +00:00
Initial Version
This commit is contained in:
parent
8438e4c902
commit
07597a3764
64
.gitignore
vendored
Normal file
64
.gitignore
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# os
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
## yarn
|
||||||
|
.yarn-integrity
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
|
||||||
|
# editor
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
.nova/
|
||||||
|
.gitpod.yml
|
||||||
|
|
||||||
|
# node
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# env
|
||||||
|
.env.test
|
||||||
|
.env*.local
|
||||||
|
apps/**/.env
|
||||||
|
|
||||||
|
# build
|
||||||
|
lib/
|
||||||
|
cjs/
|
||||||
|
esm/
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# project
|
||||||
|
next-env.d.ts
|
||||||
|
.eslintcache
|
||||||
|
storybook-static
|
||||||
|
.next
|
||||||
|
packages/*/*/styles.css
|
||||||
|
packages/*/*/styles.layer.css
|
||||||
|
packages/@mantine/core/styles
|
||||||
|
.stylelintcache
|
||||||
|
____test.*
|
||||||
|
|
||||||
|
.turbo
|
||||||
|
*.log
|
||||||
|
.next
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
.cache
|
||||||
|
*storybook.log
|
||||||
|
*vite.config.ts.timestamp*
|
||||||
|
|
||||||
|
pnpm-lock.yaml
|
1
bin/dep-check.js
Normal file
1
bin/dep-check.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import('../dist/index.js').then(module => module.default()).catch(console.error);
|
57
package.json
Normal file
57
package.json
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "@warkypublic/monorepo-dep-checker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A CLI tool to check and manage dependencies across packages in a monorepo",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"bin": {
|
||||||
|
"dep-check": "./bin/dep-check.js"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"bin",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"test": "vitest run",
|
||||||
|
"lint": "eslint src"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"monorepo",
|
||||||
|
"dependencies",
|
||||||
|
"workspace",
|
||||||
|
"package",
|
||||||
|
"dependency-checker",
|
||||||
|
"cli",
|
||||||
|
"tools",
|
||||||
|
"dependency-management",
|
||||||
|
"npm",
|
||||||
|
"yarn",
|
||||||
|
"pnpm"
|
||||||
|
],
|
||||||
|
"author": "Hein (Warkanum) Puth",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"semver": "^7.5.4",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"vite": "^5.0.0",
|
||||||
|
"vitest": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/warkanum/monorepo-dep-checker"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/warkanum/monorepo-dep-checker/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/warkanum/monorepo-dep-checker#readme"
|
||||||
|
}
|
654
src/index.js
Normal file
654
src/index.js
Normal file
@ -0,0 +1,654 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import semver from 'semver';
|
||||||
|
import yargs from 'yargs';
|
||||||
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
|
||||||
|
class DependencyChecker {
|
||||||
|
constructor(appPackageJsonPath, packagesDir) {
|
||||||
|
this.appPackageJsonPath = appPackageJsonPath;
|
||||||
|
this.packagesDir = packagesDir;
|
||||||
|
this.packageJsonFiles = [];
|
||||||
|
this.dependencyMap = new Map();
|
||||||
|
this.workspacePackages = new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkspacePackages() {
|
||||||
|
const entries = fs.readdirSync(this.packagesDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const packageJsonPath = path.join(this.packagesDir, entry.name, 'package.json');
|
||||||
|
if (fs.existsSync(packageJsonPath)) {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||||
|
this.workspacePackages.add(packageJson.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isWorkspaceDep(version) {
|
||||||
|
return version?.startsWith('workspace:') || version === '*';
|
||||||
|
}
|
||||||
|
|
||||||
|
isWorkspacePackage(packageName) {
|
||||||
|
return this.workspacePackages.has(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldIncludeDependency(depName, version, packageJson) {
|
||||||
|
// Skip if it's a workspace dependency
|
||||||
|
if (this.isWorkspaceDep(version)) return false;
|
||||||
|
|
||||||
|
// Skip if the dependency is a workspace package
|
||||||
|
if (this.isWorkspacePackage(depName)) return false;
|
||||||
|
|
||||||
|
// Skip if the package itself is referenced as a workspace dependency
|
||||||
|
const allWorkspaceDeps = {
|
||||||
|
...packageJson.dependencies,
|
||||||
|
...packageJson.devDependencies,
|
||||||
|
...packageJson.peerDependencies,
|
||||||
|
};
|
||||||
|
|
||||||
|
return !Object.entries(allWorkspaceDeps).some(
|
||||||
|
([name, ver]) => name === depName && this.isWorkspaceDep(ver)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSemverVersion(version) {
|
||||||
|
// Handle workspace protocol
|
||||||
|
if (version.startsWith('workspace:')) {
|
||||||
|
// Extract actual version from the workspace package
|
||||||
|
const workspaceVersion = version.replace('workspace:', '');
|
||||||
|
if (workspaceVersion === '*') return null;
|
||||||
|
// If it's a specific version after workspace:, use that
|
||||||
|
if (semver.valid(workspaceVersion)) return workspaceVersion;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other special cases
|
||||||
|
if (version === '*' || version === 'latest') return null;
|
||||||
|
|
||||||
|
// Remove any leading special characters (^, ~, etc)
|
||||||
|
const cleanVersion = version.replace(/^[~^]/, '');
|
||||||
|
|
||||||
|
// Try to parse as semver
|
||||||
|
try {
|
||||||
|
if (semver.valid(cleanVersion)) return cleanVersion;
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkMissingDependencies() {
|
||||||
|
console.log(chalk.bold('\nChecking for missing dependencies...\n'));
|
||||||
|
|
||||||
|
this.findWorkspacePackages();
|
||||||
|
|
||||||
|
const appPackageJson = JSON.parse(fs.readFileSync(this.appPackageJsonPath, 'utf8'));
|
||||||
|
const appDeps = {
|
||||||
|
...appPackageJson.dependencies,
|
||||||
|
...appPackageJson.peerDependencies,
|
||||||
|
};
|
||||||
|
|
||||||
|
const missingDeps = new Map();
|
||||||
|
const uniqueMissingDeps = new Set();
|
||||||
|
const uniqueExtraDeps = new Set();
|
||||||
|
|
||||||
|
this.packageJsonFiles
|
||||||
|
.filter((file) => file !== this.appPackageJsonPath)
|
||||||
|
.forEach((filePath) => {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
const packageName = packageJson.name;
|
||||||
|
const packageDeps = {
|
||||||
|
...packageJson.dependencies,
|
||||||
|
...packageJson.peerDependencies,
|
||||||
|
};
|
||||||
|
|
||||||
|
const missing = [];
|
||||||
|
const extraDeps = [];
|
||||||
|
|
||||||
|
Object.entries(packageDeps).forEach(([dep, version]) => {
|
||||||
|
if (!appDeps[dep] && this.shouldIncludeDependency(dep, version, packageJson)) {
|
||||||
|
missing.push({
|
||||||
|
name: dep,
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
uniqueMissingDeps.add(dep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(appDeps).forEach(([dep, version]) => {
|
||||||
|
if (!packageDeps[dep] && this.shouldIncludeDependency(dep, version, appPackageJson)) {
|
||||||
|
extraDeps.push({
|
||||||
|
name: dep,
|
||||||
|
version,
|
||||||
|
});
|
||||||
|
uniqueExtraDeps.add(dep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (missing.length > 0 || extraDeps.length > 0) {
|
||||||
|
missingDeps.set(packageName, {
|
||||||
|
path: path.relative(process.cwd(), filePath),
|
||||||
|
missing,
|
||||||
|
extraDeps,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (missingDeps.size === 0) {
|
||||||
|
console.log(chalk.green('✓ All dependencies are properly synchronized\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
missingDeps.forEach(({ path: pkgPath, missing, extraDeps }, packageName) => {
|
||||||
|
if (missing.length > 0 || extraDeps.length > 0) {
|
||||||
|
console.log(chalk.yellow(`\n${packageName} (${chalk.gray(pkgPath)}):`));
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
console.log(chalk.red(' Missing from main app:'));
|
||||||
|
missing.forEach(({ name, version }) => {
|
||||||
|
console.log(` - ${name}@${version}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extraDeps.length > 0) {
|
||||||
|
console.log(chalk.blue(' Not used by package but in main app:'));
|
||||||
|
extraDeps.forEach(({ name, version }) => {
|
||||||
|
console.log(` - ${name}@${version}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (uniqueMissingDeps.size > 0 || uniqueExtraDeps.size > 0) {
|
||||||
|
console.log(chalk.bold('\nSummary:'));
|
||||||
|
console.log(`Packages with dependency mismatches: ${missingDeps.size}`);
|
||||||
|
if (uniqueMissingDeps.size > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.red(`Unique dependencies missing from main app: ${uniqueMissingDeps.size}`)
|
||||||
|
);
|
||||||
|
console.log(chalk.gray(' ' + Array.from(uniqueMissingDeps).join(', ')));
|
||||||
|
}
|
||||||
|
if (uniqueExtraDeps.size > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.blue(`Unique unused dependencies from main app: ${uniqueExtraDeps.size}`)
|
||||||
|
);
|
||||||
|
console.log(chalk.gray(' ' + Array.from(uniqueExtraDeps).join(', ')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compareDependencyVersions(version1, version2) {
|
||||||
|
const v1 = this.getSemverVersion(version1);
|
||||||
|
const v2 = this.getSemverVersion(version2);
|
||||||
|
|
||||||
|
// If either version can't be parsed, return 'unknown'
|
||||||
|
if (!v1 || !v2) return 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
return semver.diff(v1, v2);
|
||||||
|
} catch {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getVersionLabel(version) {
|
||||||
|
if (version.startsWith('workspace:')) {
|
||||||
|
return `${chalk.cyan('workspace:')}${version.slice(10)}`;
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
findPackageJsonFiles() {
|
||||||
|
this.packageJsonFiles.push(this.appPackageJsonPath);
|
||||||
|
|
||||||
|
const entries = fs.readdirSync(this.packagesDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
const packageJsonPath = path.join(this.packagesDir, entry.name, 'package.json');
|
||||||
|
if (fs.existsSync(packageJsonPath)) {
|
||||||
|
this.packageJsonFiles.push(packageJsonPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeDependencies() {
|
||||||
|
this.packageJsonFiles.forEach((filePath) => {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
const normalDeps = packageJson.dependencies || {};
|
||||||
|
const peerDeps = packageJson.peerDependencies || {};
|
||||||
|
const packageName = packageJson.name;
|
||||||
|
const isMainApp = filePath === this.appPackageJsonPath;
|
||||||
|
|
||||||
|
[
|
||||||
|
{ deps: normalDeps, type: 'normal' },
|
||||||
|
{ deps: peerDeps, type: 'peer' },
|
||||||
|
].forEach(({ deps, type }) => {
|
||||||
|
Object.entries(deps).forEach(([dep, version]) => {
|
||||||
|
if (!this.dependencyMap.has(dep)) {
|
||||||
|
this.dependencyMap.set(dep, {
|
||||||
|
versions: new Map(),
|
||||||
|
usedAsNormal: false,
|
||||||
|
usedAsPeer: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const depInfo = this.dependencyMap.get(dep);
|
||||||
|
if (!depInfo.versions.has(version)) {
|
||||||
|
depInfo.versions.set(version, {
|
||||||
|
packages: new Set(),
|
||||||
|
usages: new Set(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionInfo = depInfo.versions.get(version);
|
||||||
|
versionInfo.packages.add(packageName);
|
||||||
|
versionInfo.usages.add(`${type}${isMainApp ? ' (main app)' : ''}`);
|
||||||
|
|
||||||
|
if (type === 'normal') depInfo.usedAsNormal = true;
|
||||||
|
if (type === 'peer') depInfo.usedAsPeer = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
displayVersionDifferences() {
|
||||||
|
console.log(chalk.bold('\nVersion Differences Summary:\n'));
|
||||||
|
|
||||||
|
let hasDifferences = false;
|
||||||
|
const conflictingDeps = [];
|
||||||
|
|
||||||
|
this.dependencyMap.forEach((depInfo, dep) => {
|
||||||
|
if (depInfo.versions.size > 1) {
|
||||||
|
hasDifferences = true;
|
||||||
|
const 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'));
|
||||||
|
return json.name === pkg;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
package: pkg,
|
||||||
|
path: path.relative(process.cwd(), filePath),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
usages: Array.from(info.usages),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Create comparison matrix
|
||||||
|
const comparisons = [];
|
||||||
|
for (let i = 0; i < versions.length; i++) {
|
||||||
|
for (let j = i + 1; j < versions.length; j++) {
|
||||||
|
const v1 = versions[i];
|
||||||
|
const v2 = versions[j];
|
||||||
|
const comparison = {
|
||||||
|
version1: v1.version,
|
||||||
|
version2: v2.version,
|
||||||
|
packages1: v1.paths,
|
||||||
|
packages2: v2.paths,
|
||||||
|
semverDiff: this.compareDependencyVersions(v1.version, v2.version),
|
||||||
|
};
|
||||||
|
comparisons.push(comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conflictingDeps.push({
|
||||||
|
name: dep,
|
||||||
|
versions,
|
||||||
|
comparisons,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasDifferences) {
|
||||||
|
console.log(chalk.green('✓ No version differences found across packages\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
conflictingDeps.forEach(({ name, comparisons }) => {
|
||||||
|
console.log(chalk.yellow(`\n${name}:`));
|
||||||
|
|
||||||
|
comparisons.forEach(({ version1, version2, packages1, packages2, semverDiff }) => {
|
||||||
|
console.log(chalk.cyan(`\n Difference (${semverDiff || 'unknown'}):`));
|
||||||
|
console.log(` ${this.getVersionLabel(version1)} vs ${this.getVersionLabel(version2)}`);
|
||||||
|
|
||||||
|
console.log('\n Packages using', chalk.green(this.getVersionLabel(version1)), ':');
|
||||||
|
packages1.forEach(({ package: pkg, path: filePath }) => {
|
||||||
|
console.log(` - ${chalk.blue(pkg)} (${chalk.gray(filePath)})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\n Packages using', chalk.green(this.getVersionLabel(version2)), ':');
|
||||||
|
packages2.forEach(({ package: pkg, path: filePath }) => {
|
||||||
|
console.log(` - ${chalk.blue(pkg)} (${chalk.gray(filePath)})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Suggest recommended action based on version types
|
||||||
|
console.log('\n Recommended action:');
|
||||||
|
if (version1.startsWith('workspace:') || version2.startsWith('workspace:')) {
|
||||||
|
console.log(chalk.blue(' ℹ️ Workspace dependency - No action needed'));
|
||||||
|
} else {
|
||||||
|
switch (semverDiff) {
|
||||||
|
case 'major':
|
||||||
|
console.log(
|
||||||
|
chalk.red(' ⚠️ Major version difference - Manual review recommended')
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'minor':
|
||||||
|
console.log(
|
||||||
|
chalk.yellow(' ℹ️ Minor version difference - Consider updating to latest')
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'patch':
|
||||||
|
console.log(chalk.green(' ✓ Patch version difference - Safe to update to latest'));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log(chalk.gray(' ℹ️ Version difference analysis not available'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Print total counts
|
||||||
|
console.log(chalk.bold('\nSummary Statistics:'));
|
||||||
|
const totalComparisons = conflictingDeps.reduce((acc, dep) => acc + dep.comparisons.length, 0);
|
||||||
|
console.log(`Dependencies with conflicts: ${conflictingDeps.length}`);
|
||||||
|
console.log(`Total version comparisons: ${totalComparisons}`);
|
||||||
|
|
||||||
|
// Show severity breakdown
|
||||||
|
const severityCount = conflictingDeps.reduce((acc, dep) => {
|
||||||
|
dep.comparisons.forEach(({ version1, version2, semverDiff }) => {
|
||||||
|
if (version1.startsWith('workspace:') || version2.startsWith('workspace:')) {
|
||||||
|
acc.workspace = (acc.workspace || 0) + 1;
|
||||||
|
} else {
|
||||||
|
acc[semverDiff || 'unknown'] = (acc[semverDiff || 'unknown'] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
console.log('\nSeverity breakdown:');
|
||||||
|
if (severityCount.workspace)
|
||||||
|
console.log(chalk.blue(` Workspace dependencies: ${severityCount.workspace}`));
|
||||||
|
if (severityCount.major) console.log(chalk.red(` Major differences: ${severityCount.major}`));
|
||||||
|
if (severityCount.minor)
|
||||||
|
console.log(chalk.yellow(` Minor differences: ${severityCount.minor}`));
|
||||||
|
if (severityCount.patch)
|
||||||
|
console.log(chalk.green(` Patch differences: ${severityCount.patch}`));
|
||||||
|
if (severityCount.unknown)
|
||||||
|
console.log(chalk.gray(` Unknown differences: ${severityCount.unknown}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
displayResults(format = 'text') {
|
||||||
|
// First show the version differences summary
|
||||||
|
if (format === 'text') {
|
||||||
|
this.displayVersionDifferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then show the full analysis
|
||||||
|
if (format === 'json') {
|
||||||
|
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'));
|
||||||
|
return json.name === pkg;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
package: pkg,
|
||||||
|
path: path.relative(process.cwd(), filePath),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
usages: Array.from(info.usages),
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
fullAnalysis: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.dependencyMap.forEach((depInfo, dep) => {
|
||||||
|
output.fullAnalysis[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));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original detailed display
|
||||||
|
console.log(chalk.bold('\nDetailed Dependencies Analysis:\n'));
|
||||||
|
|
||||||
|
this.dependencyMap.forEach((depInfo, dep) => {
|
||||||
|
console.log(chalk.blue(`\n${dep}:`));
|
||||||
|
|
||||||
|
if (depInfo.versions.size > 1) {
|
||||||
|
console.log(chalk.yellow('⚠️ Multiple versions detected:'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const usageTypes = [];
|
||||||
|
if (depInfo.usedAsNormal) usageTypes.push('normal dependency');
|
||||||
|
if (depInfo.usedAsPeer) usageTypes.push('peer dependency');
|
||||||
|
console.log(chalk.cyan(`Used as: ${usageTypes.join(' and ')}`));
|
||||||
|
|
||||||
|
depInfo.versions.forEach((versionInfo, version) => {
|
||||||
|
console.log(` ${chalk.green(version)}`);
|
||||||
|
console.log(` Usage types:`);
|
||||||
|
versionInfo.usages.forEach((usage) => {
|
||||||
|
console.log(` - ${usage}`);
|
||||||
|
});
|
||||||
|
console.log(` Packages:`);
|
||||||
|
versionInfo.packages.forEach((pkg) => {
|
||||||
|
console.log(` - ${pkg}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateDependencies(dryRun = false) {
|
||||||
|
console.log(chalk.bold('\nUpdating dependencies...\n'));
|
||||||
|
|
||||||
|
const updates = [];
|
||||||
|
this.dependencyMap.forEach((depInfo, dep) => {
|
||||||
|
const allVersions = Array.from(depInfo.versions.keys()).filter(
|
||||||
|
(v) => !v.startsWith('workspace:')
|
||||||
|
); // Skip workspace dependencies
|
||||||
|
|
||||||
|
if (allVersions.length === 0) return; // Skip if only workspace versions exist
|
||||||
|
|
||||||
|
const cleanVersions = allVersions.map((v) => this.getSemverVersion(v)).filter(Boolean);
|
||||||
|
|
||||||
|
if (cleanVersions.length === 0) return; // Skip if no valid versions
|
||||||
|
|
||||||
|
const highestVersion = semver.maxSatisfying(cleanVersions, '*');
|
||||||
|
|
||||||
|
if (highestVersion) {
|
||||||
|
this.packageJsonFiles.forEach((filePath) => {
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
// Don't update workspace dependencies
|
||||||
|
if (
|
||||||
|
packageJson.dependencies?.[dep] &&
|
||||||
|
!packageJson.dependencies[dep].startsWith('workspace:')
|
||||||
|
) {
|
||||||
|
if (!dryRun) {
|
||||||
|
packageJson.dependencies[dep] = `^${highestVersion}`;
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
packageJson.peerDependencies?.[dep] &&
|
||||||
|
!packageJson.peerDependencies[dep].startsWith('workspace:')
|
||||||
|
) {
|
||||||
|
if (!dryRun) {
|
||||||
|
packageJson.peerDependencies[dep] = `^${highestVersion}`;
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
if (!dryRun) {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(packageJson, null, 2));
|
||||||
|
}
|
||||||
|
updates.push({
|
||||||
|
package: packageJson.name,
|
||||||
|
dependency: dep,
|
||||||
|
version: highestVersion,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updates.forEach(({ package: pkgName, dependency, version }) => {
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
`${dryRun ? '[DRY RUN] Would update' : 'Updated'} ${dependency} to ^${version} in ${pkgName}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run({
|
||||||
|
update = false,
|
||||||
|
dryRun = false,
|
||||||
|
format = 'text',
|
||||||
|
checkVersions = false,
|
||||||
|
checkMissing = false,
|
||||||
|
} = {}) {
|
||||||
|
if (checkMissing) {
|
||||||
|
this.findPackageJsonFiles();
|
||||||
|
this.checkMissingDependencies();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkVersions) {
|
||||||
|
this.findPackageJsonFiles();
|
||||||
|
this.analyzeDependencies();
|
||||||
|
this.displayVersionDifferences();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
this.findPackageJsonFiles();
|
||||||
|
this.analyzeDependencies();
|
||||||
|
await this.updateDependencies(dryRun);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default behavior - show full analysis
|
||||||
|
this.findPackageJsonFiles();
|
||||||
|
this.analyzeDependencies();
|
||||||
|
this.displayResults(format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CLI implementation
|
||||||
|
const run = async () => {
|
||||||
|
const argv = yargs(hideBin(process.argv))
|
||||||
|
.usage('Usage: $0 [options]')
|
||||||
|
.option('app', {
|
||||||
|
alias: 'a',
|
||||||
|
describe: 'Path to main app package.json',
|
||||||
|
type: 'string',
|
||||||
|
default: './package.json',
|
||||||
|
})
|
||||||
|
.option('packages', {
|
||||||
|
alias: 'p',
|
||||||
|
describe: 'Path to packages directory',
|
||||||
|
type: 'string',
|
||||||
|
default: './packages',
|
||||||
|
})
|
||||||
|
.option('update', {
|
||||||
|
alias: 'u',
|
||||||
|
describe: 'Update dependencies to highest compatible version',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
.option('dry-run', {
|
||||||
|
alias: 'd',
|
||||||
|
describe: 'Show what would be updated without making changes',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
.option('check-versions', {
|
||||||
|
alias: 'v',
|
||||||
|
describe: 'Check for version differences between packages',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
.option('check-missing', {
|
||||||
|
alias: 'm',
|
||||||
|
describe: 'Check for dependencies missing between app and packages',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
.option('format', {
|
||||||
|
alias: 'f',
|
||||||
|
describe: 'Output format (text or json)',
|
||||||
|
choices: ['text', 'json'],
|
||||||
|
default: 'text',
|
||||||
|
})
|
||||||
|
.help()
|
||||||
|
.alias('help', 'h')
|
||||||
|
.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').argv;
|
||||||
|
|
||||||
|
const appPackageJson = path.resolve(process.cwd(), argv.app);
|
||||||
|
const packagesDir = path.resolve(process.cwd(), argv.packages);
|
||||||
|
|
||||||
|
// Validate paths
|
||||||
|
if (!fs.existsSync(appPackageJson)) {
|
||||||
|
console.error(chalk.red(`Error: Main package.json not found at ${appPackageJson}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(packagesDir)) {
|
||||||
|
console.error(chalk.red(`Error: Packages directory not found at ${packagesDir}`));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checker = new DependencyChecker(appPackageJson, packagesDir);
|
||||||
|
await checker.run({
|
||||||
|
update: argv.update,
|
||||||
|
dryRun: argv.dryRun,
|
||||||
|
format: argv.format,
|
||||||
|
checkVersions: argv.checkVersions,
|
||||||
|
checkMissing: argv.checkMissing,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export both the class and run function
|
||||||
|
export { DependencyChecker, run as default };
|
||||||
|
|
||||||
|
if (process.argv[1] === import.meta.url.slice(7)) {
|
||||||
|
console.log('Running');
|
||||||
|
run();
|
||||||
|
}
|
27
vite.config.js
Normal file
27
vite.config.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.js'),
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: 'index'
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
'fs',
|
||||||
|
'path',
|
||||||
|
'url',
|
||||||
|
'chalk',
|
||||||
|
'semver',
|
||||||
|
'yargs',
|
||||||
|
'yargs/helpers'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
target: 'node14',
|
||||||
|
outDir: 'dist',
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true
|
||||||
|
}
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user