UNPKG

sync-monorepo-packages

Version:

Synchronize files and metadata across packages in a monorepo

200 lines (184 loc) 5.21 kB
const findUp = require('find-up'); const glob = require('globby'); const {defer, from, iif, of} = require('rxjs'); const { filter, map, mergeAll, mergeMap, tap, distinct, mergeWith, } = require('rxjs/operators'); const debug = require('debug')('sync-monorepo-packages:find-package'); const fs = require('fs-extra'); const path = require('path'); const PACKAGE_JSON = 'package.json'; const LERNA_JSON = 'lerna.json'; /** * @param {string} cwd * @returns {Observable<string[]>} */ function findWorkspaces(cwd = process.cwd()) { debug('Finding workspaces from %s', cwd); return from(findUp(PACKAGE_JSON, {cwd})).pipe( filter(Boolean), mergeMap((pkgPath) => from(fs.readJSON(pkgPath))), map( /** */ (pkg) => pkg.workspaces ?? [] ), tap((value) => { debug('Found workspaces in %s: %s', PACKAGE_JSON, value); }) ); } /** * Finds a Lerna config file (lerna.json) * @param {FindLernaConfigOptions} [opts] * @returns {Observable<LernaInfo>} */ function findLernaConfig({cwd = process.cwd(), lernaJsonPath} = {}) { return iif( () => findLernaConfig.cache.has(`${cwd}:${lernaJsonPath}`), defer(() => of(findLernaConfig.cache.get(`${cwd}:${lernaJsonPath}`))), iif( () => Boolean(lernaJsonPath), of(/** @type {string} */ (lernaJsonPath)), from(findUp(LERNA_JSON, {cwd})) ).pipe( filter(Boolean), mergeMap((lernaConfigPath) => from(fs.readJSON(lernaConfigPath)).pipe( map( /** * @param {LernaJson} lernaConfig */ (lernaConfig) => ({ lernaConfig, lernaRoot: path.dirname(lernaConfigPath), }) ) ) ), tap((lernaInfo) => { debug( 'caching LernaInfo w/ key "%s:%s": %O', cwd, lernaJsonPath, lernaInfo ); findLernaConfig.cache.set(`${cwd}:${lernaJsonPath}`, lernaInfo); }) ) ); } findLernaConfig.cache = new Map(); /** * Finds one or more directories specified by `globs` * @param {string[]} globs - One ore more dirs or globs to dirs * @param {FindByGlobsOptions} [opts] */ function findDirectoriesByGlobs(globs, {cwd = process.cwd()} = {}) { return from( glob(globs, { cwd, onlyDirectories: true, expandDirectories: false, }) ).pipe(mergeAll()); } /** * Finds package.json files within one or more directories specified by `globs` * @param {string[]} globs - One ore more dirs or globs to dirs * @param {FindByGlobsOptions} [opts] */ function findPackageJsonsByGlobs(globs, {cwd = process.cwd()} = {}) { return findDirectoriesByGlobs(globs, {cwd}).pipe( mergeMap((dir) => from(glob(path.join(dir, PACKAGE_JSON)))), mergeAll(), tap((pkgPath) => { debug('Found package.json at %s', PACKAGE_JSON, pkgPath); }) ); } /** * Finds package.json files within packages as defined in a `lerna.json` file * @param {FindPackageJsonsFromLernaConfig} [opts] - Current working directory and path to lerna.json */ function findPackageJsonsFromLernaConfig({ cwd = process.cwd(), lernaJsonPath = '', sourcePkgPath = '', } = {}) { return findLernaConfig({lernaJsonPath, cwd}).pipe( filter(({lernaConfig}) => Boolean(lernaConfig.packages?.length)), mergeMap(({lernaRoot, lernaConfig}) => findPackageJsonsByGlobs(lernaConfig.packages, {cwd: lernaRoot}) ), filter((pkgPath) => pkgPath !== sourcePkgPath) ); } /** * Returns an Observable of paths to `package.json` files * @param {FindPackageJsonsOptions} opts */ function findPackageJsons({ packages: packageDirs = [], cwd = process.cwd(), lernaJsonPath = '', sourcePkgPath = '', } = {}) { return iif( () => Boolean(packageDirs.length), findPackageJsonsByGlobs(packageDirs, {cwd}), findWorkspaces(sourcePkgPath || cwd).pipe( mergeMap((workspaces) => findPackageJsonsByGlobs(workspaces, {cwd})), mergeWith( findPackageJsonsFromLernaConfig({cwd, lernaJsonPath, sourcePkgPath}) ), distinct() ) ); } exports.findWorkspaces = findWorkspaces; exports.findLernaConfig = findLernaConfig; exports.findDirectoriesByGlobs = findDirectoriesByGlobs; exports.findPackageJsonsByGlobs = findPackageJsonsByGlobs; exports.findPackageJsons = findPackageJsons; exports.PACKAGE_JSON = PACKAGE_JSON; /** * @typedef FindPackageJsonsFromLernaConfig * @property {string[]} [packageDirs] * @property {string} [cwd] * @property {string} [lernaJsonPath] * @property {string} [sourcePkgPath] */ /** * @typedef LernaInfo * @property {string} lernaRoot * @property {LernaJson} lernaConfig */ /** * @typedef FindLernaConfigOptions * @property {string} [cwd] * @property {string} [lernaJsonPath] */ /** * @typedef FindByGlobsOptions * @property {string} [cwd] */ /** * @template T * @typedef {import('rxjs').Observable<T>} Observable */ /** * @typedef LernaJson * @property {string[]} packages - Where Lerna finds packages */ /** * @typedef FindPackageJsonsOptions * @property {string[]} [packages] * @property {string} [cwd] * @property {string} [lernaJsonPath] * @property {string} [sourcePkgPath] */