Skip to content

Commit 1863f70

Browse files
committed
feat: add node API
1 parent 2f0a554 commit 1863f70

File tree

16 files changed

+887
-308
lines changed

16 files changed

+887
-308
lines changed

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ Found 2 dependencies with mismatching versions across the workspace. Fix with `-
112112

113113
## Options
114114

115+
These options are available on the CLI and as parameters to the [Node API](#node-api).
116+
115117
| Name | Description |
116-
| --- | --- |
118+
| :-- | :-- |
117119
| `--fix` | Whether to autofix inconsistencies (using latest version present). |
118120
| `--ignore-dep` | Dependency to ignore mismatches for (option can be repeated). |
119121
| `--ignore-dep-pattern` | RegExp of dependency names to ignore mismatches for (option can be repeated). |
@@ -122,6 +124,45 @@ Found 2 dependencies with mismatching versions across the workspace. Fix with `-
122124
| `--ignore-path` | Workspace-relative path of packages to ignore mismatches for (option can be repeated). |
123125
| `--ignore-path-pattern` | RegExp of workspace-relative path of packages to ignore mismatches for (option can be repeated). |
124126

127+
## Node API
128+
129+
```ts
130+
import { CDVC } from 'check-dependency-version-consistency';
131+
132+
const cdvc = new CDVC(path, options);
133+
134+
const result = cdvc.getDependency('eslint');
135+
136+
// Result could look like this:
137+
const result = {
138+
isFixable: true,
139+
isMismatching: true,
140+
name: 'eslint',
141+
versions: [
142+
{
143+
packages: ['package1', 'package2'],
144+
version: '^7.0.0',
145+
},
146+
{
147+
packages: ['package3'],
148+
version: '^8.0.0',
149+
},
150+
],
151+
};
152+
```
153+
154+
| Function | Description |
155+
| :-- | :-- |
156+
| `getDependencies()` | Returns an array of all dependencies in the workspace. |
157+
| `getDependency(name: string)` | Returns information about an individual dependency. |
158+
| `hasMismatchingDependenciesFixable()` | Returns `true` if there are any dependencies with mismatching versions that are autofixable. |
159+
| `hasMismatchingDependenciesNotFixable()` | Returns `true` if there are any dependencies with mismatching versions that are not autofixable. |
160+
| `hasMismatchingDependencies()` | Returns `true` if there are any dependencies with mismatching versions. |
161+
| `toFixedSummary()` | Returns a string summary of the mismatching dependency versions that were fixed (if the `fix` option was specified). |
162+
| `toMismatchSummary()` | Returns a string of human-readable tables describing the mismatching dependency versions. |
163+
164+
See the [`CDVC` class](./lib/cdvc.ts) or [`lib/cli.ts`](./lib/cli.ts) for an example of how to use it.
165+
125166
## Related
126167

127168
* [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))

lib/cdvc.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { check } from './check.js';
2+
import { Dependency } from './dependency.js';
3+
import {
4+
dependenciesToFixedSummary,
5+
dependenciesToMismatchSummary,
6+
} from './output.js';
7+
import { Dependencies } from './types.js';
8+
9+
export class CDVC {
10+
/** An object mapping each dependency in the workspace to information including the versions found of it. */
11+
private readonly dependencies: Dependencies;
12+
13+
/**
14+
* @param path - path to the workspace root
15+
* @param options
16+
* @param options.fix - Whether to autofix inconsistencies (using latest version present)
17+
* @param options.ignoreDep - Dependency(s) to ignore mismatches for
18+
* @param options.ignoreDepPattern - RegExp(s) of dependency names to ignore mismatches for
19+
* @param options.ignorePackage - Workspace package(s) to ignore mismatches for
20+
* @param options.ignorePackagePattern - RegExp(s) of package names to ignore mismatches for
21+
* @param options.ignorePath - Workspace-relative path(s) of packages to ignore mismatches for
22+
* @param options.ignorePathPattern - RegExp(s) of workspace-relative path of packages to ignore mismatches for
23+
*/
24+
constructor(
25+
path: string,
26+
options?: {
27+
fix?: boolean;
28+
ignoreDep?: string[];
29+
ignoreDepPattern?: string[];
30+
ignorePackage?: string[];
31+
ignorePackagePattern?: string[];
32+
ignorePath?: string[];
33+
ignorePathPattern?: string[];
34+
}
35+
) {
36+
const { dependencies } = check(path, options);
37+
38+
this.dependencies = dependencies;
39+
}
40+
41+
public toMismatchSummary(): string {
42+
return dependenciesToMismatchSummary(this.dependencies);
43+
}
44+
45+
public toFixedSummary(): string {
46+
return dependenciesToFixedSummary(this.dependencies);
47+
}
48+
49+
public getDependencies(): Dependency[] {
50+
return Object.keys(this.dependencies).map((dependency) =>
51+
this.getDependency(dependency)
52+
);
53+
}
54+
55+
public getDependency(name: string): Dependency {
56+
// Convert underlying dependency data to Dependency class which only exposes relevant data publicly.
57+
return new Dependency(
58+
name,
59+
this.dependencies[name].isFixable,
60+
this.dependencies[name].versions.map((version) => ({
61+
version: version.version,
62+
packages: version.packages.map((package_) => package_.pathRelative),
63+
}))
64+
);
65+
}
66+
67+
public hasMismatchingDependencies(): boolean {
68+
return Object.values(this.dependencies).some((dep) => dep.isMismatching);
69+
}
70+
71+
public hasMismatchingDependenciesFixable(): boolean {
72+
return Object.values(this.dependencies).some(
73+
(dep) => dep.isMismatching && dep.isFixable
74+
);
75+
}
76+
77+
public hasMismatchingDependenciesNotFixable(): boolean {
78+
return Object.values(this.dependencies).some(
79+
(dep) => dep.isMismatching && !dep.isFixable
80+
);
81+
}
82+
}

lib/check.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
calculateVersionsForEachDependency,
3+
calculateDependenciesAndVersions,
4+
filterOutIgnoredDependencies,
5+
fixVersionsMismatching,
6+
} from './dependency-versions.js';
7+
import { Dependencies } from './types.js';
8+
import { getPackages } from './workspace.js';
9+
10+
/**
11+
* Checks for inconsistencies across a workspace. Optionally fixes them.
12+
* @param path - path to the workspace root
13+
* @param options
14+
* @param options.fix - Whether to autofix inconsistencies (using latest version present)
15+
* @param options.ignoreDep - Dependency(s) to ignore mismatches for
16+
* @param options.ignoreDepPattern - RegExp(s) of dependency names to ignore mismatches for
17+
* @param options.ignorePackage - Workspace package(s) to ignore mismatches for
18+
* @param options.ignorePackagePattern - RegExp(s) of package names to ignore mismatches for
19+
* @param options.ignorePath - Workspace-relative path(s) of packages to ignore mismatches for
20+
* @param options.ignorePathPattern - RegExp(s) of workspace-relative path of packages to ignore mismatches for
21+
* @returns an object with the following properties:
22+
* - `dependencies`: An object mapping each dependency in the workspace to information about it including the versions found of it.
23+
*/
24+
export function check(
25+
path: string,
26+
options?: {
27+
fix?: boolean;
28+
ignoreDep?: string[];
29+
ignoreDepPattern?: string[];
30+
ignorePackage?: string[];
31+
ignorePackagePattern?: string[];
32+
ignorePath?: string[];
33+
ignorePathPattern?: string[];
34+
}
35+
): {
36+
dependencies: Dependencies;
37+
} {
38+
const optionsWithDefaults = {
39+
fix: false,
40+
ignoreDep: [],
41+
ignoreDepPattern: [],
42+
ignorePackage: [],
43+
ignorePackagePattern: [],
44+
ignorePath: [],
45+
ignorePathPattern: [],
46+
...options,
47+
};
48+
49+
// Calculate.
50+
const packages = getPackages(
51+
path,
52+
optionsWithDefaults.ignorePackage,
53+
optionsWithDefaults.ignorePackagePattern.map((s) => new RegExp(s)),
54+
optionsWithDefaults.ignorePath,
55+
optionsWithDefaults.ignorePathPattern.map((s) => new RegExp(s))
56+
);
57+
58+
const dependencies = calculateVersionsForEachDependency(packages);
59+
const dependenciesAndVersions =
60+
calculateDependenciesAndVersions(dependencies);
61+
const dependenciesAndVersionsWithMismatches = dependenciesAndVersions.filter(
62+
({ versions }) => versions.length > 1
63+
);
64+
65+
// Information about all dependencies.
66+
const dependenciesAndVersionsWithoutIgnored = filterOutIgnoredDependencies(
67+
dependenciesAndVersions,
68+
optionsWithDefaults.ignoreDep,
69+
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
70+
);
71+
72+
// Information about mismatches.
73+
const dependenciesAndVersionsMismatchesWithoutIgnored =
74+
filterOutIgnoredDependencies(
75+
dependenciesAndVersionsWithMismatches,
76+
optionsWithDefaults.ignoreDep,
77+
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
78+
);
79+
const resultsAfterFix = fixVersionsMismatching(
80+
packages,
81+
dependenciesAndVersionsMismatchesWithoutIgnored,
82+
!optionsWithDefaults.fix // Do dry-run if not fixing.
83+
);
84+
const versionsMismatchingFixable = resultsAfterFix.fixable;
85+
86+
return {
87+
// Information about all dependencies.
88+
dependencies: Object.fromEntries(
89+
dependenciesAndVersionsWithoutIgnored.map(({ dependency, versions }) => {
90+
return [
91+
dependency,
92+
{
93+
isFixable: versionsMismatchingFixable.some(
94+
(dep) => dep.dependency === dependency
95+
),
96+
isMismatching: dependenciesAndVersionsMismatchesWithoutIgnored.some(
97+
(dep) => dep.dependency === dependency
98+
),
99+
versions,
100+
},
101+
];
102+
})
103+
),
104+
};
105+
}

lib/cli.ts

Lines changed: 15 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import { Command, Argument } from 'commander';
22
import { readFileSync } from 'node:fs';
3-
import {
4-
calculateVersionsForEachDependency,
5-
calculateMismatchingVersions,
6-
filterOutIgnoredDependencies,
7-
fixMismatchingVersions,
8-
MismatchingDependencyVersions,
9-
} from '../lib/dependency-versions.js';
10-
import { getPackages } from '../lib/workspace.js';
11-
import {
12-
mismatchingVersionsToOutput,
13-
mismatchingVersionsFixedToOutput,
14-
} from '../lib/output.js';
153
import { join, dirname } from 'node:path';
164
import type { PackageJson } from 'type-fest';
175
import { fileURLToPath } from 'node:url';
6+
import { CDVC } from './cdvc.js';
187

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

@@ -81,53 +70,23 @@ export function run() {
8170
collect,
8271
[]
8372
)
84-
.action(function (
85-
path,
86-
options: {
87-
ignoreDep: string[];
88-
ignoreDepPattern: string[];
89-
ignorePackage: string[];
90-
ignorePackagePattern: string[];
91-
ignorePath: string[];
92-
ignorePathPattern: string[];
93-
fix: boolean;
94-
}
95-
) {
96-
// Calculate.
97-
const packages = getPackages(
98-
path,
99-
options.ignorePackage,
100-
options.ignorePackagePattern.map((s) => new RegExp(s)),
101-
options.ignorePath,
102-
options.ignorePathPattern.map((s) => new RegExp(s))
103-
);
104-
105-
const dependencyVersions = calculateVersionsForEachDependency(packages);
106-
107-
let mismatchingVersions = filterOutIgnoredDependencies(
108-
calculateMismatchingVersions(dependencyVersions),
109-
options.ignoreDep,
110-
options.ignoreDepPattern.map((s) => new RegExp(s))
111-
);
112-
let mismatchingVersionsFixed: MismatchingDependencyVersions = [];
73+
.action((path, options) => {
74+
const cdvc = new CDVC(path, options);
11375

11476
if (options.fix) {
115-
const resultsAfterFix = fixMismatchingVersions(
116-
packages,
117-
mismatchingVersions
118-
);
119-
mismatchingVersions = resultsAfterFix.notFixed;
120-
mismatchingVersionsFixed = resultsAfterFix.fixed;
121-
}
122-
123-
// Show output for dependencies we fixed.
124-
if (mismatchingVersionsFixed.length > 0) {
125-
console.log(mismatchingVersionsFixedToOutput(mismatchingVersionsFixed));
126-
}
77+
// Show output for dependencies we fixed.
78+
if (cdvc.hasMismatchingDependenciesFixable()) {
79+
console.log(cdvc.toFixedSummary());
80+
}
12781

128-
// Show output for dependencies that still have mismatches.
129-
if (mismatchingVersions.length > 0) {
130-
console.log(mismatchingVersionsToOutput(mismatchingVersions));
82+
// Show output for dependencies that still have mismatches.
83+
if (cdvc.hasMismatchingDependenciesNotFixable()) {
84+
console.log(cdvc.toMismatchSummary());
85+
process.exitCode = 1;
86+
}
87+
} else if (cdvc.hasMismatchingDependencies()) {
88+
// Show output for dependencies that have mismatches.
89+
console.log(cdvc.toMismatchSummary());
13190
process.exitCode = 1;
13291
}
13392
})

0 commit comments

Comments
 (0)