diff --git a/packages/installer/src/dappGet/fetch/DappGetFetcher.ts b/packages/installer/src/dappGet/fetch/DappGetFetcher.ts index bf9166ff9..8dc45336b 100644 --- a/packages/installer/src/dappGet/fetch/DappGetFetcher.ts +++ b/packages/installer/src/dappGet/fetch/DappGetFetcher.ts @@ -1,7 +1,7 @@ -import { Dependencies } from "@dappnode/types"; -import { validRange, satisfies, valid } from "semver"; +import { Dependencies, InstalledPackageData } from "@dappnode/types"; +import { validRange, satisfies, valid, Range } from "semver"; import { DappnodeInstaller } from "../../dappnodeInstaller.js"; -import { listPackageNoThrow } from "@dappnode/dockerapi"; +import { listPackages } from "@dappnode/dockerapi"; export class DappGetFetcher { /** @@ -17,21 +17,11 @@ export class DappGetFetcher { ): Promise { const manifest = await dappnodeInstaller.getManifestFromDir(name, version); const dependencies = manifest.dependencies || {}; + const optionalDependencies = manifest.optionalDependencies || {}; + const installedPackages = await listPackages(); - const optionalDependencies = manifest.optionalDependencies; - if (optionalDependencies) { - // Iterate over optional dependencies and inject them if installed - for (const [ - optionalDependencyName, - optionalDependencyVersion, - ] of Object.entries(optionalDependencies)) { - const optionalDependency = await listPackageNoThrow({ - dnpName: optionalDependencyName, - }); - if (!optionalDependency) continue; - dependencies[optionalDependencyName] = optionalDependencyVersion; - } - } + this.mergeOptionalDependencies(dependencies, optionalDependencies, installedPackages); + this.filterSatisfiedDependencies(dependencies, installedPackages); return dependencies; } @@ -81,4 +71,62 @@ export class DappGetFetcher { // Case 3. unvalid semver version ("/ipfs/Qmre4..."): Asume it's the only version return [versionRange]; } + + private mergeOptionalDependencies( + dependencies: Dependencies, + optionalDependencies: Dependencies, + installedPackages: InstalledPackageData[] + ): void { + for (const [optionalDepName, optionalDepVersion] of Object.entries(optionalDependencies)) { + const isInstalled = installedPackages.some( + (installedPackage) => installedPackage.dnpName === optionalDepName + ); + + if (isInstalled) { + dependencies[optionalDepName] = optionalDepVersion; + } + } + } + + private filterSatisfiedDependencies( + dependencies: Dependencies, + installedPackages: InstalledPackageData[] + ): void { + for (const [depName, depVersion] of Object.entries(dependencies)) { + const installedPackage = installedPackages.find( + (pkg) => pkg.dnpName === depName + ); + + if (!validRange(depVersion)) + throw new Error(`Invalid semver notation for dependency ${depName}: ${depVersion}`); + + if (depVersion.includes('||') || depVersion.includes(' ')) { + throw new Error(`Unsupported version range for dependency ${depName}: ${depVersion}. Only simple ranges are supported`); + } + + if (installedPackage && satisfies(installedPackage.version, depVersion)) { + console.log( + `Dependency ${depName} is already installed with version ${installedPackage.version}` + ); + // Remove the dependency if the installed version satisfies the required version + delete dependencies[depName]; + } else { + + // Use "*" (latest) if the dependency is not installed and version is >... or >=... + if (!installedPackage && /^>=?\d+\.\d+\.\d+$/.test(depVersion)) { + dependencies[depName] = '*'; + + // Use x.x.x if the dependency is not installed and version is ^x.x.x or ~x.x.x + } else if (!installedPackage && /^[\^~]\d+\.\d+\.\d+$/.test(depVersion)) { + + // Remove the operator prefix and use the defined version + dependencies[depName] = depVersion.slice(1); + + } else { + + throw new Error(`Unsupported version range for dependency ${depName}: ${depVersion}. Only simle ranges with operators ^, ~, > and >= are supported`); + } + } + } + } }