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 12, 2023
1 parent 2f0a554 commit 2c24779
Show file tree
Hide file tree
Showing 11 changed files with 577 additions and 122 deletions.
16 changes: 15 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,18 @@ 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

| Function | Description |
| :-- | :-- |
| `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. |
| `mismatchingVersionsToDetailedSummary(versions)` | Returns a string of human-readable tables describing mismatching dependency versions. |
| `mismatchingVersionsToFixedSummary(versions)` | Returns a string summary of the mismatching dependency versions that were fixed. |

More information about parameters and return values can be found in the types and JSDoc comments.

See an example of how these functions are used in [`lib/cli.ts`](./lib/cli.ts).

## 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
78 changes: 78 additions & 0 deletions lib/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
calculateVersionsForEachDependency,
calculateMismatchingVersions,
filterOutIgnoredDependencies,
fixMismatchingVersions,
} from './dependency-versions.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:
* - `mismatchingVersions`: All the mismatching versions found.
* - `mismatchingVersionsFixable`: The mismatching versions that are fixable (these will be fixed in `fix` mode)
* - `mismatchingVersionsNotFixable`: The mismatching versions that are not fixable (these will be skipped in `fix` mode).
*/
export function check(
path: string,
options?: {
fix?: boolean;
ignoreDep?: string[];
ignoreDepPattern?: string[];
ignorePackage?: string[];
ignorePackagePattern?: string[];
ignorePath?: string[];
ignorePathPattern?: string[];
}
) {
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 dependencyVersions = calculateVersionsForEachDependency(packages);

const mismatchingVersions = filterOutIgnoredDependencies(
calculateMismatchingVersions(dependencyVersions),
optionsWithDefaults.ignoreDep,
optionsWithDefaults.ignoreDepPattern.map((s) => new RegExp(s))
);

const resultsAfterFix = fixMismatchingVersions(
packages,
mismatchingVersions,
!optionsWithDefaults.fix // Do dry-run if not fixing.
);
const mismatchingVersionsFixable = resultsAfterFix.fixable;
const mismatchingVersionsNotFixable = resultsAfterFix.notFixable;

return {
mismatchingVersions,
mismatchingVersionsFixable,
mismatchingVersionsNotFixable,
};
}
83 changes: 27 additions & 56 deletions lib/cli.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
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 {
mismatchingVersionsToDetailedSummary,
mismatchingVersionsToFixedSummary,
} from './output.js';
import { check } from './check.js';

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

Expand Down Expand Up @@ -81,53 +74,31 @@ 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 {
mismatchingVersions,
mismatchingVersionsFixable,
mismatchingVersionsNotFixable,
} = check(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 (mismatchingVersionsFixable.length > 0) {
console.log(
mismatchingVersionsToFixedSummary(mismatchingVersionsFixable)
);
}

// 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 (mismatchingVersionsNotFixable.length > 0) {
console.log(
mismatchingVersionsToDetailedSummary(mismatchingVersionsNotFixable)
);
process.exitCode = 1;
}
} else if (mismatchingVersions.length > 0) {
// Show output for dependencies that have mismatches.
console.log(mismatchingVersionsToDetailedSummary(mismatchingVersions));
process.exitCode = 1;
}
})
Expand Down
71 changes: 40 additions & 31 deletions lib/dependency-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type DependenciesToVersionsSeen = Map<
{ package: Package; version: string; isLocalPackageVersion: boolean }[]
>;

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

// eslint-disable-next-line complexity
export function fixMismatchingVersions(
packages: Package[],
mismatchingVersions: MismatchingDependencyVersions
mismatchingVersions: MismatchingDependencyVersions,
dryrun = false
): {
fixed: MismatchingDependencyVersions;
notFixed: MismatchingDependencyVersions;
fixable: MismatchingDependencyVersions;
notFixable: MismatchingDependencyVersions;
} {
const fixed = [];
const notFixed = [];
const fixable: MismatchingDependencyVersions = [];
const notFixable: MismatchingDependencyVersions = [];
// Loop through each dependency that has a mismatching versions.
for (const mismatchingVersion of mismatchingVersions) {
// Decide what version we should fix to.
Expand All @@ -307,7 +310,7 @@ export function fixMismatchingVersions(
fixedVersion = getIncreasedLatestVersion(versions);
} catch {
// Skip this dependency.
notFixed.push(mismatchingVersion);
notFixable.push(mismatchingVersion);
continue;
}

Expand All @@ -321,7 +324,7 @@ export function fixMismatchingVersions(
compareVersionRanges(fixedVersion, localPackage.packageJson.version) > 0
) {
// Skip this dependency.
notFixed.push(mismatchingVersion);
notFixable.push(mismatchingVersion);
continue;
}

Expand All @@ -342,13 +345,15 @@ export function fixMismatchingVersions(
package_.packageJson.devDependencies[mismatchingVersion.dependency] !==
fixedVersion
) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'devDependencies',
mismatchingVersion.dependency,
fixedVersion
);
if (!dryrun) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'devDependencies',
mismatchingVersion.dependency,
fixedVersion
);
}
isFixed = true;
}

Expand All @@ -358,13 +363,15 @@ export function fixMismatchingVersions(
package_.packageJson.dependencies[mismatchingVersion.dependency] !==
fixedVersion
) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'dependencies',
mismatchingVersion.dependency,
fixedVersion
);
if (!dryrun) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'dependencies',
mismatchingVersion.dependency,
fixedVersion
);
}
isFixed = true;
}

Expand All @@ -374,24 +381,26 @@ export function fixMismatchingVersions(
package_.packageJson.resolutions[mismatchingVersion.dependency] !==
fixedVersion
) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'resolutions',
mismatchingVersion.dependency,
fixedVersion
);
if (!dryrun) {
writeDependencyVersion(
package_.pathPackageJson,
package_.packageJsonEndsInNewline,
'resolutions',
mismatchingVersion.dependency,
fixedVersion
);
}
isFixed = true;
}
}

if (isFixed) {
fixed.push(mismatchingVersion);
fixable.push(mismatchingVersion);
}
}

return {
fixed,
notFixed,
fixable,
notFixable,
};
}
7 changes: 7 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Public Node API.

export { check } from './check.js';
export {
mismatchingVersionsToDetailedSummary,
mismatchingVersionsToFixedSummary,
} from './output.js';
10 changes: 8 additions & 2 deletions lib/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
} from './semver.js';
import { table } from 'table';

export function mismatchingVersionsToOutput(
/**
* Returns human-readable tables describing mismatching dependency versions.
*/
export function mismatchingVersionsToDetailedSummary(
mismatchingDependencyVersions: MismatchingDependencyVersions
): string {
if (mismatchingDependencyVersions.length === 0) {
Expand Down Expand Up @@ -59,7 +62,10 @@ export function mismatchingVersionsToOutput(
].join('\n');
}

export function mismatchingVersionsFixedToOutput(
/**
* Returns a summary of the mismatching dependency versions that were fixed.
*/
export function mismatchingVersionsToFixedSummary(
mismatchingDependencyVersions: MismatchingDependencyVersions
): string {
if (mismatchingDependencyVersions.length === 0) {
Expand Down
Loading

0 comments on commit 2c24779

Please sign in to comment.