packages feed

okf-cli-0.7.0.0: src/Okf/Cli/ProfileDiscovery.hs

-- | Non-interactive discovery of local profile descriptors available to the CLI.
module Okf.Cli.ProfileDiscovery
  ( ProfileDiscovery (..),
    profileSearchRootsEnvVar,
    parseProfileSearchRoots,
    profileSearchRoots,
    discoverAvailableProfiles,
  )
where

import Data.List qualified as List
import Okf.Cli.BundleDiscovery (parseBundleSearchRoots)
import Okf.Profile.Discovery (defaultProfileDiscoveryOptions, discoverProfileDescriptors)
import System.Environment (lookupEnv)

-- | The effective search roots and normalized descriptors found below them.
data ProfileDiscovery = ProfileDiscovery
  { searchRoots :: ![FilePath],
    descriptorPaths :: ![FilePath]
  }
  deriving stock (Show, Eq)

-- | Colon-separated filesystem search roots, in the style of @PATH@ and
-- @OKF_BUNDLE_ROOTS@. Registry references use a JSON array instead because
-- their own syntax contains colons.
profileSearchRootsEnvVar :: String
profileSearchRootsEnvVar = "OKF_PROFILE_ROOTS"

-- | The shared filesystem-root parser used by bundle discovery. This alias is
-- exported under the profile name so callers need not depend on that module's
-- terminology, while the parsing behavior cannot drift.
parseProfileSearchRoots :: String -> [FilePath]
parseProfileSearchRoots = parseBundleSearchRoots

-- | Where profile discovery starts. An absent or effectively empty override
-- means the current working directory.
profileSearchRoots :: IO [FilePath]
profileSearchRoots = do
  configured <- lookupEnv profileSearchRootsEnvVar
  pure $ case configured of
    Nothing -> ["."]
    Just raw -> case parseProfileSearchRoots raw of
      [] -> ["."]
      roots -> roots

-- | Discover candidates once, sorting and deduplicating across every root.
discoverAvailableProfiles :: IO ProfileDiscovery
discoverAvailableProfiles = do
  roots <- profileSearchRoots
  discovered <-
    List.nub . List.sort . concat
      <$> traverse (discoverProfileDescriptors defaultProfileDiscoveryOptions) roots
  pure ProfileDiscovery {searchRoots = roots, descriptorPaths = discovered}