UNPKG

syncpack

Version:

Consistent dependency versions in large JavaScript Monorepos

97 lines (96 loc) 5 kB
import { Data, Effect, pipe } from 'effect'; import intersects from 'semver/ranges/intersects.js'; import { uniq } from 'tightrope/array/uniq.js'; import { Report } from '../report.js'; import { Specifier } from '../specifier/index.js'; import { groupBy } from './lib/group-by.js'; export class SameRangeVersionGroup extends Data.TaggedClass('SameRange') { groupType = 'versionGroup'; constructor(ctx, config) { super({ ctx, config, instances: [], }); } canAdd(_) { return true; } inspectAll() { return Effect.all(Object.entries(groupBy('name', this.instances)).flatMap(([name, instances]) => pipe(instances, Effect.partition(instance => pipe(Effect.succeed(Specifier.create(instance, instance.rawSpecifier.raw)), Effect.flatMap(specifier => pipe(specifier.getSemver(), Effect.matchEffect({ onFailure: () => Effect.fail( // ✘ expected version is not semver // ✘ is a mismatch we can't auto-fix new Report.UnsupportedMismatch(specifier.instance)), onSuccess: () => pipe(specifier.instance.semverGroup.getFixed(specifier), Effect.matchEffect({ onFailure: () => Effect.fail( // ✓ expected version is semver // ✘ expected version is not fixable by its semver group // ✘ is a mismatch we can't auto-fix new Report.UnsupportedMismatch(specifier.instance)), onSuccess: valid => specifier.instance.rawSpecifier.raw === valid.raw ? Effect.succeed( // ✓ expected version is semver // ✓ expected version matches its semver group // ✓ current version matches expected new Report.Valid(specifier)) : Effect.fail( // ✓ expected version is semver // ✓ expected version matches its semver group // ✘ current version mismatches expected // ✓ is a mismatch we can auto-fix new Report.SemverRangeMismatch(valid)), })), }))))), Effect.map(([allMismatches, allMatches]) => allMismatches.length === 0 ? allMatches.map(thisMatch => { if (thisMatch.specifier.instance.strategy.name === 'local') { // ✓ every instance is valid on its own // ✓ expected version is semver // ! is the original local package // ✓ others must match this, not the other way around return thisMatch; } const mismatches = getRangeMismatches(this.ctx, thisMatch, allMatches); if (mismatches.length === 0) { // ✓ every instance is valid on its own // ✓ expected version is semver // ✓ expected version matches its semver group // ✓ current version matches expected // ! is not the original local package // ✓ current specifier matches every other specifier return thisMatch; } // ✓ every instance is valid on its own // ✓ expected version is semver // ✓ expected version matches its semver group // ✓ current version matches expected // ! is not the original local package // ✘ current specifier does not match every other specifier return new Report.SameRangeMismatch(thisMatch.specifier.instance, uniq(mismatches.map(report => String(report.specifier.instance.rawSpecifier.raw)))); }) : // ✘ not every instance is valid on its own // ! report on their validity individually ! when all are valid // they can progress to being checked for having compatible // ranges [...allMatches, ...allMismatches]), Effect.map(reports => ({ name, reports }))))); } } /** Find all ranges/versions which this semver version does not cover */ function getRangeMismatches(ctx, report, others) { return others.filter(other => !matchesRange(ctx, report, other)); } /** Does semver version `a` match semver version `b`? */ function matchesRange(ctx, a, b) { const loose = true; return intersects(unwrapSemver(ctx, a.specifier), unwrapSemver(ctx, b.specifier), loose); } /** Get the semver version synchronously from a specifier known to contain semver */ function unwrapSemver(ctx, specifier) { if (specifier._tag === 'Range' || specifier._tag === 'Exact') { return specifier.raw; } if (specifier._tag === 'WorkspaceProtocol') { return Effect.runSync(specifier.getSemverEquivalent(ctx)); } return Effect.runSync(specifier.getSemver()); }