import 'reflect-metadata' ; import { flatten } from 'lodash' ; import { listMonorepoPackageNames } from 'common' ; import * as packageJson from '../package.json' ; describe ( 'package.json dependencies' , ( ) => { const monorepoPackageNames = listMonorepoPackageNames ( ) ; const isMonorepoPackage = ( packageName ) => monorepoPackageNames . includes ( packageName ) ; const monorepoDevDependencies = Object . keys ( packageJson . devDependencies ) . filter ( isMonorepoPackage ) ; const monorepoNonDevDependencies = Object . keys ( packageJson . dependencies ) . filter ( isMonorepoPackage ) ; it ( 'does not include any direct non-dev dependencies on monorepo packages' , ( ) => { expect ( monorepoNonDevDependencies ) . toEqual ( [ ] ) ; } ) ; it ( 'includes direct dependencies for each non-monorepo dependency of its transitive monorepo dependencies' , async ( ) => { const actualDirectDependencyNames = Object . keys ( packageJson . dependencies ) ; const actualDirectDependencies = actualDirectDependencyNames . map ( ( name ) => formatDependencyListing ( name , packageJson . dependencies ) ) ; const missingDependenciesToResponsibleDependents = { } ; for ( const monorepoDevDependency of monorepoDevDependencies ) { const expectedNonMonorepoDependencies = await getEdgeNonMonorepoDependencies ( monorepoDevDependency ) ; const missingDependencies = expectedNonMonorepoDependencies . filter ( ( d ) => ! actualDirectDependencies . includes ( d ) ) ; for ( const missingDependency of missingDependencies ) { if ( ! missingDependenciesToResponsibleDependents [ missingDependency ] ) { missingDependenciesToResponsibleDependents [ missingDependency ] = [ ] ; } missingDependenciesToResponsibleDependents [ missingDependency ] . push ( monorepoDevDependency ) ; } } const missingDependencyExplanations = Object . keys ( missingDependenciesToResponsibleDependents ) . sort ( ) . map ( ( missingDependency ) => { const responsibleDependents = missingDependenciesToResponsibleDependents [ missingDependency ] . sort ( ) ; return `package.json needs a dependency on ${ missingDependency } (required via ${ responsibleDependents . join ( ', ' ) })` ; } ) ; expect ( missingDependencyExplanations ) . toStrictEqual ( [ ] ) ; } ) ; function formatDependencyListing ( depName , dependencies ) { return `${ JSON . stringify ( depName ) }: ${ JSON . stringify ( dependencies [ depName ] ) }` ; } async function getEdgeNonMonorepoDependencies ( monorepoPackageName ) { const monorepoPackageJson = await import ( `${ monorepoPackageName }/package.json` ) ; const deps = Object . keys ( monorepoPackageJson . dependencies ) ; const directNonMonorepoDepNames = deps . filter ( ( d ) => ! isMonorepoPackage ( d ) ) ; const directNonMonorepoDeps = directNonMonorepoDepNames . map ( ( name ) => formatDependencyListing ( name , monorepoPackageJson . dependencies ) ) ; const directMonorepoDeps = deps . filter ( isMonorepoPackage ) ; const indirectNonMonorepoDepGroups = await Promise . all ( directMonorepoDeps . map ( getEdgeNonMonorepoDependencies ) ) ; const edgeNonMonorepoDeps = [ ... directNonMonorepoDeps , ... flatten ( indirectNonMonorepoDepGroups ) ] ; const dedupedEdgeNonMonorepoDeps = [ ... new Set ( edgeNonMonorepoDeps ) ] ; return dedupedEdgeNonMonorepoDeps ; } } ) ;