From 579531777faaca89723ea42e9892974d11f9287a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 17 Sep 2023 18:00:16 +0200
Subject: [PATCH 01/40] Remove putStrLn
---
src/web/FloraWeb/Pages/Server/Packages.hs | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs
index b7eacf3f..30cc7b7d 100644
--- a/src/web/FloraWeb/Pages/Server/Packages.hs
+++ b/src/web/FloraWeb/Pages/Server/Packages.hs
@@ -19,7 +19,6 @@ import Lucid
import Lucid.Orphans ()
import Servant (ServerT)
-import Control.Monad.IO.Class
import Flora.Logging
import Flora.Model.Package
import Flora.Model.Package.Query qualified as Query
@@ -84,7 +83,6 @@ showPackageVersion namespace packageName mversion = do
templateEnv' <- fromSession session defaultTemplateEnv
package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
releases <- Query.getReleases (package.packageId)
- liftIO $ putStrLn $ "Number of releases: " <> show (length releases)
let latestRelease =
releases
& Vector.filter (\r -> not (fromMaybe False r.deprecated))
From bc2a869846b1c1caf1320af827faa15b9f395aba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 17 Sep 2023 21:25:28 +0200
Subject: [PATCH 02/40] [NO-ISSUE] Fix logging domain for stream import
---
src/core/Flora/Import/Package/Bulk.hs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/core/Flora/Import/Package/Bulk.hs b/src/core/Flora/Import/Package/Bulk.hs
index 2bb89e6d..95f51ea5 100644
--- a/src/core/Flora/Import/Package/Bulk.hs
+++ b/src/core/Flora/Import/Package/Bulk.hs
@@ -4,6 +4,7 @@
module Flora.Import.Package.Bulk (importAllFilesInDirectory, importAllFilesInRelativeDirectory, importFromIndex) where
import Codec.Archive.Tar qualified as Tar
+import Codec.Archive.Tar.Entry qualified as Tar
import Codec.Compression.GZip qualified as GZip
import Control.Monad (join, when, (>=>))
import Data.ByteString qualified as BS
@@ -13,6 +14,7 @@ import Data.List (isSuffixOf)
import Data.Maybe (fromMaybe, isNothing)
import Data.Text (Text)
import Data.Text qualified as Text
+import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Effectful
import Effectful.Log qualified as Log
@@ -26,8 +28,6 @@ import System.Directory qualified as System
import System.FilePath
import UnliftIO.Exception (finally)
-import Codec.Archive.Tar.Entry qualified as Tar
-import Data.Time (UTCTime)
import Flora.Environment.Config (PoolConfig (..))
import Flora.Import.Package (enqueueImportJob, extractPackageDataFromCabal, loadContent, persistImportOutput, withWorkerDbPool)
import Flora.Model.Package.Update qualified as Update
@@ -139,7 +139,7 @@ importFromStream appLogger user repository directImport stream = do
. runReader poolConfig
. runDB pool
. runTime
- . Log.runLog "flora-jobs" appLogger defaultLogLevel
+ . Log.runLog "flora-cli" appLogger defaultLogLevel
. ( \(path, timestamp, content) ->
loadContent path content
>>= ( extractPackageDataFromCabal user repository timestamp
From 82e08896ebce9f026943c2454047689811cde05a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 19 Sep 2023 23:01:15 +0200
Subject: [PATCH 03/40] Bump docker/login-action from 2 to 3 (#435)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/docker-image.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
index ac1daead..6c68ed44 100644
--- a/.github/workflows/docker-image.yml
+++ b/.github/workflows/docker-image.yml
@@ -18,7 +18,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
From 6605a3a5d62528891cf697acf8d704b296ddd1d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 1 Oct 2023 19:04:28 +0200
Subject: [PATCH 04/40] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5647c554..141d13ec 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -10,7 +10,7 @@ The compiler version used is described in the `cabal.project` file.
The following Haskell command-line tools will have to be installed:
* `postgresql-migration`: To perform schema migrations
-* `fourmolu`: To style the code base. Minimum version is 0.12.0.0
+* `fourmolu`: To style the code base. Version is 0.12.0.0
* `hlint` & `apply-refact`: To enforce certain patterns in the code base ("lint")
* `cabal-fmt` and `nixfmt`: To style the cabal and nix files
* `ghcid`: To automatically reload the Haskell code base upon source changes
From 27368d2d9373736b03ab630ba183d07417d731ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 1 Oct 2023 19:08:37 +0200
Subject: [PATCH 05/40] [FLORA-438] Colourise in red deprecation markers on the
package page (#439)
---
CHANGELOG.md | 4 +
assets/css/3-screens/1-package/1-package.css | 8 +-
src/core/Flora/Model/Package/Query.hs | 3 +-
src/core/Flora/Model/Release/Query.hs | 29 ++-
src/web/FloraWeb/Pages/Server/Packages.hs | 33 ++--
src/web/FloraWeb/Pages/Templates/Packages.hs | 169 +++++++++---------
.../Pages/Templates/Pages/Packages.hs | 13 +-
7 files changed, 130 insertions(+), 129 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bef97265..eb7ebe02 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# CHANGELOG
+## 1.0.14 -- XXXX-XX-XX
+
+* Colourise in red deprecation markers on the package page ([#438](/~https://github.com/flora-pm/flora-server/pull/439))
+
## 1.0.13 -- 2023-09-17
* Exclude deprecated releases from latest versions and search ([#373](/~https://github.com/flora-pm/flora-server/pull/373))
* Add namespace browsing ([#375](/~https://github.com/flora-pm/flora-server/pull/375))
diff --git a/assets/css/3-screens/1-package/1-package.css b/assets/css/3-screens/1-package/1-package.css
index 06b15333..4a04a57f 100644
--- a/assets/css/3-screens/1-package/1-package.css
+++ b/assets/css/3-screens/1-package/1-package.css
@@ -24,6 +24,10 @@
line-height: 1.75rem;
}
+.release-deprecated {
+ color: var(--deprecated-version);
+}
+
.package-body {
justify-content: center;
display: grid;
@@ -45,10 +49,6 @@
margin-bottom: 0.75rem;
}
- .release-deprecated {
- color: var(--deprecated-version);
- }
-
.package-right-column {
order: 3;
}
diff --git a/src/core/Flora/Model/Package/Query.hs b/src/core/Flora/Model/Package/Query.hs
index 77c501be..9e63e29d 100644
--- a/src/core/Flora/Model/Package/Query.hs
+++ b/src/core/Flora/Model/Package/Query.hs
@@ -92,7 +92,8 @@ getPackageDependents namespace packageName = dbtToEff $ query Select q (namespac
getNumberOfPackageDependents :: DB :> es => Namespace -> PackageName -> Eff es Word
getNumberOfPackageDependents namespace packageName =
dbtToEff $ do
- (result :: Maybe (Only Int)) <- queryOne Select numberOfPackageDependentsQuery (namespace, packageName)
+ (result :: Maybe (Only Int)) <-
+ queryOne Select numberOfPackageDependentsQuery (namespace, packageName)
case result of
Just (Only n) -> pure $ fromIntegral n
Nothing -> pure 0
diff --git a/src/core/Flora/Model/Release/Query.hs b/src/core/Flora/Model/Release/Query.hs
index 0f4dd4b9..101336c0 100644
--- a/src/core/Flora/Model/Release/Query.hs
+++ b/src/core/Flora/Model/Release/Query.hs
@@ -3,8 +3,8 @@
module Flora.Model.Release.Query
( getReleases
+ , getRelease
, getReleaseByVersion
- , getPackageReleases
, getPackageReleasesWithoutReadme
, getPackageReleasesWithoutChangelog
, getPackageReleasesWithoutUploadTimestamp
@@ -76,20 +76,6 @@ getVersionFromManyReleaseIds releaseIds = do
where r0.release_id in ?
|]
-getPackageReleases :: DB :> es => Eff es (Vector (ReleaseId, Version, PackageName))
-getPackageReleases =
- dbtToEff $
- query Select querySpec ()
- where
- querySpec :: Query
- querySpec =
- [sql|
- select r.release_id, r.version, p."name"
- from releases as r
- join packages as p
- on p.package_id = r.package_id
- |]
-
getPackageReleasesWithoutReadme
:: DB :> es
=> Eff es (Vector (ReleaseId, Version, PackageName))
@@ -165,6 +151,19 @@ getReleaseByVersion packageId version =
dbtToEff $
queryOne Select (_selectWhere @Release [[field| package_id |], [field| version |]]) (packageId, version)
+getRelease
+ :: DB :> es
+ => Namespace
+ -> PackageName
+ -> Version
+ -> Eff es (Maybe Release)
+getRelease namespace packageName version =
+ dbtToEff $
+ queryOne
+ Select
+ (_selectWhere @Release [[field| namespace |], [field| package_name |], [field| version |]])
+ (namespace, packageName, version)
+
getNumberOfReleases :: DB :> es => PackageId -> Eff es Word
getNumberOfReleases pid =
dbtToEff $ do
diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs
index 30cc7b7d..cc83e5c8 100644
--- a/src/web/FloraWeb/Pages/Server/Packages.hs
+++ b/src/web/FloraWeb/Pages/Server/Packages.hs
@@ -70,8 +70,7 @@ showNamespaceHandler namespace pageParam = do
Search.showAllPackagesInNamespace namespace count' pageNumber results
showPackageHandler :: Namespace -> PackageName -> FloraPage (Html ())
-showPackageHandler namespace packageName = do
- showPackageVersion namespace packageName Nothing
+showPackageHandler namespace packageName = showPackageVersion namespace packageName Nothing
showVersionHandler :: Namespace -> PackageName -> Version -> FloraPage (Html ())
showVersionHandler namespace packageName version =
@@ -82,17 +81,17 @@ showPackageVersion namespace packageName mversion = do
session <- getSession
templateEnv' <- fromSession session defaultTemplateEnv
package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
- releases <- Query.getReleases (package.packageId)
+ releases <- Query.getReleases package.packageId
let latestRelease =
releases
- & Vector.filter (\r -> not (fromMaybe False r.deprecated))
+ & Vector.filter (\r -> Just True /= r.deprecated)
& maximumBy (compare `on` (.version))
version = fromMaybe latestRelease.version mversion
release <- guardThatReleaseExists package.packageId version $ const web404
numberOfReleases <- Query.getNumberOfReleases package.packageId
dependents <- Query.getPackageDependents namespace packageName
releaseDependencies <- Query.getRequirements release.releaseId
- categories <- Query.getPackageCategories (package.packageId)
+ categories <- Query.getPackageCategories package.packageId
numberOfDependents <- Query.getNumberOfPackageDependents namespace packageName
numberOfDependencies <- Query.getNumberOfPackageRequirements release.releaseId
@@ -136,16 +135,18 @@ showPackageVersion namespace packageName mversion = do
showDependentsHandler :: Namespace -> PackageName -> Maybe (Positive Word) -> FloraPage (Html ())
showDependentsHandler namespace packageName mPage = do
package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
- releases <- Query.getAllReleases (package.packageId)
+ releases <- Query.getAllReleases package.packageId
let latestRelease = maximumBy (compare `on` (.version)) releases
showVersionDependentsHandler namespace packageName latestRelease.version mPage
showVersionDependentsHandler :: Namespace -> PackageName -> Version -> Maybe (Positive Word) -> FloraPage (Html ())
-showVersionDependentsHandler namespace packageName version Nothing = showVersionDependentsHandler namespace packageName version (Just $ PositiveUnsafe 1)
+showVersionDependentsHandler namespace packageName version Nothing =
+ showVersionDependentsHandler namespace packageName version (Just $ PositiveUnsafe 1)
showVersionDependentsHandler namespace packageName version (Just pageNumber) = do
session <- getSession
templateEnv' <- fromSession session defaultTemplateEnv
- _ <- guardThatPackageExists namespace packageName (\_ _ -> web404)
+ package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
+ release <- guardThatReleaseExists package.packageId version (const web404)
let templateEnv =
templateEnv'
{ title = display namespace <> "/" <> display packageName
@@ -157,7 +158,7 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) = d
Package.showDependents
namespace
packageName
- version
+ release
totalDependents
results
pageNumber
@@ -165,7 +166,7 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) = d
showDependenciesHandler :: Namespace -> PackageName -> FloraPage (Html ())
showDependenciesHandler namespace packageName = do
package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
- releases <- Query.getAllReleases (package.packageId)
+ releases <- Query.getAllReleases package.packageId
let latestRelease = maximumBy (compare `on` (.version)) releases
showVersionDependenciesHandler namespace packageName latestRelease.version
@@ -182,7 +183,7 @@ showVersionDependenciesHandler namespace packageName version = do
}
(releaseDependencies, duration) <-
timeAction $
- Query.getAllRequirements (release.releaseId)
+ Query.getAllRequirements release.releaseId
Log.logInfo "Retrieving all dependencies of the latest release of a package" $
object
@@ -194,14 +195,14 @@ showVersionDependenciesHandler namespace packageName version = do
]
render templateEnv $
- Package.showDependencies namespace packageName version releaseDependencies
+ Package.showDependencies namespace packageName release releaseDependencies
showChangelogHandler :: Namespace -> PackageName -> FloraPage (Html ())
showChangelogHandler namespace packageName = do
package <- guardThatPackageExists namespace packageName (\_ _ -> web404)
- releases <- Query.getAllReleases (package.packageId)
+ releases <- Query.getAllReleases package.packageId
let latestRelease = maximumBy (compare `on` (.version)) releases
- showVersionChangelogHandler namespace packageName (latestRelease.version)
+ showVersionChangelogHandler namespace packageName latestRelease.version
showVersionChangelogHandler :: Namespace -> PackageName -> Version -> FloraPage (Html ())
showVersionChangelogHandler namespace packageName version = do
@@ -216,7 +217,7 @@ showVersionChangelogHandler namespace packageName version = do
, description = "Changelog of @" <> display namespace <> display packageName
}
- render templateEnv $ Package.showChangelog namespace packageName version (release.changelog)
+ render templateEnv $ Package.showChangelog namespace packageName version release.changelog
listVersionsHandler :: Namespace -> PackageName -> FloraPage (Html ())
listVersionsHandler namespace packageName = do
@@ -228,5 +229,5 @@ listVersionsHandler namespace packageName = do
{ title = display namespace <> "/" <> display packageName
, description = "Releases of " <> display namespace <> display packageName
}
- releases <- Query.getAllReleases (package.packageId)
+ releases <- Query.getAllReleases package.packageId
render templateEnv $ Package.listVersions namespace packageName releases
diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs
index 062d2fe3..e24a4b99 100644
--- a/src/web/FloraWeb/Pages/Templates/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Packages.hs
@@ -27,7 +27,7 @@ import Text.PrettyPrint (Doc, hcat, render)
import Text.PrettyPrint qualified as PP
import Data.Foldable (fold)
-import Data.Maybe (fromJust, fromMaybe)
+import Data.Maybe (fromJust)
import Distribution.Pretty (pretty)
import Flora.Model.Category.Types
import Flora.Model.Package
@@ -55,56 +55,54 @@ instance Display Target where
presentationHeaderForSubpage
:: Namespace
-> PackageName
- -> Version
+ -> Release
-> Target
-> Word
-> FloraHTML
-presentationHeaderForSubpage namespace packageName version target numberOfPackages = do
- div_ [class_ "divider"] $ do
- div_ [class_ "page-title"] $ do
- h1_ [class_ ""] $ do
- span_ [class_ "headline"] $ do
- displayNamespace namespace
- chevronRightOutline
- linkToPackageWithVersion namespace packageName version
- chevronRightOutline
- toHtml (display target)
- p_ [class_ "synopsis"] $
- span_ [class_ "version"] $
- toHtml $
- display numberOfPackages <> " results"
+presentationHeaderForSubpage namespace packageName release target numberOfPackages = div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ [class_ ""] $ do
+ span_ [class_ "headline"] $ do
+ displayNamespace namespace
+ chevronRightOutline
+ linkToPackageWithVersion namespace packageName release.version
+ chevronRightOutline
+ toHtml (display target)
+ p_ [class_ "synopsis"] $ do
+ span_ [class_ "version"] $
+ toHtml $
+ display numberOfPackages <> " results"
presentationHeaderForVersions
:: Namespace
-> PackageName
-> Word
-> FloraHTML
-presentationHeaderForVersions namespace packageName numberOfReleases = do
- div_ [class_ "divider"] $ do
- div_ [class_ "page-title"] $ do
- h1_ [class_ ""] $ do
- span_ [class_ "headline"] $ do
- displayNamespace namespace
- chevronRightOutline
- linkToPackage namespace packageName
- chevronRightOutline
- toHtml (display Versions)
- p_ [class_ "synopsis"] $
- span_ [class_ "version"] $
- toHtml $
- display numberOfReleases <> " results"
+presentationHeaderForVersions namespace packageName numberOfReleases = div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ [class_ ""] $ do
+ span_ [class_ "headline"] $ do
+ displayNamespace namespace
+ chevronRightOutline
+ linkToPackage namespace packageName
+ chevronRightOutline
+ toHtml (display Versions)
+ p_ [class_ "synopsis"] $
+ span_ [class_ "version"] $
+ toHtml $
+ display numberOfReleases <> " results"
showDependents
:: Namespace
-> PackageName
- -> Version
+ -> Release
-> Word
-> Vector DependencyInfo
-> Positive Word
-> FloraHTML
-showDependents namespace packageName version count packagesInfo currentPage =
+showDependents namespace packageName release count packagesInfo currentPage =
div_ [class_ "container"] $ do
- presentationHeaderForSubpage namespace packageName version Dependents count
+ presentationHeaderForSubpage namespace packageName release Dependents count
div_ [class_ ""] $ do
ul_ [class_ "package-list"] $
Vector.forM_
@@ -115,11 +113,11 @@ showDependents namespace packageName version count packagesInfo currentPage =
when (count > 30) $
paginationNav count currentPage (DependentsOf namespace packageName)
-showDependencies :: Namespace -> PackageName -> Version -> ComponentDependencies -> FloraHTML
-showDependencies namespace packageName version componentsInfo = do
+showDependencies :: Namespace -> PackageName -> Release -> ComponentDependencies -> FloraHTML
+showDependencies namespace packageName release componentsInfo = do
let dependenciesCount = fromIntegral $ Map.foldr (\v acc -> Vector.length v + acc) 0 componentsInfo
div_ [class_ "container"] $ do
- presentationHeaderForSubpage namespace packageName version Dependencies dependenciesCount
+ presentationHeaderForSubpage namespace packageName release Dependencies dependenciesCount
div_ [class_ ""] $ requirementListing componentsInfo
listVersions :: Namespace -> PackageName -> Vector Release -> FloraHTML
@@ -130,8 +128,7 @@ listVersions namespace packageName releases =
ul_ [class_ "package-list"] $
Vector.forM_
releases
- ( \release -> do
- versionListItem namespace packageName release
+ ( \release -> versionListItem namespace packageName release
)
versionListItem :: Namespace -> PackageName -> Release -> FloraHTML
@@ -157,8 +154,7 @@ packageListing packages =
ul_ [class_ "package-list"] $
Vector.forM_
packages
- ( \PackageInfo{..} -> do
- packageListItem (namespace, name, synopsis, version, license)
+ ( \PackageInfo{..} -> packageListItem (namespace, name, synopsis, version, license)
)
requirementListing :: ComponentDependencies -> FloraHTML
@@ -166,18 +162,17 @@ requirementListing requirements =
ul_ [class_ "component-list"] $ requirementListItem requirements
showChangelog :: Namespace -> PackageName -> Version -> Maybe TextHtml -> FloraHTML
-showChangelog namespace packageName version mChangelog = do
- div_ [class_ "container"] $ do
- div_ [class_ "divider"] $ do
- div_ [class_ "page-title"] $
- h1_ [class_ ""] $ do
- span_ [class_ "headline"] $ toHtml ("Changelog of " <> display namespace <> "/" <> display packageName)
- toHtmlRaw @Text " "
- span_ [class_ "version"] $ toHtml $ display version
- section_ [class_ "release-changelog"] $ do
- case mChangelog of
- Nothing -> toHtml @Text "This release does not have a Changelog"
- Just (MkTextHtml changelogText) -> relaxHtmlT changelogText
+showChangelog namespace packageName version mChangelog = div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $
+ h1_ [class_ ""] $ do
+ span_ [class_ "headline"] $ toHtml ("Changelog of " <> display namespace <> "/" <> display packageName)
+ toHtmlRaw @Text " "
+ span_ [class_ "version"] $ toHtml $ display version
+ section_ [class_ "release-changelog"] $ do
+ case mChangelog of
+ Nothing -> toHtml @Text "This release does not have a Changelog"
+ Just (MkTextHtml changelogText) -> relaxHtmlT changelogText
displayReleaseVersion :: Version -> FloraHTML
displayReleaseVersion = toHtml
@@ -260,11 +255,11 @@ displayVersions namespace packageName versions numberOfReleases =
displayVersion :: Release -> FloraHTML
displayVersion release =
li_ [class_ "release"] $ do
- let versionClass = "release-version" <> if fromMaybe False release.deprecated then " release-deprecated instruction-tooltip" else ""
- let dataText = ([dataText_ "This release is deprecated, pick another one" | fromMaybe False release.deprecated])
+ let versionClass = "release-version" <> if Just True == release.deprecated then " release-deprecated instruction-tooltip" else ""
+ let dataText = ([dataText_ "This release is deprecated, pick another one" | Just True == release.deprecated])
a_
- ([class_ versionClass, href_ ("/" <> toUrlPiece (Links.packageVersionLink namespace packageName (release.version)))] <> dataText)
- (toHtml $ display (release.version))
+ ([class_ versionClass, href_ ("/" <> toUrlPiece (Links.packageVersionLink namespace packageName release.version))] <> dataText)
+ (toHtml $ display release.version)
" "
case release.uploadedAt of
Nothing -> ""
@@ -312,31 +307,31 @@ displayInstructions packageName latestRelease =
displayPackageDeprecation :: PackageAlternatives -> FloraHTML
displayPackageDeprecation (PackageAlternatives inFavourOf) =
li_ [class_ ""] $ do
- h3_ [class_ "package-body-section"] "Deprecated"
- div_ [class_ "items-top"] $ div_ [class_ ""] $ do
- if Vector.null inFavourOf
- then label_ [for_ "install-string", class_ "font-light"] "This package has been deprecated"
- else do
- label_ [for_ "install-string", class_ "font-light"] "This package has been deprecated in favour of"
- ul_ [class_ "package-alternatives"] $
- Vector.forM_ inFavourOf $ \PackageAlternative{namespace, package} ->
- li_ [] $
- a_
- [href_ ("/packages/" <> display namespace <> "/" <> display package)]
- (text $ display namespace <> "/" <> display package)
+ h3_ [class_ "package-body-section release-deprecated"] "Deprecated"
+ div_ [class_ "items-top"] $
+ div_ [class_ ""] $
+ if Vector.null inFavourOf
+ then label_ [for_ "install-string", class_ "font-light"] "This package has been deprecated"
+ else do
+ label_ [for_ "install-string", class_ "font-light"] "This package has been deprecated in favour of"
+ ul_ [class_ "package-alternatives"] $
+ Vector.forM_ inFavourOf $ \PackageAlternative{namespace, package} ->
+ li_ [] $
+ a_
+ [href_ ("/packages/" <> display namespace <> "/" <> display package)]
+ (text $ display namespace <> "/" <> display package)
displayReleaseDeprecation :: Maybe (Namespace, PackageName, Version) -> FloraHTML
displayReleaseDeprecation mLatestViableRelease =
li_ [class_ ""] $ do
- h3_ [class_ "package-body-section"] "Deprecated"
- div_ [class_ "items-top"] $ div_ [class_ ""] $ do
- case mLatestViableRelease of
- Nothing -> label_ [for_ "install-string", class_ "font-light"] "This release has been deprecated"
- Just (namespace, package, version) -> do
- label_ [for_ "install-string", class_ "font-light"] (text "This release has been deprecated in favour of: ")
- a_
- [href_ ("/packages/" <> display namespace <> "/" <> display package <> "/" <> display version)]
- (text $ display namespace <> "/" <> display package <> "-" <> display version)
+ h3_ [class_ "package-body-section release-deprecated"] "Deprecated"
+ div_ [class_ "items-top"] $ div_ [class_ ""] $ case mLatestViableRelease of
+ Nothing -> label_ [for_ "install-string", class_ "font-light"] "This release has been deprecated"
+ Just (namespace, package, version) -> do
+ label_ [for_ "install-string", class_ "font-light"] (text "This release has been deprecated in favour of: ")
+ a_
+ [href_ ("/packages/" <> display namespace <> "/" <> display package <> "/" <> display version)]
+ (text $ display namespace <> "/" <> display package <> "-" <> display version)
displayTestedWith :: Vector Version -> FloraHTML
displayTestedWith compilersVersions'
@@ -348,10 +343,7 @@ displayTestedWith compilersVersions'
ul_ [class_ "compiler-badges"] $
Vector.forM_
compilersVersions
- ( \version ->
- li_ [] $
- a_ [class_ "compiler-badge"] $
- toHtml @Text (display version)
+ ( li_ [] . a_ [class_ "compiler-badge"] . toHtml @Text . display
)
displayMaintainer :: Text -> FloraHTML
@@ -429,14 +421,13 @@ displayPackageFlag MkPackageFlag{flagName, flagDescription, flagDefault} = case
pre_ [class_ "package-flag-name"] (toHtml $ Text.pack (Flag.unFlagName flagName))
toHtmlRaw @Text " "
defaultMarker flagDefault
- _ -> do
- details_ [] $ do
- summary_ [] $ do
- pre_ [class_ "package-flag-name"] (toHtml $ Text.pack (Flag.unFlagName flagName))
- toHtmlRaw @Text " "
- defaultMarker flagDefault
- div_ [class_ "package-flag-description"] $ do
- renderHaddock $ Text.pack flagDescription
+ _ -> details_ [] $ do
+ summary_ [] $ do
+ pre_ [class_ "package-flag-name"] (toHtml $ Text.pack (Flag.unFlagName flagName))
+ toHtmlRaw @Text " "
+ defaultMarker flagDefault
+ div_ [class_ "package-flag-description"] $ do
+ renderHaddock $ Text.pack flagDescription
defaultMarker :: Bool -> FloraHTML
defaultMarker True = em_ "(on by default)"
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs b/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs
index 2571f34b..c6faf00b 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs
@@ -55,7 +55,7 @@ showPackage
numberOfDependencies
categories =
div_ [class_ "larger-container"] $ do
- presentationHeader latestRelease namespace name (latestRelease.synopsis)
+ presentationHeader latestRelease namespace name latestRelease.synopsis
packageBody
package
latestRelease
@@ -70,13 +70,14 @@ showPackage
presentationHeader :: Release -> Namespace -> PackageName -> Text -> FloraHTML
presentationHeader release namespace name synopsis =
div_ [class_ "divider"] $ do
- div_ [class_ "page-title"] $
+ div_ [class_ "page-title"] $ do
h1_ [class_ "package-title"] $ do
span_ [class_ "headline"] $ do
displayNamespace namespace
chevronRightOutline
toHtml name
- span_ [class_ "version"] $ displayReleaseVersion release.version
+ let versionClass = "version" <> if Just True == release.deprecated then " release-deprecated" else ""
+ span_ [class_ versionClass] $ displayReleaseVersion release.version
div_ [class_ "synopsis"] $
p_ [class_ ""] (toHtml synopsis)
@@ -121,7 +122,11 @@ packageBody
displayDependents (namespace, packageName) numberOfDependents dependents
displayPackageFlags flags
-getLatestViableRelease :: Namespace -> PackageName -> Vector Release -> Maybe (Namespace, PackageName, Version)
+getLatestViableRelease
+ :: Namespace
+ -> PackageName
+ -> Vector Release
+ -> Maybe (Namespace, PackageName, Version)
getLatestViableRelease namespace packageName releases =
releases
& Vector.filter (\r -> not (fromMaybe False r.deprecated))
From c826afd8582d5668c778912933ee37d7eb61b1a3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 1 Oct 2023 20:20:10 +0200
Subject: [PATCH 06/40] [FLORA-436] Store revision upload time separately
(#437)
---
CHANGELOG.md | 1 +
Makefile | 2 +
assets/css/3-screens/1-package/1-package.css | 34 +++++-
assets/css/styles.css | 4 -
flake.lock | 8 +-
flora.cabal | 1 +
migrations/20230924120348_add_revised_at.sql | 2 +
src/core/Flora/Import/Package.hs | 30 ++---
src/core/Flora/Model/Job.hs | 2 +
src/core/Flora/Model/Release/Types.hs | 5 +-
src/core/Flora/Model/Release/Update.hs | 9 ++
src/jobs-worker/FloraJobs/Runner.hs | 34 +++---
.../FloraJobs/ThirdParties/Hackage/API.hs | 17 ++-
.../FloraJobs/ThirdParties/Hackage/Client.hs | 30 ++++-
src/web/FloraWeb/Pages/Server/Admin.hs | 24 ++--
src/web/FloraWeb/Pages/Templates/Packages.hs | 41 +++++--
...splay.cabal => text-display-0.0.4.0.cabal} | 0
.../fixtures/Cabal/text-display-0.0.5.0.cabal | 105 ++++++++++++++++++
18 files changed, 273 insertions(+), 76 deletions(-)
create mode 100644 migrations/20230924120348_add_revised_at.sql
rename test/fixtures/Cabal/{text-display.cabal => text-display-0.0.4.0.cabal} (100%)
create mode 100644 test/fixtures/Cabal/text-display-0.0.5.0.cabal
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eb7ebe02..6aa201ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
* Fix the margins of the search bar in mobile view ([#430](/~https://github.com/flora-pm/flora-server/pull/430))
* Have proper breadcrumbs for the package page title ([#431](/~https://github.com/flora-pm/flora-server/pull/431))
* Configure the API gateway ([#432](/~https://github.com/flora-pm/flora-server/pull/432))
+* Store and show the latest revision date of releases ([#437](/~https://github.com/flora-pm/flora-server/pull/437))
## 1.0.12 -- 2023-04-04
diff --git a/Makefile b/Makefile
index 1414b447..f42556d2 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,8 @@ db-reset: db-drop db-setup db-provision ## Reset the dev database
db-provision: build ## Load the development data in the database
@cabal run -- flora-cli create-user --username "hackage-user" --email "tech@flora.pm" --password "foobar2000"
@cabal run -- flora-cli provision categories
+
+db-provision-test-packages:
@cabal run -- flora-cli provision test-packages
import-from-hackage: ## Imports every cabal file from the ./index-01 directory
diff --git a/assets/css/3-screens/1-package/1-package.css b/assets/css/3-screens/1-package/1-package.css
index 4a04a57f..71bf741d 100644
--- a/assets/css/3-screens/1-package/1-package.css
+++ b/assets/css/3-screens/1-package/1-package.css
@@ -119,6 +119,39 @@
}
}
+.release {
+ a:hover {
+ text-decoration: underline;
+ }
+}
+
+span.revised-date::before {
+ content: attr(data-text); /* here's the magic */
+ position: absolute;
+ font-size: 0.85em;
+
+ /* vertically center */
+ top: 50%;
+ transform: translateY(-50%);
+
+ /* move to right */
+ left: 100%;
+
+ /* basic styles */
+ background: #000;
+ border-radius: 10px;
+ box-shadow: 0 1px 8px rgb(0 0 0 / 50%);
+ color: #fff;
+ padding: 5px;
+ text-align: center;
+ width: 200px;
+ display: none; /* hide by default */
+}
+
+span.revised-date:hover::before {
+ display: block;
+}
+
.instruction-tooltip {
svg {
display: inline;
@@ -140,7 +173,6 @@
/* move to right */
left: 100%;
- margin-left: 15px; /* and add a small left margin */
/* basic styles */
background: #000;
diff --git a/assets/css/styles.css b/assets/css/styles.css
index caabebeb..7825d9f8 100644
--- a/assets/css/styles.css
+++ b/assets/css/styles.css
@@ -149,10 +149,6 @@
}
}
-.release a:hover {
- text-decoration: underline;
-}
-
.theme-button--light {
display: none;
}
diff --git a/flake.lock b/flake.lock
index 24bbb448..adc96ef1 100644
--- a/flake.lock
+++ b/flake.lock
@@ -201,11 +201,11 @@
"nixpkgs": "nixpkgs_4"
},
"locked": {
- "lastModified": 1689157215,
- "narHash": "sha256-RbmIc0+wcoZzQ6MBXsFcNhOJts3yWmBrbHnZcr5hhsY=",
+ "lastModified": 1696069899,
+ "narHash": "sha256-ZLIgeaIjmzlICtCg88xiaJsM7u03F3WGxoDCAknCfvE=",
"ref": "refs/heads/master",
- "rev": "f586739656a9ac84528f09dcf2aebe48d26699d2",
- "revCount": 1126,
+ "rev": "a863ad211af049462319295cb74f0261c88532ca",
+ "revCount": 1130,
"type": "git",
"url": "https://gitlab.horizon-haskell.net/package-sets/horizon-platform"
},
diff --git a/flora.cabal b/flora.cabal
index 21432020..9ac1d7c9 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -353,6 +353,7 @@ library flora-jobs
, pg-entity
, pg-transact-effectful
, postgresql-simple
+ , req
, resource-pool
, servant
, servant-client
diff --git a/migrations/20230924120348_add_revised_at.sql b/migrations/20230924120348_add_revised_at.sql
new file mode 100644
index 00000000..e8a038b6
--- /dev/null
+++ b/migrations/20230924120348_add_revised_at.sql
@@ -0,0 +1,2 @@
+alter table releases
+ add revised_at timestamptz
diff --git a/src/core/Flora/Import/Package.hs b/src/core/Flora/Import/Package.hs
index b7a298d8..a854f608 100644
--- a/src/core/Flora/Import/Package.hs
+++ b/src/core/Flora/Import/Package.hs
@@ -237,7 +237,7 @@ loadAndExtractCabalFile userId filePath =
persistImportOutput :: (DB :> es, IOE :> es) => Poolboy.WorkQueue -> ImportOutput -> Eff es ()
persistImportOutput wq (ImportOutput package categories release components) = do
dbPool <- getPool
- liftIO . T.putStrLn $ "📦 Persisting package: " <> packageName <> ", 🗓 Release v" <> display (release.version)
+ liftIO . T.putStrLn $ "📦 Persisting package: " <> packageName <> ", 🗓 Release v" <> display release.version
persistPackage
Update.upsertRelease release
parallelRun dbPool (persistComponent dbPool) components
@@ -245,7 +245,7 @@ persistImportOutput wq (ImportOutput package categories release components) = do
where
parallelRun :: (MonadIO m, Foldable t) => Pool Connection -> (a -> Eff [DB, IOE] b) -> t a -> m ()
parallelRun pool f xs = liftIO $ forM_ xs $ Poolboy.enqueue wq . void . runEff . runDB pool . f
- packageName = display (package.namespace) <> "/" <> display (package.name)
+ packageName = display package.namespace <> "/" <> display package.name
persistPackage = do
let packageId = package.packageId
Update.upsertPackage package
@@ -254,7 +254,7 @@ persistImportOutput wq (ImportOutput package categories release components) = do
persistComponent dbPool (packageComponent, deps) = do
liftIO . T.putStrLn $
"🧩 Persisting component: "
- <> display (packageComponent.canonicalForm)
+ <> display packageComponent.canonicalForm
<> " with "
<> display (length deps)
<> " dependencies."
@@ -262,8 +262,8 @@ persistImportOutput wq (ImportOutput package categories release components) = do
parallelRun dbPool persistImportDependency deps
persistImportDependency dep = do
- Update.upsertPackage (dep.package)
- Update.upsertRequirement (dep.requirement)
+ Update.upsertPackage dep.package
+ Update.upsertRequirement dep.requirement
withWorkerDbPool :: (Reader PoolConfig :> es, IOE :> es) => (Poolboy.WorkQueue -> Eff es a) -> Eff es a
withWorkerDbPool f = do
@@ -280,12 +280,12 @@ extractPackageDataFromCabal userId repository uploadTime genericDesc = do
let packageDesc = genericDesc.packageDescription
let flags = Vector.fromList genericDesc.genPackageFlags
let packageName = force $ packageDesc ^. #package % #pkgName % to unPackageName % to pack % to PackageName
- let packageVersion = force $ packageDesc.package.pkgVersion
+ let packageVersion = force packageDesc.package.pkgVersion
let namespace = force $ chooseNamespace packageName
let packageId = force $ deterministicPackageId namespace packageName
let releaseId = force $ deterministicReleaseId packageId packageVersion
timestamp <- Time.currentTime
- let sourceRepos = getRepoURL packageName $ packageDesc.sourceRepos
+ let sourceRepos = getRepoURL packageName packageDesc.sourceRepos
let rawCategoryField = packageDesc ^. #category % to Cabal.fromShortText % to T.pack
let categoryList = fmap (Tuning.UserPackageCategory . T.stripStart . T.stripEnd) (T.splitOn "," rawCategoryField)
categories <- liftIO $ Tuning.normalisedCategories <$> Tuning.normalise categoryList
@@ -326,10 +326,11 @@ extractPackageDataFromCabal userId repository uploadTime genericDesc = do
, flags = ReleaseFlags flags
, testedWith = getVersions . extractTestedWith . Vector.fromList $ packageDesc.testedWith
, deprecated = Nothing
+ , revisedAt = Nothing
}
let lib = extractLibrary package release Nothing [] <$> allLibraries packageDesc
- let condLib = maybe [] (extractCondTree extractLibrary package release Nothing) (genericDesc.condLibrary)
+ let condLib = maybe [] (extractCondTree extractLibrary package release Nothing) genericDesc.condLibrary
let condSubLibs = extractCondTrees extractLibrary package release genericDesc.condSubLibraries
let foreignLibs = extractForeignLib package release Nothing [] <$> packageDesc.foreignLibs
@@ -367,16 +368,15 @@ extractLibrary package =
package
where
getLibName :: LibraryName -> Text
- getLibName LMainLibName = display (package.name)
+ getLibName LMainLibName = display package.name
getLibName (LSubLibName lname) = T.pack $ unUnqualComponentName lname
extractForeignLib :: Package -> Release -> Maybe UnqualComponentName -> [Condition ConfVar] -> ForeignLib -> ImportComponent
-extractForeignLib package =
+extractForeignLib =
genericComponentExtractor
Component.ForeignLib
(^. #foreignLibName % to unUnqualComponentName % to T.pack)
(^. #foreignLibBuildInfo % #targetBuildDepends)
- package
extractExecutable :: Package -> Release -> Maybe UnqualComponentName -> [Condition ConfVar] -> Executable -> ImportComponent
extractExecutable =
@@ -411,8 +411,8 @@ extractCondTree
extractCondTree extractor package release defaultComponentName = go []
where
go cond tree =
- let treeComponent = extractor package release defaultComponentName cond $ tree.condTreeData
- treeSubComponents = (tree.condTreeComponents) >>= extractBranch
+ let treeComponent = extractor package release defaultComponentName cond tree.condTreeData
+ treeSubComponents = tree.condTreeComponents >>= extractBranch
in treeComponent : treeSubComponents
extractBranch CondBranch{condBranchCondition, condBranchIfTrue, condBranchIfFalse} =
let condIfTrueComponents = go [condBranchCondition] condBranchIfTrue
@@ -486,7 +486,7 @@ buildDependency package packageComponentId (Cabal.Dependency depName versionRang
getRepoURL :: PackageName -> [Cabal.SourceRepo] -> Vector Text
getRepoURL _ [] = Vector.empty
-getRepoURL _ (repo : _) = Vector.singleton $ display $ fromMaybe mempty (repo.repoLocation)
+getRepoURL _ (repo : _) = Vector.singleton $ display $ fromMaybe mempty repo.repoLocation
chooseNamespace :: PackageName -> Namespace
chooseNamespace name | Set.member name coreLibraries = Namespace "haskell"
@@ -502,7 +502,7 @@ extractTestedWith testedWithVector =
getVersions :: Vector VersionRange -> Vector Version
getVersions supportedCompilers =
foldMap
- (\version -> Vector.foldMap (\versionRange -> checkVersion version versionRange) supportedCompilers)
+ (\version -> Vector.foldMap (checkVersion version) supportedCompilers)
versionList
checkVersion :: Version -> VersionRange -> Vector Version
diff --git a/src/core/Flora/Model/Job.hs b/src/core/Flora/Model/Job.hs
index 1d8ce000..2774c6dd 100644
--- a/src/core/Flora/Model/Job.hs
+++ b/src/core/Flora/Model/Job.hs
@@ -95,4 +95,6 @@ instance ToJSON LogEvent where
LogJobTimeout job -> toJSON ("timed-out" :: Text, job)
LogPoll -> toJSON ("poll" :: Text)
LogWebUIRequest -> toJSON ("web-ui-request" :: Text)
+ LogKillJobSuccess job -> toJSON ("kill-success" :: Text, job)
+ LogKillJobFailed job -> toJSON ("kill-failed" :: Text, job)
LogText other -> toJSON ("other" :: Text, other)
diff --git a/src/core/Flora/Model/Release/Types.hs b/src/core/Flora/Model/Release/Types.hs
index 06f0a48d..67a01f96 100644
--- a/src/core/Flora/Model/Release/Types.hs
+++ b/src/core/Flora/Model/Release/Types.hs
@@ -61,7 +61,7 @@ instance ToJSON TextHtml where
toJSON (MkTextHtml a) = String $ Text.toStrict $ Lucid.renderText a
instance FromJSON TextHtml where
- parseJSON = withText "TextHtml" (\text -> pure $ MkTextHtml $ Lucid.toHtmlRaw @Text text)
+ parseJSON = withText "TextHtml" (pure . MkTextHtml . Lucid.toHtmlRaw @Text)
instance NFData TextHtml where
rnf a = seq a ()
@@ -100,6 +100,7 @@ data Release = Release
, testedWith :: Vector Version
, deprecated :: Maybe Bool
, repository :: Maybe Text
+ , revisedAt :: Maybe UTCTime
}
deriving stock (Eq, Show, Generic)
deriving anyclass (FromRow, ToRow, NFData)
@@ -108,7 +109,7 @@ data Release = Release
via (GenericEntity '[TableName "releases"] Release)
instance Ord Release where
- compare x y = compare (x.version) (y.version)
+ compare x y = compare x.version y.version
newtype ReleaseFlags = ReleaseFlags (Vector PackageFlag)
deriving stock (Eq, Ord, Show, Generic)
diff --git a/src/core/Flora/Model/Release/Update.hs b/src/core/Flora/Model/Release/Update.hs
index fbb17b89..d716c494 100644
--- a/src/core/Flora/Model/Release/Update.hs
+++ b/src/core/Flora/Model/Release/Update.hs
@@ -47,6 +47,15 @@ updateUploadTime releaseId timestamp =
([field| release_id |], releaseId)
(Only (Just timestamp))
+updateRevisionTime :: DB :> es => ReleaseId -> UTCTime -> Eff es ()
+updateRevisionTime releaseId timestamp =
+ dbtToEff $
+ void $
+ updateFieldsBy @Release
+ [[field| revised_at |]]
+ ([field| release_id |], releaseId)
+ (Only (Just timestamp))
+
updateChangelog :: DB :> es => ReleaseId -> Maybe TextHtml -> ImportStatus -> Eff es ()
updateChangelog releaseId changelogBody status =
dbtToEff $
diff --git a/src/jobs-worker/FloraJobs/Runner.hs b/src/jobs-worker/FloraJobs/Runner.hs
index ac7481e6..6fab1422 100644
--- a/src/jobs-worker/FloraJobs/Runner.hs
+++ b/src/jobs-worker/FloraJobs/Runner.hs
@@ -8,7 +8,6 @@ import Data.Aeson (Result (..), fromJSON, toJSON)
import Data.Function
import Data.Set qualified as Set
import Data.Text.Display
-import Data.Text.Lazy.Encoding qualified as TL
import Data.Vector (Vector)
import Data.Vector qualified as Vector
import Effectful.PostgreSQL.Transact.Effect
@@ -28,7 +27,7 @@ import Flora.Model.Release.Types
import Flora.Model.Release.Update qualified as Update
import FloraJobs.Render (renderMarkdown)
import FloraJobs.Scheduler
-import FloraJobs.ThirdParties.Hackage.API (HackagePreferredVersions (..), VersionedPackage (..))
+import FloraJobs.ThirdParties.Hackage.API (HackagePackageInfo (..), HackagePreferredVersions (..), VersionedPackage (..))
import FloraJobs.ThirdParties.Hackage.Client qualified as Hackage
import FloraJobs.Types
@@ -110,23 +109,16 @@ fetchUploadTime payload@UploadTimeJobPayload{packageName, packageVersion, releas
localDomain "fetch-upload-time" $ do
logInfo "Fetching upload time" payload
let requestPayload = VersionedPackage packageName packageVersion
- result <- Hackage.request $ Hackage.getPackageUploadTime requestPayload
- case result of
- Right timestamp -> do
- logInfo_ $ "Got a timestamp for " <> display packageName
- Update.updateUploadTime releaseId timestamp
- Left e@(FailureResponse _ response)
- -- If the upload time simply doesn't exist, we skip it by marking the job as successful.
- | response.responseStatusCode == notFound404 -> pure ()
- | response.responseStatusCode == gone410 -> pure ()
- | otherwise -> do
- logAttention "Timestamp retrieval failed" $
- object
- [ "status" .= statusCode (response.responseStatusCode)
- , "body" .= TL.decodeUtf8 (response.responseBody)
- ]
- throw e
- Left e -> throw e
+ packageInfo <- liftIO $ Hackage.getPackageInfo requestPayload
+ if packageInfo.metadataRevision == 0
+ then do
+ Log.logInfo_ "No revision, using the upload time"
+ Update.updateUploadTime releaseId packageInfo.uploadedAt
+ else do
+ Log.logInfo_ "Found a revision, querying the original package info"
+ originalPackageInfo <- liftIO $ Hackage.getPackageWithRevision requestPayload 0
+ Update.updateRevisionTime releaseId packageInfo.uploadedAt
+ Update.updateUploadTime releaseId originalPackageInfo.uploadedAt
-- | This job fetches the deprecation list and inserts the appropriate metadata in the packages
fetchPackageDeprecationList :: JobsRunner ()
@@ -144,7 +136,7 @@ fetchPackageDeprecationList = do
Left e@(FailureResponse _ response) -> do
logAttention "Could not fetch package deprecation list from Hackage" $
object
- [ "status_code" .= statusCode (response.responseStatusCode)
+ [ "status_code" .= statusCode response.responseStatusCode
]
throw e
Left e -> throw e
@@ -175,7 +167,7 @@ fetchReleaseDeprecationList packageName releases = do
logAttention "Could not fetch release deprecation list from Hackage" $
object
[ "package" .= display packageName
- , "status_code" .= statusCode (response.responseStatusCode)
+ , "status_code" .= statusCode response.responseStatusCode
]
throw e
Left e -> throw e
diff --git a/src/jobs-worker/FloraJobs/ThirdParties/Hackage/API.hs b/src/jobs-worker/FloraJobs/ThirdParties/Hackage/API.hs
index 378e9eee..8aea200d 100644
--- a/src/jobs-worker/FloraJobs/ThirdParties/Hackage/API.hs
+++ b/src/jobs-worker/FloraJobs/ThirdParties/Hackage/API.hs
@@ -1,6 +1,9 @@
+{-# LANGUAGE TemplateHaskell #-}
+
module FloraJobs.ThirdParties.Hackage.API where
import Data.Aeson
+import Data.Aeson.TH
import Data.Bifunctor qualified as Bifunctor
import Data.ByteString.Lazy as ByteString
import Data.List.NonEmpty
@@ -46,7 +49,7 @@ data HackageAPI' mode = HackageAPI'
, withUser :: mode :- "user" :> Capture "username" Text :> NamedRoutes HackageUserAPI
, packages :: mode :- "packages" :> NamedRoutes HackagePackagesAPI
, withPackage :: mode :- "package" :> Capture "versioned_package" VersionedPackage :> NamedRoutes HackagePackageAPI
- , withPackageName :: mode :- "package" :> Capture "pacakgeName" PackageName :> NamedRoutes HackagePackageAPI
+ , withPackageNameOnly :: mode :- "package" :> Capture "packageName" PackageName :> NamedRoutes HackagePackageAPI
}
deriving stock (Generic)
@@ -60,6 +63,8 @@ data HackagePackageAPI mode = HackagePackageAPI
, getUploadTime :: mode :- "upload-time" :> Get '[PlainText] UTCTime
, getChangelog :: mode :- "changelog.txt" :> Get '[PlainerText] Text
, getDeprecatedReleases :: mode :- "preferred.json" :> Get '[JSON] HackagePreferredVersions
+ , getPackageInfo :: mode :- Get '[JSON] HackagePackageInfo
+ , getPackageWithRevision :: mode :- "revision" :> Capture "revision_number" Text :> Get '[JSON] HackagePackageInfo
}
deriving stock (Generic)
@@ -90,7 +95,15 @@ data HackagePreferredVersions = HackagePreferredVersions
deriving stock (Eq, Show, Generic)
instance FromJSON HackagePreferredVersions where
- parseJSON = withObject "Hacakge preferred versions" $ \o -> do
+ parseJSON = withObject "Hackage preferred versions" $ \o -> do
deprecatedVersions <- o .:? "deprecated-version" .!= Vector.empty
normalVersions <- o .: "normal-version"
pure HackagePreferredVersions{..}
+
+data HackagePackageInfo = HackagePackageInfo
+ { metadataRevision :: Word
+ , uploadedAt :: UTCTime
+ }
+ deriving stock (Eq, Show)
+
+$(deriveJSON defaultOptions{fieldLabelModifier = camelTo2 '_'} ''HackagePackageInfo)
diff --git a/src/jobs-worker/FloraJobs/ThirdParties/Hackage/Client.hs b/src/jobs-worker/FloraJobs/ThirdParties/Hackage/Client.hs
index e3f9b12d..7ba1e69f 100644
--- a/src/jobs-worker/FloraJobs/ThirdParties/Hackage/Client.hs
+++ b/src/jobs-worker/FloraJobs/ThirdParties/Hackage/Client.hs
@@ -10,8 +10,10 @@ import Data.Time (UTCTime)
import Data.Time.Orphans ()
import Data.Vector (Vector)
import Effectful.Reader.Static
+import Network.HTTP.Req (GET (GET), NoReqBody (..))
+import Network.HTTP.Req qualified as Req
import Servant.API ()
-import Servant.Client
+import Servant.Client (BaseUrl (..), Client, ClientError (..), ClientM, Scheme (..), client, mkClientEnv, runClientM, (//), (/:))
import Flora.Model.Package.Types
import FloraJobs.ThirdParties.Hackage.API as API
@@ -65,6 +67,30 @@ getDeprecatedPackages =
getDeprecatedReleasesList :: PackageName -> ClientM HackagePreferredVersions
getDeprecatedReleasesList packageName =
hackageClient
- // API.withPackageName
+ // API.withPackageNameOnly
/: packageName
// getDeprecatedReleases
+
+getPackageInfo :: VersionedPackage -> IO HackagePackageInfo
+getPackageInfo versionedPackage = do
+ Req.runReq Req.defaultHttpConfig $ do
+ response <-
+ Req.req
+ GET
+ (Req.https "hackage.haskell.org" Req./: "package" Req./~ versionedPackage)
+ NoReqBody
+ Req.jsonResponse
+ mempty
+ pure $ Req.responseBody response
+
+getPackageWithRevision :: VersionedPackage -> Word -> IO HackagePackageInfo
+getPackageWithRevision versionedPackage revision = do
+ Req.runReq Req.defaultHttpConfig $ do
+ response <-
+ Req.req
+ GET
+ (Req.https "hackage.haskell.org" Req./: "package" Req./~ versionedPackage Req./: "revision" Req./~ revision)
+ NoReqBody
+ Req.jsonResponse
+ mempty
+ pure $ Req.responseBody response
diff --git a/src/web/FloraWeb/Pages/Server/Admin.hs b/src/web/FloraWeb/Pages/Server/Admin.hs
index 23cfbe89..d5a260ae 100644
--- a/src/web/FloraWeb/Pages/Server/Admin.hs
+++ b/src/web/FloraWeb/Pages/Server/Admin.hs
@@ -47,8 +47,7 @@ server cfg env =
-- to a sub-tree of Flora pages.
-- It acts as the safeguard that rejects non-admins from protected routes.
ensureAdmin :: ServerT Routes FloraAdmin -> ServerT Routes FloraPage
-ensureAdmin adminServer = do
- hoistServer (Proxy :: Proxy Routes) checkAdmin adminServer
+ensureAdmin adminServer = hoistServer (Proxy :: Proxy Routes) checkAdmin adminServer
where
checkAdmin :: FloraAdmin a -> FloraPage a
checkAdmin adminRoutes = do
@@ -65,14 +64,14 @@ indexHandler = do
templateEnv <-
fromSession session defaultTemplateEnv
>>= \te -> pure $ set (#activeElements % #adminDashboard) True te
- FloraEnv{pool} <- liftIO $ fetchFloraEnv (session.webEnvStore)
+ FloraEnv{pool} <- liftIO $ fetchFloraEnv session.webEnvStore
report <- liftIO $ withPool pool getReport
render templateEnv (Templates.index report)
fetchMetadataHandler :: FloraAdmin FetchMetadataResponse
fetchMetadataHandler = do
session <- getSession
- FloraEnv{jobsPool} <- liftIO $ fetchFloraEnv (session.webEnvStore)
+ FloraEnv{jobsPool} <- liftIO $ fetchFloraEnv session.webEnvStore
liftIO $ void $ schedulePackageDeprecationListJob jobsPool
@@ -82,8 +81,7 @@ fetchMetadataHandler = do
forkIO $
Async.forConcurrently_
releasesWithoutReadme
- ( \(releaseId, version, packagename) -> do
- scheduleReadmeJob jobsPool releaseId packagename version
+ ( \(releaseId, version, packagename) -> scheduleReadmeJob jobsPool releaseId packagename version
)
releasesWithoutUploadTime <- Query.getPackageReleasesWithoutUploadTimestamp
@@ -92,8 +90,7 @@ fetchMetadataHandler = do
forkIO $
Async.forConcurrently_
releasesWithoutUploadTime
- ( \(releaseId, version, packagename) -> do
- scheduleUploadTimeJob jobsPool releaseId packagename version
+ ( \(releaseId, version, packagename) -> scheduleUploadTimeJob jobsPool releaseId packagename version
)
releasesWithoutChangelog <- Query.getPackageReleasesWithoutChangelog
@@ -102,8 +99,7 @@ fetchMetadataHandler = do
forkIO $
Async.forConcurrently_
releasesWithoutChangelog
- ( \(releaseId, version, packagename) -> do
- scheduleChangelogJob jobsPool releaseId packagename version
+ ( \(releaseId, version, packagename) -> scheduleChangelogJob jobsPool releaseId packagename version
)
packagesWithoutDeprecationInformation <- Query.getPackagesWithoutReleaseDeprecationInformation
@@ -112,8 +108,7 @@ fetchMetadataHandler = do
forkIO $ do
Async.forConcurrently_
packagesWithoutDeprecationInformation
- ( \a -> do
- scheduleReleaseDeprecationListJob jobsPool a
+ ( \a -> scheduleReleaseDeprecationListJob jobsPool a
)
void $ scheduleRefreshLatestVersions jobsPool
@@ -122,7 +117,7 @@ fetchMetadataHandler = do
indexImportJobHandler :: FloraAdmin ImportIndexResponse
indexImportJobHandler = do
session <- getSession
- FloraEnv{jobsPool} <- liftIO $ fetchFloraEnv (session.webEnvStore)
+ FloraEnv{jobsPool} <- liftIO $ fetchFloraEnv session.webEnvStore
liftIO $ void $ scheduleIndexImportJob jobsPool
pure $ redirect "/admin"
@@ -153,8 +148,7 @@ showUserHandler userId = do
templateEnv <- fromSession session defaultTemplateEnv
case result of
Nothing -> renderError templateEnv notFound404
- Just user -> do
- render templateEnv (Templates.showUser user)
+ Just user -> render templateEnv (Templates.showUser user)
adminPackagesHandler :: ServerT PackagesAdminRoutes FloraAdmin
adminPackagesHandler =
diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs
index e24a4b99..1ae8a44b 100644
--- a/src/web/FloraWeb/Pages/Templates/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Packages.hs
@@ -65,10 +65,10 @@ presentationHeaderForSubpage namespace packageName release target numberOfPackag
span_ [class_ "headline"] $ do
displayNamespace namespace
chevronRightOutline
- linkToPackageWithVersion namespace packageName release.version
+ linkToPackageWithVersion namespace packageName (release.version)
chevronRightOutline
toHtml (display target)
- p_ [class_ "synopsis"] $ do
+ p_ [class_ "synopsis"] $
span_ [class_ "version"] $
toHtml $
display numberOfPackages <> " results"
@@ -144,9 +144,10 @@ versionListItem namespace packageName release = do
strong_ [class_ ""] . toHtml $
"v" <> toHtml release.version
uploadedAt
- div_ [class_ "package-list-item__metadata"] $ span_ [class_ "package-list-item__license"] $ do
- licenseIcon
- toHtml release.license
+ div_ [class_ "package-list-item__metadata"] $
+ span_ [class_ "package-list-item__license"] $ do
+ licenseIcon
+ toHtml release.license
-- | Render a list of package informations
packageListing :: Vector PackageInfo -> FloraHTML
@@ -255,7 +256,7 @@ displayVersions namespace packageName versions numberOfReleases =
displayVersion :: Release -> FloraHTML
displayVersion release =
li_ [class_ "release"] $ do
- let versionClass = "release-version" <> if Just True == release.deprecated then " release-deprecated instruction-tooltip" else ""
+ let versionClass = "release-version" <> if Just True == release.deprecated then " release-deprecated" else ""
let dataText = ([dataText_ "This release is deprecated, pick another one" | Just True == release.deprecated])
a_
([class_ versionClass, href_ ("/" <> toUrlPiece (Links.packageVersionLink namespace packageName release.version))] <> dataText)
@@ -264,7 +265,18 @@ displayVersions namespace packageName versions numberOfReleases =
case release.uploadedAt of
Nothing -> ""
Just ts ->
- span_ [] (toHtml $ Time.formatTime defaultTimeLocale "%a, %_d %b %Y" ts)
+ span_ [] $ do
+ toHtml $ Time.formatTime defaultTimeLocale "%a, %_d %b %Y" ts
+ case release.revisedAt of
+ Nothing -> do
+ span_ [] ""
+ Just revisionDate -> do
+ span_
+ [ dataText_
+ ("Revised on " <> display (Time.formatTime defaultTimeLocale "%a, %_d %b %Y, %R %EZ" revisionDate))
+ , class_ "revised-date"
+ ]
+ pen
displayDependencies
:: (Namespace, PackageName, Version)
@@ -325,7 +337,7 @@ displayReleaseDeprecation :: Maybe (Namespace, PackageName, Version) -> FloraHTM
displayReleaseDeprecation mLatestViableRelease =
li_ [class_ ""] $ do
h3_ [class_ "package-body-section release-deprecated"] "Deprecated"
- div_ [class_ "items-top"] $ div_ [class_ ""] $ case mLatestViableRelease of
+ div_ [class_ "items-top"] $ case mLatestViableRelease of
Nothing -> label_ [for_ "install-string", class_ "font-light"] "This release has been deprecated"
Just (namespace, package, version) -> do
label_ [for_ "install-string", class_ "font-light"] (text "This release has been deprecated in favour of: ")
@@ -439,7 +451,7 @@ usageInstructionTooltip :: FloraHTML
usageInstructionTooltip =
toHtmlRaw @Text
[str|
-
-**Read More**
+## ⚡ Features
-* [Code of Conduct](./CODE_OF_CONDUCT.md)
-* [Contribution Guide](./CONTRIBUTING.md)
-* [Development Wiki](/~https://github.com/flora-pm/flora-server/wiki)
+* 📁 Curated category model, with elimination of duplicates
+* 🏛️ Package namespaces, so that packages with the same name can live without conflict
+* 🌓 Dark and light modes
+* 📱 Mobile user interface
-### Importing everything from Hackage
+## 🤝 Contributing
-1. Download the archive containing all packages [here](https://hackage.haskell.org/01-index.tar)
-2. Extract it in Flora's root directory. You should now have a `01-index` folder
-3. Run `make import-from-hackage`
+We welcome new contributors! Join the [Matrix chatroom](https://app.element.io/#/room/#flora-pm:matrix.org) or open a [Discussion](/~https://github.com/flora-pm/flora-server/discussions/new/choose).
----
+To setup a local installation, see [CONTRIBUTING.md#project-setup](/~https://github.com/flora-pm/flora-server/blob/development/CONTRIBUTING.md#project-setup)
+
+## 📖 Read More
-You can explore the Makefile rules by typing `make` in your shell. I promise you it's worth it.
+* [Code of Conduct](./CODE_OF_CONDUCT.md)
+* [Development Wiki](/~https://github.com/flora-pm/flora-server/wiki)
+
+---
From f45fc39ed1cea14155652908f198ec2b3d1acd65 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 12:46:54 +0100
Subject: [PATCH 31/40] Bump cachix/cachix-action from 12 to 13 (#483)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/nix-check.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-check.yml b/.github/workflows/nix-check.yml
index 6a88119a..35f90655 100644
--- a/.github/workflows/nix-check.yml
+++ b/.github/workflows/nix-check.yml
@@ -11,7 +11,7 @@ jobs:
- uses: cachix/install-nix-action@v23
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- - uses: cachix/cachix-action@v12
+ - uses: cachix/cachix-action@v13
with:
name: flora-pm
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
From 9392b81e91b22f7a57585d35cd0f10f5b57503c3 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 5 Dec 2023 12:47:16 +0100
Subject: [PATCH 32/40] Bump cachix/install-nix-action from 23 to 24 (#484)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/nix-check.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/nix-check.yml b/.github/workflows/nix-check.yml
index 35f90655..5b925a44 100644
--- a/.github/workflows/nix-check.yml
+++ b/.github/workflows/nix-check.yml
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - uses: cachix/install-nix-action@v23
+ - uses: cachix/install-nix-action@v24
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
- uses: cachix/cachix-action@v13
From 35d29d7681e6143dcf32b28c9ffe85ca5e6a4028 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Tue, 5 Dec 2023 18:56:08 +0100
Subject: [PATCH 33/40] [FLORA-466] two factor authentication (#482)
---
.github/workflows/backend.yml | 1 +
CONTRIBUTING.md | 4 +-
Makefile | 2 +
app/cli/DesignSystem.hs | 15 +-
assets/css/1-core/2-variables.css | 9 +
assets/css/2-components/7-button.css | 32 +
assets/css/2-components/8-alert.css | 27 +
assets/css/styles.css | 10 +
assets/yarn.lock | 866 +++++++++++-------
cabal.project | 11 +-
cabal.project.freeze | 218 +++--
design/stories/alerts.stories.js | 6 +
flora.cabal | 33 +-
.../20231122154627_add_totp_to_users.sql | 3 +
src/core/Flora/Model/User.hs | 10 +-
src/core/Flora/Model/User/Update.hs | 92 +-
src/core/Flora/QRCode.hs | 20 +
.../Database/PostgreSQL/Simple/Orphans.hs | 20 +-
src/web/FloraWeb/Common/Auth.hs | 81 +-
src/web/FloraWeb/Common/Auth/TwoFactor.hs | 44 +
src/web/FloraWeb/Common/Auth/Types.hs | 41 +-
src/web/FloraWeb/Common/Guards.hs | 27 +
src/web/FloraWeb/Components/Alert.hs | 20 +
src/web/FloraWeb/Components/Button.hs | 8 +
src/web/FloraWeb/Components/Icons.hs | 20 +
src/web/FloraWeb/Components/Navbar.hs | 2 +-
src/web/FloraWeb/Pages/Routes.hs | 2 +
src/web/FloraWeb/Pages/Routes/Sessions.hs | 2 +-
src/web/FloraWeb/Pages/Routes/Settings.hs | 68 ++
src/web/FloraWeb/Pages/Server.hs | 4 +-
src/web/FloraWeb/Pages/Server/Categories.hs | 2 +-
src/web/FloraWeb/Pages/Server/Packages.hs | 6 +-
src/web/FloraWeb/Pages/Server/Search.hs | 2 +-
src/web/FloraWeb/Pages/Server/Sessions.hs | 48 +-
src/web/FloraWeb/Pages/Server/Settings.hs | 130 +++
src/web/FloraWeb/Pages/Templates.hs | 10 +-
src/web/FloraWeb/Pages/Templates/Packages.hs | 9 +-
.../Pages/Templates/Pages/Categories.hs | 8 -
.../Pages/Templates/Pages/Sessions.hs | 38 -
.../Pages/Templates/Screens/Categories.hs | 8 +
.../{Pages => Screens}/Categories/Index.hs | 2 +-
.../{Pages => Screens}/Categories/Show.hs | 2 +-
.../Templates/{Pages => Screens}/Home.hs | 2 +-
.../Templates/{Pages => Screens}/Packages.hs | 2 +-
.../Templates/{Pages => Screens}/Search.hs | 2 +-
.../Pages/Templates/Screens/Sessions.hs | 48 +
.../Pages/Templates/Screens/Settings.hs | 78 ++
src/web/FloraWeb/Pages/Templates/Types.hs | 7 +-
src/web/FloraWeb/Server.hs | 91 +-
test/Flora/TestUtils.hs | 4 +
50 files changed, 1598 insertions(+), 599 deletions(-)
create mode 100644 assets/css/2-components/7-button.css
create mode 100644 assets/css/2-components/8-alert.css
create mode 100644 design/stories/alerts.stories.js
create mode 100644 migrations/20231122154627_add_totp_to_users.sql
create mode 100644 src/core/Flora/QRCode.hs
create mode 100644 src/web/FloraWeb/Common/Auth/TwoFactor.hs
create mode 100644 src/web/FloraWeb/Components/Alert.hs
create mode 100644 src/web/FloraWeb/Components/Button.hs
create mode 100644 src/web/FloraWeb/Pages/Routes/Settings.hs
create mode 100644 src/web/FloraWeb/Pages/Server/Settings.hs
delete mode 100644 src/web/FloraWeb/Pages/Templates/Pages/Categories.hs
delete mode 100644 src/web/FloraWeb/Pages/Templates/Pages/Sessions.hs
create mode 100644 src/web/FloraWeb/Pages/Templates/Screens/Categories.hs
rename src/web/FloraWeb/Pages/Templates/{Pages => Screens}/Categories/Index.hs (89%)
rename src/web/FloraWeb/Pages/Templates/{Pages => Screens}/Categories/Show.hs (90%)
rename src/web/FloraWeb/Pages/Templates/{Pages => Screens}/Home.hs (97%)
rename src/web/FloraWeb/Pages/Templates/{Pages => Screens}/Packages.hs (98%)
rename src/web/FloraWeb/Pages/Templates/{Pages => Screens}/Search.hs (96%)
create mode 100644 src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
create mode 100644 src/web/FloraWeb/Pages/Templates/Screens/Settings.hs
diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index 641d4a76..9c66f428 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -71,6 +71,7 @@ jobs:
echo "$HOME/.cabal/bin" >> $GITHUB_PATH
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "$HOME/node_modules/.bin" >> $GITHUB_PATH
+ sudo apt install libsodium-dev
source ./environment.ci.sh
touch ~/.pgpass
chmod 0600 ~/.pgpass
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6bbeee44..88603571 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,8 +18,8 @@ The following Haskell command-line tools will have to be installed:
(Some of the above packages have incompatible dependencies, so don't try to install them all at once with `cabal install`)
-You will need the [Soufflé datalog engine v2.3](/~https://github.com/souffle-lang/souffle/releases/tag/2.3)
-
+* [Soufflé datalog engine v2.3](/~https://github.com/souffle-lang/souffle/releases/tag/2.3): The datalog engine for package classification
+* `libsodium-1.0.18`: The system library that powers most of the cryptography happening in flora
* `yarn`: The tool that handles the JavaScript code bases
* `esbuild`: The tool that handles asset bundling
diff --git a/Makefile b/Makefile
index 353d4ba0..97e01e9b 100644
--- a/Makefile
+++ b/Makefile
@@ -106,6 +106,8 @@ tags: ## Generate ctags for the project with `ghc-tags`
design-system: ## Generate the HTML components used by the design system
@cabal run -- flora-cli gen-design-system
+start-design-sysytem: ## Start storybook.js
+ @cd design; yarn storybook
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.* ?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
diff --git a/app/cli/DesignSystem.hs b/app/cli/DesignSystem.hs
index 46f22eca..c1c80f39 100644
--- a/app/cli/DesignSystem.hs
+++ b/app/cli/DesignSystem.hs
@@ -25,6 +25,7 @@ import Flora.Model.Category
import Flora.Model.Category qualified as Category
import Flora.Model.Package
import Flora.Search
+import FloraWeb.Components.Alert qualified as Component
import FloraWeb.Components.CategoryCard qualified as Component
import FloraWeb.Components.PackageListItem qualified as Component
import FloraWeb.Components.PaginationNav qualified as Component
@@ -69,6 +70,7 @@ components =
)
, ("category-card", ComponentTitle "Category", ComponentName "CategoryCard", categoryCardExample)
, ("pagination-area", ComponentTitle "Pagination Area", ComponentName "Pagination", paginationExample)
+ , ("alerts", ComponentTitle "Alerts", ComponentName "Alert", alertsExample)
]
-----------------------
@@ -76,8 +78,9 @@ components =
-----------------------
storyTemplate :: ComponentTitle -> ComponentName -> TL.Text -> ByteString
-storyTemplate (ComponentTitle title) (ComponentName name) html =
- [fmt|
+storyTemplate (ComponentTitle title) (ComponentName name) unprocessedHtml =
+ let html = TL.replace "\n" " " unprocessedHtml
+ in [fmt|
export default {{
title: "Components/{title}"
}};
@@ -124,3 +127,11 @@ paginationExample = div_ $ do
div_ $ do
h4_ "Next button"
Component.paginationNav 32 1 (SearchPackages "text")
+
+alertsExample :: FloraHTML
+alertsExample = div_ $ do
+ div_ $ do
+ h4_ "Info alert"
+ Component.info "Info alert"
+ h4_ "Error alert"
+ Component.exception "Error alert!"
diff --git a/assets/css/1-core/2-variables.css b/assets/css/1-core/2-variables.css
index d6e04f4b..3473979f 100644
--- a/assets/css/1-core/2-variables.css
+++ b/assets/css/1-core/2-variables.css
@@ -31,6 +31,15 @@
--green-30: hsl(140 100% 30%);
--green-40: hsl(140 100% 40%);
--red-60: hsl(358 80% 60%);
+
+ /* Light backgrounds */
+ --light-blue-background: hsl(210 100% 96%);
+ --light-red-background: hsl(355 73% 97%);
+ --light-green: hsl(206 41% 97%);
+
+ /* Dark foregrounds to go with backgrounds */
+ --dark-blue: hsl(220 64% 33%);
+ --dark-red: hsl(0 69% 36%);
--background-color: var(--gray-100);
--brand-border: var(--gray-100);
--category-card-name-color: var(--gray-30);
diff --git a/assets/css/2-components/7-button.css b/assets/css/2-components/7-button.css
new file mode 100644
index 00000000..3cd25020
--- /dev/null
+++ b/assets/css/2-components/7-button.css
@@ -0,0 +1,32 @@
+/* stylelint-disable selector-class-pattern */
+/* stylelint-disable declaration-block-no-redundant-longhand-properties */
+
+.button {
+ background-color: var(--main-page-button-background);
+ border-radius: 50rem;
+ border-width: 1px;
+ color: var(--text-color);
+ font-weight: bolder;
+ padding-bottom: 1rem;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ padding-top: 1rem;
+}
+
+.button:hover {
+ border-color: var(--main-page-button-focus-border-color);
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 200ms;
+
+ /* offset-x | offset-y | blur-radius | spread-radius | color */
+ box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color);
+}
+
+.button:active {
+ border-color: var(--main-page-button-focus-border-color);
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 200ms;
+
+ /* offset-x | offset-y | blur-radius | spread-radius | color */
+ box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color);
+}
diff --git a/assets/css/2-components/8-alert.css b/assets/css/2-components/8-alert.css
new file mode 100644
index 00000000..63dec2d5
--- /dev/null
+++ b/assets/css/2-components/8-alert.css
@@ -0,0 +1,27 @@
+.alert {
+ padding: 1rem;
+ border-radius: 0.5rem;
+ align-items: center;
+ display: flex;
+ margin-bottom: 1rem;
+}
+
+.alert-info {
+ color: var(--dark-blue);
+ background-color: var(--light-blue-background);
+}
+
+.alert-error {
+ color: var(--dark-red);
+ background-color: var(--light-red-background);
+}
+
+svg.alert-icon {
+ flex-shrink: 0;
+ width: 1em;
+ height: 1em;
+}
+
+.alert-message {
+ margin-inline-start: 0.75rem;
+}
diff --git a/assets/css/styles.css b/assets/css/styles.css
index d69dcbbd..ef311f64 100644
--- a/assets/css/styles.css
+++ b/assets/css/styles.css
@@ -10,6 +10,8 @@
@import "2-components/4-license.css";
@import "2-components/5-primary-search.css";
@import "2-components/6-secondary-search.css";
+@import "2-components/7-button.css";
+@import "2-components/8-alert.css";
@import "3-screens/1-package/1-package.css";
@import "3-screens/1-package/2-release-changelog.css";
@@ -54,6 +56,14 @@
padding: 0.5rem;
width: 100%;
}
+
+ .totp-zone {
+ display: none;
+ }
+
+ input[type="checkbox"]:checked + div.totp-zone {
+ display: block;
+ }
}
.version-list-item {
diff --git a/assets/yarn.lock b/assets/yarn.lock
index 36942fb1..c4e879d4 100644
--- a/assets/yarn.lock
+++ b/assets/yarn.lock
@@ -2,41 +2,54 @@
# yarn lockfile v1
+"@alloc/quick-lru@^5.2.0":
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30"
+ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
+
"@babel/code-frame@^7.0.0":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a"
- integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==
+ version "7.23.5"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
+ integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==
dependencies:
- "@babel/highlight" "^7.18.6"
+ "@babel/highlight" "^7.23.4"
+ chalk "^2.4.2"
-"@babel/helper-validator-identifier@^7.18.6":
- version "7.19.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
- integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+"@babel/helper-validator-identifier@^7.22.20":
+ version "7.22.20"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
+ integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
-"@babel/highlight@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
- integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
+"@babel/highlight@^7.23.4":
+ version "7.23.4"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
+ integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
dependencies:
- "@babel/helper-validator-identifier" "^7.18.6"
- chalk "^2.0.0"
+ "@babel/helper-validator-identifier" "^7.22.20"
+ chalk "^2.4.2"
js-tokens "^4.0.0"
-"@csstools/css-parser-algorithms@^2.3.0":
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.0.tgz#0cc3a656dc2d638370ecf6f98358973bfbd00141"
- integrity sha512-dTKSIHHWc0zPvcS5cqGP+/TPFUJB0ekJ9dGKvMAFoNuBFhDPBt9OMGNZiIA5vTiNdGHHBeScYPXIGBMnVOahsA==
+"@babel/runtime@^7.21.0":
+ version "7.23.5"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.5.tgz#11edb98f8aeec529b82b211028177679144242db"
+ integrity sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==
+ dependencies:
+ regenerator-runtime "^0.14.0"
-"@csstools/css-tokenizer@^2.1.1":
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz#07ae11a0a06365d7ec686549db7b729bc036528e"
- integrity sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==
+"@csstools/css-parser-algorithms@^2.3.1":
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.3.2.tgz#1e0d581dbf4518cb3e939c3b863cb7180c8cedad"
+ integrity sha512-sLYGdAdEY2x7TSw9FtmdaTrh2wFtRJO5VMbBrA8tEqEod7GEggFmxTSK9XqExib3yMuYNcvcTdCZIP6ukdjAIA==
-"@csstools/media-query-list-parser@^2.1.2":
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.2.tgz#6ef642b728d30c1009bfbba3211c7e4c11302728"
- integrity sha512-M8cFGGwl866o6++vIY7j1AKuq9v57cf+dGepScwCcbut9ypJNr4Cj+LLTWligYUZ0uyhEoJDKt5lvyBfh2L3ZQ==
+"@csstools/css-tokenizer@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.2.1.tgz#9dc431c9a5f61087af626e41ac2a79cce7bb253d"
+ integrity sha512-Zmsf2f/CaEPWEVgw29odOj+WEVoiJy9s9NOv5GgNY9mZ1CZ7394By6wONrONrTsnNDv6F9hR02nvFihrGVGHBg==
+
+"@csstools/media-query-list-parser@^2.1.4":
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.1.5.tgz#94bc8b3c3fd7112a40b7bf0b483e91eba0654a0f"
+ integrity sha512-IxVBdYzR8pYe89JiyXQuYk4aVVoCPhMJkz6ElRwlVysjwURTsTk/bmY/z4FfeRE+CRBMlykPwXEVUg8lThv7AQ==
"@csstools/selector-specificity@^3.0.0":
version "3.0.0"
@@ -61,6 +74,50 @@
dependencies:
purgecss "^4.1.3"
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
+"@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.20"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
+ integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -82,10 +139,15 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
"@ryangjchandler/alpine-clipboard@^2.1.0":
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/@ryangjchandler/alpine-clipboard/-/alpine-clipboard-2.2.0.tgz#92e54d02bb7ff9213b23d6069454e0be725a2ea9"
- integrity sha512-2kKHd2mA6K7RuYlC+1fikIUPVJeJLQlY2w9rNGrOgVfzXUZRotjTP+EjxouDizTEvqNRkVTJnmmNle32Uhb4zw==
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@ryangjchandler/alpine-clipboard/-/alpine-clipboard-2.3.0.tgz#44d7a9e8c4446fd24ece2d6be0d23fac7dd59b20"
+ integrity sha512-r1YL/LL851vSemjgcca+M6Yz9SNtA9ATul8nJ0n0sAS1W3V1GUWvH0Od2XdQF1r36YJF+/4sUc0eHF/Zexw7dA==
"@tailwindcss/nesting@^0.0.0-insiders.565cd3e":
version "0.0.0-insiders.565cd3e"
@@ -100,24 +162,26 @@
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@types/less@^3.0.3":
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/@types/less/-/less-3.0.3.tgz#f9451dbb9548d25391107d65d6401a0cfb15db92"
- integrity sha512-1YXyYH83h6We1djyoUEqTlVyQtCfJAFXELSKW2ZRtjHD4hQ82CC4lvrv5D0l0FLcKBaiPbXyi3MpMsI9ZRgKsw==
+ version "3.0.6"
+ resolved "https://registry.yarnpkg.com/@types/less/-/less-3.0.6.tgz#279b51245ba787c810a0d286226c5900cd5e6765"
+ integrity sha512-PecSzorDGdabF57OBeQO/xFbAkYWo88g4Xvnsx7LRwqLC17I7OoKtA3bQB9uXkY6UkMWCOsA8HSVpaoitscdXw==
"@types/minimist@^1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c"
- integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e"
+ integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==
"@types/node@*":
- version "18.15.11"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
- integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
+ version "20.10.3"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.10.3.tgz#4900adcc7fc189d5af5bb41da8f543cea6962030"
+ integrity sha512-XJavIpZqiXID5Yxnxv3RUDKTN5b81ddNC3ecsA0SoFXz/QU8OGBwZGMomiq0zw+uuqbL/krztv/DINAQ/EV4gg==
+ dependencies:
+ undici-types "~5.26.4"
"@types/normalize-package-data@^2.4.0":
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301"
- integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
+ integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
"@types/sass@^1.43.1":
version "1.45.0"
@@ -126,10 +190,10 @@
dependencies:
sass "*"
-"@types/stylus@^0.48.37":
- version "0.48.38"
- resolved "https://registry.yarnpkg.com/@types/stylus/-/stylus-0.48.38.tgz#6e62a59f9350f53a253aa42b038b6aa44a642c5b"
- integrity sha512-B5otJekvD6XM8iTrnO6e2twoTY2tKL9VkL/57/2Lo4tv3EatbCaufdi68VVtn/h4yjO+HVvYEyrNQd0Lzj6riw==
+"@types/stylus@^0.48.38":
+ version "0.48.42"
+ resolved "https://registry.yarnpkg.com/@types/stylus/-/stylus-0.48.42.tgz#8fa7d99b48556bb8fe85a052aaba8c1e59a97e2f"
+ integrity sha512-CPGlr5teL4sqdap+EOowMifLuNGeIoLwc0VQ7u/BPxo+ocqiNa5jeVt0H0IVBblEh6ZwX1sGpIQIFnSSr8NBQA==
dependencies:
"@types/node" "*"
@@ -164,9 +228,9 @@ ajv@^8.0.1:
uri-js "^4.2.2"
alpinejs@^3.12.0:
- version "3.12.0"
- resolved "https://registry.yarnpkg.com/alpinejs/-/alpinejs-3.12.0.tgz#ff84d788231e7cc3fc38e363b7ebbc4f9d7031b2"
- integrity sha512-YENcRBA9dlwR8PsZNFMTHbmdlTNwd1BkCeivPvOzzCKHas6AfwNRsDK9UEFmE5dXTMEZjnnpCTxV8vkdpWiOCw==
+ version "3.13.3"
+ resolved "https://registry.yarnpkg.com/alpinejs/-/alpinejs-3.13.3.tgz#92eb7e869b99ff548e7a55044e45660597cf530b"
+ integrity sha512-WZ6WQjkAOl+WdW/jukzNHq9zHFDNKmkk/x6WF7WdyNDD6woinrfXCVsZXm0galjbco+pEpYmJLtwlZwcOfIVdg==
dependencies:
"@vue/reactivity" "~3.1.1"
@@ -175,6 +239,11 @@ ansi-regex@^5.0.1:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+ansi-regex@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
+ integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
+
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
@@ -189,6 +258,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
any-promise@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -268,13 +342,13 @@ atob@^2.1.2:
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
autoprefixer@^10.2.4, autoprefixer@^10.4.0:
- version "10.4.14"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d"
- integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==
+ version "10.4.16"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8"
+ integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==
dependencies:
- browserslist "^4.21.5"
- caniuse-lite "^1.0.30001464"
- fraction.js "^4.2.0"
+ browserslist "^4.21.10"
+ caniuse-lite "^1.0.30001538"
+ fraction.js "^4.3.6"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
@@ -350,15 +424,15 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.0.0, browserslist@^4.21.4, browserslist@^4.21.5:
- version "4.21.5"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.5.tgz#75c5dae60063ee641f977e00edd3cfb2fb7af6a7"
- integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==
+browserslist@^4.0.0, browserslist@^4.21.10, browserslist@^4.21.4:
+ version "4.22.2"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b"
+ integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==
dependencies:
- caniuse-lite "^1.0.30001449"
- electron-to-chromium "^1.4.284"
- node-releases "^2.0.8"
- update-browserslist-db "^1.0.10"
+ caniuse-lite "^1.0.30001565"
+ electron-to-chromium "^1.4.601"
+ node-releases "^2.0.14"
+ update-browserslist-db "^1.0.13"
cache-base@^1.0.1:
version "1.0.1"
@@ -410,12 +484,12 @@ caniuse-api@^3.0.0:
lodash.memoize "^4.1.2"
lodash.uniq "^4.5.0"
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001449, caniuse-lite@^1.0.30001464:
- version "1.0.30001472"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001472.tgz#3f484885f2a2986c019dc416e65d9d62798cdd64"
- integrity sha512-xWC/0+hHHQgj3/vrKYY0AAzeIUgr7L9wlELIcAvZdDUHlhL/kNxMdnQLOSOQfP8R51ZzPhmHdyMkI0MMpmxCfg==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565:
+ version "1.0.30001566"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz#61a8e17caf3752e3e426d4239c549ebbb37fef0d"
+ integrity sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==
-chalk@^2.0.0, chalk@^2.4.1:
+chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -512,7 +586,7 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-color-name@^1.1.4, color-name@~1.1.4:
+color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@@ -538,9 +612,9 @@ commander@^8.0.0:
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
component-emitter@^1.2.1:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
- integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17"
+ integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==
concat-map@0.0.1:
version "0.0.1"
@@ -567,19 +641,19 @@ copy-descriptor@^0.1.0:
integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==
cosmiconfig@^8.2.0:
- version "8.2.0"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd"
- integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==
+ version "8.3.6"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
+ integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==
dependencies:
- import-fresh "^3.2.1"
+ import-fresh "^3.3.0"
js-yaml "^4.1.0"
- parse-json "^5.0.0"
+ parse-json "^5.2.0"
path-type "^4.0.0"
cpx2@^4.2.0:
- version "4.2.2"
- resolved "https://registry.yarnpkg.com/cpx2/-/cpx2-4.2.2.tgz#bcb442a4c28312a6acf2cab053875aff9a87e5c7"
- integrity sha512-pFYHCivwNALi+yM4kATIA4XQvA72lhjcBBlwHRHtrvTcHWlMQfpgyyUHhus+iC7pHVBYnoGpza7ZrWBsJJAg5Q==
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/cpx2/-/cpx2-4.2.3.tgz#cf7fc1321e396858ffefdbcfe935a2475a0a0435"
+ integrity sha512-UM7Iza+OM8FZ2ntTml/mdb3RmSLK5I2DqFqDdMihlGyKZCAAnDP++H973Oyc/2TQpEMtg5JHeRNfewclE330EA==
dependencies:
debounce "^1.2.0"
debug "^4.1.1"
@@ -588,22 +662,31 @@ cpx2@^4.2.0:
glob-gitignore "^1.0.14"
glob2base "0.0.12"
ignore "^5.1.8"
- minimatch "^7.4.2"
+ minimatch "^8.0.2"
p-map "^4.0.0"
resolve "^1.12.0"
safe-buffer "^5.2.0"
shell-quote "^1.8.0"
subarg "^1.0.0"
+cross-spawn@^7.0.0:
+ version "7.0.3"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+ integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
css-declaration-sorter@^6.3.1:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz#630618adc21724484b3e9505bce812def44000ad"
- integrity sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==
+ version "6.4.1"
+ resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71"
+ integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==
-css-functions-list@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.1.0.tgz#cf5b09f835ad91a00e5959bcfc627cd498e1321b"
- integrity sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==
+css-functions-list@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.1.tgz#2eb205d8ce9f9ce74c5c1d7490b66b77c45ce3ea"
+ integrity sha512-Nj5YcaGgBtuUmn1D7oHqPW0c9iui7xsTsj5lIX8ZgevdfhmjFfKB3r8moHJtNJnctnYXJyYX5I1pp90HM4TPgQ==
css-select@^4.1.3:
version "4.3.0"
@@ -707,9 +790,11 @@ csso@^4.2.0:
css-tree "^1.1.2"
date-fns@^2.16.1:
- version "2.29.3"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8"
- integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==
+ version "2.30.0"
+ resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
+ integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
debounce@^1.2.0:
version "1.2.1"
@@ -832,16 +917,26 @@ duplexer@^0.1.1:
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==
-electron-to-chromium@^1.4.284:
- version "1.4.342"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.342.tgz#3c7e199c3aa89c993df4b6f5223d6d26988f58e6"
- integrity sha512-dTei3VResi5bINDENswBxhL+N0Mw5YnfWyTqO75KGsVldurEkhC9+CelJVAse8jycWyP8pv3VSj4BSyP8wTWJA==
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+electron-to-chromium@^1.4.601:
+ version "1.4.603"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.603.tgz#446907c21d333b55d0beaba1cb5b48430775a8a7"
+ integrity sha512-Dvo5OGjnl7AZTU632dFJtWj0uJK835eeOVQIuRcmBmsFsTNn3cL05FqOyHAfGQDIoHfLhyJ1Tya3PJ0ceMz54g==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
entities@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
@@ -925,21 +1020,21 @@ esbuild-openbsd-64@0.13.15:
integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==
esbuild-plugin-assets-manifest@^1.0.7:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/esbuild-plugin-assets-manifest/-/esbuild-plugin-assets-manifest-1.0.7.tgz#a896616bbfff86427251177936ff0447b1bdfa74"
- integrity sha512-UIuXaCeyWW8ydfJjU5h4/NMLHGvi8nFgDhTVbvcfab03DAkXrMP1/5hnLZ5FpAC63nnOnwmOZk4pLx4amWAyaA==
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/esbuild-plugin-assets-manifest/-/esbuild-plugin-assets-manifest-1.0.8.tgz#af597aa2753f2e087c04a31f712a54ab5ffbdab1"
+ integrity sha512-2goWUmBLpqaHASkMQIgsSqcAs3Y1rDMKcOjk8cWOTtAzmbHYQLh77MEjuQYDx3bBEQ9dVZCaU0/cBIjhvdJf+w==
esbuild-style-plugin@^1.6.0:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/esbuild-style-plugin/-/esbuild-style-plugin-1.6.1.tgz#9a0f6f47587890c56d10a4ae3fc4ce5697baaa28"
- integrity sha512-t5ZtQyGKNiM8DLedz+iwFH0LPWLnMmaEQ2RnnP1ppFHq35najxqJHAhUVVSbTFm5cR2ZumGdBMqnmuKBRBMvDw==
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/esbuild-style-plugin/-/esbuild-style-plugin-1.6.3.tgz#123c994047f1393bee6896c1c37bd4f1942c425f"
+ integrity sha512-XPEKf4FjLjEVLv/dJH4UxDzXCrFHYpD93DBO8B+izdZARW5b7nNKQbnKv3J+7VDWJbgCU+hzfgIh2AuIZzlmXQ==
dependencies:
"@types/less" "^3.0.3"
"@types/sass" "^1.43.1"
- "@types/stylus" "^0.48.37"
- glob "^8.0.1"
- postcss "^8.4.12"
- postcss-modules "^4.3.1"
+ "@types/stylus" "^0.48.38"
+ glob "^10.2.2"
+ postcss "^8.4.31"
+ postcss-modules "^6.0.0"
esbuild-sunos-64@0.13.15:
version "0.13.15"
@@ -1041,10 +1136,10 @@ fast-deep-equal@^3.1.1:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
-fast-glob@^3.2.12, fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0"
- integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==
+fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
+ integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
@@ -1064,12 +1159,12 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
-file-entry-cache@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
- integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+file-entry-cache@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-7.0.2.tgz#2d61bb70ba89b9548e3035b7c9173fe91deafff0"
+ integrity sha512-TfW7/1iI4Cy7Y8L6iqNdZQVvdXn0f8B4QcIXmkIbtTIe/Okm/nSlHb4IwGzRVOd3WfSieCgvf5cMzEfySAIl0g==
dependencies:
- flat-cache "^3.0.4"
+ flat-cache "^3.2.0"
fill-range@^4.0.0:
version "4.0.0"
@@ -1101,28 +1196,37 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-flat-cache@^3.0.4:
- version "3.0.4"
- resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
- integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+flat-cache@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
dependencies:
- flatted "^3.1.0"
+ flatted "^3.2.9"
+ keyv "^4.5.3"
rimraf "^3.0.2"
-flatted@^3.1.0:
- version "3.2.7"
- resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
- integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+flatted@^3.2.9:
+ version "3.2.9"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
+ integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==
-fraction.js@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
- integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
+foreground-child@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d"
+ integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==
+ dependencies:
+ cross-spawn "^7.0.0"
+ signal-exit "^4.0.1"
+
+fraction.js@^4.3.6:
+ version "4.3.7"
+ resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
+ integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
fragment-cache@^0.2.1:
version "0.2.1"
@@ -1141,9 +1245,9 @@ fs-extra@^10.0.0:
universalify "^2.0.0"
fs-extra@^11.1.0:
- version "11.1.1"
- resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d"
- integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==
+ version "11.2.0"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
+ integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
@@ -1165,14 +1269,14 @@ fs.realpath@^1.0.0:
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
fsevents@~2.3.2:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
- integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
+ integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
-function-bind@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
- integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
generic-names@^4.0.0:
version "4.0.0"
@@ -1241,6 +1345,17 @@ glob@7.1.6:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^10.2.2:
+ version "10.3.10"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
+ integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^2.3.5"
+ minimatch "^9.0.1"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+ path-scurry "^1.10.1"
+
glob@^7.1.3, glob@^7.1.7:
version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -1253,17 +1368,6 @@ glob@^7.1.3, glob@^7.1.7:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^8.0.1:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
- integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^5.0.1"
- once "^1.3.0"
-
global-modules@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@@ -1360,12 +1464,12 @@ has-values@^1.0.0:
is-number "^3.0.0"
kind-of "^4.0.0"
-has@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
- integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+hasown@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
+ integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==
dependencies:
- function-bind "^1.1.1"
+ function-bind "^1.1.2"
hosted-git-info@^4.0.1:
version "4.1.0"
@@ -1379,27 +1483,22 @@ html-tags@^3.3.1:
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
-icss-replace-symbols@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded"
- integrity sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==
-
-icss-utils@^5.0.0:
+icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
ignore@^5.0.5, ignore@^5.1.8, ignore@^5.1.9, ignore@^5.2.0, ignore@^5.2.4:
- version "5.2.4"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
- integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78"
+ integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==
immutable@^4.0.0:
- version "4.3.0"
- resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
- integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.4.tgz#2e07b33837b4bb7662f288c244d1ced1ef65a78f"
+ integrity sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==
-import-fresh@^3.2.1:
+import-fresh@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -1445,19 +1544,12 @@ ini@^1.3.5:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
-is-accessor-descriptor@^0.1.6:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
- integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==
- dependencies:
- kind-of "^3.0.2"
-
-is-accessor-descriptor@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
- integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==
+is-accessor-descriptor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.1.tgz#3223b10628354644b86260db29b3e693f5ceedd4"
+ integrity sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==
dependencies:
- kind-of "^6.0.0"
+ hasown "^2.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
@@ -1476,44 +1568,35 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
-is-core-module@^2.5.0, is-core-module@^2.9.0:
- version "2.11.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
- integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
+is-core-module@^2.13.0, is-core-module@^2.5.0:
+ version "2.13.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
+ integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
dependencies:
- has "^1.0.3"
-
-is-data-descriptor@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
- integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==
- dependencies:
- kind-of "^3.0.2"
+ hasown "^2.0.0"
-is-data-descriptor@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
- integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==
+is-data-descriptor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.1.tgz#2109164426166d32ea38c405c1e0945d9e6a4eeb"
+ integrity sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==
dependencies:
- kind-of "^6.0.0"
+ hasown "^2.0.0"
is-descriptor@^0.1.0:
- version "0.1.6"
- resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
- integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.7.tgz#2727eb61fd789dcd5bdf0ed4569f551d2fe3be33"
+ integrity sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==
dependencies:
- is-accessor-descriptor "^0.1.6"
- is-data-descriptor "^0.1.4"
- kind-of "^5.0.0"
+ is-accessor-descriptor "^1.0.1"
+ is-data-descriptor "^1.0.1"
is-descriptor@^1.0.0, is-descriptor@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
- integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.3.tgz#92d27cb3cd311c4977a4db47df457234a13cb306"
+ integrity sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==
dependencies:
- is-accessor-descriptor "^1.0.0"
- is-data-descriptor "^1.0.0"
- kind-of "^6.0.2"
+ is-accessor-descriptor "^1.0.1"
+ is-data-descriptor "^1.0.1"
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
@@ -1600,10 +1683,19 @@ isobject@^3.0.0, isobject@^3.0.1:
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
-jiti@^1.17.2:
- version "1.18.2"
- resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.18.2.tgz#80c3ef3d486ebf2450d9335122b32d121f2a83cd"
- integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
+jackspeak@^2.3.5:
+ version "2.3.6"
+ resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
+ integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
+jiti@^1.19.1:
+ version "1.21.0"
+ resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
+ integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==
js-tokens@^4.0.0:
version "4.0.0"
@@ -1617,6 +1709,11 @@ js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
@@ -1636,6 +1733,13 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -1650,26 +1754,26 @@ kind-of@^4.0.0:
dependencies:
is-buffer "^1.1.5"
-kind-of@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
- integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
-
-kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3:
+kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
-known-css-properties@^0.27.0:
- version "0.27.0"
- resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.27.0.tgz#82a9358dda5fe7f7bd12b5e7142c0a205393c0c5"
- integrity sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==
+known-css-properties@^0.29.0:
+ version "0.29.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.29.0.tgz#e8ba024fb03886f23cb882e806929f32d814158f"
+ integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ==
-lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.0.6:
+lilconfig@^2.0.3, lilconfig@^2.0.5, lilconfig@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
+lilconfig@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc"
+ integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==
+
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@@ -1729,6 +1833,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+"lru-cache@^9.1.1 || ^10.0.0":
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484"
+ integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==
+
make-array@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/make-array/-/make-array-1.0.5.tgz#326a7635c756a9f61ce0b2a6fdd5cc3460419bcb"
@@ -1833,17 +1942,17 @@ minimatch@^3.0.4, minimatch@^3.1.1:
dependencies:
brace-expansion "^1.1.7"
-minimatch@^5.0.1:
- version "5.1.6"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
- integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
+minimatch@^8.0.2:
+ version "8.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229"
+ integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==
dependencies:
brace-expansion "^2.0.1"
-minimatch@^7.4.2:
- version "7.4.3"
- resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-7.4.3.tgz#012cbf110a65134bb354ae9773b55256cdb045a2"
- integrity sha512-5UB4yYusDtkRPbRiy1cqZ1IpGNcJCGlEMG17RKzPddpyiPKoCdwohbED8g4QXT0ewCt8LTkQXuljsUfQ3FKM4A==
+minimatch@^9.0.1:
+ version "9.0.3"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
+ integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
dependencies:
brace-expansion "^2.0.1"
@@ -1861,6 +1970,11 @@ minimist@^1.1.0, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c"
+ integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==
+
mixin-deep@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
@@ -1895,10 +2009,10 @@ mz@^2.7.0:
object-assign "^4.0.1"
thenify-all "^1.0.0"
-nanoid@^3.3.6:
- version "3.3.6"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
- integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
+nanoid@^3.3.7:
+ version "3.3.7"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
+ integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
nanomatch@^1.2.9:
version "1.2.13"
@@ -1917,10 +2031,10 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-node-releases@^2.0.8:
- version "2.0.10"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.10.tgz#c311ebae3b6a148c89b1813fd7c4d3c024ef537f"
- integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==
+node-releases@^2.0.14:
+ version "2.0.14"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
+ integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
normalize-package-data@^3.0.2:
version "3.0.3"
@@ -2022,7 +2136,7 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
-parse-json@^5.0.0, parse-json@^5.2.0:
+parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@@ -2052,11 +2166,24 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+path-scurry@^1.10.1:
+ version "1.10.1"
+ resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698"
+ integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==
+ dependencies:
+ lru-cache "^9.1.1 || ^10.0.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -2083,9 +2210,9 @@ pify@^3.0.0:
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
pirates@^4.0.1:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b"
- integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
+ integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
posix-character-classes@^0.1.0:
version "0.1.1"
@@ -2174,7 +2301,7 @@ postcss-hash@^3.0.0:
dependencies:
mkdirp "^0.5.1"
-postcss-import@^14.0.2, postcss-import@^14.1.0:
+postcss-import@^14.0.2:
version "14.1.0"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0"
integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==
@@ -2183,14 +2310,23 @@ postcss-import@^14.0.2, postcss-import@^14.1.0:
read-cache "^1.0.0"
resolve "^1.1.7"
-postcss-js@^4.0.0:
+postcss-import@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70"
+ integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
+ dependencies:
+ postcss-value-parser "^4.0.0"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-js@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2"
integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
dependencies:
camelcase-css "^2.0.1"
-postcss-load-config@^3.0.0, postcss-load-config@^3.1.4:
+postcss-load-config@^3.0.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
@@ -2198,6 +2334,14 @@ postcss-load-config@^3.0.0, postcss-load-config@^3.1.4:
lilconfig "^2.0.5"
yaml "^1.10.2"
+postcss-load-config@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3"
+ integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==
+ dependencies:
+ lilconfig "^3.0.0"
+ yaml "^2.3.4"
+
postcss-merge-longhand@^5.1.7:
version "5.1.7"
resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16"
@@ -2254,9 +2398,9 @@ postcss-modules-extract-imports@^3.0.0:
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
- integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz#b08eb4f083050708998ba2c6061b50c2870ca524"
+ integrity sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
@@ -2276,13 +2420,13 @@ postcss-modules-values@^4.0.0:
dependencies:
icss-utils "^5.0.0"
-postcss-modules@^4.3.1:
- version "4.3.1"
- resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-4.3.1.tgz#517c06c09eab07d133ae0effca2c510abba18048"
- integrity sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==
+postcss-modules@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-modules/-/postcss-modules-6.0.0.tgz#cac283dbabbbdc2558c45391cbd0e2df9ec50118"
+ integrity sha512-7DGfnlyi/ju82BRzTIjWS5C4Tafmzl3R79YP/PASiocj+aa6yYphHhhKUOEoXQToId5rgyFgJ88+ccOUydjBXQ==
dependencies:
generic-names "^4.0.0"
- icss-replace-symbols "^1.1.0"
+ icss-utils "^5.1.0"
lodash.camelcase "^4.3.0"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
@@ -2290,13 +2434,6 @@ postcss-modules@^4.3.1:
postcss-modules-values "^4.0.0"
string-hash "^1.1.1"
-postcss-nested@6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.0.tgz#1572f1984736578f360cffc7eb7dca69e30d1735"
- integrity sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==
- dependencies:
- postcss-selector-parser "^6.0.10"
-
postcss-nested@^5.0.5:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
@@ -2304,6 +2441,13 @@ postcss-nested@^5.0.5:
dependencies:
postcss-selector-parser "^6.0.6"
+postcss-nested@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c"
+ integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==
+ dependencies:
+ postcss-selector-parser "^6.0.11"
+
postcss-normalize-charset@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed"
@@ -2408,7 +2552,7 @@ postcss-safe-parser@^6.0.0:
resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz#bb4c29894171a94bc5c996b9a30317ef402adaa1"
integrity sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==
-postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
+postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.13, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
version "6.0.13"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b"
integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==
@@ -2450,19 +2594,19 @@ postcss@^6.0.3:
source-map "^0.6.1"
supports-color "^5.4.0"
-postcss@^8.0.9, postcss@^8.2.4, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.24, postcss@^8.4.31:
- version "8.4.31"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
- integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
+postcss@^8.2.4, postcss@^8.3.5, postcss@^8.4.23, postcss@^8.4.28, postcss@^8.4.31:
+ version "8.4.32"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9"
+ integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==
dependencies:
- nanoid "^3.3.6"
+ nanoid "^3.3.7"
picocolors "^1.0.0"
source-map-js "^1.0.2"
prettier@^2.7.1:
- version "2.8.7"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450"
- integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==
+ version "2.8.8"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
+ integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
pretty-hrtime@^1.0.3:
version "1.0.3"
@@ -2470,9 +2614,9 @@ pretty-hrtime@^1.0.3:
integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==
punycode@^2.1.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
- integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
purgecss@^4.1.3:
version "4.1.3"
@@ -2535,6 +2679,11 @@ redent@^4.0.0:
indent-string "^5.0.0"
strip-indent "^4.0.0"
+regenerator-runtime@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
+ integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
+
regex-not@^1.0.0, regex-not@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
@@ -2578,12 +2727,12 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==
-resolve@^1.1.7, resolve@^1.12.0, resolve@^1.22.1:
- version "1.22.1"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
- integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
+resolve@^1.1.7, resolve@^1.12.0, resolve@^1.22.2:
+ version "1.22.8"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
+ integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
dependencies:
- is-core-module "^2.9.0"
+ is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
@@ -2631,9 +2780,9 @@ safe-regex@^1.1.0:
ret "~0.1.10"
sass@*:
- version "1.60.0"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81"
- integrity sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==
+ version "1.69.5"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.69.5.tgz#23e18d1c757a35f2e52cc81871060b9ad653dfde"
+ integrity sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
@@ -2656,15 +2805,27 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
shell-quote@^1.8.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.0.tgz#20d078d0eaf71d54f43bd2ba14a1b5b9bfa5c8ba"
- integrity sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ==
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
+ integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
signal-exit@^4.0.1:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967"
- integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
slash@^3.0.0:
version "3.0.0"
@@ -2773,9 +2934,9 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
- version "3.0.13"
- resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5"
- integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==
+ version "3.0.16"
+ resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz#a14f64e0954f6e25cc6587bd4f392522db0d998f"
+ integrity sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==
split-string@^3.0.1, split-string@^3.0.2:
version "3.1.0"
@@ -2802,7 +2963,7 @@ string-hash@^1.1.1:
resolved "https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b"
integrity sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -2811,13 +2972,29 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+string-width@^5.0.1, string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
strip-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-4.0.0.tgz#b41379433dd06f5eae805e21d631e07ee670d853"
@@ -2856,23 +3033,23 @@ stylelint-config-standard@^26.0.0:
stylelint-config-recommended "^8.0.0"
stylelint@^15.10.1:
- version "15.10.1"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.10.1.tgz#93f189958687e330c106b010cbec0c41dcae506d"
- integrity sha512-CYkzYrCFfA/gnOR+u9kJ1PpzwG10WLVnoxHDuBA/JiwGqdM9+yx9+ou6SE/y9YHtfv1mcLo06fdadHTOx4gBZQ==
+ version "15.11.0"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.11.0.tgz#3ff8466f5f5c47362bc7c8c9d382741c58bc3292"
+ integrity sha512-78O4c6IswZ9TzpcIiQJIN49K3qNoXTM8zEJzhaTE/xRTCZswaovSEVIa/uwbOltZrk16X4jAxjaOhzz/hTm1Kw==
dependencies:
- "@csstools/css-parser-algorithms" "^2.3.0"
- "@csstools/css-tokenizer" "^2.1.1"
- "@csstools/media-query-list-parser" "^2.1.2"
+ "@csstools/css-parser-algorithms" "^2.3.1"
+ "@csstools/css-tokenizer" "^2.2.0"
+ "@csstools/media-query-list-parser" "^2.1.4"
"@csstools/selector-specificity" "^3.0.0"
balanced-match "^2.0.0"
colord "^2.9.3"
cosmiconfig "^8.2.0"
- css-functions-list "^3.1.0"
+ css-functions-list "^3.2.1"
css-tree "^2.3.1"
debug "^4.3.4"
- fast-glob "^3.3.0"
+ fast-glob "^3.3.1"
fastest-levenshtein "^1.0.16"
- file-entry-cache "^6.0.1"
+ file-entry-cache "^7.0.0"
global-modules "^2.0.0"
globby "^11.1.0"
globjoin "^0.1.4"
@@ -2881,13 +3058,13 @@ stylelint@^15.10.1:
import-lazy "^4.0.0"
imurmurhash "^0.1.4"
is-plain-object "^5.0.0"
- known-css-properties "^0.27.0"
+ known-css-properties "^0.29.0"
mathml-tag-names "^2.1.3"
meow "^10.1.5"
micromatch "^4.0.5"
normalize-path "^3.0.0"
picocolors "^1.0.0"
- postcss "^8.4.24"
+ postcss "^8.4.28"
postcss-resolve-nested-selector "^0.1.1"
postcss-safe-parser "^6.0.0"
postcss-selector-parser "^6.0.13"
@@ -2908,11 +3085,12 @@ subarg@^1.0.0:
dependencies:
minimist "^1.1.0"
-sucrase@^3.29.0:
- version "3.31.0"
- resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.31.0.tgz#daae4fd458167c5d4ba1cce6aef57b988b417b33"
- integrity sha512-6QsHnkqyVEzYcaiHsOKkzOtOgdJcb8i54x6AV2hDwyZcY9ZyykGZVw6L/YN98xC0evwTP6utsWWrKRaa8QlfEQ==
+sucrase@^3.32.0:
+ version "3.34.0"
+ resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"
+ integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==
dependencies:
+ "@jridgewell/gen-mapping" "^0.3.2"
commander "^4.0.0"
glob "7.1.6"
lines-and-columns "^1.1.6"
@@ -2984,34 +3162,32 @@ table@^6.8.1:
strip-ansi "^6.0.1"
tailwindcss@^3.0.0:
- version "3.3.0"
- resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.0.tgz#8cab40e5a10a10648118c0859ba8bfbc744a761e"
- integrity sha512-hOXlFx+YcklJ8kXiCAfk/FMyr4Pm9ck477G0m/us2344Vuj355IpoEDB5UmGAsSpTBmr+4ZhjzW04JuFXkb/fw==
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.6.tgz#4dd7986bf4902ad385d90d45fd4b2fa5fab26d5f"
+ integrity sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==
dependencies:
+ "@alloc/quick-lru" "^5.2.0"
arg "^5.0.2"
chokidar "^3.5.3"
- color-name "^1.1.4"
didyoumean "^1.2.2"
dlv "^1.1.3"
- fast-glob "^3.2.12"
+ fast-glob "^3.3.0"
glob-parent "^6.0.2"
is-glob "^4.0.3"
- jiti "^1.17.2"
- lilconfig "^2.0.6"
+ jiti "^1.19.1"
+ lilconfig "^2.1.0"
micromatch "^4.0.5"
normalize-path "^3.0.0"
object-hash "^3.0.0"
picocolors "^1.0.0"
- postcss "^8.0.9"
- postcss-import "^14.1.0"
- postcss-js "^4.0.0"
- postcss-load-config "^3.1.4"
- postcss-nested "6.0.0"
+ postcss "^8.4.23"
+ postcss-import "^15.1.0"
+ postcss-js "^4.0.1"
+ postcss-load-config "^4.0.1"
+ postcss-nested "^6.0.1"
postcss-selector-parser "^6.0.11"
- postcss-value-parser "^4.2.0"
- quick-lru "^5.1.1"
- resolve "^1.22.1"
- sucrase "^3.29.0"
+ resolve "^1.22.2"
+ sucrase "^3.32.0"
thenby@^1.3.4:
version "1.3.4"
@@ -3096,6 +3272,11 @@ type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1"
integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==
+undici-types@~5.26.4:
+ version "5.26.5"
+ resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
+ integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
+
union-value@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
@@ -3107,9 +3288,9 @@ union-value@^1.0.0:
set-value "^2.0.1"
universalify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
- integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
+ integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
unset-value@^1.0.0:
version "1.0.0"
@@ -3119,10 +3300,10 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
-update-browserslist-db@^1.0.10:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
- integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==
+update-browserslist-db@^1.0.13:
+ version "1.0.13"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4"
+ integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==
dependencies:
escalade "^3.1.1"
picocolors "^1.0.0"
@@ -3169,7 +3350,14 @@ which@^1.3.1:
dependencies:
isexe "^2.0.0"
-wrap-ansi@^7.0.0:
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -3178,6 +3366,15 @@ wrap-ansi@^7.0.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -3206,6 +3403,11 @@ yaml@^1.10.2:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+yaml@^2.3.4:
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2"
+ integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==
+
yargs-parser@^20.2.2, yargs-parser@^20.2.9:
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
@@ -3230,9 +3432,9 @@ yargs@^16.2.0:
yargs-parser "^20.2.2"
yargs@^17.0.0:
- version "17.7.1"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.1.tgz#34a77645201d1a8fc5213ace787c220eabbd0967"
- integrity sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==
+ version "17.7.2"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
+ integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
dependencies:
cliui "^8.0.1"
escalade "^3.1.1"
diff --git a/cabal.project b/cabal.project
index 0f0dcfeb..9221f5fa 100644
--- a/cabal.project
+++ b/cabal.project
@@ -1,6 +1,8 @@
-packages: ./
+packages:
+ ./
+ https://hackage.haskell.org/package/sel-0.0.1.0/candidate/sel-0.0.1.0.tar.gz
-with-compiler: ghc-9.4.5
+with-compiler: ghc-9.4.7
tests: True
@@ -62,3 +64,8 @@ source-repository-package
type: git
location: /~https://github.com/saurabhnanda/odd-jobs
tag: 51c7443
+
+source-repository-package
+ type: git
+ location: /~https://github.com/haskell-cryptography/one-time-password
+ tag: 2ca2313
diff --git a/cabal.project.freeze b/cabal.project.freeze
index 5d943a81..6d2928a8 100644
--- a/cabal.project.freeze
+++ b/cabal.project.freeze
@@ -4,20 +4,23 @@ constraints: any.Cabal ==3.8.1.0,
any.HUnit ==1.6.2.0,
any.OneTuple ==0.4.1.1,
any.Only ==0.1,
- any.PyF ==0.11.1.1,
+ any.PyF ==0.11.2.1,
PyF -python_test,
any.QuickCheck ==2.14.3,
QuickCheck -old-random +templatehaskell,
+ any.RSA ==2.4.1,
+ any.SHA ==1.6.4.4,
+ SHA -exe,
any.StateVar ==1.2.2,
any.abstract-deque ==0.3,
abstract-deque -usecas,
any.adjunctions ==4.4.2,
any.aeson ==2.1.2.1,
aeson -cffi +ordered-keymap,
- any.aeson-pretty ==0.8.9,
+ any.aeson-pretty ==0.8.10,
aeson-pretty -lib-only,
- any.ansi-terminal ==0.11.5,
- ansi-terminal -example +win32-2-13-1,
+ any.ansi-terminal ==1.0,
+ ansi-terminal -example,
any.ansi-terminal-types ==0.11.5,
any.appar ==0.1.8,
any.array ==0.5.4.0,
@@ -32,13 +35,16 @@ constraints: any.Cabal ==3.8.1.0,
atomic-primops -debug,
any.attoparsec ==0.14.4,
attoparsec -developer,
- any.attoparsec-iso8601 ==1.1.0.0,
+ any.attoparsec-aeson ==2.1.0.0,
+ any.attoparsec-iso8601 ==1.1.0.1,
+ any.authenticate-oauth ==1.7,
any.auto-update ==0.1.6,
- any.barbies ==2.0.4.0,
- any.base ==4.17.1.0,
- any.base-compat ==0.12.2,
- any.base-compat-batteries ==0.12.2,
- any.base-orphans ==0.9.0,
+ any.barbies ==2.0.5.0,
+ any.base ==4.17.2.0,
+ any.base-compat ==0.12.3,
+ any.base-compat-batteries ==0.12.3,
+ any.base-orphans ==0.9.1,
+ any.base16 ==1.0,
any.base16-bytestring ==1.0.2.0,
any.base64 ==0.4.2.4,
any.base64-bytestring ==1.2.1.0,
@@ -46,16 +52,21 @@ constraints: any.Cabal ==3.8.1.0,
any.bifunctors ==5.5.15,
bifunctors +semigroups +tagged,
any.binary ==0.8.9.1,
- any.bitvec ==1.1.4.0,
- bitvec -libgmp,
- any.blaze-builder ==0.4.2.2,
+ any.bitvec ==1.1.5.0,
+ bitvec +simd,
+ any.blaze-builder ==0.4.2.3,
any.blaze-html ==0.9.1.2,
- any.blaze-markup ==0.8.2.8,
+ any.blaze-markup ==0.8.3.0,
any.boring ==0.2.1,
boring +tagged,
any.bsb-http-chunked ==0.0.0.4,
+ any.bytebuild ==0.3.14.0,
+ bytebuild -checked,
any.byteorder ==1.0.4,
- any.bytestring ==0.11.4.0,
+ any.byteslice ==0.2.11.1,
+ byteslice +avoid-rawmemchr,
+ any.bytesmith ==0.3.10.0,
+ any.bytestring ==0.11.5.2,
any.bytestring-builder ==0.10.8.2.0,
bytestring-builder +bytestring_has_builder,
any.cabal-doctest ==1.0.9,
@@ -63,29 +74,36 @@ constraints: any.Cabal ==3.8.1.0,
any.case-insensitive ==1.2.1.0,
any.cereal ==0.5.8.3,
cereal -bytestring-builder,
- any.clock ==0.8.3,
+ any.chronos ==1.1.5.1,
+ any.clock ==0.8.4,
clock -llvm,
- any.cmark-gfm ==0.2.5,
+ any.cmark-gfm ==0.2.6,
cmark-gfm -pkgconfig,
any.cmdargs ==0.10.22,
cmdargs +quotation -testprog,
any.colour ==2.3.6,
any.colourista ==0.1.0.2,
- any.commonmark ==0.2.2,
- any.commonmark-extensions ==0.2.3.4,
+ any.commonmark ==0.2.4,
+ any.commonmark-extensions ==0.2.4,
any.comonad ==5.0.8,
comonad +containers +distributive +indexed-traversable,
- any.concurrent-output ==1.10.18,
+ any.concurrent-output ==1.10.20,
any.conduit ==1.3.5,
any.conduit-extra ==1.3.6,
any.constraints ==0.13.4,
any.containers ==0.6.7,
+ any.contiguous ==0.6.4.0,
any.contravariant ==1.5.5,
contravariant +semigroups +statevar +tagged,
any.cookie ==0.4.6,
+ any.crypto-api ==0.13.3,
+ crypto-api -all_cpolys,
+ any.crypto-pubkey-types ==0.4.3,
any.cryptohash-md5 ==0.11.101.0,
any.cryptohash-sha1 ==0.11.101.0,
- any.crypton ==0.31,
+ any.cryptohash-sha256 ==0.11.102.1,
+ cryptohash-sha256 -exe +use-cbits,
+ any.crypton ==0.34,
crypton -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq +support_pclmuldq +support_rdrand -support_sse +use_target_attributes,
any.crypton-connection ==0.3.1,
any.crypton-x509 ==1.7.6,
@@ -95,7 +113,6 @@ constraints: any.Cabal ==3.8.1.0,
any.cryptonite ==0.30,
cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes,
any.cryptonite-conduit ==0.2.2,
- any.daemons ==0.3.0,
any.data-default ==0.7.1.1,
any.data-default-class ==0.1.2.0,
any.data-default-instances-containers ==0.0.1,
@@ -106,6 +123,7 @@ constraints: any.Cabal ==3.8.1.0,
any.data-sketches-core ==0.1.0.0,
any.dec ==0.0.5,
any.deepseq ==1.4.8.0,
+ any.deriving-aeson ==0.2.9,
any.directory ==1.3.7.1,
any.distributive ==0.6.2.1,
distributive +semigroups +tagged,
@@ -116,13 +134,15 @@ constraints: any.Cabal ==3.8.1.0,
effectful -benchmark-foreign-libraries,
any.effectful-core ==2.2.2.2,
any.either ==5.0.2,
- any.emojis ==0.1.2,
+ any.emojis ==0.1.3,
any.entropy ==0.4.1.10,
entropy -donotgetentropy,
any.envparse ==0.5.0,
any.erf ==2.0.0.0,
any.exceptions ==0.10.5,
- any.fast-logger ==3.2.1,
+ any.extensible-exceptions ==0.1.1.4,
+ any.extra ==1.7.14,
+ any.fast-logger ==3.2.2,
any.file-embed ==0.0.15.0,
any.filepath ==1.4.2.2,
any.filtrable ==0.1.6.0,
@@ -134,52 +154,68 @@ constraints: any.Cabal ==3.8.1.0,
any.free ==5.1.10,
any.friendly-time ==0.4.1,
any.fusion-plugin-types ==0.1.0,
- any.generic-deriving ==1.14.4,
+ any.generic-deriving ==1.14.5,
generic-deriving +base-4-9,
any.generically ==0.1.1,
- any.ghc ==9.4.5,
+ any.generics-sop ==0.5.1.3,
+ any.ghc ==9.4.7,
any.ghc-bignum ==1.3,
- any.ghc-boot ==9.4.5,
- any.ghc-boot-th ==9.4.5,
- any.ghc-heap ==9.4.5,
- any.ghc-prim ==0.9.0,
- any.ghci ==9.4.5,
+ any.ghc-boot ==9.4.7,
+ any.ghc-boot-th ==9.4.7,
+ any.ghc-heap ==9.4.7,
+ any.ghc-prim ==0.9.1,
+ any.ghci ==9.4.7,
any.haddock-library ==1.11.0,
any.happy ==1.20.1.1,
- any.hashable ==1.4.2.0,
+ any.hashable ==1.4.3.0,
hashable +integer-gmp -random-initial-seed,
any.haskell-lexer ==1.1.1,
+ any.haskell-src-exts ==1.23.1,
+ any.haskell-src-meta ==0.8.13,
+ any.hdaemonize ==0.5.7,
any.heaps ==0.4,
- any.hedgehog ==1.2,
+ any.hedgehog ==1.4,
any.hostname ==1.0,
any.hourglass ==0.2.12,
any.hpc ==0.6.1.0,
- any.hsc2hs ==0.68.9,
+ any.hsc2hs ==0.68.10,
hsc2hs -in-ghc-tree,
+ any.hspec ==2.11.7,
+ any.hspec-core ==2.11.7,
+ any.hspec-discover ==2.11.7,
+ any.hspec-expectations ==0.8.4,
+ any.hsyslog ==5.0.2,
+ hsyslog -install-examples,
any.http-api-data ==0.5,
http-api-data -use-text-show,
- any.http-client ==0.7.13.1,
+ any.http-client ==0.7.15,
http-client +network-uri,
- any.http-client-tls ==0.3.6.2,
- any.http-conduit ==2.3.8.2,
+ any.http-client-tls ==0.3.6.3,
+ any.http-conduit ==2.3.8.3,
http-conduit +aeson,
any.http-date ==0.0.11,
- any.http-media ==0.8.0.0,
+ any.http-media ==0.8.1.1,
any.http-types ==0.12.3,
- any.http2 ==4.1.4,
+ any.http2 ==4.2.2,
http2 -devel -h2spec,
- any.indexed-profunctors ==0.1.1,
- any.indexed-traversable ==0.1.2.1,
+ any.indexed-profunctors ==0.1.1.1,
+ any.indexed-traversable ==0.1.3,
any.indexed-traversable-instances ==0.1.1.2,
+ any.insert-ordered-containers ==0.2.5.3,
+ any.integer-conversion ==0.1.0.1,
any.integer-gmp ==1.1,
any.integer-logarithms ==1.0.3.1,
integer-logarithms -check-bounds +integer-gmp,
- any.invariant ==0.6.1,
+ any.invariant ==0.6.2,
any.iproute ==1.7.12,
any.iso8601-time ==0.1.5,
iso8601-time +new-time,
any.kan-extensions ==5.2.5,
- any.lifted-async ==0.10.2.4,
+ any.lens ==5.2.3,
+ lens -benchmark-uniplate -dump-splices +inlining -j +test-hunit +test-properties +test-templates +trustworthy,
+ any.libsodium-bindings ==0.0.1.0,
+ libsodium-bindings -homebrew-libsodium -use-pkg-config,
+ any.lifted-async ==0.10.2.5,
any.lifted-base ==0.2.3.12,
any.lockfree-queue ==0.2.4,
any.log-base ==0.12.0.1,
@@ -187,13 +223,17 @@ constraints: any.Cabal ==3.8.1.0,
any.lucid ==2.11.20230408,
any.lucid-alpine ==0.1.0.7,
any.lucid-svg ==0.7.1.1,
- any.math-functions ==0.3.4.2,
+ any.math-functions ==0.3.4.3,
math-functions +system-erf +system-expm1,
+ any.megaparsec ==9.6.1,
+ megaparsec -dev,
any.memory ==0.18.0,
memory +support_bytestring +support_deepseq,
any.microlens ==0.4.13.1,
- any.mime-types ==0.1.1.0,
+ any.mime-types ==0.1.2.0,
any.mmorph ==1.2.0,
+ any.modern-uri ==0.3.6.1,
+ modern-uri -dev,
any.monad-control ==1.0.3.1,
any.monad-logger ==0.3.40,
monad-logger +template_haskell,
@@ -203,20 +243,30 @@ constraints: any.Cabal ==3.8.1.0,
any.monad-time-effectful ==1.0.0.0,
any.mono-traversable ==1.0.15.3,
any.mtl ==2.2.2,
+ any.mtl-compat ==0.2.2,
+ mtl-compat -two-point-one -two-point-two,
any.mwc-random ==0.15.0.2,
+ any.natural-arithmetic ==0.1.4.0,
any.network ==3.1.4.0,
network -devel,
- any.network-byte-order ==0.1.6,
+ any.network-byte-order ==0.1.7,
any.network-info ==0.2.1,
any.network-uri ==2.6.4.2,
any.odd-jobs ==0.2.3,
any.old-locale ==1.0.0.7,
any.old-time ==1.1.0.3,
- any.optics-core ==0.4.1,
+ any.one-time-password ==3.0.0.0,
+ any.openapi3 ==3.2.4,
+ any.optics-core ==0.4.1.1,
optics-core -explicit-generic-labels,
+ any.optics-extra ==0.4.2.1,
+ any.optics-th ==0.4.1,
any.optparse-applicative ==0.18.1.0,
optparse-applicative +process,
+ any.parallel ==3.2.2.0,
any.parsec ==3.1.16.1,
+ any.parser-combinators ==1.3.0,
+ parser-combinators -dev,
any.password ==3.0.2.1,
password +argon2 +bcrypt +pbkdf2 +scrypt,
any.password-types ==1.0.0.0,
@@ -226,35 +276,47 @@ constraints: any.Cabal ==3.8.1.0,
pg-entity -book -prod,
any.pg-transact ==0.3.2.0,
any.pg-transact-effectful ==0.0.1.0,
- any.pipes ==4.3.16,
- any.poolboy ==0.2.1.0,
+ any.poolboy ==0.2.2.0,
any.postgresql-libpq ==0.9.5.0,
postgresql-libpq -use-pkg-config,
any.postgresql-migration ==0.2.1.7,
- any.postgresql-simple ==0.6.5,
+ any.postgresql-simple ==0.6.5.1,
any.pretty ==1.1.3.6,
any.pretty-show ==1.10,
any.prettyprinter ==1.7.1,
prettyprinter -buildreadme +text,
any.prettyprinter-ansi-terminal ==1.1.3,
any.primitive ==0.8.0.0,
- any.process ==1.6.16.0,
+ any.primitive-addr ==0.1.0.2,
+ any.primitive-offset ==0.2.0.0,
+ any.primitive-unlifted ==2.1.0.0,
+ any.process ==1.6.17.0,
any.profunctors ==5.6.2,
any.prometheus-client ==1.1.0,
any.prometheus-metrics-ghc ==1.0.1.2,
any.prometheus-proc ==0.1.5.0,
- any.psqueues ==0.2.7.3,
+ any.psqueues ==0.2.8.0,
+ any.quickcheck-io ==0.2.0,
any.random ==1.2.1.1,
any.raven-haskell ==0.1.4.1,
+ raven-haskell -tests,
any.recv ==0.1.0,
+ any.reflection ==2.1.7,
+ reflection -slow +template-haskell,
any.regex-applicative ==0.3.4,
+ any.req ==3.13.1,
+ req -dev,
any.resource-pool ==0.4.0.0,
any.resourcet ==1.3.0,
+ any.retry ==0.9.3.1,
+ retry -lib-werror,
any.rts ==1.0.2,
+ any.run-st ==0.1.3.2,
any.safe ==0.3.19,
- any.safe-exceptions ==0.1.7.3,
+ any.safe-exceptions ==0.1.7.4,
any.scientific ==0.3.7.0,
scientific -bytestring-builder -integer-simple,
+ any.sel ==0.0.1.0,
any.semialign ==1.3,
semialign +semigroupoids,
any.semigroupoids ==5.3.7,
@@ -267,6 +329,7 @@ constraints: any.Cabal ==3.8.1.0,
any.servant-client-core ==0.19,
any.servant-effectful ==0.0.1.0,
any.servant-lucid ==0.9.0.6,
+ any.servant-openapi3 ==2.0.1.6,
any.servant-server ==0.19.2,
any.servant-static-th ==1.0.0.0,
servant-static-th -buildexample,
@@ -275,12 +338,12 @@ constraints: any.Cabal ==3.8.1.0,
any.singleton-bool ==0.1.6,
any.slugify ==0.1.0.1,
any.socks ==0.6.1,
- any.some ==1.0.5,
+ any.some ==1.0.6,
some +newtype-unsafe,
any.sop-core ==0.5.0.2,
any.souffle-haskell ==3.5.1,
- any.split ==0.2.3.5,
- any.splitmix ==0.1.0.4,
+ any.split ==0.2.4,
+ any.splitmix ==0.1.0.5,
splitmix -optimised-mixer,
any.stm ==2.5.1.0,
any.stm-chans ==3.0.0.9,
@@ -294,39 +357,49 @@ constraints: any.Cabal ==3.8.1.0,
any.string-conv ==0.2.0,
string-conv -lib-werror,
any.string-conversions ==0.4.0.1,
- any.tagged ==0.8.7,
+ any.syb ==0.7.2.4,
+ any.tagged ==0.8.8,
tagged +deepseq +transformers,
- any.tasty ==1.4.3,
+ any.tar ==0.5.1.1,
+ tar -old-bytestring -old-time,
+ any.tasty ==1.5,
tasty +unix,
- any.tasty-hunit ==0.10.0.3,
+ any.tasty-hunit ==0.10.1,
any.template-haskell ==2.19.0.0,
any.temporary ==1.3,
any.terminal-size ==0.3.4,
any.terminfo ==0.4.1.5,
any.text ==2.0.2,
any.text-conversions ==0.3.1.1,
- any.text-display ==0.0.5.0,
+ any.text-display ==0.0.5.1,
text-display -book,
any.text-manipulate ==0.3.1.0,
any.text-short ==0.1.5,
text-short -asserts,
+ any.tf-random ==0.5,
any.th-abstraction ==0.5.0.0,
any.th-compat ==0.1.4,
+ any.th-expand-syns ==0.4.11.0,
+ any.th-lift ==0.8.4,
+ any.th-orphans ==0.13.14,
+ any.th-reify-many ==0.1.10,
any.these ==1.2,
any.time ==1.12.2,
any.time-compat ==1.9.6.1,
time-compat -old-locale,
- any.time-manager ==0.0.0,
+ any.time-manager ==0.0.1,
any.timing-convenience ==0.1,
- any.tls ==1.7.0,
+ any.tls ==1.9.0,
tls +compat -hans +network,
+ any.torsor ==0.1,
any.transformers ==0.5.6.2,
any.transformers-base ==0.4.6,
transformers-base +orphaninstances,
any.transformers-compat ==0.7.2,
transformers-compat -five +five-three -four +generic-deriving +mtl -three -two,
+ any.tuples ==0.1.0.0,
any.type-equality ==1,
- any.typed-process ==0.2.11.0,
+ any.typed-process ==0.2.11.1,
any.unicode-data ==0.4.0.1,
unicode-data -ucd2haskell,
any.unicode-transforms ==0.4.0.1,
@@ -335,17 +408,17 @@ constraints: any.Cabal ==3.8.1.0,
any.unix-compat ==0.7,
unix-compat -old-time,
any.unix-memory ==0.1.2,
- any.unix-time ==0.4.9,
+ any.unix-time ==0.4.11,
any.unliftio ==0.2.25.0,
any.unliftio-core ==0.2.1.0,
any.unordered-containers ==0.2.19.1,
unordered-containers -debug,
any.utf8-string ==1.0.2,
any.uuid ==1.3.15,
- any.uuid-types ==1.0.5,
+ any.uuid-types ==1.0.5.1,
any.vault ==0.3.1.5,
vault +useghc,
- any.vector ==0.13.0.0,
+ any.vector ==0.13.1.0,
vector +boundschecks -internalchecks -unsafechecks -wall,
any.vector-algorithms ==0.9.0.1,
vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks,
@@ -353,21 +426,24 @@ constraints: any.Cabal ==3.8.1.0,
any.void ==0.7.3,
void -safe,
any.wai ==3.2.3,
- any.wai-app-static ==3.1.7.4,
- wai-app-static +cryptonite -print,
+ any.wai-app-static ==3.1.8,
+ wai-app-static +crypton -print,
any.wai-extra ==3.1.13.0,
wai-extra -build-example,
any.wai-log ==0.4.0.1,
any.wai-logger ==2.4.0,
any.wai-middleware-heartbeat ==0.0.1.0,
any.wai-middleware-prometheus ==1.0.0.1,
- any.warp ==3.3.28,
+ any.warp ==3.3.30,
warp +allow-sendfilefd -network-bytestring -warp-debug -x509,
+ any.wide-word ==0.1.6.0,
any.witherable ==0.4.2,
any.wl-pprint-annotated ==0.1.0.1,
any.word8 ==0.1.3,
- any.xml-conduit ==1.9.1.2,
- any.xml-conduit-writer ==0.1.1.2,
+ any.xml-conduit ==1.9.1.3,
+ any.xml-conduit-writer ==0.1.1.4,
any.xml-types ==0.3.8,
+ any.zigzag ==0.0.1.0,
any.zlib ==0.6.3.0,
zlib -bundled-c-zlib -non-blocking-ffi -pkg-config
+index-state: hackage.haskell.org 2023-11-22T07:29:39Z
diff --git a/design/stories/alerts.stories.js b/design/stories/alerts.stories.js
new file mode 100644
index 00000000..e4c582d5
--- /dev/null
+++ b/design/stories/alerts.stories.js
@@ -0,0 +1,6 @@
+
+export default {
+ title: "Components/Alerts"
+};
+
+export const Alert = () => "Info alert
Error alert
"
diff --git a/flora.cabal b/flora.cabal
index 1e4e4b92..dd47b17b 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -12,7 +12,7 @@ extra-source-files:
LICENSE
README.md
-tested-with: GHC ==9.4.5
+tested-with: GHC ==9.4.7
source-repository head
type: git
@@ -131,6 +131,7 @@ library
Flora.Model.User.Query
Flora.Model.User.Update
Flora.Publish
+ Flora.QRCode
Flora.Search
JSON
Log.Backend.File
@@ -161,6 +162,7 @@ library
, http-api-data
, http-media
, iso8601-time
+ , JuicyPixels
, log-base
, log-effectful
, lucid
@@ -168,6 +170,7 @@ library
, monad-time-effectful
, mtl
, odd-jobs
+ , one-time-password
, openapi3
, optics-core
, password
@@ -179,7 +182,10 @@ library
, poolboy
, postgresql-simple
, pretty
+ , qrcode-core
+ , qrcode-juicypixels
, resource-pool
+ , sel
, servant
, servant-lucid
, servant-server
@@ -213,6 +219,7 @@ library flora-web
FloraWeb.API.Server.Packages
FloraWeb.Client
FloraWeb.Common.Auth
+ FloraWeb.Common.Auth.TwoFactor
FloraWeb.Common.Auth.Types
FloraWeb.Common.Guards
FloraWeb.Common.Metrics
@@ -220,6 +227,8 @@ library flora-web
FloraWeb.Common.Pagination
FloraWeb.Common.Tracing
FloraWeb.Common.Utils
+ FloraWeb.Components.Alert
+ FloraWeb.Components.Button
FloraWeb.Components.CategoryCard
FloraWeb.Components.Footer
FloraWeb.Components.Header
@@ -240,12 +249,14 @@ library flora-web
FloraWeb.Pages.Routes.Packages
FloraWeb.Pages.Routes.Search
FloraWeb.Pages.Routes.Sessions
+ FloraWeb.Pages.Routes.Settings
FloraWeb.Pages.Server
FloraWeb.Pages.Server.Admin
FloraWeb.Pages.Server.Categories
FloraWeb.Pages.Server.Packages
FloraWeb.Pages.Server.Search
FloraWeb.Pages.Server.Sessions
+ FloraWeb.Pages.Server.Settings
FloraWeb.Pages.Templates
FloraWeb.Pages.Templates.Admin
FloraWeb.Pages.Templates.Admin.Packages
@@ -253,13 +264,14 @@ library flora-web
FloraWeb.Pages.Templates.Error
FloraWeb.Pages.Templates.Haddock
FloraWeb.Pages.Templates.Packages
- FloraWeb.Pages.Templates.Pages.Categories
- FloraWeb.Pages.Templates.Pages.Categories.Index
- FloraWeb.Pages.Templates.Pages.Categories.Show
- FloraWeb.Pages.Templates.Pages.Home
- FloraWeb.Pages.Templates.Pages.Packages
- FloraWeb.Pages.Templates.Pages.Search
- FloraWeb.Pages.Templates.Pages.Sessions
+ FloraWeb.Pages.Templates.Screens.Categories
+ FloraWeb.Pages.Templates.Screens.Categories.Index
+ FloraWeb.Pages.Templates.Screens.Categories.Show
+ FloraWeb.Pages.Templates.Screens.Home
+ FloraWeb.Pages.Templates.Screens.Packages
+ FloraWeb.Pages.Templates.Screens.Search
+ FloraWeb.Pages.Templates.Screens.Sessions
+ FloraWeb.Pages.Templates.Screens.Settings
FloraWeb.Pages.Templates.Types
FloraWeb.Routes
FloraWeb.Servant.Common
@@ -271,8 +283,10 @@ library flora-web
, aeson
, async
, base ^>=4.17
+ , base32
, bytestring
, Cabal-syntax
+ , chronos
, clock
, cmark-gfm
, colourista
@@ -302,6 +316,7 @@ library flora-web
, mtl
, network-uri
, odd-jobs
+ , one-time-password
, openapi3
, optics-core
, password
@@ -316,6 +331,7 @@ library flora-web
, raven-haskell
, resource-pool
, safe-exceptions
+ , sel
, servant
, servant-client
, servant-client-core
@@ -325,6 +341,7 @@ library flora-web
, text
, text-display
, time
+ , torsor
, uuid
, vector
, vector-algorithms
diff --git a/migrations/20231122154627_add_totp_to_users.sql b/migrations/20231122154627_add_totp_to_users.sql
new file mode 100644
index 00000000..cb6fbb24
--- /dev/null
+++ b/migrations/20231122154627_add_totp_to_users.sql
@@ -0,0 +1,3 @@
+alter table users
+ add column totp_key text,
+ add column totp_enabled boolean not null;
diff --git a/src/core/Flora/Model/User.hs b/src/core/Flora/Model/User.hs
index 122ba828..8bf454c7 100644
--- a/src/core/Flora/Model/User.hs
+++ b/src/core/Flora/Model/User.hs
@@ -1,5 +1,5 @@
{-# LANGUAGE UndecidableInstances #-}
-{-# OPTIONS_GHC -fno-warn-orphans -Wno-redundant-constraints #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
module Flora.Model.User
( UserId (..)
@@ -34,12 +34,14 @@ import Database.PostgreSQL.Entity
import Database.PostgreSQL.Entity.Types
import Database.PostgreSQL.Simple.FromField (FromField (..), fromJSONField)
import Database.PostgreSQL.Simple.FromRow (FromRow (..))
+import Database.PostgreSQL.Simple.Orphans ()
import Database.PostgreSQL.Simple.ToField (ToField (..), toJSONField)
import Database.PostgreSQL.Simple.ToRow (ToRow (..))
import Effectful
import Effectful.Time qualified as Time
import GHC.Generics
import GHC.TypeLits (ErrorMessage (..), TypeError)
+import Sel.HMAC.SHA256 qualified as HMAC
import Web.HttpApiData (FromHttpApiData, ToHttpApiData)
newtype UserId = UserId {getUserId :: UUID}
@@ -60,6 +62,8 @@ data User = User
, userFlags :: UserFlags
, createdAt :: UTCTime
, updatedAt :: UTCTime
+ , totpKey :: Maybe HMAC.AuthenticationKey
+ , totpEnabled :: Bool
}
deriving stock (Eq, Generic, Show)
deriving anyclass (FromRow, ToRow, NFData)
@@ -125,6 +129,8 @@ mkUser UserCreationForm{username, email, password} = do
let updatedAt = timestamp
let displayName = ""
let userFlags = UserFlags{isAdmin = False, canLogin = True}
+ let totpKey = Nothing
+ let totpEnabled = False
pure User{..}
mkAdmin :: IOE :> es => AdminCreationForm -> Eff es User
@@ -135,6 +141,8 @@ mkAdmin AdminCreationForm{username, email, password} = do
let updatedAt = timestamp
let displayName = ""
let userFlags = UserFlags{isAdmin = True, canLogin = False}
+ let totpKey = Nothing
+ let totpEnabled = False
pure User{..}
hashPassword :: IOE :> es => Password -> Eff es (PasswordHash Argon2)
diff --git a/src/core/Flora/Model/User/Update.hs b/src/core/Flora/Model/User/Update.hs
index a09ccb80..45d45c0e 100644
--- a/src/core/Flora/Model/User/Update.hs
+++ b/src/core/Flora/Model/User/Update.hs
@@ -1,40 +1,60 @@
{-# LANGUAGE QuasiQuotes #-}
-module Flora.Model.User.Update where
+module Flora.Model.User.Update
+ ( addAdmin
+ , lockAccount
+ , unlockAccount
+ , insertUser
+ , deleteUser
+ , setupTOTP
+ , confirmTOTP
+ , unSetTOTP
+ ) where
import Control.Monad
import Database.PostgreSQL.Entity (delete, insert)
import Database.PostgreSQL.Entity.DBT (QueryNature (Update), execute)
import Database.PostgreSQL.Simple (Only (Only))
import Database.PostgreSQL.Simple.SqlQQ (sql)
-
import Effectful (Eff, IOE, type (:>))
import Effectful.PostgreSQL.Transact.Effect (DB, dbtToEff)
+import Effectful.Time (Time)
+import Effectful.Time qualified as Time
+import Sel.HMAC.SHA256 qualified as HMAC
+
import Flora.Model.User
-addAdmin :: (DB :> es, IOE :> es) => AdminCreationForm -> Eff es User
+addAdmin :: (DB :> es, Time :> es, IOE :> es) => AdminCreationForm -> Eff es User
addAdmin form = do
adminUser <- mkAdmin form
insertUser adminUser
- unlockAccount (adminUser.userId)
+ unlockAccount adminUser.userId
pure adminUser
-lockAccount :: DB :> es => UserId -> Eff es ()
-lockAccount userId = dbtToEff $ void $ execute Update q (Only userId)
+lockAccount :: (DB :> es, Time :> es) => UserId -> Eff es ()
+lockAccount userId = do
+ ts <- Time.currentTime
+ dbtToEff $ void $ execute Update q (ts, userId)
where
q =
[sql|
- update users as u set user_flags = jsonb_set(user_flags, '{can_login}', 'false', false)
+ update users as u
+ set user_flags = jsonb_set(user_flags, '{can_login}', 'false', false),
+ updated_at = ?
where u.user_id = ?;
|]
-unlockAccount :: DB :> es => UserId -> Eff es ()
-unlockAccount userId = dbtToEff $ void $ execute Update q (Only userId)
+unlockAccount :: (DB :> es, Time :> es) => UserId -> Eff es ()
+unlockAccount userId = do
+ ts <- Time.currentTime
+ dbtToEff $ void $ execute Update q (ts, userId)
where
q =
[sql|
- update users as u set user_flags = jsonb_set(user_flags, '{can_login}', 'true', false)
- where u.user_id = ?;
+ update users as u
+ set user_flags = jsonb_set(user_flags, '{can_login}', 'true', false),
+ updated_at = ?
+ where u.user_id = ?
|]
insertUser :: DB :> es => User -> Eff es ()
@@ -42,3 +62,53 @@ insertUser user = dbtToEff $ insert @User user
deleteUser :: DB :> es => UserId -> Eff es ()
deleteUser userId = dbtToEff $ delete @User (Only userId)
+
+setupTOTP
+ :: (DB :> es, Time :> es)
+ => UserId
+ -> HMAC.AuthenticationKey
+ -> Eff es ()
+setupTOTP userId key = do
+ ts <- Time.currentTime
+ dbtToEff $ void $ execute Update q (key, ts, userId)
+ where
+ q =
+ [sql|
+ update users as u
+ set totp_key = ?,
+ updated_at = ?
+ where u.user_id = ?;
+ |]
+
+confirmTOTP
+ :: (DB :> es, Time :> es)
+ => UserId
+ -> Eff es ()
+confirmTOTP userId = do
+ ts <- Time.currentTime
+ dbtToEff $ void $ execute Update q (ts, userId)
+ where
+ q =
+ [sql|
+ update users as u
+ set totp_enabled = true,
+ updated_at = ?
+ where u.user_id = ?;
+ |]
+
+unSetTOTP
+ :: (DB :> es, Time :> es)
+ => UserId
+ -> Eff es ()
+unSetTOTP userId = do
+ ts <- Time.currentTime
+ dbtToEff $ void $ execute Update q (ts, userId)
+ where
+ q =
+ [sql|
+ update users as u
+ set totp_enabled = false,
+ totp_key = Null,
+ updated_at = ?
+ where u.user_id = ?;
+ |]
diff --git a/src/core/Flora/QRCode.hs b/src/core/Flora/QRCode.hs
new file mode 100644
index 00000000..9849fc20
--- /dev/null
+++ b/src/core/Flora/QRCode.hs
@@ -0,0 +1,20 @@
+module Flora.QRCode where
+
+import Codec.Picture
+import Codec.QRCode
+import Codec.QRCode.JuicyPixels
+import Data.ByteString (StrictByteString)
+import Data.ByteString.Base64
+import Data.ByteString.Lazy qualified as BSL
+import Data.Function ((&))
+import Data.Text (Text)
+
+generateQRCode :: Text -> StrictByteString
+generateQRCode uri =
+ case encodeText (defaultQRCodeOptions L) Iso8859_1OrUtf8WithoutECI uri of
+ Nothing -> error $ "QR code can't be encoded for text " <> show uri
+ Just qrImage ->
+ toImage 4 20 qrImage
+ & encodePng
+ & BSL.toStrict
+ & encodeBase64'
diff --git a/src/datatypes/Database/PostgreSQL/Simple/Orphans.hs b/src/datatypes/Database/PostgreSQL/Simple/Orphans.hs
index 2df5e805..c09a7d39 100644
--- a/src/datatypes/Database/PostgreSQL/Simple/Orphans.hs
+++ b/src/datatypes/Database/PostgreSQL/Simple/Orphans.hs
@@ -4,6 +4,24 @@ module Database.PostgreSQL.Simple.Orphans where
import Control.DeepSeq
import Data.ByteString (ByteString)
-import Database.PostgreSQL.Simple.Types
+import Data.Text qualified as Text
+import Database.PostgreSQL.Simple.FromField (FromField (..), ResultError (..), returnError)
+import Database.PostgreSQL.Simple.ToField
+import Database.PostgreSQL.Simple.Types (Binary (..))
+import Sel.HMAC.SHA256 qualified as HMAC
deriving newtype instance NFData (Binary ByteString)
+
+instance FromField HMAC.AuthenticationKey where
+ fromField f Nothing = returnError UnexpectedNull f ""
+ fromField f (Just bs) =
+ case HMAC.authenticationKeyFromHexByteString bs of
+ Left err -> returnError ConversionFailed f (Text.unpack err)
+ Right value -> pure value
+
+instance ToField HMAC.AuthenticationKey where
+ toField = Escape . HMAC.unsafeAuthenticationKeyToHexByteString
+
+instance NFData HMAC.AuthenticationKey where
+ rnf :: HMAC.AuthenticationKey -> ()
+ rnf a = seq a ()
diff --git a/src/web/FloraWeb/Common/Auth.hs b/src/web/FloraWeb/Common/Auth.hs
index 4637003a..73830461 100644
--- a/src/web/FloraWeb/Common/Auth.hs
+++ b/src/web/FloraWeb/Common/Auth.hs
@@ -1,7 +1,9 @@
module FloraWeb.Common.Auth
( module FloraWeb.Common.Auth.Types
- , FloraAuthContext
- , authHandler
+ , OptionalAuthContext
+ , StrictAuthContext
+ , optionalAuthHandler
+ , strictAuthHandler
)
where
@@ -37,36 +39,56 @@ import FloraWeb.Session
import FloraWeb.Types
import Servant qualified
-type FloraAuthContext = AuthHandler Request (Headers '[Header "Set-Cookie" SetCookie] Session)
+type OptionalAuthContext = AuthHandler Request (Headers '[Header "Set-Cookie" SetCookie] Session)
+type StrictAuthContext = AuthHandler Request (Headers '[Header "Set-Cookie" SetCookie] Session)
-authHandler :: Logger -> FloraEnv -> FloraAuthContext
-authHandler logger floraEnv =
+optionalAuthHandler :: Logger -> FloraEnv -> OptionalAuthContext
+optionalAuthHandler logger floraEnv =
mkAuthHandler
( \request ->
- handler request
- & Logging.runLog (floraEnv.environment) logger
- & DB.runDB (floraEnv.pool)
+ handler False floraEnv request
+ & Logging.runLog floraEnv.environment logger
+ & DB.runDB floraEnv.pool
& runVisitorSession
& effToHandler
)
- where
- handler :: Request -> Eff '[Log, DB, IsVisitor, Error ServerError, IOE] (Headers '[Header "Set-Cookie" SetCookie] Session)
- handler req = do
- let cookies = getCookies req
- mbPersistentSessionId <- handlerToEff $ getSessionId cookies
- mbPersistentSession <- getInTheFuckingSessionShinji mbPersistentSessionId
- mUserInfo <- fetchUser mbPersistentSession
- requestID <- liftIO $ getRequestID req
- (mUser, sessionId) <- do
- case mUserInfo of
- Nothing -> do
+
+strictAuthHandler :: Logger -> FloraEnv -> StrictAuthContext
+strictAuthHandler logger floraEnv =
+ mkAuthHandler
+ ( \request ->
+ handler True floraEnv request
+ & Logging.runLog floraEnv.environment logger
+ & DB.runDB floraEnv.pool
+ & runVisitorSession
+ & effToHandler
+ )
+
+handler
+ :: Bool
+ -> FloraEnv
+ -> Request
+ -> Eff
+ '[Log, DB, IsVisitor, Error ServerError, IOE]
+ (Headers '[Header "Set-Cookie" SetCookie] Session)
+handler mustBeConnected floraEnv req = do
+ let cookies = getCookies req
+ mbPersistentSessionId <- handlerToEff $ getSessionId cookies
+ mbPersistentSession <- getInTheFuckingSessionShinji mbPersistentSessionId
+ mUserInfo <- fetchUser mbPersistentSession
+ requestID <- liftIO $ getRequestID req
+ (mUser, sessionId) <- do
+ case mUserInfo of
+ Nothing ->
+ if mustBeConnected
+ then throwError $ err401{errBody = "Connect first"}
+ else do
nSessionId <- liftIO newPersistentSessionId
pure (Nothing, nSessionId)
- Just (user, userSession) -> do
- pure (Just user, userSession.persistentSessionId)
- webEnvStore <- liftIO $ newWebEnvStore (WebEnv floraEnv)
- let sessionCookie = craftSessionCookie sessionId False
- pure $ addCookie sessionCookie (Session{..})
+ Just (user, userSession) -> pure (Just user, userSession.persistentSessionId)
+ webEnvStore <- liftIO $ newWebEnvStore (WebEnv floraEnv)
+ let sessionCookie = craftSessionCookie sessionId False
+ pure $ addCookie sessionCookie (Session{..})
getCookies :: Request -> Cookies
getCookies req =
@@ -101,10 +123,13 @@ getInTheFuckingSessionShinji (Just persistentSessionId) = do
Nothing -> pure Nothing
(Just userSession) -> pure (Just userSession)
-fetchUser :: (Error ServerError :> es, DB :> es) => Maybe PersistentSession -> Eff es (Maybe (User, PersistentSession))
+fetchUser
+ :: (Error ServerError :> es, DB :> es)
+ => Maybe PersistentSession
+ -> Eff es (Maybe (User, PersistentSession))
fetchUser Nothing = pure Nothing
fetchUser (Just userSession) = do
- user <- lookupUser (userSession.userId)
+ user <- lookupUser userSession.userId
pure (Just (user, userSession))
lookupUser :: (Error ServerError :> es, DB :> es) => UserId -> Eff es User
@@ -119,8 +144,8 @@ handlerToEff
. Error ServerError :> es
=> Handler a
-> Eff es a
-handlerToEff handler = do
- v <- unsafeEff_ $ Servant.runHandler handler
+handlerToEff handler' = do
+ v <- unsafeEff_ $ Servant.runHandler handler'
either throwError pure v
effToHandler
diff --git a/src/web/FloraWeb/Common/Auth/TwoFactor.hs b/src/web/FloraWeb/Common/Auth/TwoFactor.hs
new file mode 100644
index 00000000..30bcab89
--- /dev/null
+++ b/src/web/FloraWeb/Common/Auth/TwoFactor.hs
@@ -0,0 +1,44 @@
+module FloraWeb.Common.Auth.TwoFactor
+ ( uriFromKey
+ , validateTOTP
+ ) where
+
+import Chronos (Timespan, now, second)
+import Data.ByteString.Base32 qualified as Base32
+import Data.Maybe (fromJust)
+import Data.Text (Text)
+import OTP.Commons
+import OTP.TOTP
+import Sel.HMAC.SHA256 qualified as HMAC
+import Torsor (scale)
+
+period :: Timespan
+period = scale 30 second
+
+sixDigits :: Digits
+sixDigits = fromJust $ mkDigits 6
+
+uriFromKey :: Text -> Text -> HMAC.AuthenticationKey -> Text
+uriFromKey domain email key =
+ let
+ issuer = "Flora (" <> domain <> ")"
+ in
+ totpToURI
+ (Base32.encodeBase32Unpadded $ HMAC.unsafeAuthenticationKeyToBinary key)
+ email
+ issuer
+ sixDigits
+ period
+ HMAC_SHA1
+
+validateTOTP :: HMAC.AuthenticationKey -> Text -> IO Bool
+validateTOTP key code = do
+ timestamp <- now
+ pure $
+ totpSHA1Check
+ key
+ (1, 1)
+ timestamp
+ period
+ sixDigits
+ code
diff --git a/src/web/FloraWeb/Common/Auth/Types.hs b/src/web/FloraWeb/Common/Auth/Types.hs
index b2325653..72c811a6 100644
--- a/src/web/FloraWeb/Common/Auth/Types.hs
+++ b/src/web/FloraWeb/Common/Auth/Types.hs
@@ -68,33 +68,24 @@ demoteSession
-> Eff (IsVisitor : es) a
demoteSession = putVisitorTag . runAdminSession
+type BaseEffects =
+ '[ DB
+ , Time
+ , Reader (Headers '[Header "Set-Cookie" SetCookie] Session)
+ , Reader FeatureEnv
+ , BlobStoreAPI
+ , Log
+ , Error ServerError
+ , IOE
+ ]
+
-- | Datatypes used for every route that doesn't *need* an authenticated user
type FloraPage =
- Eff
- '[ IsVisitor
- , DB
- , Time
- , Reader (Headers '[Header "Set-Cookie" SetCookie] Session)
- , Reader FeatureEnv
- , BlobStoreAPI
- , Log
- , Error ServerError
- , IOE
- ]
+ Eff (IsVisitor ': BaseEffects)
-- | Datatypes used for routes that *need* an admin
type FloraAdmin =
- Eff
- '[ IsAdmin
- , DB
- , Time
- , Reader (Headers '[Header "Set-Cookie" SetCookie] Session)
- , Reader FeatureEnv
- , BlobStoreAPI
- , Log
- , Error ServerError
- , IOE
- ]
+ Eff (IsAdmin ': BaseEffects)
-- | The effect stack for the development websockets
type FloraDevSocket = Eff [Reader (), Log, Error ServerError, IOE]
@@ -103,6 +94,6 @@ type instance
AuthServerData (AuthProtect "optional-cookie-auth") =
(Headers '[Header "Set-Cookie" SetCookie] Session)
--- type instance
--- AuthServerData (AuthProtect "cookie-auth") =
--- (Headers '[Header "Set-Cookie" SetCookie] (Session 'Authenticated))
+type instance
+ AuthServerData (AuthProtect "cookie-auth") =
+ (Headers '[Header "Set-Cookie" SetCookie] Session)
diff --git a/src/web/FloraWeb/Common/Guards.hs b/src/web/FloraWeb/Common/Guards.hs
index 5ca15720..2eb3902b 100644
--- a/src/web/FloraWeb/Common/Guards.hs
+++ b/src/web/FloraWeb/Common/Guards.hs
@@ -2,17 +2,28 @@
module FloraWeb.Common.Guards where
+import Data.Text (Text)
import Distribution.Types.Version (Version)
import Effectful
import Effectful.Log (Log)
import Effectful.PostgreSQL.Transact.Effect
import Effectful.Time (Time)
+import FloraWeb.Pages.Templates
+import Log qualified
+import Optics.Core
+import Servant (respond)
+import Servant.API.UVerb
+
import Flora.Model.Package
import Flora.Model.Package.Query qualified as Query
import Flora.Model.PackageIndex.Query as Query
import Flora.Model.PackageIndex.Types (PackageIndex)
import Flora.Model.Release.Query qualified as Query
import Flora.Model.Release.Types (Release)
+import FloraWeb.Common.Auth
+import FloraWeb.Pages.Routes.Sessions (CreateSessionResponses)
+import FloraWeb.Pages.Templates.Screens.Sessions qualified as Sessions
+import FloraWeb.Session (getSession)
guardThatPackageExists
:: (DB :> es, Log :> es, Time :> es)
@@ -54,3 +65,19 @@ guardThatPackageIndexExists namespace action = do
case result of
Just packageIndex -> pure packageIndex
Nothing -> action namespace
+
+guardThatUserHasProvidedTOTP
+ :: Maybe Text
+ -> (Text -> FloraPage (Union CreateSessionResponses))
+ -> FloraPage (Union CreateSessionResponses)
+guardThatUserHasProvidedTOTP mTOTP action = do
+ case mTOTP of
+ Just totp -> action totp
+ Nothing -> do
+ session <- getSession
+ Log.logInfo_ "User did not provide a TOTP code"
+ templateDefaults <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateDefaults
+ & (#flashError ?~ mkError "Must provide an OTP code")
+ respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
diff --git a/src/web/FloraWeb/Components/Alert.hs b/src/web/FloraWeb/Components/Alert.hs
new file mode 100644
index 00000000..b240a1ca
--- /dev/null
+++ b/src/web/FloraWeb/Components/Alert.hs
@@ -0,0 +1,20 @@
+module FloraWeb.Components.Alert where
+
+import Data.Text (Text)
+import FloraWeb.Components.Icons qualified as Icons
+import FloraWeb.Pages.Templates.Types
+import Lucid
+
+info :: Text -> FloraHTML
+info message =
+ output_ [role_ "status", class_ "alert alert-info"] $ do
+ Icons.information
+ div_ [class_ "alert-message"] $
+ toHtml message
+
+exception :: Text -> FloraHTML
+exception message =
+ output_ [role_ "status", class_ "alert alert-error"] $ do
+ Icons.exception
+ div_ [class_ "alert-message"] $
+ toHtml message
diff --git a/src/web/FloraWeb/Components/Button.hs b/src/web/FloraWeb/Components/Button.hs
new file mode 100644
index 00000000..8f78457c
--- /dev/null
+++ b/src/web/FloraWeb/Components/Button.hs
@@ -0,0 +1,8 @@
+module FloraWeb.Components.Button where
+
+import FloraWeb.Pages.Templates
+import Lucid
+
+button :: FloraHTML -> FloraHTML
+button text =
+ button_ [type_ "submit", class_ "button"] text
diff --git a/src/web/FloraWeb/Components/Icons.hs b/src/web/FloraWeb/Components/Icons.hs
index 0b4a4847..35e34cd6 100644
--- a/src/web/FloraWeb/Components/Icons.hs
+++ b/src/web/FloraWeb/Components/Icons.hs
@@ -5,6 +5,8 @@ module FloraWeb.Components.Icons
, chevronRightOutline
, pen
, lookingGlass
+ , information
+ , exception
) where
import Data.Text (Text)
@@ -55,3 +57,21 @@ lookingGlass =
button_ [type_ "submit"] $
svg_ [xmlns_ "http://www.w3.org/2000/svg", style_ "color: gray", fill_ "none", viewBox_ "0 0 24 24", stroke_ "currentColor"] $
path_ [stroke_linecap_ "round", stroke_linejoin_ "round", stroke_width_ "2", d_ "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"]
+
+information :: FloraHTML
+information =
+ toHtmlRaw @Text
+ [str|
+
+
+
+ |]
+
+exception :: FloraHTML
+exception =
+ toHtmlRaw @Text
+ [str|
+
+
+
+ |]
diff --git a/src/web/FloraWeb/Components/Navbar.hs b/src/web/FloraWeb/Components/Navbar.hs
index b32b7bcd..b7e8ea62 100644
--- a/src/web/FloraWeb/Components/Navbar.hs
+++ b/src/web/FloraWeb/Components/Navbar.hs
@@ -28,7 +28,7 @@ navbar = do
navBarLink' "/about" "About" aboutNav
navBarLink' "/categories" "Categories" packagesNav
navBarLink' "/packages" "Packages" packagesNav
- -- userMenu
+ userMenu
themeToggle
brand :: FloraHTML
diff --git a/src/web/FloraWeb/Pages/Routes.hs b/src/web/FloraWeb/Pages/Routes.hs
index e4c16263..703a5ade 100644
--- a/src/web/FloraWeb/Pages/Routes.hs
+++ b/src/web/FloraWeb/Pages/Routes.hs
@@ -5,6 +5,7 @@ import FloraWeb.Pages.Routes.Categories qualified as Categories
import FloraWeb.Pages.Routes.Packages qualified as Packages
import FloraWeb.Pages.Routes.Search qualified as Search
import FloraWeb.Pages.Routes.Sessions qualified as Sessions
+import FloraWeb.Pages.Routes.Settings qualified as Settings
import Lucid
import Servant
import Servant.API.Generic
@@ -20,6 +21,7 @@ data Routes' mode = Routes'
, packages :: mode :- "packages" :> Packages.Routes
, categories :: mode :- "categories" :> Categories.Routes
, search :: mode :- "search" :> Search.Routes
+ , settings :: mode :- AuthProtect "cookie-auth" :> "settings" :> Settings.Routes
, notFound :: mode :- Get '[HTML] (Html ())
}
deriving stock (Generic)
diff --git a/src/web/FloraWeb/Pages/Routes/Sessions.hs b/src/web/FloraWeb/Pages/Routes/Sessions.hs
index f517336e..4d67b46f 100644
--- a/src/web/FloraWeb/Pages/Routes/Sessions.hs
+++ b/src/web/FloraWeb/Pages/Routes/Sessions.hs
@@ -54,7 +54,7 @@ data Routes' mode = Routes'
data LoginForm = LoginForm
{ email :: Text
, password :: Text
- , remember :: Maybe ()
+ , totp :: Maybe Text
}
deriving stock (Generic)
diff --git a/src/web/FloraWeb/Pages/Routes/Settings.hs b/src/web/FloraWeb/Pages/Routes/Settings.hs
new file mode 100644
index 00000000..ad4033ea
--- /dev/null
+++ b/src/web/FloraWeb/Pages/Routes/Settings.hs
@@ -0,0 +1,68 @@
+module FloraWeb.Pages.Routes.Settings
+ ( Routes
+ , Routes' (..)
+ , TwoFactorSetupResponses
+ , TwoFactorConfirmationForm (..)
+ , DeleteTwoFactorSetupResponse
+ )
+where
+
+import Lucid
+import Servant
+import Servant.API.Generic
+import Servant.HTML.Lucid
+
+import Data.Text (Text)
+import FloraWeb.Common.Auth ()
+import Web.FormUrlEncoded
+
+type Routes =
+ NamedRoutes Routes'
+
+type GetUserSettings =
+ Get '[HTML] (Html ())
+
+type GetUserSecuritySettings =
+ "security"
+ :> Get '[HTML] (Html ())
+
+type GetTwoFactorSettingsPage =
+ "security"
+ :> "two-factor"
+ :> Get '[HTML] (Html ())
+
+type TwoFactorSetupResponses =
+ '[ WithStatus 200 (Html ())
+ , WithStatus 301 (Headers '[Header "Location" Text] NoContent)
+ ]
+
+data TwoFactorConfirmationForm = TwoFactorConfirmationForm
+ { code :: Text
+ }
+ deriving stock (Generic)
+ deriving anyclass (FromForm, ToForm)
+
+type PostTwoFactorSetup =
+ "security"
+ :> "two-factor"
+ :> "setup"
+ :> ReqBody '[FormUrlEncoded] TwoFactorConfirmationForm
+ :> UVerb 'POST '[HTML] TwoFactorSetupResponses
+
+type DeleteTwoFactorSetup =
+ "security"
+ :> "two-factor"
+ :> "delete"
+ :> Verb 'POST 301 '[HTML] DeleteTwoFactorSetupResponse
+
+type DeleteTwoFactorSetupResponse =
+ Headers '[Header "Location" Text] NoContent
+
+data Routes' mode = Routes'
+ { index :: mode :- GetUserSettings
+ , getSecuritySettings :: mode :- GetUserSecuritySettings
+ , getTwoFactorSettings :: mode :- GetTwoFactorSettingsPage
+ , postTwoFactorSetup :: mode :- PostTwoFactorSetup
+ , deleteTwoFactorSetup :: mode :- DeleteTwoFactorSetup
+ }
+ deriving stock (Generic)
diff --git a/src/web/FloraWeb/Pages/Server.hs b/src/web/FloraWeb/Pages/Server.hs
index 7f33ad22..f003b739 100644
--- a/src/web/FloraWeb/Pages/Server.hs
+++ b/src/web/FloraWeb/Pages/Server.hs
@@ -13,9 +13,10 @@ import FloraWeb.Pages.Server.Categories qualified as Categories
import FloraWeb.Pages.Server.Packages qualified as Packages
import FloraWeb.Pages.Server.Search qualified as Search
import FloraWeb.Pages.Server.Sessions qualified as Sessions
+import FloraWeb.Pages.Server.Settings qualified as Settings
import FloraWeb.Pages.Templates
import FloraWeb.Pages.Templates.Error (web404)
-import FloraWeb.Pages.Templates.Pages.Home qualified as Home
+import FloraWeb.Pages.Templates.Screens.Home qualified as Home
import FloraWeb.Session
import OddJobs.Endpoints qualified as OddJobs
import OddJobs.Types qualified as OddJobs
@@ -30,6 +31,7 @@ server cfg env =
, packages = Packages.server
, categories = Categories.server
, search = Search.server
+ , settings = \_ -> hoistServerWithContext (Proxy @Settings.Routes) (Proxy @'[OptionalAuthContext]) id Settings.server
, notFound = serveNotFound
}
diff --git a/src/web/FloraWeb/Pages/Server/Categories.hs b/src/web/FloraWeb/Pages/Server/Categories.hs
index ecbc4e28..a50f6f18 100644
--- a/src/web/FloraWeb/Pages/Server/Categories.hs
+++ b/src/web/FloraWeb/Pages/Server/Categories.hs
@@ -12,7 +12,7 @@ import FloraWeb.Common.Auth
import FloraWeb.Pages.Routes.Categories
import FloraWeb.Pages.Templates (defaultTemplateEnv, fromSession, render)
import FloraWeb.Pages.Templates.Error
-import FloraWeb.Pages.Templates.Pages.Categories qualified as Template
+import FloraWeb.Pages.Templates.Screens.Categories qualified as Template
import FloraWeb.Session (getSession)
server :: ServerT Routes FloraPage
diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs
index 259d436c..01094d95 100644
--- a/src/web/FloraWeb/Pages/Server/Packages.hs
+++ b/src/web/FloraWeb/Pages/Server/Packages.hs
@@ -41,8 +41,8 @@ import FloraWeb.Pages.Routes.Packages
import FloraWeb.Pages.Templates
import FloraWeb.Pages.Templates.Error
import FloraWeb.Pages.Templates.Packages qualified as Package
-import FloraWeb.Pages.Templates.Pages.Packages qualified as Packages
-import FloraWeb.Pages.Templates.Pages.Search qualified as Search
+import FloraWeb.Pages.Templates.Screens.Packages qualified as Packages
+import FloraWeb.Pages.Templates.Screens.Search qualified as Search
import FloraWeb.Session
server :: ServerT Routes FloraPage
@@ -272,7 +272,7 @@ constructTarballPath pname v = display pname <> "-" <> display v <> ".tar.gz"
getTarballHandler :: Namespace -> PackageName -> Version -> Text -> FloraPage ByteString
getTarballHandler namespace packageName version tarballName = do
features <- ask @FeatureEnv
- unless (isJust $ features.blobStoreImpl) $! throwError err404
+ unless (isJust features.blobStoreImpl) $ throwError err404
package <- guardThatPackageExists namespace packageName $ \_ _ -> web404
release <- guardThatReleaseExists package.packageId version $ const web404
case release.tarballRootHash of
diff --git a/src/web/FloraWeb/Pages/Server/Search.hs b/src/web/FloraWeb/Pages/Server/Search.hs
index 18612442..244dae95 100644
--- a/src/web/FloraWeb/Pages/Server/Search.hs
+++ b/src/web/FloraWeb/Pages/Server/Search.hs
@@ -12,7 +12,7 @@ import Flora.Search qualified as Search
import FloraWeb.Common.Pagination
import FloraWeb.Pages.Routes.Search (Routes, Routes' (..))
import FloraWeb.Pages.Templates (TemplateEnv (..), defaultTemplateEnv, fromSession, render)
-import FloraWeb.Pages.Templates.Pages.Search qualified as Search
+import FloraWeb.Pages.Templates.Screens.Search qualified as Search
import FloraWeb.Session
server :: ServerT Routes FloraPage
diff --git a/src/web/FloraWeb/Pages/Server/Sessions.hs b/src/web/FloraWeb/Pages/Server/Sessions.hs
index e1714946..ba922cc8 100644
--- a/src/web/FloraWeb/Pages/Server/Sessions.hs
+++ b/src/web/FloraWeb/Pages/Server/Sessions.hs
@@ -1,21 +1,28 @@
+{-# LANGUAGE OverloadedRecordDot #-}
+
module FloraWeb.Pages.Server.Sessions where
+import Data.Maybe
import Data.Password.Argon2
import Data.Text.Display
import Log qualified
import Optics.Core
+import Servant
+import Control.Monad.IO.Class
+import Data.Text (Text)
import Flora.Model.PersistentSession
import Flora.Model.User
import Flora.Model.User.Orphans ()
import Flora.Model.User.Query qualified as Query
import FloraWeb.Common.Auth
+import FloraWeb.Common.Auth.TwoFactor qualified as TwoFactor
+import FloraWeb.Common.Guards (guardThatUserHasProvidedTOTP)
import FloraWeb.Common.Utils
import FloraWeb.Pages.Routes.Sessions
import FloraWeb.Pages.Templates
-import FloraWeb.Pages.Templates.Pages.Sessions as Sessions
+import FloraWeb.Pages.Templates.Screens.Sessions as Sessions
import FloraWeb.Session
-import Servant
server :: ServerT Routes FloraPage
server =
@@ -39,7 +46,7 @@ newSessionHandler = do
respond $ WithStatus @301 (redirect "/")
createSessionHandler :: LoginForm -> FloraPage (Union CreateSessionResponses)
-createSessionHandler LoginForm{email, password} = do
+createSessionHandler LoginForm{email, password, totp} = do
session <- getSession
mUser <- Query.getUserByEmail email
case mUser of
@@ -51,12 +58,16 @@ createSessionHandler LoginForm{email, password} = do
& (#flashError ?~ mkError "Could not authenticate")
respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
Just user ->
- if validatePassword (mkPassword password) (user.password)
+ if validatePassword (mkPassword password) user.password
then do
- Log.logInfo_ "[+] User connected!"
- sessionId <- persistSession (session.sessionId) (user.userId)
- let sessionCookie = craftSessionCookie sessionId True
- respond $ WithStatus @301 $ redirectWithCookie "/" sessionCookie
+ if user.totpEnabled
+ then guardThatUserHasProvidedTOTP totp $ \userCode -> do
+ checkTOTPIsValid userCode user
+ else do
+ Log.logInfo_ "[+] User connected!"
+ sessionId <- persistSession session.sessionId user.userId
+ let sessionCookie = craftSessionCookie sessionId True
+ respond $ WithStatus @301 $ redirectWithCookie "/" sessionCookie
else do
Log.logInfo_ "[+] Couldn't authenticate user"
templateDefaults <- fromSession session defaultTemplateEnv
@@ -65,6 +76,27 @@ createSessionHandler LoginForm{email, password} = do
& (#flashError ?~ mkError "Could not authenticate")
respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
+checkTOTPIsValid
+ :: Text
+ -> User
+ -> FloraPage (Union CreateSessionResponses)
+checkTOTPIsValid userCode user = do
+ session <- getSession
+ validated <- liftIO $ TwoFactor.validateTOTP (fromJust user.totpKey) userCode
+ if validated
+ then do
+ Log.logInfo_ "[+] User connected!"
+ sessionId <- persistSession session.sessionId user.userId
+ let sessionCookie = craftSessionCookie sessionId True
+ respond $ WithStatus @301 $ redirectWithCookie "/" sessionCookie
+ else do
+ Log.logInfo_ "[+] Couldn't authenticate user's TOTP code"
+ templateDefaults <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateDefaults
+ & (#flashError ?~ mkError "Could not authenticate")
+ respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
+
deleteSessionHandler :: PersistentSessionId -> FloraPage DeleteSessionResponse
deleteSessionHandler sessionId = do
Log.logInfo_ $ "[+] Logging-off session " <> display sessionId
diff --git a/src/web/FloraWeb/Pages/Server/Settings.hs b/src/web/FloraWeb/Pages/Server/Settings.hs
new file mode 100644
index 00000000..cd500eff
--- /dev/null
+++ b/src/web/FloraWeb/Pages/Server/Settings.hs
@@ -0,0 +1,130 @@
+module FloraWeb.Pages.Server.Settings
+ ( Routes
+ , server
+ ) where
+
+import Control.Monad.IO.Class
+import Data.ByteString.Base32 qualified as Base32
+import Data.Maybe (fromJust)
+import Data.Text.Encoding qualified as Text
+import Log qualified
+import Lucid
+import Optics.Core
+import Sel.HMAC.SHA256 qualified as HMAC
+import Servant
+
+import Flora.Environment
+import Flora.Model.User
+import Flora.Model.User.Update qualified as Update
+import Flora.QRCode qualified as QRCode
+import FloraWeb.Common.Auth.TwoFactor qualified as TwoFactor
+import FloraWeb.Common.Utils (redirect)
+import FloraWeb.Pages.Routes.Settings
+import FloraWeb.Pages.Templates (render, renderUVerb)
+import FloraWeb.Pages.Templates.Screens.Settings qualified as Settings
+import FloraWeb.Pages.Templates.Types
+import FloraWeb.Session
+
+server :: ServerT Routes FloraPage
+server =
+ Routes'
+ { index = userSettingsHandler
+ , getSecuritySettings = userSecuritySettingsHandler
+ , getTwoFactorSettings = getTwoFactorSettingsHandler
+ , postTwoFactorSetup = postTwoFactorSetupHandler
+ , deleteTwoFactorSetup = deleteTwoFactorSetupHandler
+ }
+
+userSettingsHandler :: FloraPage (Html ())
+userSettingsHandler = do
+ session <- getSession
+ templateEnv' <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateEnv'
+ & #title .~ "Account settings"
+ let user = fromJust session.mUser
+ render templateEnv $
+ Settings.dashboard user
+
+userSecuritySettingsHandler :: FloraPage (Html ())
+userSecuritySettingsHandler = do
+ session <- getSession
+ templateEnv' <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateEnv'
+ & #title .~ "Security settings"
+ render
+ templateEnv
+ Settings.securitySettings
+
+getTwoFactorSettingsHandler :: FloraPage (Html ())
+getTwoFactorSettingsHandler = do
+ FloraEnv{domain} <- getEnv
+ session <- getSession
+ templateEnv' <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateEnv'
+ & #title .~ "Security settings"
+ let user = fromJust session.mUser
+ case user.totpKey of
+ Nothing -> do
+ userKey <- liftIO HMAC.newAuthenticationKey
+ Update.setupTOTP user.userId userKey
+ let uri = TwoFactor.uriFromKey domain user.email userKey
+ let qrCode =
+ QRCode.generateQRCode uri
+ & Text.decodeUtf8
+ render templateEnv $
+ Settings.twoFactorSettings
+ qrCode
+ (Base32.encodeBase32Unpadded $ HMAC.unsafeAuthenticationKeyToBinary userKey)
+ Just userKey -> do
+ if user.totpEnabled
+ then render templateEnv Settings.twoFactorSettingsRemove
+ else do
+ let uri = TwoFactor.uriFromKey domain user.email userKey
+ let qrCode =
+ QRCode.generateQRCode uri
+ & Text.decodeUtf8
+ render templateEnv $
+ Settings.twoFactorSettings
+ qrCode
+ (Base32.encodeBase32Unpadded $ HMAC.unsafeAuthenticationKeyToBinary userKey)
+
+postTwoFactorSetupHandler :: TwoFactorConfirmationForm -> FloraPage (Union TwoFactorSetupResponses)
+postTwoFactorSetupHandler TwoFactorConfirmationForm{code = userCode} = do
+ session <- getSession
+ templateEnv' <- fromSession session defaultTemplateEnv
+ let user = fromJust session.mUser
+ case user.totpKey of
+ Nothing -> respond $ WithStatus @301 (redirect "/settings/security/two-factor")
+ Just userKey -> do
+ validated <- liftIO $ TwoFactor.validateTOTP userKey userCode
+ if validated
+ then do
+ Update.confirmTOTP user.userId
+ Log.logInfo_ "Code validation succeeded"
+ respond $ WithStatus @301 (redirect "/settings/security/two-factor")
+ else do
+ Log.logAttention_ "Code validation failed"
+ let templateEnv =
+ templateEnv'
+ & #title .~ "Security settings"
+ & #flashError ?~ mkError "Code validation failed, please retry"
+ let uri = TwoFactor.uriFromKey "localhost" user.email userKey
+ let qrCode =
+ QRCode.generateQRCode uri
+ & Text.decodeUtf8
+ respond $
+ WithStatus @200 $
+ renderUVerb templateEnv $
+ Settings.twoFactorSettings
+ qrCode
+ (Base32.encodeBase32Unpadded $ HMAC.unsafeAuthenticationKeyToBinary userKey)
+
+deleteTwoFactorSetupHandler :: FloraPage DeleteTwoFactorSetupResponse
+deleteTwoFactorSetupHandler = do
+ session <- getSession
+ let user = fromJust session.mUser
+ Update.unSetTOTP user.userId
+ pure $ redirect "/settings/security"
diff --git a/src/web/FloraWeb/Pages/Templates.hs b/src/web/FloraWeb/Pages/Templates.hs
index df0b79cd..e8c61cdf 100644
--- a/src/web/FloraWeb/Pages/Templates.hs
+++ b/src/web/FloraWeb/Pages/Templates.hs
@@ -6,12 +6,15 @@ module FloraWeb.Pages.Templates
)
where
+import Control.Monad.Extra (whenJust)
import Control.Monad.Identity (runIdentity)
-import Control.Monad.Reader (runReaderT)
+import Control.Monad.Reader (ask, runReaderT)
import Data.ByteString.Lazy
+import Data.Text.Display
import Lucid
import Flora.Environment (DeploymentEnv (..))
+import FloraWeb.Components.Alert qualified as Alert
import FloraWeb.Components.Header (header)
import FloraWeb.Pages.Templates.Types as Types
@@ -30,7 +33,12 @@ mkErrorPage env template =
rendered :: DeploymentEnv -> FloraHTML -> FloraHTML
rendered _deploymentEnv target = do
+ TemplateEnv{flashInfo, flashError} <- ask
header
+ whenJust flashInfo $ \msg -> do
+ Alert.info (display msg)
+ whenJust flashError $ \msg -> do
+ Alert.exception (display msg)
main_ [] target
-- when (deploymentEnv == Development) $
diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs
index b170f273..2025150d 100644
--- a/src/web/FloraWeb/Pages/Templates/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Packages.hs
@@ -188,11 +188,10 @@ requirementListing requirements =
showChangelog :: Namespace -> PackageName -> Version -> Maybe TextHtml -> FloraHTML
showChangelog namespace packageName version mChangelog = div_ [class_ "container"] $ div_ [class_ "divider"] $ do
div_ [class_ "page-title"] $
- h1_ [class_ ""] $
- do
- span_ [class_ "headline"] $ toHtml ("Changelog of " <> display namespace <> "/" <> display packageName)
- toHtmlRaw @Text " "
- span_ [class_ "version"] $ toHtml $ display version
+ h1_ [class_ ""] $ do
+ span_ [class_ "headline"] $ toHtml ("Changelog of " <> display namespace <> "/" <> display packageName)
+ toHtmlRaw @Text " "
+ span_ [class_ "version"] $ toHtml $ display version
section_ [class_ "release-changelog"] $ do
case mChangelog of
Nothing -> toHtml @Text "This release does not have a Changelog"
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Categories.hs b/src/web/FloraWeb/Pages/Templates/Pages/Categories.hs
deleted file mode 100644
index aec412b3..00000000
--- a/src/web/FloraWeb/Pages/Templates/Pages/Categories.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-module FloraWeb.Pages.Templates.Pages.Categories
- ( index
- , showCategory
- )
-where
-
-import FloraWeb.Pages.Templates.Pages.Categories.Index (index)
-import FloraWeb.Pages.Templates.Pages.Categories.Show (showCategory)
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Sessions.hs b/src/web/FloraWeb/Pages/Templates/Pages/Sessions.hs
deleted file mode 100644
index 6f2cfeeb..00000000
--- a/src/web/FloraWeb/Pages/Templates/Pages/Sessions.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-module FloraWeb.Pages.Templates.Pages.Sessions where
-
-import FloraWeb.Pages.Templates.Types
-import Lucid
-
-newSession :: FloraHTML
-newSession = do
- let formClasses = "login-form"
- form_ [action_ "/sessions/new", method_ "POST", class_ formClasses] $ do
- h2_ [class_ ""] "Sign in"
- div_ $ do
- label_ [for_ "email", class_ "sr-only"] "Email address"
- input_
- [ id_ "email"
- , name_ "email"
- , type_ "email"
- , autocomplete_ "email"
- , required_ ""
- , placeholder_ "Email address"
- , class_ "form-input"
- ]
- div_ $ do
- label_ [for_ "password", class_ "sr-only"] "Email address"
- input_
- [ id_ "password"
- , name_ "password"
- , type_ "password"
- , autocomplete_ "current-password"
- , required_ ""
- , placeholder_ "Password"
- , class_ "form-input"
- ]
- -- div_ $ do
- -- label_ [for_ "remember", class_ "text-xl mr-3"] "Remember me"
- -- input_ [id_ "remember", name_ "remember", type_ "checkbox", class_ ""]
-
- div_ $
- button_ [type_ "submit", class_ "btn bg-brand-purple text-white w-full my-2"] "Sign in"
diff --git a/src/web/FloraWeb/Pages/Templates/Screens/Categories.hs b/src/web/FloraWeb/Pages/Templates/Screens/Categories.hs
new file mode 100644
index 00000000..ace512ae
--- /dev/null
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Categories.hs
@@ -0,0 +1,8 @@
+module FloraWeb.Pages.Templates.Screens.Categories
+ ( index
+ , showCategory
+ )
+where
+
+import FloraWeb.Pages.Templates.Screens.Categories.Index (index)
+import FloraWeb.Pages.Templates.Screens.Categories.Show (showCategory)
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Categories/Index.hs b/src/web/FloraWeb/Pages/Templates/Screens/Categories/Index.hs
similarity index 89%
rename from src/web/FloraWeb/Pages/Templates/Pages/Categories/Index.hs
rename to src/web/FloraWeb/Pages/Templates/Screens/Categories/Index.hs
index 3d6df3af..e7bde21c 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Categories/Index.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Categories/Index.hs
@@ -1,4 +1,4 @@
-module FloraWeb.Pages.Templates.Pages.Categories.Index where
+module FloraWeb.Pages.Templates.Screens.Categories.Index where
import Data.Vector (Vector)
import Data.Vector qualified as V
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Categories/Show.hs b/src/web/FloraWeb/Pages/Templates/Screens/Categories/Show.hs
similarity index 90%
rename from src/web/FloraWeb/Pages/Templates/Pages/Categories/Show.hs
rename to src/web/FloraWeb/Pages/Templates/Screens/Categories/Show.hs
index 4269f9d2..c04cdbdd 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Categories/Show.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Categories/Show.hs
@@ -1,4 +1,4 @@
-module FloraWeb.Pages.Templates.Pages.Categories.Show where
+module FloraWeb.Pages.Templates.Screens.Categories.Show where
import Data.Vector (Vector)
import Data.Vector qualified as V
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Home.hs b/src/web/FloraWeb/Pages/Templates/Screens/Home.hs
similarity index 97%
rename from src/web/FloraWeb/Pages/Templates/Pages/Home.hs
rename to src/web/FloraWeb/Pages/Templates/Screens/Home.hs
index 358f58f4..acbe2470 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Home.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Home.hs
@@ -1,6 +1,6 @@
{-# LANGUAGE QuasiQuotes #-}
-module FloraWeb.Pages.Templates.Pages.Home where
+module FloraWeb.Pages.Templates.Screens.Home where
import CMarkGFM
import Control.Monad.Reader
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs b/src/web/FloraWeb/Pages/Templates/Screens/Packages.hs
similarity index 98%
rename from src/web/FloraWeb/Pages/Templates/Pages/Packages.hs
rename to src/web/FloraWeb/Pages/Templates/Screens/Packages.hs
index 8fd93a65..cc1e85ad 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Packages.hs
@@ -1,4 +1,4 @@
-module FloraWeb.Pages.Templates.Pages.Packages where
+module FloraWeb.Pages.Templates.Screens.Packages where
import Data.Function ((&))
import Data.Maybe (fromMaybe)
diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Search.hs b/src/web/FloraWeb/Pages/Templates/Screens/Search.hs
similarity index 96%
rename from src/web/FloraWeb/Pages/Templates/Pages/Search.hs
rename to src/web/FloraWeb/Pages/Templates/Screens/Search.hs
index 13f998b6..438d03b5 100644
--- a/src/web/FloraWeb/Pages/Templates/Pages/Search.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Search.hs
@@ -1,4 +1,4 @@
-module FloraWeb.Pages.Templates.Pages.Search where
+module FloraWeb.Pages.Templates.Screens.Search where
import Control.Monad (when)
import Data.Positive
diff --git a/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs b/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
new file mode 100644
index 00000000..29f7cb68
--- /dev/null
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
@@ -0,0 +1,48 @@
+module FloraWeb.Pages.Templates.Screens.Sessions where
+
+import FloraWeb.Pages.Templates.Types
+import Lucid
+
+newSession :: FloraHTML
+newSession = do
+ let formClasses = "login-form"
+ form_ [action_ "/sessions/new", method_ "POST", class_ formClasses] $ do
+ h2_ [class_ ""] "Sign in"
+ label_ [for_ "email", class_ "sr-only"] "Email address"
+ input_
+ [ id_ "email"
+ , name_ "email"
+ , type_ "email"
+ , autocomplete_ "email"
+ , required_ ""
+ , placeholder_ "Email address"
+ , class_ "form-input"
+ ]
+ label_ [for_ "password", class_ "sr-only"] "Email address"
+ input_
+ [ id_ "password"
+ , name_ "password"
+ , type_ "password"
+ , autocomplete_ "current-password"
+ , required_ ""
+ , placeholder_ "Password"
+ , class_ "form-input"
+ ]
+ label_ [for_ "use_totp"] "Use two-factor authentication"
+ input_
+ [ id_ "use_totp"
+ , name_ "use_totp"
+ , type_ "checkbox"
+ ]
+ div_ [class_ "totp-zone"] $ do
+ label_ [for_ "totp"] "Two-factor code"
+ input_
+ [ id_ "totp"
+ , name_ "totp"
+ , type_ "text"
+ , pattern_ "0-9]+"
+ , autocomplete_ "off"
+ , class_ "form-input"
+ ]
+ div_ $
+ button_ [type_ "submit", class_ "btn bg-brand-purple text-white w-full my-2"] "Sign in"
diff --git a/src/web/FloraWeb/Pages/Templates/Screens/Settings.hs b/src/web/FloraWeb/Pages/Templates/Screens/Settings.hs
new file mode 100644
index 00000000..6a935cf8
--- /dev/null
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Settings.hs
@@ -0,0 +1,78 @@
+module FloraWeb.Pages.Templates.Screens.Settings where
+
+import Lucid
+
+import Data.Text (Text)
+import Flora.Model.User
+import FloraWeb.Components.Button (button)
+import FloraWeb.Pages.Templates
+
+-- import FloraWeb.Components.Button
+
+dashboard :: User -> FloraHTML
+dashboard user = main_ $
+ div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ "Account settings"
+ section_ [class_ "settings_menu"] $ do
+ ul_ [] $ do
+ li_ $ a_ [href_ "/settings/profile"] "Profile"
+ li_ $ a_ [href_ "/settings/security"] "Security"
+
+profileSettings :: User -> FloraHTML
+profileSettings user = do
+ div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ "Profile settings"
+ section_ [] $ do
+ form_ [] $ do
+ h6_ [] "User information"
+ div_ $ do
+ label_ [for_ "email"] "Email address"
+ input_ [type_ "text", name_ "email", id_ "email", required_ "", class_ "form-input"]
+ div_ $
+ button_ [type_ "submit", class_ ""] "Update profile"
+ hr_ [class_ "settings_separator"]
+
+securitySettings :: FloraHTML
+securitySettings = do
+ div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ "Security settings"
+ section_ [] $ do
+ ul_ [] $ do
+ li_ [] $
+ a_ [href_ "/settings/security/two-factor"] "Two-factor authentication"
+
+twoFactorSettings :: Text -> Text -> FloraHTML
+twoFactorSettings qrCode base32Key = do
+ div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ "Two-Factor Authentication"
+ h2_ "Scan the QR Code"
+ img_ [src_ ("data:image/png;base64," <> qrCode), height_ "300", width_ "300"]
+ toHtml base32Key
+ form_ [action_ "/settings/security/two-factor/setup", method_ "POST"] $ do
+ label_ [for_ "code"] "Code from the authenticator app"
+ input_
+ [ id_ "code"
+ , name_ "code"
+ , type_ "text"
+ , required_ ""
+ , placeholder_ "XXXXXX"
+ , class_ "form-input"
+ ]
+ button "Save"
+
+twoFactorSettingsRemove :: FloraHTML
+twoFactorSettingsRemove = do
+ div_ [class_ "container"] $ do
+ div_ [class_ "divider"] $ do
+ div_ [class_ "page-title"] $ do
+ h1_ "Two-Factor Authentication"
+ form_ [action_ "/settings/security/two-factor/delete", method_ "POST"] $
+ button "Delete authenticator application"
diff --git a/src/web/FloraWeb/Pages/Templates/Types.hs b/src/web/FloraWeb/Pages/Templates/Types.hs
index 9168df6f..7a5dbbde 100644
--- a/src/web/FloraWeb/Pages/Templates/Types.hs
+++ b/src/web/FloraWeb/Pages/Templates/Types.hs
@@ -20,6 +20,7 @@ import GHC.Generics
import Lucid
import Optics.Core
+import Data.Text.Display
import Effectful
import Effectful.Reader.Static (Reader, ask)
import Flora.Environment
@@ -32,13 +33,13 @@ import FloraWeb.Types
type FloraHTML = HtmlT (ReaderT TemplateEnv Identity) ()
newtype FlashInfo = FlashInfo {getFlashInfo :: Text}
- deriving (Show) via Text
+ deriving (Show, Display) via Text
mkInfo :: Text -> FlashInfo
mkInfo = FlashInfo
newtype FlashError = FlashError {getFlashInfo :: Text}
- deriving (Show) via Text
+ deriving (Show, Display) via Text
mkError :: Text -> FlashError
mkError = FlashError
@@ -130,6 +131,6 @@ fromSession session defaults = do
let TemplateDefaults{..} =
defaults
& (#mUser .~ muser)
- & (#environment .~ (floraEnv.environment))
+ & (#environment .~ floraEnv.environment)
& (#features .~ featuresEnv)
pure TemplateEnv{..}
diff --git a/src/web/FloraWeb/Server.hs b/src/web/FloraWeb/Server.hs
index 8241ad5b..071e0080 100644
--- a/src/web/FloraWeb/Server.hs
+++ b/src/web/FloraWeb/Server.hs
@@ -2,7 +2,6 @@ module FloraWeb.Server where
import Colourista.IO (blueMessage)
import Control.Exception (bracket)
-
import Control.Exception.Safe qualified as Safe
import Control.Monad (void, when)
import Data.Aeson qualified as Aeson
@@ -40,6 +39,7 @@ import Optics.Core
import Prometheus qualified
import Prometheus.Metric.GHC (ghcMetrics)
import Prometheus.Metric.Proc (procMetrics)
+import Sel
import Servant
( Application
, Context (..)
@@ -68,7 +68,7 @@ import FloraJobs.Runner (runner)
import FloraJobs.Types (JobsRunnerEnv (..), makeConfig, makeUIConfig)
import FloraWeb.API.Routes qualified as API
import FloraWeb.API.Server qualified as API
-import FloraWeb.Common.Auth (FloraAuthContext, authHandler, requestID, runVisitorSession)
+import FloraWeb.Common.Auth (OptionalAuthContext, StrictAuthContext, optionalAuthHandler, requestID, runVisitorSession, strictAuthHandler)
import FloraWeb.Common.Metrics
import FloraWeb.Common.OpenSearch
import FloraWeb.Common.Tracing
@@ -83,29 +83,30 @@ import FloraWeb.Types
runFlora :: IO ()
runFlora =
- bracket
- (getFloraEnv & runFailIO & runEff)
- (runEff . shutdownFlora)
- ( \env ->
- runEff . runTime . runConcurrent $ do
- let baseURL = "http://localhost:" <> display (env.httpPort)
- liftIO $ blueMessage $ "🌺 Starting Flora server on " <> baseURL
- liftIO $ when (isJust $ env.logging.sentryDSN) (blueMessage "📋 Connected to Sentry endpoint")
- liftIO $ when env.logging.prometheusEnabled $ do
- blueMessage $ "📋 Service Prometheus metrics on " <> baseURL <> "/metrics"
- void $ Prometheus.register ghcMetrics
- void $ Prometheus.register procMetrics
- let withLogger = Logging.makeLogger (env.logging.logger)
- withLogger
- ( \appLogger ->
- runServer appLogger env
- )
- )
+ secureMain $
+ bracket
+ (getFloraEnv & runFailIO & runEff)
+ (runEff . shutdownFlora)
+ ( \env ->
+ runEff . runTime . runConcurrent $ do
+ let baseURL = "http://localhost:" <> display env.httpPort
+ liftIO $ blueMessage $ "🌺 Starting Flora server on " <> baseURL
+ liftIO $ when (isJust env.logging.sentryDSN) (blueMessage "📋 Connected to Sentry endpoint")
+ liftIO $ when env.logging.prometheusEnabled $ do
+ blueMessage $ "📋 Service Prometheus metrics on " <> baseURL <> "/metrics"
+ void $ Prometheus.register ghcMetrics
+ void $ Prometheus.register procMetrics
+ let withLogger = Logging.makeLogger env.logging.logger
+ withLogger
+ ( \appLogger ->
+ runServer appLogger env
+ )
+ )
shutdownFlora :: FloraEnv -> Eff '[IOE] ()
shutdownFlora env =
liftIO $
- Pool.destroyAllResources (env.pool)
+ Pool.destroyAllResources env.pool
logException
:: DeploymentEnv
@@ -122,33 +123,36 @@ runServer :: (Concurrent :> es, IOE :> es) => Logger -> FloraEnv -> Eff es ()
runServer appLogger floraEnv = do
httpManager <- liftIO $ HTTP.newManager tlsManagerSettings
let runnerEnv = JobsRunnerEnv httpManager
- let oddjobsUiCfg = makeUIConfig (floraEnv.config) appLogger (floraEnv.jobsPool)
+ let oddjobsUiCfg = makeUIConfig floraEnv.config appLogger floraEnv.jobsPool
oddJobsCfg =
makeConfig
runnerEnv
floraEnv
appLogger
- (floraEnv.jobsPool)
+ floraEnv.jobsPool
runner
- void $ forkIO $ unsafeEff_ $ Safe.withException (startJobRunner oddJobsCfg) (logException (floraEnv.environment) appLogger)
- loggingMiddleware <- Logging.runLog (floraEnv.environment) appLogger WaiLog.mkLogMiddleware
+ void $
+ forkIO $
+ unsafeEff_ $
+ Safe.withException (startJobRunner oddJobsCfg) (logException floraEnv.environment appLogger)
+ loggingMiddleware <- Logging.runLog floraEnv.environment appLogger WaiLog.mkLogMiddleware
oddJobsEnv <- OddJobs.mkEnv oddjobsUiCfg ("/admin/odd-jobs/" <>)
let webEnv = WebEnv floraEnv
webEnvStore <- liftIO $ newWebEnvStore webEnv
let server = mkServer appLogger webEnvStore floraEnv oddjobsUiCfg oddJobsEnv
let warpSettings =
- setPort (fromIntegral $ floraEnv.httpPort) $
+ setPort (fromIntegral floraEnv.httpPort) $
setOnException
( onException
appLogger
- (floraEnv.environment)
- (floraEnv.logging)
+ floraEnv.environment
+ floraEnv.logging
)
defaultSettings
liftIO
$ runSettings warpSettings
- $ prometheusMiddleware (floraEnv.environment) (floraEnv.logging)
+ $ prometheusMiddleware floraEnv.environment floraEnv.logging
. heartbeatMiddleware
. loggingMiddleware
. const
@@ -161,10 +165,10 @@ mkServer
-> OddJobs.UIConfig
-> OddJobs.Env
-> Application
-mkServer logger webEnvStore floraEnv cfg jobsRunnerEnv = do
+mkServer logger webEnvStore floraEnv cfg jobsRunnerEnv =
genericServeTWithContext
- (naturalTransform (floraEnv.environment) (floraEnv.features) logger webEnvStore)
- (floraServer (floraEnv.pool) cfg jobsRunnerEnv)
+ (naturalTransform floraEnv.environment floraEnv.features logger webEnvStore)
+ (floraServer floraEnv.pool cfg jobsRunnerEnv)
(genAuthServerContext logger floraEnv)
-- What the fuck is happening here:
@@ -174,7 +178,7 @@ mkServer logger webEnvStore floraEnv cfg jobsRunnerEnv = do
-- [IsVisitor, DB, Time, Reader (Headers '[Header "Set-Cookie" SetCookie] Session), Log, Error ServerError, IOE]
-- api has effects:
-- [DB, Time, Reader (), Log, Error ServerError, IOE]
--- An the intermediate effect list of effects:
+-- And the intermediate effect list of effects:
-- [Reader WebEnvStore, Log, Error ServerError, IOE]
--
-- What must happen is that the list of effects of 'pages' and 'api' must correspond to the intermediate 'Flora'
@@ -193,7 +197,7 @@ floraServer pool cfg jobsRunnerEnv =
, pages = \sessionWithCookies ->
hoistServerWithContext
(Proxy @Pages.Routes)
- (Proxy @'[FloraAuthContext])
+ (Proxy @'[OptionalAuthContext])
( \floraPage ->
floraPage
& runVisitorSession
@@ -229,8 +233,12 @@ naturalTransform deploymentEnv features logger webEnvStore app =
& runLog deploymentEnv logger
& effToHandler
-genAuthServerContext :: Logger -> FloraEnv -> Context '[FloraAuthContext, ErrorFormatters]
-genAuthServerContext logger floraEnv = authHandler logger floraEnv :. errorFormatters floraEnv.assets :. EmptyContext
+genAuthServerContext :: Logger -> FloraEnv -> Context '[OptionalAuthContext, StrictAuthContext, ErrorFormatters]
+genAuthServerContext logger floraEnv =
+ optionalAuthHandler logger floraEnv
+ :. strictAuthHandler logger floraEnv
+ :. errorFormatters floraEnv.assets
+ :. EmptyContext
errorFormatters :: Assets -> ErrorFormatters
errorFormatters assets =
@@ -238,7 +246,10 @@ errorFormatters assets =
notFoundPage :: Assets -> NotFoundErrorFormatter
notFoundPage assets _req =
- let result = runPureEff $ runErrorNoCallStack $ renderError (defaultsToEnv assets defaultTemplateEnv) notFound404
+ let result =
+ runPureEff $
+ runErrorNoCallStack $
+ renderError (defaultsToEnv assets defaultTemplateEnv) notFound404
in case result of
Left err -> err
Right _ -> err404
@@ -246,6 +257,6 @@ notFoundPage assets _req =
openApiHandler :: OpenApi
openApiHandler =
toOpenApi (Proxy @API.Routes)
- & (#info % #title .~ "Flora API")
- & (#info % #version .~ "v0")
- & (#info % #description ?~ "Flora API Documentation")
+ & #info % #title .~ "Flora API"
+ & #info % #version .~ "v0"
+ & #info % #description ?~ "Flora API Documentation"
diff --git a/test/Flora/TestUtils.hs b/test/Flora/TestUtils.hs
index 96f802fb..8bcf60f8 100644
--- a/test/Flora/TestUtils.hs
+++ b/test/Flora/TestUtils.hs
@@ -290,6 +290,8 @@ genUser = do
userFlags <- genUserFlags
createdAt <- genUTCTime
updatedAt <- genUTCTime
+ let totpKey = Nothing
+ let totpEnabled = False
pure User{..}
data RandomUserTemplate m = RandomUserTemplate
@@ -337,4 +339,6 @@ randomUser
userFlags <- generateUserFlags
createdAt <- generateCreatedAt
updatedAt <- generateUpdatedAt
+ let totpKey = Nothing
+ let totpEnabled = False
pure User{..}
From 4c7ad912a3ebd59abe0aa26c262bd8d4d594ce72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Tue, 5 Dec 2023 20:23:24 +0100
Subject: [PATCH 34/40] [NO-ISSUE] Style the sign-in button
---
assets/css/styles.css | 32 +++++++++++++++++++
.../Pages/Templates/Screens/Sessions.hs | 6 ++--
2 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/assets/css/styles.css b/assets/css/styles.css
index ef311f64..631dfa54 100644
--- a/assets/css/styles.css
+++ b/assets/css/styles.css
@@ -57,6 +57,10 @@
width: 100%;
}
+ .password {
+ margin-bottom: 1rem;
+ }
+
.totp-zone {
display: none;
}
@@ -64,6 +68,34 @@
input[type="checkbox"]:checked + div.totp-zone {
display: block;
}
+
+ .login-button {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ margin-top: 1rem;
+ }
+
+ div.login-button button {
+ background-color: var(--main-page-button-background);
+ border-radius: 50rem;
+ border-width: 1px;
+ color: var(--text-color);
+ font-weight: bolder;
+ padding-bottom: 1rem;
+ padding-left: 2rem;
+ padding-right: 2rem;
+ padding-top: 1rem;
+ }
+
+ div.login-button button:hover {
+ border-color: var(--main-page-button-focus-border-color);
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+ transition-duration: 200ms;
+
+ /* offset-x | offset-y | blur-radius | spread-radius | color */
+ box-shadow: 0 0 4px 2px var(--main-page-button-focus-border-color);
+ }
}
.version-list-item {
diff --git a/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs b/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
index 29f7cb68..3c9217a5 100644
--- a/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Sessions.hs
@@ -26,7 +26,7 @@ newSession = do
, autocomplete_ "current-password"
, required_ ""
, placeholder_ "Password"
- , class_ "form-input"
+ , class_ "form-input password"
]
label_ [for_ "use_totp"] "Use two-factor authentication"
input_
@@ -44,5 +44,5 @@ newSession = do
, autocomplete_ "off"
, class_ "form-input"
]
- div_ $
- button_ [type_ "submit", class_ "btn bg-brand-purple text-white w-full my-2"] "Sign in"
+ div_ [class_ "login-button"] $
+ button_ [type_ "submit"] "Sign in"
From d79142ab80af896a50761fa3f7924cd53c9d55db Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 6 Dec 2023 14:05:36 +0100
Subject: [PATCH 35/40] Bump vite from 4.4.9 to 4.4.12 in /design (#485)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
design/package-lock.json | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/design/package-lock.json b/design/package-lock.json
index 6f30c5fd..300fae26 100644
--- a/design/package-lock.json
+++ b/design/package-lock.json
@@ -12095,9 +12095,9 @@
}
},
"node_modules/vite": {
- "version": "4.4.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
- "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.12.tgz",
+ "integrity": "sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",
@@ -21106,9 +21106,9 @@
"dev": true
},
"vite": {
- "version": "4.4.9",
- "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
- "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
+ "version": "4.4.12",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.12.tgz",
+ "integrity": "sha512-KtPlUbWfxzGVul8Nut8Gw2Qe8sBzWY+8QVc5SL8iRFnpnrcoCaNlzO40c1R6hPmcdTwIPEDkq0Y9+27a5tVbdQ==",
"dev": true,
"requires": {
"esbuild": "^0.18.10",
From 70523e216350db3ae5b886774822fa1bcfc56c7b Mon Sep 17 00:00:00 2001
From: Matt Roberts
Date: Sun, 10 Dec 2023 15:00:02 +0000
Subject: [PATCH 36/40] [FLORA-448] Add description field in package index
(#486)
---
CHANGELOG.md | 3 ++-
Makefile | 6 ++++--
app/cli/Main.hs | 11 +++++-----
flora.cabal | 2 +-
...10311_add_description_to_package_index.sql | 2 ++
src/core/Flora/Model/PackageIndex/Types.hs | 5 +++--
src/core/Flora/Model/PackageIndex/Update.hs | 8 ++++----
.../FloraWeb/Components/PackageListHeader.hs | 2 +-
src/web/FloraWeb/Pages/Server/Packages.hs | 20 +++++++++++++++++--
.../Pages/Templates/Screens/Search.hs | 6 +++---
test/Flora/ImportSpec.hs | 5 ++++-
test/Main.hs | 2 +-
12 files changed, 49 insertions(+), 23 deletions(-)
create mode 100644 migrations/20231210110311_add_description_to_package_index.sql
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36ee64a6..ae8fcdff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,14 @@
## 1.0.14 -- XXXX-XX-XX
* Colourise in red deprecation markers on the package page ([#438](/~https://github.com/flora-pm/flora-server/pull/439))
-* Added more matches to the the natural language processing catergory ([#440](/~https://github.com/flora-pm/flora-server/pull/440))
+* Added more matches to the natural language processing category ([#440](/~https://github.com/flora-pm/flora-server/pull/440))
* Allow package imports from multiple repositories ([#444](/~https://github.com/flora-pm/flora-server/pull/444))
* Add a page on namespaces in the documentation ([#451](/~https://github.com/flora-pm/flora-server/pull/451))
* Add initial support for hosting package tarballs ([#452](/~https://github.com/flora-pm/flora-server/pull/452))
* Show depended on components in dependencies page ([#464](/~https://github.com/flora-pm/flora-server/pull/464))
* Add search bar for reverse dependencies ([#476](/~https://github.com/flora-pm/flora-server/pull/476))
* Support non Hackage repo URLs ([#479](/~https://github.com/flora-pm/flora-server/pull/479))
+* Add description field in package index ([#486](/~https://github.com/flora-pm/flora-server/pull/486))
## 1.0.13 -- 2023-09-17
* Exclude deprecated releases from latest versions and search ([#373](/~https://github.com/flora-pm/flora-server/pull/373))
diff --git a/Makefile b/Makefile
index 97e01e9b..56874d0d 100644
--- a/Makefile
+++ b/Makefile
@@ -45,8 +45,10 @@ db-reset: db-drop db-setup db-provision ## Reset the dev database
db-provision: ## Create categories and repositories
@cabal run -- flora-cli create-user --username "hackage-user" --email "tech@flora.pm" --password "foobar2000"
@cabal run -- flora-cli provision categories
- @cabal run -- flora-cli provision-repository --name "hackage" --url https://hackage.haskell.org
- @cabal run -- flora-cli provision-repository --name "cardano" --url https://input-output-hk.github.io/cardano-haskell-packages
+ @cabal run -- flora-cli provision-repository --name "hackage" --url https://hackage.haskell.org \
+ --description "Central package repository"
+ @cabal run -- flora-cli provision-repository --name "cardano" --url https://input-output-hk.github.io/cardano-haskell-packages \
+ --description "Packages of the Cardano project"
db-provision-test-packages: ## Load development data in the database
@cabal run -- flora-cli provision test-packages
diff --git a/app/cli/Main.hs b/app/cli/Main.hs
index 29d56685..2002fecb 100644
--- a/app/cli/Main.hs
+++ b/app/cli/Main.hs
@@ -46,7 +46,7 @@ data Command
| GenDesignSystemComponents
| ImportPackages FilePath Text
| ImportIndex FilePath Text
- | ProvisionRepository Text Text
+ | ProvisionRepository Text Text Text
| ImportPackageTarball PackageName Version FilePath
deriving stock (Show, Eq)
@@ -135,6 +135,7 @@ parseProvisionRepository =
ProvisionRepository
<$> option str (long "name" <> metavar "" <> help "Name of the repository")
<*> option str (long "url" <> metavar "" <> help "Link to the package repository")
+ <*> option str (long "description" <> metavar "" <> help "Description of the package repository" <> value "" <> showDefault)
parseImportPackageTarball :: Parser Command
parseImportPackageTarball =
@@ -174,12 +175,12 @@ runOptions (Options (CreateUser opts)) = do
runOptions (Options GenDesignSystemComponents) = generateComponents
runOptions (Options (ImportPackages path repository)) = importFolderOfCabalFiles path repository
runOptions (Options (ImportIndex path repository)) = importIndex path repository
-runOptions (Options (ProvisionRepository name url)) = provisionRepository name url
+runOptions (Options (ProvisionRepository name url description)) = provisionRepository name url description
runOptions (Options (ImportPackageTarball pname version path)) = importPackageTarball pname version path
-provisionRepository :: (DB :> es, IOE :> es) => Text -> Text -> Eff es ()
-provisionRepository name url = do
- Update.createPackageIndex name url Nothing
+provisionRepository :: (DB :> es, IOE :> es) => Text -> Text -> Text -> Eff es ()
+provisionRepository name url description = do
+ Update.createPackageIndex name url description Nothing
importFolderOfCabalFiles :: (Reader PoolConfig :> es, DB :> es, IOE :> es) => FilePath -> Text -> Eff es ()
importFolderOfCabalFiles path repository = Log.withStdOutLogger $ \appLogger -> do
diff --git a/flora.cabal b/flora.cabal
index dd47b17b..08fedfea 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -27,13 +27,13 @@ flag prod
common common-extensions
default-extensions:
+ NoStarIsType
DataKinds
DeriveAnyClass
DerivingStrategies
DerivingVia
DuplicateRecordFields
LambdaCase
- NoStarIsType
OverloadedLabels
OverloadedRecordDot
OverloadedStrings
diff --git a/migrations/20231210110311_add_description_to_package_index.sql b/migrations/20231210110311_add_description_to_package_index.sql
new file mode 100644
index 00000000..e33108bb
--- /dev/null
+++ b/migrations/20231210110311_add_description_to_package_index.sql
@@ -0,0 +1,2 @@
+alter table package_indexes
+ add column description text not null;
diff --git a/src/core/Flora/Model/PackageIndex/Types.hs b/src/core/Flora/Model/PackageIndex/Types.hs
index 2053abc4..c7608717 100644
--- a/src/core/Flora/Model/PackageIndex/Types.hs
+++ b/src/core/Flora/Model/PackageIndex/Types.hs
@@ -27,6 +27,7 @@ data PackageIndex = PackageIndex
, repository :: Text
, timestamp :: Maybe UTCTime
, url :: Text
+ , description :: Text
}
deriving stock (Eq, Show, Generic)
deriving anyclass (FromRow, ToRow, NFData)
@@ -34,8 +35,8 @@ data PackageIndex = PackageIndex
(Entity)
via (GenericEntity '[TableName "package_indexes"] PackageIndex)
-mkPackageIndex :: IOE :> es => Text -> Text -> Maybe UTCTime -> Eff es PackageIndex
-mkPackageIndex repository url timestamp = do
+mkPackageIndex :: IOE :> es => Text -> Text -> Text -> Maybe UTCTime -> Eff es PackageIndex
+mkPackageIndex repository url description timestamp = do
packageIndexId <- PackageIndexId <$> liftIO UUID.nextRandom
pure $ PackageIndex{..}
diff --git a/src/core/Flora/Model/PackageIndex/Update.hs b/src/core/Flora/Model/PackageIndex/Update.hs
index bd188c7e..5b8ef702 100644
--- a/src/core/Flora/Model/PackageIndex/Update.hs
+++ b/src/core/Flora/Model/PackageIndex/Update.hs
@@ -16,7 +16,7 @@ import Effectful
import Effectful.PostgreSQL.Transact.Effect (DB, dbtToEff)
import Flora.Model.PackageIndex.Types
- ( PackageIndex
+ ( PackageIndex (..)
, mkPackageIndex
)
@@ -29,7 +29,7 @@ updatePackageIndexByName repositoryName newTimestamp = do
([field| repository |], repositoryName)
(Only newTimestamp)
-createPackageIndex :: (IOE :> es, DB :> es) => Text -> Text -> Maybe UTCTime -> Eff es ()
-createPackageIndex repositoryName url timestamp = do
- packageIndex <- mkPackageIndex repositoryName url timestamp
+createPackageIndex :: (IOE :> es, DB :> es) => Text -> Text -> Text -> Maybe UTCTime -> Eff es ()
+createPackageIndex repositoryName url description timestamp = do
+ packageIndex <- mkPackageIndex repositoryName url description timestamp
void $ dbtToEff $ insert @PackageIndex packageIndex
diff --git a/src/web/FloraWeb/Components/PackageListHeader.hs b/src/web/FloraWeb/Components/PackageListHeader.hs
index e5b097f3..1aa59ec7 100644
--- a/src/web/FloraWeb/Components/PackageListHeader.hs
+++ b/src/web/FloraWeb/Components/PackageListHeader.hs
@@ -18,6 +18,6 @@ presentationHeader title subtitle numberOfPackages = do
div_ [class_ "page-title"] $ do
h1_ [class_ ""] $ do
span_ [class_ "headline"] $ toHtml title
- p_ [class_ "package-count"] $ toHtml $ display numberOfPackages <> " results"
div_ [class_ "synopsis lg:text-xl text-center"] $
p_ [class_ ""] (toHtml subtitle)
+ p_ [class_ "package-count"] $ toHtml $ display numberOfPackages <> " results"
diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs
index 01094d95..c72bbd65 100644
--- a/src/web/FloraWeb/Pages/Server/Packages.hs
+++ b/src/web/FloraWeb/Pages/Server/Packages.hs
@@ -30,6 +30,7 @@ import Flora.Logging
import Flora.Model.BlobIndex.Query qualified as Query
import Flora.Model.Package
import Flora.Model.Package.Query qualified as Query
+import Flora.Model.PackageIndex.Query qualified as Query
import Flora.Model.PackageIndex.Types (PackageIndex (..))
import Flora.Model.Release.Query qualified as Query
import Flora.Model.Release.Types
@@ -44,6 +45,7 @@ import FloraWeb.Pages.Templates.Packages qualified as Package
import FloraWeb.Pages.Templates.Screens.Packages qualified as Packages
import FloraWeb.Pages.Templates.Screens.Search qualified as Search
import FloraWeb.Session
+import Network.HTTP.Types (notFound404)
server :: ServerT Routes FloraPage
server =
@@ -76,8 +78,22 @@ showNamespaceHandler namespace pageParam = do
session <- getSession
templateDefaults <- fromSession session defaultTemplateEnv
(count', results) <- Search.listAllPackagesInNamespace namespace (fromPage pageNumber)
- render templateDefaults $
- Search.showAllPackagesInNamespace namespace count' pageNumber results
+ if extractNamespaceText namespace == "haskell"
+ then
+ render templateDefaults $
+ Search.showAllPackagesInNamespace
+ namespace
+ "Core Haskell packages"
+ count'
+ pageNumber
+ results
+ else do
+ mPackageIndex <- Query.getPackageIndexByName (extractNamespaceText namespace)
+ case mPackageIndex of
+ Nothing -> renderError templateDefaults notFound404
+ Just packageIndex ->
+ render templateDefaults $
+ Search.showAllPackagesInNamespace namespace packageIndex.description count' pageNumber results
showPackageHandler :: Namespace -> PackageName -> FloraPage (Html ())
showPackageHandler namespace packageName = showPackageVersion namespace packageName Nothing
diff --git a/src/web/FloraWeb/Pages/Templates/Screens/Search.hs b/src/web/FloraWeb/Pages/Templates/Screens/Search.hs
index 438d03b5..85a0c49a 100644
--- a/src/web/FloraWeb/Pages/Templates/Screens/Search.hs
+++ b/src/web/FloraWeb/Pages/Templates/Screens/Search.hs
@@ -21,10 +21,10 @@ showAllPackages count currentPage packagesInfo = do
div_ [class_ ""] $ packageListing Nothing packagesInfo
paginationNav count currentPage ListAllPackages
-showAllPackagesInNamespace :: Namespace -> Word -> Positive Word -> Vector PackageInfo -> FloraHTML
-showAllPackagesInNamespace namespace count currentPage packagesInfo = do
+showAllPackagesInNamespace :: Namespace -> Text -> Word -> Positive Word -> Vector PackageInfo -> FloraHTML
+showAllPackagesInNamespace namespace description count currentPage packagesInfo = do
div_ [class_ "container"] $ do
- presentationHeader (display namespace) "" count
+ presentationHeader (display namespace) description count
div_ [class_ ""] $ packageListing Nothing packagesInfo
paginationNav count currentPage (ListAllPackagesInNamespace namespace)
diff --git a/test/Flora/ImportSpec.hs b/test/Flora/ImportSpec.hs
index a25bea82..68429e56 100644
--- a/test/Flora/ImportSpec.hs
+++ b/test/Flora/ImportSpec.hs
@@ -35,12 +35,15 @@ defaultRepo = "test-namespace"
defaultRepoURL :: Text
defaultRepoURL = "localhost"
+defaultDescription :: Text
+defaultDescription = "test-description"
+
testImportIndex :: Fixtures -> TestEff ()
testImportIndex fixture = withStdOutLogger $
\logger -> do
mIndex <- Query.getPackageIndexByName defaultRepo
case mIndex of
- Nothing -> Update.createPackageIndex defaultRepo defaultRepoURL Nothing
+ Nothing -> Update.createPackageIndex defaultRepo defaultRepoURL defaultDescription Nothing
Just _ -> pure ()
importFromIndex
logger
diff --git a/test/Main.hs b/test/Main.hs
index 37e1e4c7..ca210cbb 100644
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -37,7 +37,7 @@ main = do
. runBlobStorePure
. runFailIO
$ do
- Update.createPackageIndex "hackage" "" Nothing
+ Update.createPackageIndex "hackage" "" "" Nothing
testMigrations
f' <- getFixtures
importAllPackages f'
From bd5890e368fe0fa76fe4895ef9ae329f18949d04 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 10 Dec 2023 16:28:52 +0100
Subject: [PATCH 37/40] [NO-ISSUE] Enforce the 'canLogin' property
---
flora.cabal | 2 +-
src/core/Flora/Model/User.hs | 2 +-
src/web/FloraWeb/Common/Auth.hs | 2 +-
src/web/FloraWeb/Pages/Server/Sessions.hs | 28 +++++++++++++++--------
4 files changed, 21 insertions(+), 13 deletions(-)
diff --git a/flora.cabal b/flora.cabal
index 08fedfea..dd47b17b 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -27,13 +27,13 @@ flag prod
common common-extensions
default-extensions:
- NoStarIsType
DataKinds
DeriveAnyClass
DerivingStrategies
DerivingVia
DuplicateRecordFields
LambdaCase
+ NoStarIsType
OverloadedLabels
OverloadedRecordDot
OverloadedStrings
diff --git a/src/core/Flora/Model/User.hs b/src/core/Flora/Model/User.hs
index 8bf454c7..3c54d7d4 100644
--- a/src/core/Flora/Model/User.hs
+++ b/src/core/Flora/Model/User.hs
@@ -140,7 +140,7 @@ mkAdmin AdminCreationForm{username, email, password} = do
let createdAt = timestamp
let updatedAt = timestamp
let displayName = ""
- let userFlags = UserFlags{isAdmin = True, canLogin = False}
+ let userFlags = UserFlags{isAdmin = True, canLogin = True}
let totpKey = Nothing
let totpEnabled = False
pure User{..}
diff --git a/src/web/FloraWeb/Common/Auth.hs b/src/web/FloraWeb/Common/Auth.hs
index 73830461..ebb896dd 100644
--- a/src/web/FloraWeb/Common/Auth.hs
+++ b/src/web/FloraWeb/Common/Auth.hs
@@ -81,7 +81,7 @@ handler mustBeConnected floraEnv req = do
case mUserInfo of
Nothing ->
if mustBeConnected
- then throwError $ err401{errBody = "Connect first"}
+ then throwError $ err401{errBody = "Log-in first"}
else do
nSessionId <- liftIO newPersistentSessionId
pure (Nothing, nSessionId)
diff --git a/src/web/FloraWeb/Pages/Server/Sessions.hs b/src/web/FloraWeb/Pages/Server/Sessions.hs
index ba922cc8..44f83cdc 100644
--- a/src/web/FloraWeb/Pages/Server/Sessions.hs
+++ b/src/web/FloraWeb/Pages/Server/Sessions.hs
@@ -58,18 +58,26 @@ createSessionHandler LoginForm{email, password, totp} = do
& (#flashError ?~ mkError "Could not authenticate")
respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
Just user ->
- if validatePassword (mkPassword password) user.password
- then do
- if user.totpEnabled
- then guardThatUserHasProvidedTOTP totp $ \userCode -> do
- checkTOTPIsValid userCode user
+ if user.userFlags.canLogin
+ then
+ if validatePassword (mkPassword password) user.password
+ then do
+ if user.totpEnabled
+ then guardThatUserHasProvidedTOTP totp $ \userCode -> do
+ checkTOTPIsValid userCode user
+ else do
+ sessionId <- persistSession session.sessionId user.userId
+ let sessionCookie = craftSessionCookie sessionId True
+ respond $ WithStatus @301 $ redirectWithCookie "/" sessionCookie
else do
- Log.logInfo_ "[+] User connected!"
- sessionId <- persistSession session.sessionId user.userId
- let sessionCookie = craftSessionCookie sessionId True
- respond $ WithStatus @301 $ redirectWithCookie "/" sessionCookie
+ Log.logInfo_ "Invalid password"
+ templateDefaults <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateDefaults
+ & (#flashError ?~ mkError "Could not authenticate")
+ respond $ WithStatus @401 $ renderUVerb templateEnv Sessions.newSession
else do
- Log.logInfo_ "[+] Couldn't authenticate user"
+ Log.logInfo_ "User not allowed to log-in"
templateDefaults <- fromSession session defaultTemplateEnv
let templateEnv =
templateDefaults
From 4da0f0d8a7a8c4a1b04f409f4a09907e32456973 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Sun, 10 Dec 2023 16:29:17 +0100
Subject: [PATCH 38/40] [NO-ISSUE] Add a default to the description in package
index migration
---
migrations/20231210110311_add_description_to_package_index.sql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/migrations/20231210110311_add_description_to_package_index.sql b/migrations/20231210110311_add_description_to_package_index.sql
index e33108bb..14ee8e13 100644
--- a/migrations/20231210110311_add_description_to_package_index.sql
+++ b/migrations/20231210110311_add_description_to_package_index.sql
@@ -1,2 +1,2 @@
alter table package_indexes
- add column description text not null;
+ add column description text not null default '';
From 0bf3d8838500e663c6d556f0c9b65b1407bcb136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Wed, 13 Dec 2023 15:05:46 +0100
Subject: [PATCH 39/40] [FLORA-35] Search bar modifiers (#487)
---
CHANGELOG.md | 1 +
docs/docs/intro.md | 1 +
docs/docs/search.md | 14 ++
flora.cabal | 4 +-
scripts/run-tests.sh | 5 -
src/core/Flora/Model/Package/Query.hs | 63 ++++++++-
src/core/Flora/Model/Package/Types.hs | 6 +-
src/core/Flora/Search.hs | 141 ++++++++++++++++++-
src/web/FloraWeb/Components/Navbar.hs | 8 +-
src/web/FloraWeb/Pages/Server/Packages.hs | 25 +++-
src/web/FloraWeb/Pages/Server/Search.hs | 19 +--
src/web/FloraWeb/Pages/Templates/Packages.hs | 11 +-
src/web/FloraWeb/Pages/Templates/Types.hs | 3 +
test/Flora/PackageSpec.hs | 4 +-
test/Flora/SearchSpec.hs | 45 ++++++
test/Main.hs | 14 +-
16 files changed, 317 insertions(+), 47 deletions(-)
create mode 100644 docs/docs/search.md
create mode 100644 test/Flora/SearchSpec.hs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae8fcdff..e08058ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
* Add search bar for reverse dependencies ([#476](/~https://github.com/flora-pm/flora-server/pull/476))
* Support non Hackage repo URLs ([#479](/~https://github.com/flora-pm/flora-server/pull/479))
* Add description field in package index ([#486](/~https://github.com/flora-pm/flora-server/pull/486))
+* Introduce search bar modifiers ([#487](/~https://github.com/flora-pm/flora-server/pull/487))
## 1.0.13 -- 2023-09-17
* Exclude deprecated releases from latest versions and search ([#373](/~https://github.com/flora-pm/flora-server/pull/373))
diff --git a/docs/docs/intro.md b/docs/docs/intro.md
index 5bac245c..1c856170 100644
--- a/docs/docs/intro.md
+++ b/docs/docs/intro.md
@@ -6,3 +6,4 @@ slug: /
Read more about:
* [Namespaces](/namespaces)
+* [Search features](/search-features)
diff --git a/docs/docs/search.md b/docs/docs/search.md
new file mode 100644
index 00000000..448cadc5
--- /dev/null
+++ b/docs/docs/search.md
@@ -0,0 +1,14 @@
+---
+title: Search features
+slug: search-features
+---
+
+While searching for packages you may want to refine the search terms with modifiers.
+Currently, the following modifiers are available:
+
+* `depends:<@namespace>/`: Shows the dependents page for a package
+* `in:<@namespace> `: Searches for a package name in the specified namespace
+* `in:<@namespace>`: Lists packages in a namespace
+
+These modifiers must be placed at the very beginning of the search query, otherwise they will
+be interpreted as a search term.
diff --git a/flora.cabal b/flora.cabal
index dd47b17b..5c8f31ef 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -170,7 +170,6 @@ library
, monad-time-effectful
, mtl
, odd-jobs
- , one-time-password
, openapi3
, optics-core
, password
@@ -465,6 +464,7 @@ test-suite flora-test
, monad-time-effectful
, optics-core
, password
+ , password-types
, pg-entity
, pg-transact
, pg-transact-effectful
@@ -480,6 +480,7 @@ test-suite flora-test
, text
, time
, transformers
+ , typed-process
, uuid
, vector
, zlib
@@ -491,6 +492,7 @@ test-suite flora-test
Flora.ImportSpec
Flora.OddJobSpec
Flora.PackageSpec
+ Flora.SearchSpec
Flora.TemplateSpec
Flora.TestUtils
Flora.UserSpec
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 72b11e41..9ac307c1 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -7,11 +7,6 @@ source ./environment.test.sh
export DATALOG_DIR="cbits/"
-make db-drop
-make db-setup
-
-cabal run -- flora-cli create-user --username "hackage-user" --email "tech@flora.pm" --password "foobar2000"
-
if [ -z "$1" ] ;
then
cabal test
diff --git a/src/core/Flora/Model/Package/Query.hs b/src/core/Flora/Model/Package/Query.hs
index 18298c0b..46f9134c 100644
--- a/src/core/Flora/Model/Package/Query.hs
+++ b/src/core/Flora/Model/Package/Query.hs
@@ -1,7 +1,34 @@
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE QuasiQuotes #-}
-module Flora.Model.Package.Query where
+module Flora.Model.Package.Query
+ ( countPackages
+ , countPackagesByName
+ , countPackagesInNamespace
+ , getAllPackageDependents
+ , getAllPackageDependentsWithLatestVersion
+ , getAllPackages
+ , getAllRequirements
+ , getComponent
+ , getNonDeprecatedPackages
+ , getNumberOfPackageDependents
+ , getPackageByNamespaceAndName
+ , getPackageCategories
+ , getPackageDependents
+ , getPackageDependentsByName
+ , getPackageDependentsWithLatestVersion
+ , getPackagesByNamespace
+ , getPackagesFromCategoryWithLatestVersion
+ , getRequirements
+ , listAllPackages
+ , listAllPackagesInNamespace
+ , numberOfPackageRequirementsQuery
+ , searchPackage
+ , unsafeGetComponent
+ , getComponentById
+ , searchPackageByNamespace
+ , getNumberOfPackageRequirements
+ ) where
import Data.Text (Text)
import Data.Text.Display (display)
@@ -437,6 +464,40 @@ searchPackage (offset, limit) searchString =
|]
(searchString, searchString, offset, limit)
+searchPackageByNamespace
+ :: DB :> es
+ => (Word, Word)
+ -> Namespace
+ -> Text
+ -> Eff es (Vector PackageInfo)
+searchPackageByNamespace (offset, limit) namespace searchString =
+ dbtToEff $
+ query
+ Select
+ [sql|
+ SELECT lv."namespace"
+ , lv."name"
+ , lv."synopsis"
+ , lv."version"
+ , lv."license"
+ , word_similarity(lv.name, ?) as rating
+ FROM latest_versions as lv
+ WHERE
+ ? <% lv."name"
+ AND lv."namespace" = ?
+ GROUP BY
+ lv."namespace"
+ , lv."name"
+ , lv."synopsis"
+ , lv."version"
+ , lv."license"
+ ORDER BY rating desc, count(lv."namespace") desc, lv.name asc
+ OFFSET ?
+ LIMIT ?
+ ;
+ |]
+ (searchString, searchString, namespace, offset, limit)
+
-- | Returns a summary of packages
listAllPackages
:: DB :> es
diff --git a/src/core/Flora/Model/Package/Types.hs b/src/core/Flora/Model/Package/Types.hs
index 3d9ecb21..86f7b95e 100644
--- a/src/core/Flora/Model/Package/Types.hs
+++ b/src/core/Flora/Model/Package/Types.hs
@@ -85,7 +85,7 @@ extractPackageNameText (PackageName text) = text
parsePackageName :: Text -> Maybe PackageName
parsePackageName txt =
- if matches "[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*" txt
+ if matches "^[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*$" txt
then Just $ PackageName txt
else Nothing
@@ -109,7 +109,7 @@ packageNameSchema :: Schema
packageNameSchema =
mempty
& #description
- ?~ "Name of a package\n It corresponds to the regular expression: `[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*`"
+ ?~ "Name of a package\n It corresponds to the regular expression: `^[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*$`"
newtype Namespace = Namespace Text
deriving stock (Show, Generic)
@@ -148,7 +148,7 @@ instance FromHttpApiData Namespace where
parseNamespace :: Text -> Maybe Namespace
parseNamespace txt =
- if matches "@[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*" txt
+ if matches "^@[[:digit:]]*[[:alpha:]][[:alnum:]]*(-[[:digit:]]*[[:alpha:]][[:alnum:]]*)*$" txt
then Just $ Namespace txt
else Nothing
diff --git a/src/core/Flora/Search.hs b/src/core/Flora/Search.hs
index cf0feaa1..ccfc637a 100644
--- a/src/core/Flora/Search.hs
+++ b/src/core/Flora/Search.hs
@@ -1,8 +1,14 @@
+{-# LANGUAGE ViewPatterns #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Use <$>" #-}
+
module Flora.Search where
import Data.Aeson
import Data.List qualified as List
import Data.Text (Text)
+import Data.Text qualified as Text
import Data.Text.Display (Display (..))
import Data.Text.Lazy.Builder qualified as Builder
import Data.Vector (Vector)
@@ -16,12 +22,21 @@ import Log qualified
import Flora.Logging
import Flora.Model.Package (Namespace (..), PackageInfo (..), PackageName (..), formatPackage)
import Flora.Model.Package.Query qualified as Query
+import Flora.Model.Package.Types qualified as Package
+import Flora.Model.Requirement
data SearchAction
= ListAllPackages
| ListAllPackagesInNamespace Namespace
| SearchPackages Text
- | DependentsOf Namespace PackageName (Maybe Text)
+ | DependentsOf
+ Namespace
+ -- ^ Namespace
+ PackageName
+ -- ^ Package
+ (Maybe Text)
+ -- ^ Search within the package
+ | SearchInNamespace Namespace PackageName
deriving (Eq, Ord, Show)
instance Display SearchAction where
@@ -34,6 +49,22 @@ instance Display SearchAction where
<> "/"
<> displayBuilder packageName
<> foldMap (\searchString -> " \"" <> Builder.fromText searchString <> "\"") mbSearchString
+ displayBuilder (SearchInNamespace namespace packageName) =
+ "Package " <> displayBuilder namespace <> "/" <> displayBuilder packageName
+
+search
+ :: (DB :> es, Log :> es, Time :> es)
+ => (Word, Word)
+ -> Text
+ -> Eff es (Word, Vector PackageInfo)
+search pagination queryString =
+ case parseSearchQuery queryString of
+ Just (ListAllPackagesInNamespace namespace) -> listAllPackagesInNamespace pagination namespace
+ Just ListAllPackages -> listAllPackages pagination
+ Just (SearchInNamespace namespace (PackageName packageName)) -> searchPackageByNamespaceAndName pagination namespace packageName
+ Just (DependentsOf namespace packageName mSearchString) -> searchDependents pagination namespace packageName mSearchString
+ Just (SearchPackages _) -> searchPackageByName pagination queryString
+ Nothing -> searchPackageByName pagination queryString
searchPackageByName
:: (DB :> es, Log :> es, Time :> es)
@@ -62,13 +93,68 @@ searchPackageByName (offset, limit) queryString = do
count <- Query.countPackagesByName queryString
pure (count, results)
+searchPackageByNamespaceAndName
+ :: (DB :> es, Log :> es, Time :> es)
+ => (Word, Word)
+ -> Namespace
+ -> Text
+ -> Eff es (Word, Vector PackageInfo)
+searchPackageByNamespaceAndName (offset, limit) namespace queryString = do
+ (results, duration) <- timeAction $ Query.searchPackageByNamespace (offset, limit) namespace queryString
+
+ Log.logInfo "search-results" $
+ object
+ [ "search_string" .= queryString
+ , "duration" .= duration
+ , "results_count" .= Vector.length results
+ , "results"
+ .= List.map
+ ( \PackageInfo{name, rating} ->
+ object
+ [ "package" .= formatPackage namespace name
+ , "score" .= rating
+ ]
+ )
+ (Vector.toList results)
+ ]
+
+ count <- Query.countPackagesByName queryString
+ pure (count, results)
+
+searchDependents
+ :: DB :> es
+ => (Word, Word)
+ -> Namespace
+ -> PackageName
+ -> Maybe Text
+ -> Eff es (Word, Vector PackageInfo)
+searchDependents pagination namespace packageName mSearchString = do
+ results <-
+ Query.getAllPackageDependentsWithLatestVersion
+ namespace
+ packageName
+ pagination
+ mSearchString
+ totalDependents <- Query.getNumberOfPackageDependents namespace packageName mSearchString
+ pure (totalDependents, fmap dependencyInfoToPackageInfo results)
+
+dependencyInfoToPackageInfo :: DependencyInfo -> PackageInfo
+dependencyInfoToPackageInfo dep =
+ PackageInfo
+ dep.namespace
+ dep.name
+ dep.latestSynopsis
+ dep.latestVersion
+ dep.latestLicense
+ Nothing
+
listAllPackagesInNamespace
:: (DB :> es, Time :> es, Log :> es)
- => Namespace
- -> (Word, Word)
+ => (Word, Word)
+ -> Namespace
-> Eff es (Word, Vector PackageInfo)
-listAllPackagesInNamespace namespace (offset, limit) = do
- (results, duration) <- timeAction $ Query.listAllPackagesInNamespace (offset, limit) namespace
+listAllPackagesInNamespace pagination namespace = do
+ (results, duration) <- timeAction $ Query.listAllPackagesInNamespace pagination namespace
Log.logInfo "packages-in-namespace" $
object
@@ -98,3 +184,48 @@ listAllPackages (offset, limit) = do
results <- Query.listAllPackages (offset, limit)
count <- Query.countPackages
pure (count, results)
+
+-- | Search modifiers:
+--
+-- * depends:<@namespace>/
+-- * in:<@namespace>/
+-- * in:<@namespace>
+parseSearchQuery :: Text -> Maybe SearchAction
+parseSearchQuery = \case
+ (Text.stripPrefix "depends:" -> Just rest) ->
+ case parseNamespacedPackageSearch rest of
+ Just (namespace, packageName) ->
+ Just $ DependentsOf namespace packageName Nothing
+ Nothing -> Just $ SearchPackages rest
+ (Text.stripPrefix "in:" -> Just rest) ->
+ case parseNamespaceAndPackageSearch rest of
+ (Just namespace, Just packageName) ->
+ Just $ SearchInNamespace namespace packageName
+ (Just namespace, Nothing) ->
+ Just $ ListAllPackagesInNamespace namespace
+ _ -> Just $ SearchPackages rest
+ e -> Just $ SearchPackages e
+
+-- Determine if the string is
+-- <@namespace>/
+parseNamespacedPackageSearch :: Text -> Maybe (Namespace, PackageName)
+parseNamespacedPackageSearch text =
+ case Text.breakOn "/" text of
+ (_, "") -> Nothing
+ (Package.parseNamespace -> Just namespace, Text.stripPrefix "/" -> Just potentialPackageName) ->
+ case Package.parsePackageName potentialPackageName of
+ Just packageName -> Just (namespace, packageName)
+ Nothing -> Nothing
+ (_, _) -> Nothing
+
+parseNamespaceAndPackageSearch :: Text -> (Maybe Namespace, Maybe PackageName)
+parseNamespaceAndPackageSearch text =
+ case Text.breakOn " " text of
+ (Package.parseNamespace -> Just namespace, "") ->
+ (Just namespace, Nothing)
+ (_, "") -> (Nothing, Nothing)
+ (Package.parseNamespace -> Just namespace, Text.stripPrefix " " -> Just potentialPackageName) ->
+ case Package.parsePackageName potentialPackageName of
+ Just packageName -> (Just namespace, Just packageName)
+ Nothing -> (Just namespace, Nothing)
+ (_, _) -> (Nothing, Nothing)
diff --git a/src/web/FloraWeb/Components/Navbar.hs b/src/web/FloraWeb/Components/Navbar.hs
index b7e8ea62..8c0c1f41 100644
--- a/src/web/FloraWeb/Components/Navbar.hs
+++ b/src/web/FloraWeb/Components/Navbar.hs
@@ -110,18 +110,24 @@ userMenu = do
navbarSearch :: FloraHTML
navbarSearch = do
flag <- asks displayNavbarSearch
+ mContent <- asks navbarSearchContent
if flag
then do
+ let contentValue =
+ case mContent of
+ Nothing -> []
+ Just content -> [value_ content]
form_ [action_ "/search", method_ "GET"] $ do
div_ [class_ "flex items-center py-2"] $ do
label_ [for_ "search"] ""
- input_
+ input_ $
[ class_ "navbar-search"
, id_ "search"
, type_ "search"
, name_ "q"
, placeholder_ "Search a package"
]
+ ++ contentValue
else pure mempty
logOff :: Maybe User -> PersistentSessionId -> FloraHTML
diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs
index c72bbd65..a8666590 100644
--- a/src/web/FloraWeb/Pages/Server/Packages.hs
+++ b/src/web/FloraWeb/Pages/Server/Packages.hs
@@ -77,13 +77,19 @@ showNamespaceHandler namespace pageParam = do
let pageNumber = pageParam ?: PositiveUnsafe 1
session <- getSession
templateDefaults <- fromSession session defaultTemplateEnv
- (count', results) <- Search.listAllPackagesInNamespace namespace (fromPage pageNumber)
+ (count', results) <- Search.listAllPackagesInNamespace (fromPage pageNumber) namespace
if extractNamespaceText namespace == "haskell"
- then
- render templateDefaults $
+ then do
+ let description = "Core Haskell packages"
+ let templateEnv =
+ templateDefaults
+ { navbarSearchContent = Just $ "in:" <> display namespace <> " "
+ , description = description
+ }
+ render templateEnv $
Search.showAllPackagesInNamespace
namespace
- "Core Haskell packages"
+ description
count'
pageNumber
results
@@ -91,8 +97,13 @@ showNamespaceHandler namespace pageParam = do
mPackageIndex <- Query.getPackageIndexByName (extractNamespaceText namespace)
case mPackageIndex of
Nothing -> renderError templateDefaults notFound404
- Just packageIndex ->
- render templateDefaults $
+ Just packageIndex -> do
+ let templateEnv =
+ templateDefaults
+ { navbarSearchContent = Just $ "in:" <> display namespace <> " "
+ , description = packageIndex.description
+ }
+ render templateEnv $
Search.showAllPackagesInNamespace namespace packageIndex.description count' pageNumber results
showPackageHandler :: Namespace -> PackageName -> FloraPage (Html ())
@@ -194,6 +205,7 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) mSe
templateEnv'
{ title = display namespace <> "/" <> display packageName
, description = "Dependents of " <> display namespace <> display packageName
+ , navbarSearchContent = Just $ "depends:" <> display namespace <> "/" <> display packageName <> " "
}
results <-
Query.getAllPackageDependentsWithLatestVersion
@@ -211,7 +223,6 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) mSe
totalDependents
results
pageNumber
- mSearch
showDependenciesHandler :: Namespace -> PackageName -> FloraPage (Html ())
showDependenciesHandler namespace packageName = do
diff --git a/src/web/FloraWeb/Pages/Server/Search.hs b/src/web/FloraWeb/Pages/Server/Search.hs
index 244dae95..91cc1a76 100644
--- a/src/web/FloraWeb/Pages/Server/Search.hs
+++ b/src/web/FloraWeb/Pages/Server/Search.hs
@@ -4,14 +4,13 @@ import Data.Positive
import Data.Text (Text)
import Data.Vector qualified as Vector
import Lucid (Html)
-import Optics.Core
import Servant (ServerT)
import Flora.Model.Package.Types
import Flora.Search qualified as Search
import FloraWeb.Common.Pagination
import FloraWeb.Pages.Routes.Search (Routes, Routes' (..))
-import FloraWeb.Pages.Templates (TemplateEnv (..), defaultTemplateEnv, fromSession, render)
+import FloraWeb.Pages.Templates
import FloraWeb.Pages.Templates.Screens.Search qualified as Search
import FloraWeb.Session
@@ -23,18 +22,14 @@ server =
searchHandler :: Maybe Text -> Maybe (Positive Word) -> FloraPage (Html ())
searchHandler Nothing pageParam = searchHandler (Just "") pageParam
-searchHandler (Just "") pageParam = do
- let pageNumber = pageParam ?: PositiveUnsafe 1
- session <- getSession
- templateDefaults <- fromSession session defaultTemplateEnv
- (count, results) <- Search.listAllPackages (fromPage pageNumber)
- let (templateEnv :: TemplateEnv) =
- templateDefaults & #displayNavbarSearch .~ False
- render templateEnv $ Search.showAllPackages count pageNumber results
searchHandler (Just searchString) pageParam = do
let pageNumber = pageParam ?: PositiveUnsafe 1
session <- getSession
- templateEnv <- fromSession session defaultTemplateEnv
- (count, results) <- Search.searchPackageByName (fromPage pageNumber) searchString
+ templateDefaults <- fromSession session defaultTemplateEnv
+ let templateEnv =
+ templateDefaults
+ { navbarSearchContent = Just searchString
+ }
+ (count, results) <- Search.search (fromPage pageNumber) searchString
let (matchVector, packagesInfo) = Vector.partition (\p -> p.name == PackageName searchString) results
render templateEnv $ Search.showResults searchString count pageNumber matchVector packagesInfo
diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs
index 2025150d..1a13aeb5 100644
--- a/src/web/FloraWeb/Pages/Templates/Packages.hs
+++ b/src/web/FloraWeb/Pages/Templates/Packages.hs
@@ -6,7 +6,7 @@ import Control.Monad.Reader (ask)
import Data.Foldable (fold, forM_)
import Data.List qualified as List
import Data.Map.Strict qualified as Map
-import Data.Maybe (fromJust, fromMaybe, isJust)
+import Data.Maybe (fromJust, isJust)
import Data.Positive
import Data.Text (Text)
import Data.Text qualified as Text
@@ -37,7 +37,6 @@ import Flora.Search (SearchAction (..))
import FloraWeb.Components.Icons
import FloraWeb.Components.PackageListItem (licenseIcon, packageListItem, requirementListItem)
import FloraWeb.Components.PaginationNav (paginationNav)
-import FloraWeb.Components.SlimSearchBar
import FloraWeb.Components.Utils
import FloraWeb.Links qualified as Links
import FloraWeb.Pages.Templates (FloraHTML, TemplateEnv (..))
@@ -66,7 +65,7 @@ presentationHeaderForSubpage namespace packageName release target numberOfPackag
span_ [class_ "headline"] $ do
displayNamespace namespace
chevronRightOutline
- linkToPackageWithVersion namespace packageName (release.version)
+ linkToPackageWithVersion namespace packageName release.version
chevronRightOutline
toHtml (display target)
p_ [class_ "synopsis"] $
@@ -101,15 +100,11 @@ showDependents
-> Word
-> Vector DependencyInfo
-> Positive Word
- -> Maybe Text
-> FloraHTML
-showDependents namespace packageName release count packagesInfo currentPage mSearch =
+showDependents namespace packageName release count packagesInfo currentPage =
div_ [class_ "container"] $ do
presentationHeaderForSubpage namespace packageName release Dependents count
- let placeholder = fromMaybe "Search dependents" mSearch
- let value = fromMaybe "" mSearch
ul_ [class_ "package-list"] $ do
- slimSearchBar (SearchBarOptions{actionUrl = "", placeholder, value})
Vector.forM_
packagesInfo
( \dep ->
diff --git a/src/web/FloraWeb/Pages/Templates/Types.hs b/src/web/FloraWeb/Pages/Templates/Types.hs
index 7a5dbbde..b1987aa7 100644
--- a/src/web/FloraWeb/Pages/Templates/Types.hs
+++ b/src/web/FloraWeb/Pages/Templates/Types.hs
@@ -58,6 +58,7 @@ data TemplateEnv = TemplateEnv
, activeElements :: ActiveElements
, assets :: Assets
, indexPage :: Bool
+ , navbarSearchContent :: Maybe Text
}
deriving stock (Show, Generic)
@@ -82,6 +83,7 @@ data TemplateDefaults = TemplateDefaults
, features :: FeatureEnv
, activeElements :: ActiveElements
, indexPage :: Bool
+ , navbarSearchContent :: Maybe Text
}
deriving stock (Show, Generic)
@@ -108,6 +110,7 @@ defaultTemplateEnv =
, features = FeatureEnv Nothing
, activeElements = defaultActiveElements
, indexPage = True
+ , navbarSearchContent = Nothing
}
-- | ⚠ DO NOT USE THIS FUNCTION IF YOU DON'T KNOW WHAT YOU'RE DOING
diff --git a/test/Flora/PackageSpec.hs b/test/Flora/PackageSpec.hs
index 4ef929fc..029f0606 100644
--- a/test/Flora/PackageSpec.hs
+++ b/test/Flora/PackageSpec.hs
@@ -228,9 +228,9 @@ testReleaseDeprecation = do
assertEqual 68 (length result)
binary <- fromJust <$> Query.getPackageByNamespaceAndName (Namespace "haskell") (PackageName "binary")
- deprecatedBinaryVersion' <- assertJust =<< Query.getReleaseByVersion (binary.packageId) (mkVersion [0, 10, 0, 0])
+ deprecatedBinaryVersion' <- assertJust =<< Query.getReleaseByVersion binary.packageId (mkVersion [0, 10, 0, 0])
Update.setReleasesDeprecationMarker (Vector.singleton (True, deprecatedBinaryVersion'.releaseId))
- deprecatedBinaryVersion <- assertJust =<< Query.getReleaseByVersion (binary.packageId) (mkVersion [0, 10, 0, 0])
+ deprecatedBinaryVersion <- assertJust =<< Query.getReleaseByVersion binary.packageId (mkVersion [0, 10, 0, 0])
assertEqual deprecatedBinaryVersion.deprecated (Just True)
---
diff --git a/test/Flora/SearchSpec.hs b/test/Flora/SearchSpec.hs
new file mode 100644
index 00000000..9463df0d
--- /dev/null
+++ b/test/Flora/SearchSpec.hs
@@ -0,0 +1,45 @@
+module Flora.SearchSpec where
+
+import Test.Tasty
+
+import Flora.Model.Package.Types
+import Flora.Search
+import Flora.TestUtils
+
+spec :: TestEff TestTree
+spec =
+ testThese
+ "Search bar mdifiers"
+ [ testThis "Parsing of \"depends:<@namespace>/\" search modifier" testParsingDependsSearchModifier
+ , testThis "Parsing of \"in:<@namespace> \" modifier" testParsingNamespacePackageModifier
+ , testThis "Parsing of \"in:<@namespace>\" modifier" testParsingNamespaceModifier
+ , testThis "Parsing of a query containing a modifier" testParsingQueryContainingModifier
+ ]
+
+testParsingDependsSearchModifier :: TestEff ()
+testParsingDependsSearchModifier = do
+ let result = parseSearchQuery "depends:@haskell/base"
+ assertEqual
+ (Just $ DependentsOf (Namespace "@haskell") (PackageName "base") Nothing)
+ result
+
+testParsingNamespacePackageModifier :: TestEff ()
+testParsingNamespacePackageModifier = do
+ let result = parseSearchQuery "in:@haskell base"
+ assertEqual
+ (Just $ SearchInNamespace (Namespace "@haskell") (PackageName "base"))
+ result
+
+testParsingNamespaceModifier :: TestEff ()
+testParsingNamespaceModifier = do
+ let result = parseSearchQuery "in:@haskell"
+ assertEqual
+ (Just $ ListAllPackagesInNamespace (Namespace "@haskell"))
+ result
+
+testParsingQueryContainingModifier :: TestEff ()
+testParsingQueryContainingModifier = do
+ let result = parseSearchQuery "bah blah blah depends:@haskell/base"
+ assertEqual
+ (Just (SearchPackages "bah blah blah depends:@haskell/base"))
+ result
diff --git a/test/Main.hs b/test/Main.hs
index ca210cbb..c921283b 100644
--- a/test/Main.hs
+++ b/test/Main.hs
@@ -1,5 +1,6 @@
module Main where
+import Data.Password.Types
import Effectful
import Effectful.Fail (runFailIO)
import Effectful.Log qualified as Log
@@ -9,18 +10,21 @@ import Effectful.Time
import Log.Backend.StandardOutput qualified as Log
import Log.Data
import System.IO
+import System.Process.Typed qualified as Process
import Test.Tasty (defaultMain, testGroup)
-import Flora.Model.BlobStore.API
-
import Flora.BlobSpec qualified as BlobSpec
import Flora.CabalSpec qualified as CabalSpec
import Flora.CategorySpec qualified as CategorySpec
import Flora.Environment
import Flora.ImportSpec qualified as ImportSpec
+import Flora.Model.BlobStore.API
import Flora.Model.PackageIndex.Update qualified as Update
+import Flora.Model.User (UserCreationForm (..), hashPassword, mkUser)
+import Flora.Model.User.Update qualified as Update
import Flora.OddJobSpec qualified as OddJobSpec
import Flora.PackageSpec qualified as PackageSpec
+import Flora.SearchSpec qualified as SearchSpec
import Flora.TemplateSpec qualified as TemplateSpec
import Flora.TestUtils
import Flora.UserSpec qualified as UserSpec
@@ -28,6 +32,8 @@ import Flora.UserSpec qualified as UserSpec
main :: IO ()
main = do
hSetBuffering stdout LineBuffering
+ Process.runProcess "make db-drop"
+ Process.runProcess "make db-setup"
env <- runEff getFloraTestEnv
fixtures <- runEff $ Log.withStdOutLogger $ \stdOutLogger -> do
runTime
@@ -38,6 +44,9 @@ main = do
. runFailIO
$ do
Update.createPackageIndex "hackage" "" "" Nothing
+ password <- hashPassword $ mkPassword "foobar2000"
+ templateUser <- mkUser $ UserCreationForm "hackage-user" "tech@flora.pm" password
+ Update.insertUser templateUser
testMigrations
f' <- getFixtures
importAllPackages f'
@@ -54,4 +63,5 @@ specs fixtures =
, CabalSpec.spec
, ImportSpec.spec fixtures
, BlobSpec.spec
+ , SearchSpec.spec
]
From 52550e593afd7002d30c9a0a9d1736017c1d0b6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?=
Date: Wed, 13 Dec 2023 17:29:09 +0100
Subject: [PATCH 40/40] [NO-ISSUE] Flora 1.0.14 (#488)
---
CHANGELOG.md | 2 +-
flora.cabal | 2 +-
src/core/Flora/Import/Package.hs | 3 ++-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e08058ca..02f0aaff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
# CHANGELOG
-## 1.0.14 -- XXXX-XX-XX
+## 1.0.14 -- 2023-12-13
* Colourise in red deprecation markers on the package page ([#438](/~https://github.com/flora-pm/flora-server/pull/439))
* Added more matches to the natural language processing category ([#440](/~https://github.com/flora-pm/flora-server/pull/440))
* Allow package imports from multiple repositories ([#444](/~https://github.com/flora-pm/flora-server/pull/444))
diff --git a/flora.cabal b/flora.cabal
index 5c8f31ef..c39e599e 100644
--- a/flora.cabal
+++ b/flora.cabal
@@ -1,6 +1,6 @@
cabal-version: 3.0
name: flora
-version: 1.0.13
+version: 1.0.14
homepage: /~https://github.com/flora-pm/flora-server/#readme
bug-reports: /~https://github.com/flora-pm/flora-server/issues
author: Théophile Choutri
diff --git a/src/core/Flora/Import/Package.hs b/src/core/Flora/Import/Package.hs
index 81cc1f9a..9b031d46 100644
--- a/src/core/Flora/Import/Package.hs
+++ b/src/core/Flora/Import/Package.hs
@@ -125,7 +125,8 @@ coreLibraries =
versionList :: Set Version
versionList =
Set.fromList
- [ Version.mkVersion [9, 8, 1]
+ [ Version.mkVersion [9, 10, 1]
+ , Version.mkVersion [9, 8, 1]
, Version.mkVersion [9, 6, 3]
, Version.mkVersion [9, 6, 2]
, Version.mkVersion [9, 6, 1]