import { Connection } from "@salesforce/core" ; import Component from "./Component" ; import DependencyViolation from "./DependencyViolation" ; import { ComponentSet , registry } from '@salesforce/source-deploy-retrieve' ; import PackageManifest from "../package/PackageManifest" ; import ProjectConfig from "../project/ProjectConfig" ; import * as fs from "fs-extra" ; import InstalledPackagesFetcher from "../package/packageQuery/InstalledPackagesFetcher" ; import DependencyFetcher from "./DependencyFetcher" ; const REGISTRY_SUPPORTED_TYPES = Object . values ( registry . types ) . map ( type => type . name ) ; export default class DependencyAnalysis { constructor ( private conn , private components ) { } async exec ( ) { const violations = [ ] ; const projectConfig = ProjectConfig . getSFDXPackageManifest ( null ) ; const managedPackages = await new InstalledPackagesFetcher ( this . conn ) . fetchManagedPackages ( ) ; const managedPackageNamespaces = managedPackages . map ( pkg => pkg . namespacePrefix ) ; const componentsWithDependencies = await new DependencyFetcher ( this . conn , this . components ) . fetch ( ) ; for ( const component of componentsWithDependencies ) { component . dependencies = component . dependencies . filter ( dependency => { const isComponentInManagedPackage = managedPackageNamespaces . find ( namespace => namespace === dependency . namespace ) ? true : false ; return REGISTRY_SUPPORTED_TYPES . includes ( dependency . type ) && ! isComponentInManagedPackage ; } ) ; if ( component . dependencies . length > 0 ) { await this . populatePackageFieldsForDependencies ( component . dependencies , projectConfig ) ; const sourceBackedDependencies = component . dependencies . filter ( cmp => cmp . files . length > 0 ) ; sourceBackedDependencies . forEach ( cmp => { if ( component . indexOfPackage < cmp . indexOfPackage ) { violations . push ( { component : component , dependency : cmp , description : `Invalid Dependency: ${ component . fullName } is dependent on ${ cmp . fullName } found in ${ cmp . package }` } ) ; } if ( component . packageType === "Unlocked" && cmp . packageType === "Unlocked" && component . package !== cmp . package ) { const isDependencyDefined = projectConfig . packageDirectories [ component . indexOfPackage ] . dependencies ?. find ( dependency => { const packageName = dependency . package . split ( "@" ) [ 0 ] ; return packageName === cmp . package ; } ) ? true : false ; if ( ! isDependencyDefined ) { violations . push ( { component : component , dependency : cmp , description : `Missing Dependency: ${ component . package } unlocked package is dependent on ${ cmp . package }` } ) ; } } } ) ; } else { continue ; } } return violations ; } param dependencies param projectConfig private async populatePackageFieldsForDependencies ( dependencies , projectConfig ) { const cmps = dependencies . map ( cmp => { return { fullName : cmp . fullName , type : cmp . type } ; } ) ; const packageManifest = PackageManifest . createFromScratch ( cmps , "50.0" ) ; fs . writeFileSync ( `.sfpowerscripts/package.xml` , packageManifest . manifestXml ) ; const componentSet = await ComponentSet . fromManifest ( { manifestPath : '.sfpowerscripts/package.xml' , resolveSourcePaths : projectConfig . packageDirectories . map ( pkg => pkg . path ) } ) ; dependencies . forEach ( cmp => { const componentFilenames = componentSet . getComponentFilenamesByNameAndType ( { fullName : cmp . fullName , type : cmp . type } ) ; cmp . files = componentFilenames ; const indexOfPackage = projectConfig . packageDirectories . findIndex ( pkg => componentFilenames . find ( file => file . includes ( pkg . path ) ) ) ; cmp . indexOfPackage = indexOfPackage ; const packageName = projectConfig . packageDirectories [ indexOfPackage ] ?. package ; if ( packageName ) { cmp . package = packageName ; cmp . packageType = ProjectConfig . getPackageType ( projectConfig , packageName ) ; } } ) ; } }