module Ecluse.Core.Registry.Npm.Project (
parsePackageInfo,
parsePackageInfoFromValue,
parseVersionDetails,
parseVersionList,
projectVersionEntry,
enforceTarballScheme,
enforceTarballSchemeDetails,
Projection (..),
projectName,
) where
import Data.Aeson (FromJSON (parseJSON), Object, Value (String), eitherDecodeStrict, withObject, (.!=), (.:?))
import Data.Aeson.Types (Parser, parseEither, parseMaybe)
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as T
import Data.Time (UTCTime)
import Ecluse.Core.Ecosystem (Ecosystem (Npm))
import Ecluse.Core.Package (
Artifact (..),
ArtifactKind (Tarball),
Availability (Available, Deprecated),
CodeExecSignal (NoCodeOnInstall, RunsCodeOnInstall),
Hash,
HashAlg (SHA1, SRI),
InvalidEntry (..),
InvalidEntryKind (InvalidDistTag, InvalidPublishTime, InvalidVersionManifest),
PackageDetails (..),
PackageInfo (..),
PackageName,
Person (..),
Scope,
Trust (TrustUnknown),
mkHash,
mkPackageName,
mkScope,
renderPackageName,
)
import Ecluse.Core.Registry (ParseError (..), RegistryResponse (responseBody))
import Ecluse.Core.Registry.Npm.Wire (
Dist (..),
License (LicenseObject, LicenseSpdx),
VersionManifest (..),
)
import Ecluse.Core.Registry.Npm.Wire qualified as Wire
import Ecluse.Core.Security (hostAddress)
import Ecluse.Core.Security.Egress (registryUrlText, resolveTarballUrl)
import Ecluse.Core.Version (Version, mkVersion, renderVersion, unVersion)
data WirePackument = WirePackument
{ WirePackument -> Text
wpName :: Text
, WirePackument -> Map Text Text
wpDistTags :: Map Text Text
, WirePackument -> Map Text VersionEntry
wpVersions :: Map Text VersionEntry
, WirePackument -> Map Text UTCTime
wpTime :: Map Text UTCTime
, WirePackument -> [InvalidEntry]
wpInvalidEntries :: [InvalidEntry]
}
instance FromJSON WirePackument where
parseJSON :: Value -> Parser WirePackument
parseJSON = String
-> (Object -> Parser WirePackument)
-> Value
-> Parser WirePackument
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"npm packument" ((Object -> Parser WirePackument) -> Value -> Parser WirePackument)
-> (Object -> Parser WirePackument)
-> Value
-> Parser WirePackument
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
name <- Object
o Object -> Key -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"name" Parser (Maybe Text) -> Text -> Parser Text
forall a. Parser (Maybe a) -> a -> Parser a
.!= Text
""
(distTags, distTagDrops) <- lenientDistTags o
(versions, versionDrops) <- lenientVersionMap o
(time, timeDrops) <- lenientTimeMap (Map.keysSet versions) o
pure
WirePackument
{ wpName = name
, wpDistTags = distTags
, wpVersions = versions
, wpTime = time
,
wpInvalidEntries = versionDrops <> distTagDrops <> timeDrops
}
partitionLenient :: InvalidEntryKind -> (Value -> Either String a) -> Map Text Value -> (Map Text a, [InvalidEntry])
partitionLenient :: forall a.
InvalidEntryKind
-> (Value -> Either String a)
-> Map Text Value
-> (Map Text a, [InvalidEntry])
partitionLenient InvalidEntryKind
kind Value -> Either String a
decode =
(Text
-> Value
-> (Map Text a, [InvalidEntry])
-> (Map Text a, [InvalidEntry]))
-> (Map Text a, [InvalidEntry])
-> Map Text Value
-> (Map Text a, [InvalidEntry])
forall k a b. (k -> a -> b -> b) -> b -> Map k a -> b
Map.foldrWithKey Text
-> Value
-> (Map Text a, [InvalidEntry])
-> (Map Text a, [InvalidEntry])
step (Map Text a
forall k a. Map k a
Map.empty, [])
where
step :: Text
-> Value
-> (Map Text a, [InvalidEntry])
-> (Map Text a, [InvalidEntry])
step Text
key Value
value (Map Text a
kept, [InvalidEntry]
dropped) = case Value -> Either String a
decode Value
value of
Right a
a -> (Text -> a -> Map Text a -> Map Text a
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Text
key a
a Map Text a
kept, [InvalidEntry]
dropped)
Left String
err -> (Map Text a
kept, InvalidEntryKind -> Text -> Value -> Text -> InvalidEntry
InvalidEntry InvalidEntryKind
kind Text
key Value
value (String -> Text
forall a. ToText a => a -> Text
toText String
err) InvalidEntry -> [InvalidEntry] -> [InvalidEntry]
forall a. a -> [a] -> [a]
: [InvalidEntry]
dropped)
lenientVersionMap :: Object -> Parser (Map Text VersionEntry, [InvalidEntry])
lenientVersionMap :: Object -> Parser (Map Text VersionEntry, [InvalidEntry])
lenientVersionMap Object
o = do
raw <- Object
o Object -> Key -> Parser (Maybe (Map Text Value))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"versions" Parser (Maybe (Map Text Value))
-> Map Text Value -> Parser (Map Text Value)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Value
forall a. Monoid a => a
mempty
pure (partitionLenient InvalidVersionManifest (parseEither parseJSON) raw)
lenientDistTags :: Object -> Parser (Map Text Text, [InvalidEntry])
lenientDistTags :: Object -> Parser (Map Text Text, [InvalidEntry])
lenientDistTags Object
o = do
raw <- Object
o Object -> Key -> Parser (Maybe (Map Text Value))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"dist-tags" Parser (Maybe (Map Text Value))
-> Map Text Value -> Parser (Map Text Value)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Value
forall a. Monoid a => a
mempty
pure (partitionLenient InvalidDistTag (parseEither parseJSON) raw)
lenientTimeMap :: Set Text -> Object -> Parser (Map Text UTCTime, [InvalidEntry])
lenientTimeMap :: Set Text -> Object -> Parser (Map Text UTCTime, [InvalidEntry])
lenientTimeMap Set Text
versionKeys Object
o = do
raw <- Object
o Object -> Key -> Parser (Maybe (Map Text Value))
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"time" Parser (Maybe (Map Text Value))
-> Map Text Value -> Parser (Map Text Value)
forall a. Parser (Maybe a) -> a -> Parser a
.!= Map Text Value
forall a. Monoid a => a
mempty
let (kept, dropped) = partitionLenient InvalidPublishTime (parseEither parseJSON) raw
pure (kept, filter ((`Set.member` versionKeys) . invalidKey) dropped)
data VersionEntry = VersionEntry
{ VersionEntry -> VersionManifest
veManifest :: VersionManifest
, VersionEntry -> Maybe Person
vePublisher :: Maybe Wire.Person
}
instance FromJSON VersionEntry where
parseJSON :: Value -> Parser VersionEntry
parseJSON Value
v =
String
-> (Object -> Parser VersionEntry) -> Value -> Parser VersionEntry
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"npm version object" (\Object
o -> VersionManifest -> Maybe Person -> VersionEntry
VersionEntry (VersionManifest -> Maybe Person -> VersionEntry)
-> Parser VersionManifest -> Parser (Maybe Person -> VersionEntry)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Value -> Parser VersionManifest
forall a. FromJSON a => Value -> Parser a
parseJSON Value
v Parser (Maybe Person -> VersionEntry)
-> Parser (Maybe Person) -> Parser VersionEntry
forall a b. Parser (a -> b) -> Parser a -> Parser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Object
o Object -> Key -> Parser (Maybe Person)
forall a. FromJSON a => Object -> Key -> Parser (Maybe a)
.:? Key
"_npmUser") Value
v
data Projection
=
Projected PackageInfo
|
NameMismatch Text
deriving stock (Projection -> Projection -> Bool
(Projection -> Projection -> Bool)
-> (Projection -> Projection -> Bool) -> Eq Projection
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Projection -> Projection -> Bool
== :: Projection -> Projection -> Bool
$c/= :: Projection -> Projection -> Bool
/= :: Projection -> Projection -> Bool
Eq, Int -> Projection -> ShowS
[Projection] -> ShowS
Projection -> String
(Int -> Projection -> ShowS)
-> (Projection -> String)
-> ([Projection] -> ShowS)
-> Show Projection
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Projection -> ShowS
showsPrec :: Int -> Projection -> ShowS
$cshow :: Projection -> String
show :: Projection -> String
$cshowList :: [Projection] -> ShowS
showList :: [Projection] -> ShowS
Show)
parsePackageInfo :: PackageName -> RegistryResponse -> Either ParseError PackageInfo
parsePackageInfo :: PackageName -> RegistryResponse -> Either ParseError PackageInfo
parsePackageInfo PackageName
requestedName RegistryResponse
resp =
RegistryResponse -> Either ParseError WirePackument
decodePackument RegistryResponse
resp Either ParseError WirePackument
-> (WirePackument -> Either ParseError Projection)
-> Either ParseError Projection
forall a b.
Either ParseError a
-> (a -> Either ParseError b) -> Either ParseError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= PackageName -> WirePackument -> Either ParseError Projection
projectValidated PackageName
requestedName Either ParseError Projection
-> (Projection -> Either ParseError PackageInfo)
-> Either ParseError PackageInfo
forall a b.
Either ParseError a
-> (a -> Either ParseError b) -> Either ParseError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Projection -> Either ParseError PackageInfo
requireMatch
parsePackageInfoFromValue :: PackageName -> Value -> Either ParseError Projection
parsePackageInfoFromValue :: PackageName -> Value -> Either ParseError Projection
parsePackageInfoFromValue PackageName
requestedName Value
value =
Value -> Either ParseError WirePackument
decodePackumentValue Value
value Either ParseError WirePackument
-> (WirePackument -> Either ParseError Projection)
-> Either ParseError Projection
forall a b.
Either ParseError a
-> (a -> Either ParseError b) -> Either ParseError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= PackageName -> WirePackument -> Either ParseError Projection
projectValidated PackageName
requestedName
projectValidated :: PackageName -> WirePackument -> Either ParseError Projection
projectValidated :: PackageName -> WirePackument -> Either ParseError Projection
projectValidated PackageName
requestedName WirePackument
pkmt = do
info <- WirePackument -> Either ParseError PackageInfo
projectPackageInfo WirePackument
pkmt
pure $
if infoName info == requestedName
then Projected info
else NameMismatch (renderPackageName (infoName info))
requireMatch :: Projection -> Either ParseError PackageInfo
requireMatch :: Projection -> Either ParseError PackageInfo
requireMatch = \case
Projected PackageInfo
info -> PackageInfo -> Either ParseError PackageInfo
forall a b. b -> Either a b
Right PackageInfo
info
NameMismatch Text
reported ->
ParseError -> Either ParseError PackageInfo
forall a b. a -> Either a b
Left (Text -> ParseError
ParseError (Text
"upstream packument self-reported the name " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
reported Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", which is not the requested package"))
projectPackageInfo :: WirePackument -> Either ParseError PackageInfo
projectPackageInfo :: WirePackument -> Either ParseError PackageInfo
projectPackageInfo WirePackument
pkmt = do
name <- Text -> Either ParseError PackageName
projectName (WirePackument -> Text
wpName WirePackument
pkmt)
pure
PackageInfo
{ infoName = name
, infoVersions = projectVersions name pkmt
, infoDistTags = projectDistTags pkmt
, infoInvalidEntries = wpInvalidEntries pkmt
}
parseVersionDetails :: RegistryResponse -> Version -> Either ParseError PackageDetails
parseVersionDetails :: RegistryResponse -> Version -> Either ParseError PackageDetails
parseVersionDetails RegistryResponse
resp Version
version = do
info <- RegistryResponse -> Either ParseError WirePackument
decodePackument RegistryResponse
resp Either ParseError WirePackument
-> (WirePackument -> Either ParseError PackageInfo)
-> Either ParseError PackageInfo
forall a b.
Either ParseError a
-> (a -> Either ParseError b) -> Either ParseError b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= WirePackument -> Either ParseError PackageInfo
projectPackageInfo
case Map.lookup (renderVersion version) (infoVersions info) of
Just PackageDetails
details -> PackageDetails -> Either ParseError PackageDetails
forall a b. b -> Either a b
Right PackageDetails
details
Maybe PackageDetails
Nothing ->
ParseError -> Either ParseError PackageDetails
forall a b. a -> Either a b
Left (Text -> ParseError
ParseError (Text
"version not present in packument: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Version -> Text
renderVersion Version
version))
projectVersionEntry :: PackageName -> Version -> Maybe UTCTime -> Value -> Maybe PackageDetails
projectVersionEntry :: PackageName
-> Version -> Maybe UTCTime -> Value -> Maybe PackageDetails
projectVersionEntry PackageName
name Version
version Maybe UTCTime
publishedAt Value
value =
PackageName
-> Version -> Maybe UTCTime -> VersionEntry -> PackageDetails
projectDetails PackageName
name Version
version Maybe UTCTime
publishedAt (VersionEntry -> PackageDetails)
-> Maybe VersionEntry -> Maybe PackageDetails
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Value -> Parser VersionEntry) -> Value -> Maybe VersionEntry
forall a b. (a -> Parser b) -> a -> Maybe b
parseMaybe Value -> Parser VersionEntry
forall a. FromJSON a => Value -> Parser a
parseJSON Value
value
enforceTarballScheme :: Text -> PackageInfo -> PackageInfo
enforceTarballScheme :: Text -> PackageInfo -> PackageInfo
enforceTarballScheme Text
upstreamBaseUrl PackageInfo
info =
case Text -> Maybe Text
httpsUpstreamHost Text
upstreamBaseUrl of
Maybe Text
Nothing -> PackageInfo
info
Just Text
upstreamHost ->
let (Map Text PackageDetails
kept, [InvalidEntry]
drops) = (Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry]))
-> (Map Text PackageDetails, [InvalidEntry])
-> Map Text PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
forall k a b. (k -> a -> b -> b) -> b -> Map k a -> b
Map.foldrWithKey (Text
-> Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry])
step Text
upstreamHost) (Map Text PackageDetails
forall k a. Map k a
Map.empty, []) (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
in PackageInfo
info{infoVersions = kept, infoInvalidEntries = infoInvalidEntries info <> drops}
where
step :: Text
-> Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry])
step Text
upstreamHost Text
rawVersion PackageDetails
details (Map Text PackageDetails
keptAcc, [InvalidEntry]
dropAcc) =
case Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details of
Right PackageDetails
ok -> (Text
-> PackageDetails
-> Map Text PackageDetails
-> Map Text PackageDetails
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Text
rawVersion PackageDetails
ok Map Text PackageDetails
keptAcc, [InvalidEntry]
dropAcc)
Left (Text
reason, Text
badUrl) ->
(Map Text PackageDetails
keptAcc, InvalidEntryKind -> Text -> Value -> Text -> InvalidEntry
InvalidEntry InvalidEntryKind
InvalidVersionManifest Text
rawVersion (Text -> Value
String Text
badUrl) Text
reason InvalidEntry -> [InvalidEntry] -> [InvalidEntry]
forall a. a -> [a] -> [a]
: [InvalidEntry]
dropAcc)
enforceTarballSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails
enforceTarballSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails
enforceTarballSchemeDetails Text
upstreamBaseUrl PackageDetails
details =
case Text -> Maybe Text
httpsUpstreamHost Text
upstreamBaseUrl of
Maybe Text
Nothing -> PackageDetails -> Maybe PackageDetails
forall a. a -> Maybe a
Just PackageDetails
details
Just Text
upstreamHost -> Either (Text, Text) PackageDetails -> Maybe PackageDetails
forall l r. Either l r -> Maybe r
rightToMaybe (Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details)
httpsUpstreamHost :: Text -> Maybe Text
httpsUpstreamHost :: Text -> Maybe Text
httpsUpstreamHost Text
baseUrl
| Text
"https://" Text -> Text -> Bool
`T.isPrefixOf` Text -> Text
T.toLower Text
baseUrl = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Text
hostAddress Text
baseUrl)
| Bool
otherwise = Maybe Text
forall a. Maybe a
Nothing
resolveDetails :: Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails :: Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details =
(\NonEmpty Artifact
arts -> PackageDetails
details{pkgArtifacts = arts}) (NonEmpty Artifact -> PackageDetails)
-> Either (Text, Text) (NonEmpty Artifact)
-> Either (Text, Text) PackageDetails
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Artifact -> Either (Text, Text) Artifact)
-> NonEmpty Artifact -> Either (Text, Text) (NonEmpty Artifact)
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> NonEmpty a -> f (NonEmpty b)
traverse (Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact Text
upstreamHost) (PackageDetails -> NonEmpty Artifact
pkgArtifacts PackageDetails
details)
resolveArtifact :: Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact :: Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact Text
upstreamHost Artifact
art =
case Text -> Text -> Either Text RegistryUrl
resolveTarballUrl Text
upstreamHost (Artifact -> Text
artUrl Artifact
art) of
Right RegistryUrl
resolved -> Artifact -> Either (Text, Text) Artifact
forall a b. b -> Either a b
Right Artifact
art{artUrl = registryUrlText resolved}
Left Text
reason -> (Text, Text) -> Either (Text, Text) Artifact
forall a b. a -> Either a b
Left (Text
reason, Artifact -> Text
artUrl Artifact
art)
parseVersionList :: RegistryResponse -> Either ParseError [Version]
parseVersionList :: RegistryResponse -> Either ParseError [Version]
parseVersionList RegistryResponse
resp = do
pkmt <- RegistryResponse -> Either ParseError WirePackument
decodePackument RegistryResponse
resp
pure (map (mkVersion Npm) (Map.keys (wpVersions pkmt)))
decodePackument :: RegistryResponse -> Either ParseError WirePackument
decodePackument :: RegistryResponse -> Either ParseError WirePackument
decodePackument =
(String -> ParseError)
-> Either String WirePackument -> Either ParseError WirePackument
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (Text -> ParseError
ParseError (Text -> ParseError) -> (String -> Text) -> String -> ParseError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. ToText a => a -> Text
toText) (Either String WirePackument -> Either ParseError WirePackument)
-> (RegistryResponse -> Either String WirePackument)
-> RegistryResponse
-> Either ParseError WirePackument
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> Either String WirePackument
forall a. FromJSON a => ByteString -> Either String a
eitherDecodeStrict (ByteString -> Either String WirePackument)
-> (RegistryResponse -> ByteString)
-> RegistryResponse
-> Either String WirePackument
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RegistryResponse -> ByteString
responseBody
decodePackumentValue :: Value -> Either ParseError WirePackument
decodePackumentValue :: Value -> Either ParseError WirePackument
decodePackumentValue =
(String -> ParseError)
-> Either String WirePackument -> Either ParseError WirePackument
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (Text -> ParseError
ParseError (Text -> ParseError) -> (String -> Text) -> String -> ParseError
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. ToText a => a -> Text
toText) (Either String WirePackument -> Either ParseError WirePackument)
-> (Value -> Either String WirePackument)
-> Value
-> Either ParseError WirePackument
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Value -> Parser WirePackument)
-> Value -> Either String WirePackument
forall a b. (a -> Parser b) -> a -> Either String b
parseEither Value -> Parser WirePackument
forall a. FromJSON a => Value -> Parser a
parseJSON
projectVersions :: PackageName -> WirePackument -> Map Text PackageDetails
projectVersions :: PackageName -> WirePackument -> Map Text PackageDetails
projectVersions PackageName
name WirePackument
pkmt =
(Text -> VersionEntry -> PackageDetails)
-> Map Text VersionEntry -> Map Text PackageDetails
forall k a b. (k -> a -> b) -> Map k a -> Map k b
Map.mapWithKey Text -> VersionEntry -> PackageDetails
projectAt (WirePackument -> Map Text VersionEntry
wpVersions WirePackument
pkmt)
where
projectAt :: Text -> VersionEntry -> PackageDetails
projectAt Text
rawVersion =
PackageName
-> Version -> Maybe UTCTime -> VersionEntry -> PackageDetails
projectDetails
PackageName
name
(Ecosystem -> Text -> Version
mkVersion Ecosystem
Npm Text
rawVersion)
(Text -> Map Text UTCTime -> Maybe UTCTime
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
rawVersion (WirePackument -> Map Text UTCTime
wpTime WirePackument
pkmt))
projectDetails :: PackageName -> Version -> Maybe UTCTime -> VersionEntry -> PackageDetails
projectDetails :: PackageName
-> Version -> Maybe UTCTime -> VersionEntry -> PackageDetails
projectDetails PackageName
name Version
version Maybe UTCTime
publishedAt VersionEntry
entry =
PackageDetails
{ pkgName :: PackageName
pkgName = PackageName
name
, pkgVersion :: Version
pkgVersion = Version
version
, pkgPublishedAt :: Maybe UTCTime
pkgPublishedAt = Maybe UTCTime
publishedAt
, pkgInstallCode :: CodeExecSignal
pkgInstallCode = VersionManifest -> CodeExecSignal
installCode VersionManifest
vm
, pkgTrust :: Trust
pkgTrust = Trust
TrustUnknown
, pkgAvailability :: Availability
pkgAvailability = VersionManifest -> Availability
availability VersionManifest
vm
, pkgArtifacts :: NonEmpty Artifact
pkgArtifacts = Version -> Dist -> Artifact
projectArtifact Version
version (VersionManifest -> Dist
vmDist VersionManifest
vm) Artifact -> [Artifact] -> NonEmpty Artifact
forall a. a -> [a] -> NonEmpty a
:| []
, pkgLicenses :: [Text]
pkgLicenses = [Text] -> (License -> [Text]) -> Maybe License -> [Text]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (Text -> [Text]
OneItem [Text] -> [Text]
forall x. One x => OneItem x -> x
one (Text -> [Text]) -> (License -> Text) -> License -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. License -> Text
licenseText) (VersionManifest -> Maybe License
vmLicense VersionManifest
vm)
, pkgPublisher :: Maybe Person
pkgPublisher = Person -> Person
projectPerson (Person -> Person) -> Maybe Person -> Maybe Person
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> VersionEntry -> Maybe Person
vePublisher VersionEntry
entry
}
where
vm :: VersionManifest
vm = VersionEntry -> VersionManifest
veManifest VersionEntry
entry
licenseText :: License -> Text
licenseText :: License -> Text
licenseText = \case
LicenseSpdx Text
spdx -> Text
spdx
LicenseObject Text
name Maybe Text
_url -> Text
name
installCode :: VersionManifest -> CodeExecSignal
installCode :: VersionManifest -> CodeExecSignal
installCode VersionManifest
vm
| Bool -> Bool
not ([Text] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text]
hooks) =
Text -> CodeExecSignal
RunsCodeOnInstall (Text
"declares install script(s): " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> Text
T.intercalate Text
", " [Text]
hooks)
| VersionManifest -> Maybe Bool
vmHasInstallScript VersionManifest
vm Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True =
Text -> CodeExecSignal
RunsCodeOnInstall Text
"declares an install script (hasInstallScript)"
| Bool
otherwise = CodeExecSignal
NoCodeOnInstall
where
hooks :: [Text]
hooks = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Map Text Text -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`Map.member` VersionManifest -> Map Text Text
vmScripts VersionManifest
vm) [Text]
installHooks
installHooks :: [Text]
installHooks :: [Text]
installHooks = [Text
"preinstall", Text
"install", Text
"postinstall"]
availability :: VersionManifest -> Availability
availability :: VersionManifest -> Availability
availability VersionManifest
vm = Availability
-> (Text -> Availability) -> Maybe Text -> Availability
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Availability
Available Text -> Availability
Deprecated (VersionManifest -> Maybe Text
vmDeprecated VersionManifest
vm)
projectArtifact :: Version -> Dist -> Artifact
projectArtifact :: Version -> Dist -> Artifact
projectArtifact Version
version Dist
dist =
Artifact
{ artFilename :: Text
artFilename = Text -> Version -> Text
tarballFilename (Dist -> Text
distTarball Dist
dist) Version
version
, artUrl :: Text
artUrl = Dist -> Text
distTarball Dist
dist
, artKind :: ArtifactKind
artKind = ArtifactKind
Tarball
, artHashes :: [Hash]
artHashes = [Maybe Hash] -> [Hash]
forall a. [Maybe a] -> [a]
catMaybes [Maybe Hash
sriHash, Maybe Hash
sha1Hash]
, artSize :: Maybe Int
artSize = Dist -> Maybe Int
distUnpackedSize Dist
dist
, artInterpreter :: Maybe Text
artInterpreter = Maybe Text
forall a. Maybe a
Nothing
, artYanked :: Bool
artYanked = Bool
False
, artProvenance :: Maybe Text
artProvenance = Maybe Text
forall a. Maybe a
Nothing
}
where
toHash :: HashAlg -> Text -> Maybe Hash
toHash :: HashAlg -> Text -> Maybe Hash
toHash HashAlg
alg = Either Text Hash -> Maybe Hash
forall l r. Either l r -> Maybe r
rightToMaybe (Either Text Hash -> Maybe Hash)
-> (Text -> Either Text Hash) -> Text -> Maybe Hash
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashAlg -> Text -> Either Text Hash
mkHash HashAlg
alg
sriHash :: Maybe Hash
sriHash = Dist -> Maybe Text
distIntegrity Dist
dist Maybe Text -> (Text -> Maybe Hash) -> Maybe Hash
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HashAlg -> Text -> Maybe Hash
toHash HashAlg
SRI
sha1Hash :: Maybe Hash
sha1Hash = Dist -> Maybe Text
distShasum Dist
dist Maybe Text -> (Text -> Maybe Hash) -> Maybe Hash
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= HashAlg -> Text -> Maybe Hash
toHash HashAlg
SHA1
tarballFilename :: Text -> Version -> Text
tarballFilename :: Text -> Version -> Text
tarballFilename Text
url Version
version =
let afterLastSlash :: Text
afterLastSlash = (Text, Text) -> Text
forall a b. (a, b) -> b
snd (HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
T.breakOnEnd Text
"/" Text
url)
in if Text -> Bool
T.null Text
afterLastSlash then Version -> Text
unVersion Version
version Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".tgz" else Text
afterLastSlash
projectDistTags :: WirePackument -> Map Text Version
projectDistTags :: WirePackument -> Map Text Version
projectDistTags = (Text -> Version) -> Map Text Text -> Map Text Version
forall a b k. (a -> b) -> Map k a -> Map k b
Map.map (Ecosystem -> Text -> Version
mkVersion Ecosystem
Npm) (Map Text Text -> Map Text Version)
-> (WirePackument -> Map Text Text)
-> WirePackument
-> Map Text Version
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WirePackument -> Map Text Text
wpDistTags
projectName :: Text -> Either ParseError PackageName
projectName :: Text -> Either ParseError PackageName
projectName Text
raw
| Text -> Bool
T.null Text
raw = ParseError -> Either ParseError PackageName
forall a b. a -> Either a b
Left (Text -> ParseError
ParseError Text
"empty package name")
| Bool
otherwise = case Text -> Maybe (Scope, Text)
scopeOf Text
raw of
Just (Scope
scope, Text
base) -> PackageName -> Either ParseError PackageName
forall a b. b -> Either a b
Right (Ecosystem -> Maybe Scope -> Text -> PackageName
mkPackageName Ecosystem
Npm (Scope -> Maybe Scope
forall a. a -> Maybe a
Just Scope
scope) Text
base)
Maybe (Scope, Text)
Nothing -> PackageName -> Either ParseError PackageName
forall a b. b -> Either a b
Right (Ecosystem -> Maybe Scope -> Text -> PackageName
mkPackageName Ecosystem
Npm Maybe Scope
forall a. Maybe a
Nothing Text
raw)
scopeOf :: Text -> Maybe (Scope, Text)
scopeOf :: Text -> Maybe (Scope, Text)
scopeOf Text
raw = do
afterAt <- Text -> Text -> Maybe Text
T.stripPrefix Text
"@" Text
raw
let (scopeText, rest) = T.break (== '/') afterAt
base = Int -> Text -> Text
T.drop Int
1 Text
rest
guard (not (T.null scopeText))
guard (not (T.null base))
pure (mkScope scopeText, base)
projectPerson :: Wire.Person -> Person
projectPerson :: Person -> Person
projectPerson Person
p =
Person
{ personName :: Text
personName = Person -> Text
Wire.personName Person
p
, personEmail :: Maybe Text
personEmail = Person -> Maybe Text
Wire.personEmail Person
p
, personUrl :: Maybe Text
personUrl = Person -> Maybe Text
Wire.personUrl Person
p
}