import minimatch from "minimatch" ; import path from "path" ; import { IFluidRepoPackageEntry } from "../common/fluidRepo" ; import { getPackageManifest } from "../common/fluidUtils" ; import { FluidRepoBuild } from "../fluidBuild/fluidRepoBuild" ; import { getResolvedFluidRoot } from "../common/fluidUtils" ; import { getPackageDetails } from "./packageJson" ; import { BreakingIncrement , BrokenTypes , validatePackage } from "./packageValidator" ; import { enableLogging , log } from "./validatorUtils" ; /**
 * Groupings of packages that should be versioned in lockstep
 * include/exclude are glob paths relative to the repo root
 * Not all packages need to be attributed to groups
 * Currently expects package groups to not cross other packages in the
 * dependency tree, i.e. a package receiving an version increment due to
 * another package in its group should not also then require other packages
 * to need a major/minor version increment that isn't already needed.
 * TODO: verify/fix this expectation
 */ interface PackageGroup { name : string ; directory : string ; ignoredDirs ? : string [ ] ; } export interface IValidationOptions { /**
     * Enable verbose logging for specific packages rather than everything
     */ logForPackages ? : Set < string > ; } function buildPackageGroups ( repoRoot ) { const manifest = getPackageManifest ( repoRoot ) ; const groups = [ ] ; const repoPackages = manifest . repoPackages ?? [ ] ; const addGroup = ( name , entry ) => { if ( name === "client" ) { groups . push ( { name , directory : "packages/**" } ) ; } else if ( Array . isArray ( entry ) ) { entry . map ( subEntry => addGroup ( name , subEntry ) ) ; } else if ( typeof entry === "string" ) { groups . push ( { name , directory : path . join ( entry , "**" ) } ) ; } else { groups . push ( { name , directory : path . join ( entry . directory , "**" ) , ignoredDirs : entry . ignoredDirs ?. map ( relDir => path . join ( entry . directory , relDir , "**" ) ) , } ) ; } } ; for ( const name in repoPackages ) { addGroup ( name , repoPackages [ name ] ) ; } console . log ( groups ) ; return groups ; } function groupForPackage ( groups , pkgJsonPath ) { for ( const group of groups ) { group_block : { if ( minimatch ( pkgJsonPath , group . directory ) ) { for ( const exclude of group . ignoredDirs ?? [ ] ) { if ( minimatch ( pkgJsonPath , exclude ) ) { break group_block ; } } return group . name ; } } } return undefined ; } param pkgDir param pkgIncrement param groups param groupBreaks returns function setPackageGroupIncrement ( pkgDir , pkgIncrement , groups , groupBreaks ) { let pkgGroupName = groups ? groupForPackage ( groups , pkgDir ) : undefined ; if ( pkgGroupName !== undefined ) { groupBreaks . set ( pkgGroupName , pkgIncrement | ( groupBreaks . get ( pkgGroupName ) ?? BreakingIncrement . none ) ) ; } return pkgGroupName ; } export async function validateRepo ( options ? ) { const repoRoot = await getResolvedFluidRoot ( ) ; const repo = new FluidRepoBuild ( repoRoot , false ) ; repo . setMatched ( { all : true , match : [ ] , dirs : [ ] } as any ) ; const buildGraph = repo . createBuildGraph ( { symlink : true , fullSymlink : false } , [ "build" ] ) ; const packages = buildGraph . buildPackages ; const packageGroups = buildPackageGroups ( repoRoot ) ; const groupBreaks = new Map < string , BreakingIncrement > ( ) ; const allBrokenTypes = new Map ( ) ; const breakSummary = [ ] ; for ( let i = 0 ; packages . size > 0 ; i ++ ) { packages . forEach ( ( buildPkg , pkgName ) => { if ( buildPkg . level === i ) { if ( options ?. logForPackages ?. has ( pkgName ) ) { enableLogging ( true ) ; } const packageData = getPackageDetails ( buildPkg . pkg . directory ) ; const pkgJsonPath = path . join ( buildPkg . pkg . directory , "package.json" ) ; const pkgJsonRelativePath = path . relative ( repoRoot , pkgJsonPath ) ; if ( packageData . oldVersions . length > 0 ) { log ( `${ pkgName }, ${ buildPkg . level }` ) ; let { increment , brokenTypes } = validatePackage ( packageData , buildPkg . pkg . directory , allBrokenTypes ) ; brokenTypes . forEach ( ( v , k ) => allBrokenTypes . set ( k , v ) ) ; const groupName = setPackageGroupIncrement ( pkgJsonRelativePath , increment , packageGroups , groupBreaks ) ; breakSummary . push ( { name : packageData . name , level : increment , group : groupName } ) ; } packages . delete ( pkgName ) ; if ( options ?. logForPackages ?. has ( pkgName ) ) { enableLogging ( false ) ; } } } ) ; } for ( const pkgBreak of breakSummary ) { if ( pkgBreak . group !== undefined ) { const groupLevel = groupBreaks . get ( pkgBreak . group ) ! ; if ( groupLevel > pkgBreak . level ) { pkgBreak . level = groupLevel ; log ( `${ pkgBreak . name } inherited break from its group ${ pkgBreak . group }` ) ; } } } for ( const pkgBreak of breakSummary ) { console . log ( pkgBreak ) ; } }