Skip to content

Commit

Permalink
feat: add node API
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed May 19, 2023
1 parent 2f0a554 commit 1863f70
Show file tree
Hide file tree
Showing 16 changed files with 887 additions and 308 deletions.
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ Found 2 dependencies with mismatching versions across the workspace. Fix with `-

## Options

These options are available on the CLI and as parameters to the [Node API](#node-api).

| Name | Description |
| --- | --- |
| :-- | :-- |
| `--fix` | Whether to autofix inconsistencies (using latest version present). |
| `--ignore-dep` | Dependency to ignore mismatches for (option can be repeated). |
| `--ignore-dep-pattern` | RegExp of dependency names to ignore mismatches for (option can be repeated). |
Expand All @@ -122,6 +124,45 @@ Found 2 dependencies with mismatching versions across the workspace. Fix with `-
| `--ignore-path` | Workspace-relative path of packages to ignore mismatches for (option can be repeated). |
| `--ignore-path-pattern` | RegExp of workspace-relative path of packages to ignore mismatches for (option can be repeated). |

## Node API

```ts
import { CDVC } from 'check-dependency-version-consistency';

const cdvc = new CDVC(path, options);

const result = cdvc.getDependency('eslint');

// Result could look like this:
const result = {
isFixable: true,
isMismatching: true,
name: 'eslint',
versions: [
{
packages: ['package1', 'package2'],
version: '^7.0.0',
},
{
packages: ['package3'],
version: '^8.0.0',
},
],
};
```

| Function | Description |
| :-- | :-- |
| `getDependencies()` | Returns an array of all dependencies in the workspace. |
| `getDependency(name: string)` | Returns information about an individual dependency. |
| `hasMismatchingDependenciesFixable()` | Returns `true` if there are any dependencies with mismatching versions that are autofixable. |
| `hasMismatchingDependenciesNotFixable()` | Returns `true` if there are any dependencies with mismatching versions that are not autofixable. |
| `hasMismatchingDependencies()` | Returns `true` if there are any dependencies with mismatching versions. |
| `toFixedSummary()` | Returns a string summary of the mismatching dependency versions that were fixed (if the `fix` option was specified). |
| `toMismatchSummary()` | Returns a string of human-readable tables describing the mismatching dependency versions. |

See the [`CDVC` class](./lib/cdvc.ts) or [`lib/cli.ts`](./lib/cli.ts) for an example of how to use it.

## Related

* [npm-package-json-lint](https://github.com/tclindner/npm-package-json-lint) — use this complementary tool to enforce that your dependency versions use consistent range types (i.e. [prefer-caret-version-dependencies](https://npmpackagejsonlint.org/docs/rules/dependencies/prefer-caret-version-dependencies), [prefer-caret-version-devDependencies](https://npmpackagejsonlint.org/docs/rules/dependencies/prefer-caret-version-devDependencies))
Expand Down
82 changes: 82 additions & 0 deletions lib/cdvc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { check } from './check.js';
import { Dependency } from './dependency.js';
import {
dependenciesToFixedSummary,
dependenciesToMismatchSummary,
} from './output.js';
import { Dependencies } from './types.js';

export class CDVC {
/** An object mapping each dependency in the workspace to information including the versions found of it. */
private readonly dependencies: Dependencies;

/**
* @param path - path to the workspace root
* @param options
* @param options.fix - Whether to autofix inconsistencies (using latest version present)
* @param options.ignoreDep - Dependency(s) to ignore mismatches for
* @param options.ignoreDepPattern - RegExp(s) of dependency names to ignore mismatches for
* @param options.ignorePackage - Workspace package(s) to ignore mismatches for
* @param options.ignorePackagePattern - RegExp(s) of package names to ignore mismatches for
* @param options.ignorePath - Workspace-relative path(s) of packages to ignore mismatches for
* @param options.ignorePathPattern - RegExp(s) of workspace-relative path of packages to ignore mismatches for
*/
constructor(
path: string,
options?: {
fix?: boolean;
ignoreDep?: string[];
ignoreDepPattern?: string[];
ignorePackage?: string[];
ignorePackagePattern?: string[];
ignorePath?: string[];
ignorePathPattern?: string[];
}
) {
const { dependencies } = check(path, options);

this.dependencies = dependencies;
}

public toMismatchSummary(): string {
return dependenciesToMismatchSummary(this.dependencies);
}

public toFixedSummary(): string {
return dependenciesToFixedSummary(this.dependencies);
}

public getDependencies(): Dependency[] {
return Object.keys(this.dependencies).map((dependency) =>
this.getDependency(dependency)
);
}

public getDependency(name: string): Dependency {
// Convert underlying dependency data to Dependency class which only exposes relevant data publicly.
return new Dependency(
name,
this.dependencies[name].isFixable,
this.dependencies[name].versions.map((version) => ({
version: version.version,
packages: version.packages.map((package_) => package_.pathRelative),
}))
);
}

public hasMismatchingDependencies(): boolean {
return Object.values(this.dependencies).some((dep) => dep.isMismatching);
}

public hasMismatchingDependenciesFixable(): boolean {
return Object.values(this.dependencies).some(
(dep) => dep.isMismatching && dep.isFixable
);
}

public hasMismatchingDependenciesNotFixable(): boolean {
return Object.values(this.dependencies).some(
(dep) => dep.isMismatching && !dep.isFixable
);
}
}
105 changes: 105 additions & 0 deletions lib/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import {
calculateVersionsForEachDependency,
calculateDependenciesAndVersions,
filterOutIgnoredDependencies,
fixVersionsMismatching,
} from './dependency-versions.js';
import { Dependencies } from './types.js';
import { getPackages } from './workspace.js';

/**
* Checks for inconsistencies across a workspace. Optionally fixes them.
* @param path - path to the workspace root
* @param options
* @param options.fix - Whether to autofix inconsistencies (using latest version present)
* @param options.ignoreDep - Dependency(s) to ignore mismatches for
* @param options.ignoreDepPattern - RegExp(s) of dependency names to ignore mismatches for
* @param options.ignorePackage - Workspace package(s) to ignore mismatches for
* @param options.ignorePackagePattern - RegExp(s) of package names to ignore mismatches for
* @param options.ignorePath - Workspace-relative path(s) of packages to ignore mismatches for
* @param options.ignorePathPattern - RegExp(s) of workspace-relative path of packages to ignore mismatches for
* @returns an object with the following properties:
* - `dependencies`: An object mapping each dependency in the workspace to information about it including the versions found of it.
*/
export function check(
path: string,
options?: {
fix?: boolean;
ignoreDep?: string[];
ignoreDepPattern?: string[];
ignorePackage?: string[];
ignorePackagePattern?: string[];
ignorePath?: string[];
ignorePathPattern?: string[];
}
): {
dependencies: Dependencies;
} {
const optionsWithDefaults = {
fix: false,
ignoreDep: [],
ignoreDepPattern: [],
ignorePackage: [],
ignorePackagePattern: [],
ignorePath: [],
ignorePathPattern: [],
...options,
};

// Calculate.
const packages = getPackages(
path,
optionsWithDefaults.ignorePackage,
optionsWithDefaults.ignorePackagePattern.map((s) => new RegExp(s)),
optionsWithDefaults.ignorePath,
optionsWithDefaults.ignorePathPattern.map((s) => new RegExp(s))
);

const dependencies = calculateVersionsForEachDependency(packages);
const dependenciesAndVersions =
calculateDependenciesAndVersions(dependencies);
const dependenciesAndVersionsWithMismatches = dependenciesAndVersions.filter(
({ versions }) => versions.length > 1
);

// Information about all dependencies.
const dependenciesAndVersionsWithoutIgnored = filterOutIgnoredDependencies(
dependenciesAndVersions,
optionsWithDefaults.ignoreDep,
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
);

// Information about mismatches.
const dependenciesAndVersionsMismatchesWithoutIgnored =
filterOutIgnoredDependencies(
dependenciesAndVersionsWithMismatches,
optionsWithDefaults.ignoreDep,
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
);
const resultsAfterFix = fixVersionsMismatching(
packages,
dependenciesAndVersionsMismatchesWithoutIgnored,
!optionsWithDefaults.fix // Do dry-run if not fixing.
);
const versionsMismatchingFixable = resultsAfterFix.fixable;

return {
// Information about all dependencies.
dependencies: Object.fromEntries(
dependenciesAndVersionsWithoutIgnored.map(({ dependency, versions }) => {
return [
dependency,
{
isFixable: versionsMismatchingFixable.some(
(dep) => dep.dependency === dependency
),
isMismatching: dependenciesAndVersionsMismatchesWithoutIgnored.some(
(dep) => dep.dependency === dependency
),
versions,
},
];
})
),
};
}
71 changes: 15 additions & 56 deletions lib/cli.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
import { Command, Argument } from 'commander';
import { readFileSync } from 'node:fs';
import {
calculateVersionsForEachDependency,
calculateMismatchingVersions,
filterOutIgnoredDependencies,
fixMismatchingVersions,
MismatchingDependencyVersions,
} from '../lib/dependency-versions.js';
import { getPackages } from '../lib/workspace.js';
import {
mismatchingVersionsToOutput,
mismatchingVersionsFixedToOutput,
} from '../lib/output.js';
import { join, dirname } from 'node:path';
import type { PackageJson } from 'type-fest';
import { fileURLToPath } from 'node:url';
import { CDVC } from './cdvc.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -81,53 +70,23 @@ export function run() {
collect,
[]
)
.action(function (
path,
options: {
ignoreDep: string[];
ignoreDepPattern: string[];
ignorePackage: string[];
ignorePackagePattern: string[];
ignorePath: string[];
ignorePathPattern: string[];
fix: boolean;
}
) {
// Calculate.
const packages = getPackages(
path,
options.ignorePackage,
options.ignorePackagePattern.map((s) => new RegExp(s)),
options.ignorePath,
options.ignorePathPattern.map((s) => new RegExp(s))
);

const dependencyVersions = calculateVersionsForEachDependency(packages);

let mismatchingVersions = filterOutIgnoredDependencies(
calculateMismatchingVersions(dependencyVersions),
options.ignoreDep,
options.ignoreDepPattern.map((s) => new RegExp(s))
);
let mismatchingVersionsFixed: MismatchingDependencyVersions = [];
.action((path, options) => {
const cdvc = new CDVC(path, options);

if (options.fix) {
const resultsAfterFix = fixMismatchingVersions(
packages,
mismatchingVersions
);
mismatchingVersions = resultsAfterFix.notFixed;
mismatchingVersionsFixed = resultsAfterFix.fixed;
}

// Show output for dependencies we fixed.
if (mismatchingVersionsFixed.length > 0) {
console.log(mismatchingVersionsFixedToOutput(mismatchingVersionsFixed));
}
// Show output for dependencies we fixed.
if (cdvc.hasMismatchingDependenciesFixable()) {
console.log(cdvc.toFixedSummary());
}

// Show output for dependencies that still have mismatches.
if (mismatchingVersions.length > 0) {
console.log(mismatchingVersionsToOutput(mismatchingVersions));
// Show output for dependencies that still have mismatches.
if (cdvc.hasMismatchingDependenciesNotFixable()) {
console.log(cdvc.toMismatchSummary());
process.exitCode = 1;
}
} else if (cdvc.hasMismatchingDependencies()) {
// Show output for dependencies that have mismatches.
console.log(cdvc.toMismatchSummary());
process.exitCode = 1;
}
})
Expand Down
Loading

0 comments on commit 1863f70

Please sign in to comment.