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}`); } };