syncpack
Version:
Consistent dependency versions in large JavaScript Monorepos
93 lines (92 loc) • 3.92 kB
JavaScript
import { Context, Effect, flow, pipe } from 'effect';
import { isArray } from 'tightrope/guard/is-array.js';
import { isNonEmptyString } from 'tightrope/guard/is-non-empty-string.js';
import { isObject } from 'tightrope/guard/is-object.js';
import { getSortAz } from '../config/get-sort-az.js';
import { getSortExports } from '../config/get-sort-exports.js';
import { getSortFirst } from '../config/get-sort-first.js';
import { CliConfigTag } from '../config/tag.js';
import { defaultErrorHandlers } from '../error-handlers/default-error-handlers.js';
import { getContext } from '../get-context/index.js';
import { exitIfInvalid } from '../io/exit-if-invalid.js';
import { IoTag } from '../io/index.js';
import { toFormattedJson } from '../io/to-formatted-json.js';
import { writeIfChanged } from '../io/write-if-changed.js';
import { withLogger } from '../lib/with-logger.js';
export function format({ io, cli, errorHandlers = defaultErrorHandlers, }) {
return pipe(getContext({ io, cli, errorHandlers }), Effect.flatMap(pipeline), Effect.flatMap(ctx => pipe(writeIfChanged(ctx), Effect.catchTags({
WriteFileError: flow(errorHandlers.WriteFileError, Effect.map(() => {
ctx.isInvalid = true;
return ctx;
})),
}))), Effect.flatMap(exitIfInvalid), Effect.provide(pipe(Context.empty(), Context.add(CliConfigTag, cli), Context.add(IoTag, io))), withLogger);
}
export function pipeline(ctx) {
const { config, packageJsonFiles } = ctx;
const sortAz = getSortAz(config);
const sortExports = getSortExports(config);
const sortFirst = getSortFirst(config);
const sortPackages = config.rcFile.sortPackages !== false;
const formatBugs = config.rcFile.formatBugs !== false;
const formatRepository = config.rcFile.formatRepository !== false;
packageJsonFiles.forEach(file => {
const { contents } = file.jsonFile;
const chain = contents;
if (formatBugs) {
const bugsUrl = chain?.bugs?.url;
if (bugsUrl) {
contents.bugs = bugsUrl;
}
}
if (formatRepository) {
const repoUrl = chain?.repository?.url;
const repoDir = chain?.repository?.directory;
if (isNonEmptyString(repoUrl) && !isNonEmptyString(repoDir)) {
contents.repository = repoUrl.includes('github.com')
? repoUrl.replace(/^.+github\.com\//, '')
: repoUrl;
}
}
if (sortExports.length > 0) {
visitExports(sortExports, contents.exports);
}
if (sortAz.length > 0) {
sortAz.forEach(key => sortAlphabetically(contents[key]));
}
if (sortPackages) {
const sortedKeys = Object.keys(contents).sort();
sortObject(sortedKeys, contents);
}
if (sortFirst.length > 0) {
const otherKeys = Object.keys(contents);
const sortedKeys = new Set([...sortFirst, ...otherKeys]);
sortObject(sortedKeys, contents);
}
file.nextJson = toFormattedJson(ctx, file);
});
return Effect.succeed(ctx);
}
function visitExports(sortExports, value) {
if (isObject(value)) {
const otherKeys = Object.keys(value);
const sortedKeys = new Set([...sortExports, ...otherKeys]);
sortObject(sortedKeys, value);
Object.values(value).forEach(nextValue => visitExports(sortExports, nextValue));
}
}
function sortObject(sortedKeys, obj) {
sortedKeys.forEach((key) => {
const value = obj[key];
delete obj[key];
obj[key] = value;
});
}
function sortAlphabetically(value) {
const localeComparison = (a, b) => a.localeCompare(b);
if (isArray(value)) {
value.sort(localeComparison);
}
else if (isObject(value)) {
sortObject(Object.keys(value).sort(localeComparison), value);
}
}