Skip to content

Commit 3ac14cd

Browse files
committed
feat: add node API
1 parent 2f0a554 commit 3ac14cd

File tree

11 files changed

+582
-122
lines changed

11 files changed

+582
-122
lines changed

README.md

Lines changed: 15 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,18 @@ 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+
| Function | Description |
130+
| :-- | :-- |
131+
| `check(path, options)` | Checks for inconsistencies across a workspace. Optionally fixes them. Returns lists of inconsistencies: a complete list, a fixable list, and an un-fixable list. |
132+
| `mismatchingVersionsToDetailedSummary(versions)` | Returns a string of human-readable tables describing mismatching dependency versions. |
133+
| `mismatchingVersionsToFixedSummary(versions)` | Returns a string summary of the mismatching dependency versions that were fixed. |
134+
135+
More information about parameters and return values can be found in the types and JSDoc comments.
136+
137+
See an example of how these functions are used in [`lib/cli.ts`](./lib/cli.ts).
138+
125139
## Related
126140

127141
* [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/check.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
calculateVersionsForEachDependency,
3+
calculateMismatchingVersions,
4+
filterOutIgnoredDependencies,
5+
fixMismatchingVersions,
6+
MismatchingDependencyVersions,
7+
} from './dependency-versions.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+
* - `mismatchingVersions`: All the mismatching versions found.
23+
* - `mismatchingVersionsFixable`: The mismatching versions that are fixable (these will be fixed in `fix` mode)
24+
* - `mismatchingVersionsNotFixable`: The mismatching versions that are not fixable (these will be skipped in `fix` mode).
25+
*/
26+
export function check(
27+
path: string,
28+
options?: {
29+
fix?: boolean;
30+
ignoreDep?: string[];
31+
ignoreDepPattern?: string[];
32+
ignorePackage?: string[];
33+
ignorePackagePattern?: string[];
34+
ignorePath?: string[];
35+
ignorePathPattern?: string[];
36+
}
37+
): {
38+
mismatchingVersions: MismatchingDependencyVersions;
39+
mismatchingVersionsFixable: MismatchingDependencyVersions;
40+
mismatchingVersionsNotFixable: MismatchingDependencyVersions;
41+
} {
42+
const optionsWithDefaults = {
43+
fix: false,
44+
ignoreDep: [],
45+
ignoreDepPattern: [],
46+
ignorePackage: [],
47+
ignorePackagePattern: [],
48+
ignorePath: [],
49+
ignorePathPattern: [],
50+
...options,
51+
};
52+
53+
// Calculate.
54+
const packages = getPackages(
55+
path,
56+
optionsWithDefaults.ignorePackage,
57+
optionsWithDefaults.ignorePackagePattern.map((s) => new RegExp(s)),
58+
optionsWithDefaults.ignorePath,
59+
optionsWithDefaults.ignorePathPattern.map((s) => new RegExp(s))
60+
);
61+
62+
const dependencyVersions = calculateVersionsForEachDependency(packages);
63+
64+
const mismatchingVersions = filterOutIgnoredDependencies(
65+
calculateMismatchingVersions(dependencyVersions),
66+
optionsWithDefaults.ignoreDep,
67+
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
68+
);
69+
70+
const resultsAfterFix = fixMismatchingVersions(
71+
packages,
72+
mismatchingVersions,
73+
!optionsWithDefaults.fix // Do dry-run if not fixing.
74+
);
75+
const mismatchingVersionsFixable = resultsAfterFix.fixable;
76+
const mismatchingVersionsNotFixable = resultsAfterFix.notFixable;
77+
78+
return {
79+
mismatchingVersions,
80+
mismatchingVersionsFixable,
81+
mismatchingVersionsNotFixable,
82+
};
83+
}

lib/cli.ts

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,13 @@
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 {
7+
mismatchingVersionsToDetailedSummary,
8+
mismatchingVersionsToFixedSummary,
9+
} from './output.js';
10+
import { check } from './check.js';
1811

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

@@ -81,53 +74,31 @@ export function run() {
8174
collect,
8275
[]
8376
)
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 = [];
77+
.action((path, options) => {
78+
const {
79+
mismatchingVersions,
80+
mismatchingVersionsFixable,
81+
mismatchingVersionsNotFixable,
82+
} = check(path, options);
11383

11484
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-
}
85+
// Show output for dependencies we fixed.
86+
if (mismatchingVersionsFixable.length > 0) {
87+
console.log(
88+
mismatchingVersionsToFixedSummary(mismatchingVersionsFixable)
89+
);
90+
}
12791

128-
// Show output for dependencies that still have mismatches.
129-
if (mismatchingVersions.length > 0) {
130-
console.log(mismatchingVersionsToOutput(mismatchingVersions));
92+
// Show output for dependencies that still have mismatches.
93+
if (mismatchingVersionsNotFixable.length > 0) {
94+
console.log(
95+
mismatchingVersionsToDetailedSummary(mismatchingVersionsNotFixable)
96+
);
97+
process.exitCode = 1;
98+
}
99+
} else if (mismatchingVersions.length > 0) {
100+
// Show output for dependencies that have mismatches.
101+
console.log(mismatchingVersionsToDetailedSummary(mismatchingVersions));
131102
process.exitCode = 1;
132103
}
133104
})

lib/dependency-versions.ts

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type DependenciesToVersionsSeen = Map<
1414
{ package: Package; version: string; isLocalPackageVersion: boolean }[]
1515
>;
1616

17+
/** A dependency that was found to have mismatching versions, the versions present of it, and the packages each of those versions are seen in. */
1718
export type MismatchingDependencyVersions = Array<{
1819
dependency: string;
1920
versions: {
@@ -287,15 +288,17 @@ function writeDependencyVersion(
287288
);
288289
}
289290

291+
// eslint-disable-next-line complexity
290292
export function fixMismatchingVersions(
291293
packages: Package[],
292-
mismatchingVersions: MismatchingDependencyVersions
294+
mismatchingVersions: MismatchingDependencyVersions,
295+
dryrun = false
293296
): {
294-
fixed: MismatchingDependencyVersions;
295-
notFixed: MismatchingDependencyVersions;
297+
fixable: MismatchingDependencyVersions;
298+
notFixable: MismatchingDependencyVersions;
296299
} {
297-
const fixed = [];
298-
const notFixed = [];
300+
const fixable: MismatchingDependencyVersions = [];
301+
const notFixable: MismatchingDependencyVersions = [];
299302
// Loop through each dependency that has a mismatching versions.
300303
for (const mismatchingVersion of mismatchingVersions) {
301304
// Decide what version we should fix to.
@@ -307,7 +310,7 @@ export function fixMismatchingVersions(
307310
fixedVersion = getIncreasedLatestVersion(versions);
308311
} catch {
309312
// Skip this dependency.
310-
notFixed.push(mismatchingVersion);
313+
notFixable.push(mismatchingVersion);
311314
continue;
312315
}
313316

@@ -321,7 +324,7 @@ export function fixMismatchingVersions(
321324
compareVersionRanges(fixedVersion, localPackage.packageJson.version) > 0
322325
) {
323326
// Skip this dependency.
324-
notFixed.push(mismatchingVersion);
327+
notFixable.push(mismatchingVersion);
325328
continue;
326329
}
327330

@@ -342,13 +345,15 @@ export function fixMismatchingVersions(
342345
package_.packageJson.devDependencies[mismatchingVersion.dependency] !==
343346
fixedVersion
344347
) {
345-
writeDependencyVersion(
346-
package_.pathPackageJson,
347-
package_.packageJsonEndsInNewline,
348-
'devDependencies',
349-
mismatchingVersion.dependency,
350-
fixedVersion
351-
);
348+
if (!dryrun) {
349+
writeDependencyVersion(
350+
package_.pathPackageJson,
351+
package_.packageJsonEndsInNewline,
352+
'devDependencies',
353+
mismatchingVersion.dependency,
354+
fixedVersion
355+
);
356+
}
352357
isFixed = true;
353358
}
354359

@@ -358,13 +363,15 @@ export function fixMismatchingVersions(
358363
package_.packageJson.dependencies[mismatchingVersion.dependency] !==
359364
fixedVersion
360365
) {
361-
writeDependencyVersion(
362-
package_.pathPackageJson,
363-
package_.packageJsonEndsInNewline,
364-
'dependencies',
365-
mismatchingVersion.dependency,
366-
fixedVersion
367-
);
366+
if (!dryrun) {
367+
writeDependencyVersion(
368+
package_.pathPackageJson,
369+
package_.packageJsonEndsInNewline,
370+
'dependencies',
371+
mismatchingVersion.dependency,
372+
fixedVersion
373+
);
374+
}
368375
isFixed = true;
369376
}
370377

@@ -374,24 +381,26 @@ export function fixMismatchingVersions(
374381
package_.packageJson.resolutions[mismatchingVersion.dependency] !==
375382
fixedVersion
376383
) {
377-
writeDependencyVersion(
378-
package_.pathPackageJson,
379-
package_.packageJsonEndsInNewline,
380-
'resolutions',
381-
mismatchingVersion.dependency,
382-
fixedVersion
383-
);
384+
if (!dryrun) {
385+
writeDependencyVersion(
386+
package_.pathPackageJson,
387+
package_.packageJsonEndsInNewline,
388+
'resolutions',
389+
mismatchingVersion.dependency,
390+
fixedVersion
391+
);
392+
}
384393
isFixed = true;
385394
}
386395
}
387396

388397
if (isFixed) {
389-
fixed.push(mismatchingVersion);
398+
fixable.push(mismatchingVersion);
390399
}
391400
}
392401

393402
return {
394-
fixed,
395-
notFixed,
403+
fixable,
404+
notFixable,
396405
};
397406
}

lib/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Public Node API.
2+
3+
export { check } from './check.js';
4+
export {
5+
mismatchingVersionsToDetailedSummary,
6+
mismatchingVersionsToFixedSummary,
7+
} from './output.js';

lib/output.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
} from './semver.js';
77
import { table } from 'table';
88

9-
export function mismatchingVersionsToOutput(
9+
/**
10+
* Returns human-readable tables describing mismatching dependency versions.
11+
*/
12+
export function mismatchingVersionsToDetailedSummary(
1013
mismatchingDependencyVersions: MismatchingDependencyVersions
1114
): string {
1215
if (mismatchingDependencyVersions.length === 0) {
@@ -59,7 +62,10 @@ export function mismatchingVersionsToOutput(
5962
].join('\n');
6063
}
6164

62-
export function mismatchingVersionsFixedToOutput(
65+
/**
66+
* Returns a summary of the mismatching dependency versions that were fixed.
67+
*/
68+
export function mismatchingVersionsToFixedSummary(
6369
mismatchingDependencyVersions: MismatchingDependencyVersions
6470
): string {
6571
if (mismatchingDependencyVersions.length === 0) {

0 commit comments

Comments
 (0)