syncpack
Version:
Consistent dependency versions in large JavaScript Monorepos
61 lines (60 loc) • 3.23 kB
JavaScript
import { Effect, Option, identity, pipe } from 'effect';
import { isObject } from 'tightrope/guard/is-object.js';
import { get } from '../lib/get.js';
import { DELETE } from '../version-group/lib/delete.js';
import { getNonEmptyStringProp } from './lib/get-non-empty-string-prop.js';
const getOptionOfObject = Option.liftPredicate((isObject));
export class NameAndVersionPropsStrategy {
_tag = 'name~version';
name;
path;
namePath;
constructor(name, path, namePath) {
this.name = name;
this.path = path;
this.namePath = namePath;
}
read(file) {
return pipe(Effect.Do,
// get the name prop
Effect.bind('name', () => getNonEmptyStringProp(this.namePath, file)),
// add the version prop
Effect.bind('version', () => pipe(getNonEmptyStringProp(this.path, file),
/**
* In order to report a `MissingLocalVersion`, we need to ensure that
* a value is returned for `local` package .version properties so we
* can know that `this.name` is a package developed in this repo but
* that its version is missing.
*
* Not doing this results in the invalid local package being ignored
* and each installation of it being checked for mismatches amongst
* themselves.
*/
this.name === 'local'
? Effect.catchAll(() => Effect.succeed('PACKAGE_JSON_HAS_NO_VERSION'))
: Effect.map(identity))),
// if both are non empty strings, we can return them
Effect.map(({ name, version }) => [[name, version]]), Effect.tapError(() => Effect.logDebug(`NameAndVersionPropsStrategy#${this.name} found nothing at <${file.jsonFile.shortPath}>.${this.path} & .${this.namePath}`)),
// if either are invalid, default to empty
Effect.catchAll(() => Effect.succeed([])));
}
write(file, [, version]) {
const path = this.path;
const { contents } = file.jsonFile;
const isNestedPath = path.includes('.');
const nextValue = version === DELETE ? undefined : version;
if (isNestedPath) {
const fullPath = path.split('.');
const pathToParent = fullPath.slice(0, fullPath.length - 1).join('.');
const key = fullPath.slice(-1).join('');
return pipe(get(contents, ...pathToParent.split('.')), Effect.flatMap(getOptionOfObject), Effect.flatMap(parent => Effect.try(() => {
parent[key] = nextValue;
file.applyEdit(fullPath, nextValue);
})), Effect.tapError(() => Effect.logDebug(`strategy ${this._tag} with name ${this.name} failed to write to <${file.jsonFile.shortPath}>.${this.path}`)), Effect.catchAll(() => Effect.succeed(file)), Effect.map(() => file));
}
return pipe(getOptionOfObject(contents), Effect.flatMap(parent => Effect.try(() => {
parent[this.path] = nextValue;
file.applyEdit([this.path], nextValue);
})), Effect.tapError(() => Effect.logDebug(`strategy ${this._tag} with name ${this.name} failed to write to <${file.jsonFile.shortPath}>.${this.path}`)), Effect.catchAll(() => Effect.succeed(file)), Effect.map(() => file));
}
}