syncpack
Version:
Consistent dependency versions in large JavaScript Monorepos
225 lines (224 loc) • 10.4 kB
JavaScript
import { EOL } from 'node:os';
import chalk from 'chalk-template';
import { Context, Effect, pipe } from 'effect';
import { CliConfigTag } from '../config/tag.js';
import { ICON } from '../constants.js';
import { defaultErrorHandlers } from '../error-handlers/default-error-handlers.js';
import { getContext } from '../get-context/index.js';
import { getInstances } from '../get-instances/index.js';
import { exitIfInvalid } from '../io/exit-if-invalid.js';
import { IoTag } from '../io/index.js';
import { getVersionGroupHeader } from '../lib/get-group-header.js';
import { padStart } from '../lib/pad-start.js';
import { withLogger } from '../lib/with-logger.js';
export function listMismatches({ io, cli, errorHandlers = defaultErrorHandlers, }) {
return pipe(getContext({ io, cli, errorHandlers }), Effect.flatMap(ctx => pipeline(ctx, io, errorHandlers)), Effect.flatMap(exitIfInvalid), Effect.provide(pipe(Context.empty(), Context.add(CliConfigTag, cli), Context.add(IoTag, io))), withLogger);
}
/** Exported to be reused by `syncpack lint` */
export function pipeline(ctx, io, errorHandlers) {
return Effect.gen(function* ($) {
const { versionGroups } = yield* $(getInstances(ctx, io, errorHandlers));
let index = 0;
for (const group of versionGroups) {
const countByReportGroup = {
Excluded: 0,
Fixable: 0,
Unfixable: 0,
Valid: 0,
};
yield* $(Effect.logInfo(getVersionGroupHeader({ group, index })));
for (const groupReport of yield* $(group.inspectAll())) {
for (const report of groupReport.reports) {
countByReportGroup[report._tagGroup]++;
if (report.isInvalid) {
ctx.isInvalid = true;
}
const logReport = onReportTag[report._tag];
if (logReport) {
yield* $(logReport(report));
}
}
}
yield* $(onReportGroup.Valid(countByReportGroup.Valid));
yield* $(onReportGroup.Fixable(countByReportGroup.Fixable));
yield* $(onReportGroup.Unfixable(countByReportGroup.Unfixable));
yield* $(onReportGroup.Excluded(countByReportGroup.Excluded));
index++;
}
return ctx;
});
}
const onReportGroup = {
Excluded(amount) {
if (amount === 0) {
return Effect.void;
}
const msg = chalk `{gray ${padStart(amount)} ${ICON.rightArrow} ignored}`;
return Effect.logInfo(msg);
},
Fixable(amount) {
if (amount === 0) {
return Effect.void;
}
const msg = chalk `${padStart(amount)} {green ${ICON.tick}} can be auto-fixed`;
return Effect.logInfo(msg);
},
Unfixable(amount) {
if (amount === 0) {
return Effect.void;
}
const msg = chalk `{red ${padStart(amount)} ${ICON.panic} can be fixed manually using} {blue syncpack prompt}`;
return Effect.logInfo(msg);
},
Valid(amount) {
if (amount === 0) {
return Effect.void;
}
const msg = chalk `${padStart(amount)} {green ${ICON.tick}} already valid`;
return Effect.logInfo(msg);
},
};
const onReportTag = {
Banned(report) {
const _tag = report._tag;
const instance = report.fixable.instance;
const name = instance.name;
const jsonFile = instance.packageJsonFile.jsonFile;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red banned} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
Disabled(_report) {
return Effect.void;
},
FilteredOut(_report) {
return Effect.void;
},
HighestSemverMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const jsonFile = instance.packageJsonFile.jsonFile;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
Ignored(_report) {
return Effect.void;
},
LocalPackageMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = instance.packageJsonFile.jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
LowestSemverMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = instance.packageJsonFile.jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
MissingLocalVersion(report) {
const instance = report.unfixable;
const localPath = report.localInstance.packageJsonFile.jsonFile.shortPath;
const jsonFile = instance.packageJsonFile.jsonFile;
const actual = instance.rawSpecifier.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
return Effect.logInfo([
chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {red ???} {gray ${shortPath} > ${path}} {blue [MissingLocalVersion]}`,
chalk ` {red ${localPath} does not have a .version property which is exact semver}`,
].join(EOL));
},
MissingSnappedToMismatch(report) {
const instance = report.unfixable;
const jsonFile = instance.packageJsonFile.jsonFile;
const actual = instance.rawSpecifier.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
return Effect.logInfo([
chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {red ???} {gray ${shortPath} > ${path}} {blue [MissingSnappedToMismatch]}`,
chalk ` {red no package in this groups .snapTo array depend on ${name}}`,
].join(EOL));
},
PinnedMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = instance.packageJsonFile.jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
SameRangeMismatch(report) {
const instance = report.unfixable;
const jsonFile = instance.packageJsonFile.jsonFile;
const actual = instance.rawSpecifier.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
const mismatches = report.mismatches;
return Effect.logInfo([
chalk `{red ${ICON.cross}} ${name} {red range ${actual} does not include ${mismatches.join(', ')}} {gray ${shortPath} > ${path}} {blue [SameRangeMismatch]}`,
chalk ` {gray use {blue syncpack prompt} to fix this manually}`,
].join(EOL));
},
SemverRangeMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = instance.packageJsonFile.jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
SnappedToMismatch(report) {
const _tag = report._tag;
const fixable = report.fixable;
const instance = fixable.instance;
const actual = instance.rawSpecifier.raw;
const expected = fixable.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = instance.packageJsonFile.jsonFile.shortPath;
return Effect.logInfo(chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {green ${expected}} {gray ${shortPath} > ${path}} {blue [${_tag}]}`);
},
UnsupportedMismatch(report) {
const instance = report.unfixable;
const jsonFile = instance.packageJsonFile.jsonFile;
const actual = instance.rawSpecifier.raw;
const name = instance.name;
const path = instance.strategy.path;
const shortPath = jsonFile.shortPath;
return Effect.logInfo([
chalk `{red ${ICON.cross}} ${name} {red ${actual}} {dim ${ICON.rightArrow}} {red ???} {gray ${shortPath} > ${path}} {blue [UnsupportedMismatch]}`,
chalk ` {red use {blue syncpack prompt} to fix this manually}`,
].join(EOL));
},
Valid(_report) {
return Effect.void;
},
};
export const logMissingLocalVersion = onReportTag.MissingLocalVersion;
export const logMissingSnappedToMismatch = onReportTag.MissingSnappedToMismatch;
export const logUnsupportedMismatch = onReportTag.UnsupportedMismatch;
export const logSameRangeMismatch = onReportTag.SameRangeMismatch;