src/bin-list/list.ts
import chalk from 'chalk';
import { Context, Effect, pipe } from 'effect';
import { EOL } from 'os';
import { CliConfigTag } from '../config/tag';
import { type CliConfig } from '../config/types';
import { ICON } from '../constants';
import type { ErrorHandlers } from '../error-handlers/default-error-handlers';
import { defaultErrorHandlers } from '../error-handlers/default-error-handlers';
import type { Ctx } from '../get-context';
import { getContext } from '../get-context';
import { getInstances } from '../get-instances';
import type { Io } from '../io';
import { IoTag } from '../io';
import { exitIfInvalid } from '../io/exit-if-invalid';
import { getVersionGroupHeader } from '../lib/get-group-header';
import { padStart } from '../lib/pad-start';
import { withLogger } from '../lib/with-logger';
import type { Report } from '../report';
import type { VersionGroup } from '../version-group';
interface Input {
io: Io;
cli: Partial<CliConfig>;
errorHandlers?: ErrorHandlers;
}
export function list({ io, cli, errorHandlers = defaultErrorHandlers }: Input) {
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,
);
}
export function pipeline(
ctx: Ctx,
io: Io,
errorHandlers: ErrorHandlers,
): Effect.Effect<never, never, Ctx> {
return Effect.gen(function* ($) {
const { versionGroups } = yield* $(getInstances(ctx, io, errorHandlers));
let index = 0;
for (const group of versionGroups) {
yield* $(Effect.logInfo(getVersionGroupHeader({ group, index })));
yield* $(onGroupTag[group._tag](group));
if (group._tag === 'Banned' || group._tag === 'FilteredOut' || group._tag === 'Ignored') {
if (group._tag === 'Banned') ctx.isInvalid = true;
index++;
continue;
}
for (const groupReport of yield* $(group.inspectAll())) {
const matches = new Set<string>();
const mismatches = new Set<string>();
for (const report of groupReport.reports) {
if (report.isInvalid) ctx.isInvalid = true;
switch (report._tagGroup) {
case 'Valid': {
const actual = report.specifier.raw;
matches.add(chalk`{gray ${actual}}`);
break;
}
case 'Fixable': {
mismatches.add(getLogForFixable(report));
break;
}
case 'Unfixable': {
mismatches.add(getLogForUnfixable(report));
break;
}
}
}
if (mismatches.size === 0) {
yield* $(logMatchingReport(groupReport, matches));
} else {
yield* $(logMismatchingReport(groupReport, mismatches));
}
}
index++;
}
yield* $(logOtherCommands());
return ctx;
});
}
const onGroupTag: Record<
VersionGroup.Any['_tag'],
(group: any) => Effect.Effect<never, never, void>
> = {
Banned(group: VersionGroup.Banned) {
return Effect.gen(function* ($) {
for (const groupReport of yield* $(group.inspectAll())) {
const name = groupReport.name;
const usages = `${padStart(groupReport.reports.length)}x`;
const invalidLabel = chalk`{gray ${usages}} {red ${name}}`;
const msg = chalk`${invalidLabel} {gray [Banned]}`;
yield* $(Effect.logInfo(msg));
}
});
},
FilteredOut(group: VersionGroup.FilteredOut) {
return Effect.gen(function* ($) {
for (const groupReport of yield* $(group.inspectAll())) {
const name = groupReport.name;
const usages = `${padStart(groupReport.reports.length)}x`;
const invalidLabel = chalk`{gray ${usages}} {gray ${name}}`;
const msg = chalk`${invalidLabel} {gray [FilteredOut]}`;
yield* $(Effect.logInfo(msg));
}
});
},
Ignored(group: VersionGroup.Ignored) {
return Effect.gen(function* ($) {
for (const groupReport of yield* $(group.inspectAll())) {
const name = groupReport.name;
const usages = `${padStart(groupReport.reports.length)}x`;
const invalidLabel = chalk`{gray ${usages}} {gray ${name}}`;
const msg = chalk`${invalidLabel} {gray [Ignored]}`;
yield* $(Effect.logInfo(msg));
}
});
},
Pinned(_group: VersionGroup.Pinned) {
return Effect.unit;
},
SameRange(_group: VersionGroup.SameRange) {
return Effect.unit;
},
SnappedTo(_group: VersionGroup.SnappedTo) {
return Effect.unit;
},
Standard(_group: VersionGroup.Standard) {
return Effect.unit;
},
};
function logMatchingReport(groupReport: Report.Version.Group, messages: Set<string>) {
const name = groupReport.name;
const usages = `${padStart(groupReport.reports.length)}x`;
const label = chalk`{gray ${usages}} ${name}{gray :}`;
return Effect.logInfo(chalk`${label} ${[...messages].join(chalk`{gray , }`)}`);
}
function logMismatchingReport(groupReport: Report.Version.Group, messages: Set<string>) {
const name = groupReport.name;
const usages = `${padStart(groupReport.reports.length)}x`;
const label = chalk`{gray ${usages}} {red ${name}}{gray :}`;
const indent = usages.replace(/./g, ' ');
return Effect.logInfo(
chalk`${label}${['', ...messages].join(chalk`${EOL}${indent} {red ${ICON.cross}} `)}`,
);
}
function getLogForFixable(report: Report.Version.Fixable.Any) {
const _tag = report._tag;
const actual = report.fixable.instance.rawSpecifier.raw;
const expected = report.fixable.raw;
return chalk`{red ${actual}} {gray ${ICON.rightArrow}} {green ${expected}} {gray [${_tag}]}`;
}
function getLogForUnfixable(report: Report.Version.Unfixable.Any) {
const _tag = report._tag;
const actual = report.unfixable.rawSpecifier.raw;
return chalk`{red ${actual}} {gray ${ICON.rightArrow}} {gray [${_tag}]}`;
}
export function logOtherCommands() {
return Effect.logInfo(
[
'',
' What next?',
chalk`{dim -} {yellow syncpack list-mismatches} to see more detail about mismatching versions`,
chalk`{dim -} {yellow syncpack fix-mismatches} to fix version mismatches automatically`,
chalk`{dim -} {yellow syncpack format} to sort and prettify your package.json files`,
chalk`{dim -} {yellow syncpack update} to choose updates from the npm registry`,
chalk`{dim -} {yellow syncpack --help} for everything else`,
'',
].join(EOL),
);
}