syncpack
Version:
Consistent dependency versions in large JavaScript Monorepos
120 lines (119 loc) • 7.52 kB
JavaScript
import { Data, Effect, pipe } from 'effect';
import { uniq } from 'tightrope/array/uniq.js';
import { Report } from '../report.js';
import { Specifier } from '../specifier/index.js';
import { getPreferredVersion } from './lib/get-preferred-version.js';
import { groupBy } from './lib/group-by.js';
export class StandardVersionGroup extends Data.TaggedClass('Standard') {
groupType = 'versionGroup';
constructor(isCatchAll, config) {
super({
config,
instances: [],
isCatchAll,
});
}
canAdd(_) {
return true;
}
inspectAll() {
return Effect.all(Object.entries(groupBy('name', this.instances)).flatMap(([name, instances]) => {
const localInstance = getLocalInstance(instances);
if (localInstance) {
const localVersion = localInstance?.rawSpecifier.raw;
return pipe(Effect.succeed(Specifier.create(localInstance, localVersion)), Effect.flatMap(local => Effect.all(local._tag !== 'Exact' && instances.length > 1
? instances.map(instance =>
// ! dependency is a package developed in this repo
// ✘ local package has an invalid .version property
// ✘ is a mismatch we can't auto-fix
Effect.succeed(new Report.MissingLocalVersion(instance, localInstance)))
: instances.flatMap(instance =>
// instances.flatMap((instance) =>
pipe(Effect.succeed(Specifier.create(instance, instance.rawSpecifier.raw)), Effect.flatMap(specifier => specifier.instance === localInstance
? // ✓ this is the local package which the others should match
// ! its version must always remain as exact semver
// ! other instances need to be adjusted for their semver groups
Effect.succeed(new Report.Valid(specifier))
: pipe(specifier.replaceWith(local), specifier.instance.semverGroup.getFixed, Effect.match({
onFailure: /* istanbul ignore next */ () =>
// ! is not the local package instance
// ✘ local version is not fixable by this semver group
// ✘ is a mismatch we can't auto-fix
// ✘ this should be impossible - we already proved the local version is exact semver
new Report.UnsupportedMismatch(specifier.instance),
onSuccess: valid => specifier.instance.rawSpecifier.raw ===
valid.raw
? // ! is not the local package instance
// ✓ local version matches this semver group
// ✓ current version matches local
new Report.Valid(specifier)
: localVersion ===
'PACKAGE_JSON_HAS_NO_VERSION'
? // ! is not the local package instance
// ✘ local package has a version defined
// ✓ local version matches this semver group
// ✘ current version mismatches local
new Report.MissingLocalVersion(specifier.instance, localInstance)
: // ! is not the local package instance
// ✓ local package has a version defined
// ✓ local version matches this semver group
// ✘ current version mismatches local
new Report.LocalPackageMismatch(valid, localInstance),
}))))))), Effect.map(reports => ({ name, reports })));
}
const PreferredMismatch = this.config.preferVersion === 'lowestSemver'
? Report.LowestSemverMismatch
: Report.HighestSemverMismatch;
return pipe(Effect.succeed(instances.map(instance => Specifier.create(instance, instance.rawSpecifier.raw))), Effect.flatMap(specifiers => pipe(getPreferredVersion(this.config.preferVersion, specifiers), Effect.matchEffect({
onFailure: () => Effect.succeed(uniq(specifiers.map(specifier => specifier.instance.rawSpecifier.raw)).length === 1
? specifiers.map(specifier =>
// ✘ not every version is semver
// ✓ every version is identical
// ✓ is a match
new Report.Valid(specifier))
: instances.map(instance =>
// ✘ not every version is semver
// ✘ some versions are not identical
// ✘ is a mismatch we can't auto-fix
new Report.UnsupportedMismatch(instance))),
onSuccess: expectedVersion => pipe(specifiers, Effect.forEach(current => pipe(current.replaceWith(expectedVersion), current.instance.semverGroup.getFixed, Effect.match({
onFailure: /* istanbul ignore next */ () =>
// ✓ every version is semver
// ✘ expected version is not fixable by its semver group
// ✘ is a mismatch we can't auto-fix
// ✘ this should be impossible - any valid semver is fixable by a semver group
new Report.UnsupportedMismatch(current.instance),
onSuccess: expectedRange => current.instance.rawSpecifier.raw ===
expectedRange.raw
? // ✓ every version is semver
// ✓ current version matches expected semver
// ✓ current version matches expected version
new Report.Valid(current)
: current.instance.rawSpecifier.raw ===
expectedVersion.raw
? // ✓ every version is semver
// ✓ current version matches expected version
// ✘ current version does not match expected semver
// ✓ is a mismatch we can auto-fix
new Report.SemverRangeMismatch(expectedRange)
: // ✓ every version is semver
// ✘ current version does not match expected version
// ✘ expected version does not match expected semver
// ✓ is a mismatch we can auto-fix
new PreferredMismatch(expectedRange),
})))),
}))), Effect.map(reports => ({ name, reports })));
}));
}
}
/**
* If this dependency is developed in this monorepo, get the instance which
* represents the canonical .version property of its package.json file.
*/
function getLocalInstance(instances) {
return instances.find(isLocalInstance);
}
/** Is this dependency developed in this monorepo */
function isLocalInstance(instance) {
return instance.strategy.name === 'local';
}