import * as path from 'path';
import { pipe } from 'fp-ts/function';
import * as O from 'fp-ts/Option';
import * as R from 'fp-ts/Record';
import * as t from 'io-ts';
import * as JsonUtils from '../utils/JsonUtils';
import * as PromiseUtils from '../utils/PromiseUtils';
import * as IotsUtils from '../utils/IotsUtils';
import * as PreRelease from './PreRelease';
import * as Version from './Version';
type Option = O.Option;
type Version = Version.Version;
export interface PackageJson {
readonly name: string;
readonly version?: Version;
readonly workspaces?: string[];
readonly beehiveFlow?: {
readonly primaryWorkspace?: string;
};
readonly dependencies?: Record;
readonly devDependencies?: Record;
readonly [k: string]: unknown;
}
export const versionCodec = new t.Type(
'versionCodec',
// We don't need a type guard function, so just provide a dummy one that always fails
(input: unknown): input is Version => false,
IotsUtils.validateEither(Version.parseVersionE),
Version.versionToString
);
export const packageJsonCodec = (): t.Type => {
const mandatory = t.type({
name: t.string
});
const partial = t.partial({
version: t.string.pipe(versionCodec),
workspaces: t.array(t.string),
beehiveFlow: t.partial({
primaryWorkspace: t.string
}),
dependencies: t.record(t.string, t.string),
devDependencies: t.record(t.string, t.string)
});
return t.intersection([ mandatory, partial ]);
};
export const pjInFolder = (folder: string): string =>
path.join(folder, 'package.json');
export const decodeE = (j: unknown): t.Validation =>
packageJsonCodec().decode(j);
export const decode = async (j: unknown): Promise =>
PromiseUtils.eitherToPromise(decodeE(j));
export const parsePackageJsonFile = (file: string): Promise =>
JsonUtils.parseJsonFile(file).then(decode);
export const parsePackageJsonFileInFolder = (folder: string): Promise =>
parsePackageJsonFile(pjInFolder(folder));
export const toJson = (pj: PackageJson): unknown =>
packageJsonCodec().encode(pj);
export const writePackageJsonFile = (file: string, pj: PackageJson): Promise =>
JsonUtils.writeJsonFile(file, toJson(pj));
export const setVersion = (pj: PackageJson, version: Option): PackageJson => ({
...pj,
version: O.toUndefined(version)
});
export const writePackageJsonFileWithNewVersion = async (pj: PackageJson, newVersion: Version, pjFile: string): Promise => {
console.log(`Setting version in ${pjFile} to: ${Version.versionToString(newVersion)}`);
const newPj = setVersion(pj, O.some(newVersion));
await writePackageJsonFile(pjFile, newPj);
return newPj;
};
const hasPreReleaseDependency = (version: string) =>
version.includes('-' + PreRelease.releaseCandidate) ||
version.includes('-' + PreRelease.featureBranch) ||
version.includes('-' + PreRelease.hotfixBranch) ||
version.includes('-' + PreRelease.spikeBranch) ||
version.includes('-spike'); // Legacy spike branch prefix
const lookupDeps = (pj: PackageJson, name: 'dependencies' | 'devDependencies', filter: (name: string) => boolean) => pipe(
O.fromNullable(pj[name]),
O.getOrElse((): Record => ({})),
R.filterWithIndex(filter)
);
export const shouldNotHavePreReleasePackages = async (pj: PackageJson): Promise => {
const getInvalidDeps = (dependencies: Record) => pipe(
dependencies,
R.filter(hasPreReleaseDependency),
R.keys
);
const invalidDeps = getInvalidDeps({
...lookupDeps(pj, 'dependencies', () => true),
...lookupDeps(pj, 'devDependencies', (key) => key === '@tinymce/beehive-flow')
});
if (invalidDeps.length > 0) {
const names = invalidDeps.join(', ');
return PromiseUtils.fail(`Pre-release versions were found for: ${names}`);
}
};