From 59ff7bdda3e722ffdec72cfc40181094b367e9b2 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Fri, 23 Oct 2026 00:06:10 +0000 Subject: [PATCH 1/3] Remove legacy Bower and Dhall manifest publishing Require purs.json or spago.yaml for new source publications, including retries. Remove conversion code and its SemVer dependency while retaining stored package compatibility and license detection. --- .env.example | 2 +- SPEC.md | 6 +- app-e2e/src/Test/E2E/Endpoint/Publish.purs | 13 +- app-e2e/src/Test/E2E/Support/Fixtures.purs | 8 +- .../github-packages/console-6.1.0/purs.json | 14 + .../github-packages/effect-4.0.0/purs.json | 13 + .../github-packages/slug-3.0.0/spago.yaml | 13 + .../type-equality-4.0.1/purs.json | 11 + .../unsafe-coerce-6.0.0/purs.json | 11 + app/src/App/API.purs | 99 +----- app/src/App/Legacy/LenientRange.purs | 143 -------- app/src/App/Legacy/Manifest.purs | 210 ------------ app/src/App/Legacy/Types.purs | 10 - app/test/App/API.purs | 118 +++++-- app/test/App/Legacy/LenientRange.purs | 64 ---- app/test/App/Legacy/Manifest.purs | 311 ------------------ app/test/Main.purs | 4 - foreign/package.json | 1 - foreign/src/Foreign/SemVer.js | 26 -- foreign/src/Foreign/SemVer.purs | 25 -- nix/overlay.nix | 2 +- nix/test/config.nix | 79 +++-- package-lock.json | 13 - 23 files changed, 222 insertions(+), 974 deletions(-) create mode 100644 app/fixtures/github-packages/console-6.1.0/purs.json create mode 100644 app/fixtures/github-packages/effect-4.0.0/purs.json create mode 100644 app/fixtures/github-packages/slug-3.0.0/spago.yaml create mode 100644 app/fixtures/github-packages/type-equality-4.0.1/purs.json create mode 100644 app/fixtures/github-packages/unsafe-coerce-6.0.0/purs.json delete mode 100644 app/src/App/Legacy/LenientRange.purs delete mode 100644 app/src/App/Legacy/Manifest.purs delete mode 100644 app/test/App/Legacy/LenientRange.purs delete mode 100644 app/test/App/Legacy/Manifest.purs delete mode 100644 foreign/src/Foreign/SemVer.js delete mode 100644 foreign/src/Foreign/SemVer.purs diff --git a/.env.example b/.env.example index ea0488ffe..4e38b063b 100644 --- a/.env.example +++ b/.env.example @@ -45,7 +45,7 @@ SPACES_SECRET="digitalocean_spaces_secret" # READONLY=true # ----------------------------------------------------------------------------- -# Script-only Secrets (not used by server, used by scripts like legacy-importer) +# Script-only Secrets (not used by server, used by scripts like daily-importer) # ----------------------------------------------------------------------------- # Personal GitHub token for API requests when running scripts diff --git a/SPEC.md b/SPEC.md index f58117bcc..f30ffcfd2 100644 --- a/SPEC.md +++ b/SPEC.md @@ -623,7 +623,11 @@ First, the registry uses the [`PackageName`](#packagename) listed in the JSON pa 1. The package name is not registered (ie. no metadata exists) AND a location is provided in the payload. In this case, the registry will create new metadata to register this package at the given location. It is possible to register a package at the same location as one that already exists. 2. The package name is registered (ie. metadata exists) AND either no location is provided in the payload OR the provided location matches the location recorded in the metadata. When no location is provided, the registry will use the location from the metadata. -Next, the registry will use the provided [`Location`](#location) to fetch the package source at the provided `ref`. Packages must contain a `purs.json` manifest or `spago.yaml` manifest in the package root (in the presence of multiple manifest files, the `purs.json` file takes precedence and others are ignored). The registry will verify that the manifest matches with the JSON payload and metadata: +Next, the registry will use the provided [`Location`](#location) to fetch the package source at the provided `ref`. Packages must contain a `purs.json` manifest or `spago.yaml` manifest in the package root (in the presence of multiple manifest files, the `purs.json` file takes precedence and others are ignored). + +Legacy `bower.json` and `spago.dhall` manifests are no longer accepted for publishing. Packages using these formats must migrate to `purs.json` or `spago.yaml`, including when retrying publication from an old source tag. Existing published archives remain available. Legacy files may still accompany a supported manifest and are subject to the normal packaging and license checks. + +The registry will verify that the manifest matches with the JSON payload and metadata: 1. The manifest MUST be usable to produce a well-formed [`Manifest`](#33-manifest). 2. The manifest package name and JSON payload package name MUST match. diff --git a/app-e2e/src/Test/E2E/Endpoint/Publish.purs b/app-e2e/src/Test/E2E/Endpoint/Publish.purs index ce5e96dbe..a6e334cd2 100644 --- a/app-e2e/src/Test/E2E/Endpoint/Publish.purs +++ b/app-e2e/src/Test/E2E/Endpoint/Publish.purs @@ -26,7 +26,7 @@ import Test.Spec as Spec spec :: E2ESpec spec = do Spec.describe "Publish workflow" do - Spec.it "can publish effect@4.0.0 and verify all state changes" do + Spec.it "can publish effect@4.0.1 from purs.json and verify all state changes" do { jobId, disposition: submissionDisposition } <- Client.publish Fixtures.effectPublishData Assert.shouldEqual submissionDisposition (Just V1.Created) job <- Env.pollJobOrFail jobId @@ -40,7 +40,7 @@ spec = do uploadOccurred <- Env.hasStorageUpload Fixtures.effect unless uploadOccurred do storageRequests <- WireMock.getStorageRequests - WireMock.failWithRequests "Expected S3 PUT for effect/4.0.0.tar.gz" storageRequests + WireMock.failWithRequests "Expected S3 PUT for effect/4.0.1.tar.gz" storageRequests Metadata metadata <- Env.readMetadata Fixtures.effect.name case Map.lookup Fixtures.effect.version metadata.published of @@ -76,11 +76,16 @@ spec = do let actualCompilers = Set.fromFoldable $ NEA.toArray publishedMetaAfter.compilers Assert.shouldEqual actualCompilers expectedCompilers - Spec.describe "Publish with spago.dhall manifest" do - Spec.it "can publish slug@3.0.0 which uses spago.dhall format" do + Spec.describe "Publish with spago.yaml manifest" do + Spec.it "can publish slug@3.0.0 which uses spago.yaml format" do { jobId } <- Client.publish Fixtures.slugPublishData job <- Env.pollJobOrFail jobId Assert.shouldSatisfy (V1.jobInfo job).finishedAt isJust + case job of + PublishJob details -> do + Assert.shouldEqual details.disposition (Just V1.Published) + Assert.shouldEqual details.error Nothing + _ -> Assert.fail "Expected a publish job." uploadOccurred <- Env.hasStorageUpload Fixtures.slug unless uploadOccurred do diff --git a/app-e2e/src/Test/E2E/Support/Fixtures.purs b/app-e2e/src/Test/E2E/Support/Fixtures.purs index 55cc53e97..89396babd 100644 --- a/app-e2e/src/Test/E2E/Support/Fixtures.purs +++ b/app-e2e/src/Test/E2E/Support/Fixtures.purs @@ -56,12 +56,12 @@ console = { name: Utils.unsafePackageName "console", version: Utils.unsafeVersio prelude :: PackageFixture prelude = { name: Utils.unsafePackageName "prelude", version: Utils.unsafeVersion "6.0.1" } --- | slug@3.0.0 fixture package (uses spago.dhall manifest format) +-- | slug@3.0.0 fixture package (uses spago.yaml manifest format) slug :: PackageFixture slug = { name: Utils.unsafePackageName "slug", version: Utils.unsafeVersion "3.0.0" } -- | Standard publish data for effect@4.0.1, used by E2E tests. --- | This matches the fixtures in app/fixtures/github-packages/effect-4.0.1 +-- | Generated from app/fixtures/github-packages/effect-4.0.0 with updated version/ref. effectPublishData :: Operation.PublishData effectPublishData = { name: effect.name @@ -116,8 +116,8 @@ consolePublishData = , version: console.version } --- | Publish data for slug@3.0.0, used for spago.dhall manifest format test. --- | Slug depends on prelude only and uses spago.dhall as its manifest format. +-- | Publish data for slug@3.0.0, used for spago.yaml manifest format test. +-- | Slug depends on prelude only and uses spago.yaml as its manifest format. -- | This matches the fixtures in app/fixtures/github-packages/slug-3.0.0 slugPublishData :: Operation.PublishData slugPublishData = diff --git a/app/fixtures/github-packages/console-6.1.0/purs.json b/app/fixtures/github-packages/console-6.1.0/purs.json new file mode 100644 index 000000000..7a613e79a --- /dev/null +++ b/app/fixtures/github-packages/console-6.1.0/purs.json @@ -0,0 +1,14 @@ +{ + "name": "console", + "version": "6.1.0", + "license": "BSD-3-Clause", + "location": { + "githubOwner": "purescript", + "githubRepo": "purescript-console" + }, + "ref": "v6.1.0", + "dependencies": { + "effect": ">=4.0.1 <5.0.0", + "prelude": ">=6.0.0 <7.0.0" + } +} diff --git a/app/fixtures/github-packages/effect-4.0.0/purs.json b/app/fixtures/github-packages/effect-4.0.0/purs.json new file mode 100644 index 000000000..c72dcd660 --- /dev/null +++ b/app/fixtures/github-packages/effect-4.0.0/purs.json @@ -0,0 +1,13 @@ +{ + "name": "effect", + "version": "4.0.0", + "license": "BSD-3-Clause", + "location": { + "githubOwner": "purescript", + "githubRepo": "purescript-effect" + }, + "ref": "v4.0.0", + "dependencies": { + "prelude": ">=6.0.0 <7.0.0" + } +} diff --git a/app/fixtures/github-packages/slug-3.0.0/spago.yaml b/app/fixtures/github-packages/slug-3.0.0/spago.yaml new file mode 100644 index 000000000..ed13278d8 --- /dev/null +++ b/app/fixtures/github-packages/slug-3.0.0/spago.yaml @@ -0,0 +1,13 @@ +package: + name: slug + dependencies: + - prelude: ">=6.0.1 <6.0.2" + publish: + version: 3.0.0 + license: MIT + location: + githubOwner: purescript + githubRepo: purescript-slug + +# Keep this source fixture separate from the registry development workspace. +workspace: {} diff --git a/app/fixtures/github-packages/type-equality-4.0.1/purs.json b/app/fixtures/github-packages/type-equality-4.0.1/purs.json new file mode 100644 index 000000000..d27444979 --- /dev/null +++ b/app/fixtures/github-packages/type-equality-4.0.1/purs.json @@ -0,0 +1,11 @@ +{ + "name": "type-equality", + "version": "4.0.1", + "license": "BSD-3-Clause", + "location": { + "githubOwner": "purescript", + "githubRepo": "purescript-type-equality" + }, + "ref": "v4.0.1", + "dependencies": {} +} diff --git a/app/fixtures/github-packages/unsafe-coerce-6.0.0/purs.json b/app/fixtures/github-packages/unsafe-coerce-6.0.0/purs.json new file mode 100644 index 000000000..1b1655f14 --- /dev/null +++ b/app/fixtures/github-packages/unsafe-coerce-6.0.0/purs.json @@ -0,0 +1,11 @@ +{ + "name": "unsafe-coerce", + "version": "6.0.0", + "license": "BSD-3-Clause", + "location": { + "githubOwner": "purescript", + "githubRepo": "purescript-unsafe-coerce" + }, + "ref": "v6.0.0", + "dependencies": {} +} diff --git a/app/src/App/API.purs b/app/src/App/API.purs index e148a236a..9a52cad0e 100644 --- a/app/src/App/API.purs +++ b/app/src/App/API.purs @@ -85,7 +85,6 @@ import Registry.App.Effect.Source (SOURCE) import Registry.App.Effect.Source as Source import Registry.App.Effect.Storage (STORAGE) import Registry.App.Effect.Storage as Storage -import Registry.App.Legacy.Manifest as Legacy.Manifest import Registry.App.Legacy.Types (RawPackageName(..), rawPackageNameMapCodec) import Registry.App.Manifest.SpagoYaml as SpagoYaml import Registry.App.Server.MatrixBuilder as MatrixBuilder @@ -123,8 +122,6 @@ import Run.Except as Except data ManifestOrigin = FromPursJson | FromSpagoYaml - | FromBowerJson - | FromSpagoDhall derive instance Eq ManifestOrigin @@ -415,8 +412,7 @@ resolveCompilerAndDeps compilerIndex manifest@(Manifest { dependencies }) maybeC ] -- | Detect which manifest format is present in the package directory. --- | Tries formats in order of preference: purs.json, spago.yaml, bower.json, --- | spago.dhall+packages.dhall. Throws if no supported manifest format is found. +-- | Prefers purs.json over spago.yaml. Throws if neither is present. detectManifestOrigin :: forall r . FilePath @@ -425,33 +421,22 @@ detectManifestOrigin packageDir = do let pursJsonPath = Path.concat [ packageDir, "purs.json" ] spagoYamlPath = Path.concat [ packageDir, "spago.yaml" ] - bowerJsonPath = Path.concat [ packageDir, "bower.json" ] - spagoDhallPath = Path.concat [ packageDir, "spago.dhall" ] - packagesDhallPath = Path.concat [ packageDir, "packages.dhall" ] hasPursJson <- Run.liftEffect $ FS.Sync.exists pursJsonPath hasSpagoYaml <- Run.liftEffect $ FS.Sync.exists spagoYamlPath - hasBowerJson <- Run.liftEffect $ FS.Sync.exists bowerJsonPath - hasSpagoDhall <- Run.liftEffect $ FS.Sync.exists spagoDhallPath - hasPackagesDhall <- Run.liftEffect $ FS.Sync.exists packagesDhallPath if hasPursJson then pure FromPursJson else if hasSpagoYaml then pure FromSpagoYaml - -- These are legacy formats which will be removed at some point after August 1, 2026 - else if hasBowerJson then pure FromBowerJson - else if hasSpagoDhall && hasPackagesDhall then pure FromSpagoDhall - else if hasSpagoDhall then - Except.throw "Package source contains a spago.dhall file but is missing a packages.dhall file required for publishing." else - Except.throw "Package source does not contain a purs.json, spago.yaml, bower.json, or spago.dhall manifest file." + Except.throw "Package source must contain a purs.json or spago.yaml manifest file. Publishing from bower.json or spago.dhall is no longer supported; migrate to purs.json or spago.yaml." -- | Parse a manifest from the package source directory. Returns the parsed -- | manifest along with which format it originated from. parseSourceManifest :: forall r - . { packageDir :: FilePath, name :: PackageName, version :: Version, ref :: String, location :: Location } + . { packageDir :: FilePath, ref :: String } -> Run (RESOURCE_ENV + LOG + EXCEPT String + AFF + EFFECT + r) ParsedManifest -parseSourceManifest { packageDir, name, version, ref, location } = do +parseSourceManifest { packageDir, ref } = do origin <- detectManifestOrigin packageDir manifest <- case origin of FromPursJson -> do @@ -491,79 +476,6 @@ parseSourceManifest { packageDir, name, version, ref, location } = do ] pure m - FromBowerJson -> do - let bowerJsonPath = Path.concat [ packageDir, "bower.json" ] - Log.notice $ "Package source does not have a purs.json or spago.yaml file, creating one from your bower.json file..." - string <- Run.liftAff (Aff.attempt (FS.Aff.readTextFile UTF8 bowerJsonPath)) >>= case _ of - Left error -> Except.throw $ "Could not read bower.json from path " <> bowerJsonPath <> ": " <> Aff.message error - Right s -> pure s - case parseJson Legacy.Manifest.bowerfileCodec string of - Left err -> do - Log.error $ "Failed to parse bower.json: " <> CJ.DecodeError.print err - Except.throw $ "Found a bower.json file in the package source, but it could not be decoded." - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left err -> - Except.throw $ "Could not convert bower.json to purs.json manifest: " <> err - Right { license, description, dependencies } -> do - let - manifest = Manifest - { name - , version - , license - , location - , ref - , description - , dependencies - , includeFiles: Nothing - , excludeFiles: Nothing - , owners: Nothing - } - Log.notice $ Array.fold - [ "Converted your bower.json into a purs.json manifest to use for publishing:" - , "\n```json\n" - , printJson Manifest.codec manifest - , "\n```\n" - ] - pure manifest - - FromSpagoDhall -> do - let spagoDhallPath = Path.concat [ packageDir, "spago.dhall" ] - Log.notice $ "Package source does not have a purs.json, spago.yaml, or bower.json file, creating one from your spago.dhall file..." - spagoDhallContent <- Run.liftAff (Aff.attempt (FS.Aff.readTextFile UTF8 spagoDhallPath)) >>= case _ of - Left error -> Except.throw $ "Could not read spago.dhall from path " <> spagoDhallPath <> ": " <> Aff.message error - Right content -> pure content - dhallJson <- Run.liftAff $ Legacy.Manifest.dhallToJson { dhall: spagoDhallContent, cwd: Just packageDir } - case dhallJson of - Left err -> Except.throw $ "Could not convert spago.dhall to JSON: " <> err - Right json -> case CJ.decode Legacy.Manifest.spagoDhallJsonCodec json of - Left err -> do - Log.error $ "Failed to parse spago.dhall JSON: " <> CJ.DecodeError.print err - Except.throw $ "Found a spago.dhall file in the package source, but it could not be decoded." - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left err -> - Except.throw $ "Could not convert spago.dhall to purs.json manifest: " <> err - Right { license, description, dependencies } -> do - let - manifest = Manifest - { name - , version - , license - , location - , ref - , description - , dependencies - , includeFiles: Nothing - , excludeFiles: Nothing - , owners: Nothing - } - Log.notice $ Array.fold - [ "Converted your spago.dhall into a purs.json manifest to use for publishing:" - , "\n```json\n" - , printJson Manifest.codec manifest - , "\n```\n" - ] - pure manifest - pure { manifest, origin } -- | Publish a package via the 'publish' operation. If the package has not been @@ -637,10 +549,7 @@ publish payload = do { manifest: Manifest receivedManifest, origin: manifestOrigin } <- parseSourceManifest { packageDir: downloadedPackage - , name: payload.name - , version: payload.version , ref: payload.ref - , location: existingMetadata.location } -- We trust the manifest for any changes to the 'owners' field, but for all diff --git a/app/src/App/Legacy/LenientRange.purs b/app/src/App/Legacy/LenientRange.purs deleted file mode 100644 index fcb276966..000000000 --- a/app/src/App/Legacy/LenientRange.purs +++ /dev/null @@ -1,143 +0,0 @@ -module Registry.App.Legacy.LenientRange - ( LenientRange(..) - , parse - , print - , range - , raw - ) where - -import Registry.App.Prelude - -import Control.Monad.Error.Class as Error -import Data.Array as Array -import Data.CodePoint.Unicode as Unicode -import Data.Either (fromRight) -import Data.Function (on) -import Data.String as String -import Data.String.CodeUnits as String.CodeUnits -import Parsing (Parser) -import Parsing as Parsing -import Parsing.Combinators as Parsing.Combinators -import Parsing.String as Parsing.String -import Registry.App.Legacy.LenientVersion (LenientVersion(..)) -import Registry.App.Legacy.LenientVersion as LenientVersion -import Registry.Foreign.SemVer as SemVer -import Registry.Internal.Parsing as Internal.Parsing -import Registry.Range as Range -import Registry.Version as Version - --- | A lenient variant on the `Registry.Range` type, which attempts to correct --- | issues with the input string while parsing and retains the raw input. -newtype LenientRange = LenientRange { range :: Range, raw :: String } - -derive instance Newtype LenientRange _ - -instance Eq LenientRange where - eq = eq `on` (_.range <<< un LenientRange) - --- | Print the `Range` from a lenient range in the form ">=X.Y.Z String -print (LenientRange r) = Range.print r.range - --- | Extract the `Range` from a lenient range -range :: LenientRange -> Range -range (LenientRange r) = r.range - --- | Print the raw string used to produce a lenient range. -raw :: LenientRange -> String -raw (LenientRange r) = r.raw - --- | Parse a `Range` from a string, fixing issues with the input string where --- | possible, such as trimming prerelease identifiers, and trimming leading and --- | trailing spaces. -parse :: String -> Either String LenientRange -parse input = lmap Parsing.parseErrorMessage do - -- We trim off prerelease identifiers and build metadata in lenient parsing - -- mode. Notably, we do not do this at the version level -- even lenient - -- version parsing will fail if these identifiers are present -- so we need - -- to pass through the range and do the work here. - -- - -- Trimming prerelease identifiers in lenient mode can produce ranges - -- where the lhs was less than the rhs, but no longer is. For example: - -- '>=1.0.0-rc.1 <1.0.0-rc.5' -> '>=1.0.0 <1.0.0'. - -- - -- We fix these ranges by bumping the rhs patch by one. - { lhs, rhs } <- Parsing.runParser (convertRange input) do - _ <- Parsing.String.string ">=" <|> Parsing.fail "Ranges must begin with >=" - LenientVersion { version: lhs } <- versionParser =<< map String.CodeUnits.fromCharArray Internal.Parsing.charsUntilSpace - _ <- Parsing.String.char '<' <|> Parsing.fail "Ranges must end with <" - LenientVersion { version: rhs } <- versionParser =<< map String.CodeUnits.fromCharArray Internal.Parsing.chars - let rhs' = if lhs == rhs then Version.bumpPatch rhs else rhs - pure { lhs, rhs: rhs' } - - -- If we were able to parse out an lhs and rhs, then we can attempt to parse - -- a well-formed range using them. - let normalizedRange = Array.fold [ ">=", Version.print lhs, " <", Version.print rhs ] - parsed <- Parsing.runParser normalizedRange Range.parser - pure $ LenientRange { raw: input, range: parsed } - --- In lenient mode we attempt to clean up ranges that are valid but use a syntax --- other than one supported by the registry. --- --- When only an upper bound is specified we can set the lower bound to 0.0.0 --- "<1.0.0" -> ">=0.0.0 <1.0.0", "<=0.1.1" -> ">=0.0.0 <0.1.0" --- --- When ranges unexpectedly end with `<=` we can increment the patch version: --- ">=1.0.0 <=2.0.0" -> ">=1.0.0 <2.0.1" --- --- When ranges unexpectedly begin with `>` we can increment the patch version: --- ">1.1.0 <2.0.0" -> ">=1.1.1 <2.0.0" --- --- When ranges are exact, we can create a range: --- "1.0.0" -> ">=1.0.0 <1.0.1" --- --- When ranges contain prerelease identifiers or build metadata we can strip it --- ">=1.0.0-rc.1 <2.0.0-0" -> ">=1.0.0 <2.0.0" -convertRange :: String -> String -convertRange input = fromRight input do - -- The `parseRange` function converts most ranges into simple ranges that are - -- likely to be accepted by the registry. - semVer <- note "Could not parse with parseRange" (SemVer.parseRange input) - pure $ semVer - # fixGtLhs - # fixLtRhs - # fixUpperOnly - # fixExactVersions - where - -- Fix ranges that begin with '>' instead of '>=' - fixGtLhs :: String -> String - fixGtLhs str = fromRight str $ Parsing.runParser str do - _ <- Parsing.String.char '>' - Parsing.Combinators.notFollowedBy (Parsing.String.char '=') - lhsChars <- map String.CodeUnits.fromCharArray Internal.Parsing.charsUntilSpace - LenientVersion { version: lhs } <- versionParser lhsChars - Parsing.ParseState suffix _ _ <- Parsing.getParserT - pure $ Array.fold [ ">=", Version.print (Version.bumpPatch lhs), " ", suffix ] - - -- Fix ranges that end with '<=' instead of '<' - fixLtRhs str = fromRight str $ Parsing.runParser str do - _ <- Parsing.String.string ">=" - LenientVersion { version: lhs } <- versionParser =<< map String.CodeUnits.fromCharArray Internal.Parsing.charsUntilSpace - _ <- Parsing.String.string "<=" - LenientVersion { version: rhs } <- versionParser =<< map String.CodeUnits.fromCharArray Internal.Parsing.chars - Parsing.String.eof - pure $ Array.fold [ ">=", Version.print lhs, " <", Version.print (Version.bumpPatch rhs) ] - - -- Fix ranges that only consist of an upper bound ('<1.0.0') - fixUpperOnly str = fromRight str $ Parsing.runParser str do - _ <- Parsing.String.char '<' - hasEq <- Parsing.Combinators.optionMaybe (Parsing.String.char '=') - LenientVersion { version: lhs } <- versionParser =<< map String.CodeUnits.fromCharArray Internal.Parsing.chars - Parsing.String.eof - pure $ Array.fold [ ">=0.0.0 <", Version.print (if isJust hasEq then Version.bumpPatch lhs else lhs) ] - - -- Replace exact ranges ('1.0.0') with ranges ('>=1.0.0 <1.0.1') - fixExactVersions str = fromRight str do - LenientVersion { version } <- LenientVersion.parse str - pure $ Array.fold [ ">=", Version.print version, " <", Version.print (Version.bumpPatch version) ] - -versionParser :: String -> Parser String LenientVersion -versionParser = - Error.liftEither - <<< flip Parsing.runParser LenientVersion.parser - <<< String.takeWhile (Unicode.isAlphaNum || eq (String.codePointFromChar '.')) diff --git a/app/src/App/Legacy/Manifest.purs b/app/src/App/Legacy/Manifest.purs deleted file mode 100644 index 266b8680a..000000000 --- a/app/src/App/Legacy/Manifest.purs +++ /dev/null @@ -1,210 +0,0 @@ --- | Support for parsing legacy manifest formats (bower.json, spago.dhall) and --- | converting them to the registry's purs.json format. -module Registry.App.Legacy.Manifest - ( Bowerfile(..) - , SpagoDhallJson(..) - , bowerfileCodec - , bowerfileToPursJson - , dhallToJson - , legacyManifestCodec - , spagoDhallJsonCodec - , spagoDhallToPursJson - ) where - -import Registry.App.Prelude - -import Codec.JSON.DecodeError as CJ.DecodeError -import Data.Array as Array -import Data.Array.NonEmpty as NonEmptyArray -import Data.Codec as Codec -import Data.Codec.JSON as CJ -import Data.Codec.JSON.Common as CJ.Common -import Data.Codec.JSON.Record as CJ.Record -import Data.Map as Map -import Data.Profunctor as Profunctor -import Data.String as String -import Data.String.NonEmpty as NonEmptyString -import Node.ChildProcess.Types (Exit(..)) -import Node.Library.Execa as Execa -import Registry.App.Legacy.LenientRange as LenientRange -import Registry.App.Legacy.LenientVersion as LenientVersion -import Registry.App.Legacy.Types (RawPackageName(..), RawVersion(..), RawVersionRange(..), rawPackageNameMapCodec, rawVersionCodec, rawVersionRangeCodec) -import Registry.Internal.Codec as Internal.Codec -import Registry.License as License -import Registry.LimitedString as LimitedString -import Registry.Location as Location -import Registry.Manifest (ManifestDescription) -import Registry.Owner as Owner -import Registry.PackageName as PackageName -import Registry.Range (Range) -import Registry.Range as Range -import Registry.Version as Version - --- | A minimal representation of a bower.json file containing the fields --- | we need to convert to a purs.json manifest. -newtype Bowerfile = Bowerfile - { description :: Maybe String - , dependencies :: Map RawPackageName RawVersionRange - , license :: Array String - } - -derive instance Newtype Bowerfile _ -derive newtype instance Eq Bowerfile - -bowerfileCodec :: CJ.Codec Bowerfile -bowerfileCodec = Profunctor.dimap toRep fromRep $ CJ.named "Bowerfile" $ CJ.Record.object - { description: CJ.Record.optional CJ.string - , dependencies: CJ.Record.optional dependenciesCodec - , license: CJ.Record.optional licenseCodec - } - where - toRep (Bowerfile fields) = fields { dependencies = Just fields.dependencies, license = Just fields.license } - fromRep fields = Bowerfile $ fields { dependencies = fromMaybe Map.empty fields.dependencies, license = fromMaybe [] fields.license } - - dependenciesCodec :: CJ.Codec (Map RawPackageName RawVersionRange) - dependenciesCodec = rawPackageNameMapCodec rawVersionRangeCodec - - licenseCodec :: CJ.Codec (Array String) - licenseCodec = Codec.codec' decode encode - where - encode = CJ.encode (CJ.array CJ.string) - decode json = Codec.decode (CJ.array CJ.string) json - <|> map Array.singleton (Codec.decode CJ.string json) - --- | Convert a Bowerfile to the fields needed for a purs.json manifest. --- | Returns an error message if the conversion fails. -bowerfileToPursJson - :: Bowerfile - -> Either String { license :: License, description :: Maybe ManifestDescription, dependencies :: Map PackageName Range } -bowerfileToPursJson (Bowerfile { description: bowerDescription, dependencies, license }) = do - let - -- Best effort: keep any licenses that parse cleanly and drop the rest. - validLicenses = Array.mapMaybe (hush <<< License.parseCanonical) license - - parsedLicense <- - case NonEmptyArray.fromArray validLicenses of - Nothing -> Left "No valid SPDX license found in bower.json" - Just multiple -> Right $ License.joinWith License.And multiple - - parsedDeps <- parseDependencies dependencies - description <- traverse LimitedString.parse bowerDescription - - Right { license: parsedLicense, description, dependencies: parsedDeps } - --- | Parse bower.json dependencies (which use package names like "purescript-prelude") --- | into registry dependencies (which use names like "prelude"). -parseDependencies :: Map RawPackageName RawVersionRange -> Either String (Map PackageName Range) -parseDependencies deps = do - let - parseDep (Tuple (RawPackageName rawName) (RawVersionRange rawRange)) = do - let name = fromMaybe rawName $ String.stripPrefix (String.Pattern "purescript-") rawName - pkgName <- lmap (\_ -> "Invalid package name: " <> name) $ PackageName.parse name - range <- lmap (\_ -> "Invalid version range for " <> name <> ": " <> rawRange) $ LenientRange.parse rawRange - Right $ Tuple pkgName (un LenientRange.LenientRange range).range - - parsedPairs <- traverse parseDep (Map.toUnfoldable deps :: Array _) - Right $ Map.fromFoldable parsedPairs - --- | The result of calling 'dhall-to-json' on a 'spago.dhall' file and --- | accompanying 'packages.dhall' file. -newtype SpagoDhallJson = SpagoDhallJson - { license :: Maybe NonEmptyString - , dependencies :: Array RawPackageName - , packages :: Map RawPackageName { version :: RawVersion } - } - -derive instance Newtype SpagoDhallJson _ - -spagoDhallJsonCodec :: CJ.Codec SpagoDhallJson -spagoDhallJsonCodec = Profunctor.dimap toRep fromRep $ CJ.named "SpagoDhallJson" $ CJ.Record.object - { license: CJ.Record.optional CJ.Common.nonEmptyString - , dependencies: CJ.Record.optional (CJ.array (Profunctor.wrapIso RawPackageName CJ.string)) - , packages: CJ.Record.optional packageVersionMapCodec - } - where - packageVersionMapCodec :: CJ.Codec (Map RawPackageName { version :: RawVersion }) - packageVersionMapCodec = rawPackageNameMapCodec $ CJ.named "VersionObject" $ CJ.Record.object { version: rawVersionCodec } - - toRep (SpagoDhallJson fields) = fields - { dependencies = if Array.null fields.dependencies then Nothing else Just fields.dependencies - , packages = if Map.isEmpty fields.packages then Nothing else Just fields.packages - } - - fromRep fields = SpagoDhallJson $ fields - { dependencies = fromMaybe [] fields.dependencies - , packages = fromMaybe Map.empty fields.packages - } - --- | Convert a SpagoDhallJson to the fields needed for a purs.json manifest. --- | Returns an error message if the conversion fails. -spagoDhallToPursJson - :: SpagoDhallJson - -> Either String { license :: License, description :: Maybe ManifestDescription, dependencies :: Map PackageName Range } -spagoDhallToPursJson (SpagoDhallJson { license, dependencies, packages }) = do - parsedLicense <- case license of - Nothing -> Left "No license found in spago.dhall" - Just lic -> case License.parseCanonical (NonEmptyString.toString lic) of - Left _ -> Left $ "Invalid SPDX license in spago.dhall: " <> NonEmptyString.toString lic - Right l -> Right l - - -- Build a map from package names to version ranges by looking up versions in packages - parsedDeps <- parseDhallDependencies dependencies packages - - Right { license: parsedLicense, description: Nothing, dependencies: parsedDeps } - --- | Parse spago.dhall dependencies by looking up versions in the packages map. --- | Dependency names may have "purescript-" prefix which should be stripped. -parseDhallDependencies :: Array RawPackageName -> Map RawPackageName { version :: RawVersion } -> Either String (Map PackageName Range) -parseDhallDependencies deps packages = do - let - parseDep (RawPackageName rawName) = do - let name = fromMaybe rawName $ String.stripPrefix (String.Pattern "purescript-") rawName - pkgName <- lmap (\_ -> "Invalid package name: " <> name) $ PackageName.parse name - -- Look up version in packages map (try both with and without prefix) - let - lookupName = RawPackageName rawName - lookupNameStripped = RawPackageName name - lookupNamePrefixed = RawPackageName ("purescript-" <> name) - case Map.lookup lookupName packages <|> Map.lookup lookupNameStripped packages <|> Map.lookup lookupNamePrefixed packages of - Nothing -> Left $ "Dependency " <> name <> " not found in packages" - Just { version: RawVersion rawVersion } -> do - parsedVersion <- lmap (\_ -> "Invalid version for " <> name <> ": " <> rawVersion) $ LenientVersion.parse rawVersion - let ver = LenientVersion.version parsedVersion - Right $ Tuple pkgName (Range.exact ver) - - parsedPairs <- traverse parseDep deps - Right $ Map.fromFoldable parsedPairs - --- | Convert a string representing a Dhall expression into JSON using the --- | `dhall-to-json` CLI. -dhallToJson :: { dhall :: String, cwd :: Maybe FilePath } -> Aff (Either String JSON) -dhallToJson { dhall, cwd } = do - let cmd = "dhall-to-json" - let args = [] - process <- Execa.execa cmd args (_ { cwd = cwd }) - for_ process.stdin \{ writeUtf8End } -> writeUtf8End dhall - result <- process.getResult - pure case result.exit of - Normally 0 -> lmap CJ.DecodeError.print $ parseJson CJ.json result.stdout - _ -> Left result.stderr - --- | A codec for parsing legacy purs.json manifests that may not have a `ref` --- | field. If `ref` is missing, we use the provided fallback (typically the --- | version prefixed with "v", matching the convention used when building --- | manifests from legacy sources). -legacyManifestCodec :: String -> CJ.Codec Manifest -legacyManifestCodec fallbackRef = Profunctor.dimap toRep fromRep $ CJ.named "Manifest" $ CJ.object - $ CJ.recordProp @"name" PackageName.codec - $ CJ.recordProp @"version" Version.codec - $ CJ.recordProp @"license" License.codec - $ CJ.recordPropOptional @"description" LimitedString.codec - $ CJ.recordProp @"location" Location.codec - $ CJ.recordPropOptional @"ref" CJ.string - $ CJ.recordPropOptional @"owners" (CJ.Common.nonEmptyArray Owner.codec) - $ CJ.recordPropOptional @"includeFiles" (CJ.Common.nonEmptyArray CJ.Common.nonEmptyString) - $ CJ.recordPropOptional @"excludeFiles" (CJ.Common.nonEmptyArray CJ.Common.nonEmptyString) - $ CJ.recordProp @"dependencies" (Internal.Codec.packageMap Range.codec) - $ CJ.record - where - toRep (Manifest m) = m { ref = Just m.ref } - fromRep r = Manifest r { ref = fromMaybe fallbackRef r.ref } diff --git a/app/src/App/Legacy/Types.purs b/app/src/App/Legacy/Types.purs index 9ed8a6ec8..6783bc0c5 100644 --- a/app/src/App/Legacy/Types.purs +++ b/app/src/App/Legacy/Types.purs @@ -71,13 +71,3 @@ rawVersionCodec = Profunctor.wrapIso RawVersion CJ.string rawVersionMapCodec :: forall a. CJ.Codec a -> CJ.Codec (Map RawVersion a) rawVersionMapCodec = Internal.Codec.strMap "RawVersionMap" (Right <<< RawVersion) (un RawVersion) - --- | An unprocessed version range -newtype RawVersionRange = RawVersionRange String - -derive instance Newtype RawVersionRange _ -derive newtype instance Eq RawVersionRange -derive newtype instance Ord RawVersionRange - -rawVersionRangeCodec :: CJ.Codec RawVersionRange -rawVersionRangeCodec = Profunctor.wrapIso RawVersionRange CJ.string diff --git a/app/test/App/API.purs b/app/test/App/API.purs index 8679e0040..205239d0e 100644 --- a/app/test/App/API.purs +++ b/app/test/App/API.purs @@ -31,6 +31,7 @@ import Registry.Foreign.FastGlob as FastGlob import Registry.Foreign.Tmp as Tmp import Registry.License as License import Registry.Location (Location(..)) +import Registry.Manifest as Manifest import Registry.Operation (PublishData) import Registry.PackageName as PackageName import Registry.Test.Assert as Assert @@ -162,7 +163,7 @@ spec = do result <- Assert.Run.runTestEffects testEnv $ Except.runExcept do -- We'll publish effect@4.0.0 from the fixtures/github-packages - -- directory, which has a bower.json manifest. + -- directory, which has a purs.json manifest. let name = Utils.unsafePackageName "effect" version = Utils.unsafeVersion "4.0.0" @@ -292,21 +293,17 @@ spec = do Run.liftAff $ FS.Aff.writeFile storedTarballPath storedTarball let sourceDir = Path.concat [ env.githubDir, "effect-4.0.0" ] - let bowerPath = Path.concat [ sourceDir, "bower.json" ] - let licensePath = Path.concat [ sourceDir, "LICENSE" ] - originalBower <- Run.liftAff $ FS.Aff.readTextFile UTF8 bowerPath - originalLicense <- Run.liftAff $ FS.Aff.readTextFile UTF8 licensePath - mitLicense <- Run.liftAff $ FS.Aff.readTextFile UTF8 (Path.concat [ env.githubDir, "slug-3.0.0", "LICENSE" ]) - Run.liftAff $ FS.Aff.writeTextFile UTF8 bowerPath $ String.replace (Pattern "BSD-3-Clause") (Replacement "MIT") originalBower - Run.liftAff $ FS.Aff.writeTextFile UTF8 licensePath mitLicense + let manifestPath = Path.concat [ sourceDir, "purs.json" ] + originalManifest <- Run.liftAff $ FS.Aff.readTextFile UTF8 manifestPath + Run.liftAff $ FS.Aff.writeTextFile UTF8 manifestPath $ + String.replace (Pattern "\"dependencies\"") (Replacement "\"description\": \"Changed source manifest\", \"dependencies\"") originalManifest Except.runExcept (API.publish effectPublishArgs) >>= case _ of Left error | String.contains (Pattern "source manifest differs from the manifest in the existing tarball") error -> pure unit Left error -> Except.throw $ "Expected a changed source manifest failure but got: " <> error Right _ -> Except.throw "Expected retrying from a changed source manifest to fail." assertPublicationState effectName effectVersion { storage: true, metadata: true, manifest: false } - Run.liftAff $ FS.Aff.writeTextFile UTF8 bowerPath originalBower - Run.liftAff $ FS.Aff.writeTextFile UTF8 licensePath originalLicense + Run.liftAff $ FS.Aff.writeTextFile UTF8 manifestPath originalManifest API.publish effectPublishArgs >>= case _ of { matrix: Just _ } -> pure unit _ -> Except.throw "Expected retrying the incomplete publication to report a newly completed publication." @@ -415,35 +412,84 @@ checkBuildPlanToResolutions = do parseSourceManifestSpec :: Spec.Spec Unit parseSourceManifestSpec = do - Spec.it "Rejects deprecated SPDX identifiers in purs.json" do + for_ + [ { label: "purs.json alone", files: [ Tuple "purs.json" pursJson ], origin: API.FromPursJson } + , { label: "spago.yaml alone", files: [ Tuple "spago.yaml" spagoYaml ], origin: API.FromSpagoYaml } + , { label: "purs.json before valid spago.yaml and legacy files", files: [ Tuple "purs.json" pursJson, Tuple "spago.yaml" spagoYaml ] <> legacyFiles, origin: API.FromPursJson } + , { label: "purs.json despite invalid spago.yaml and legacy files", files: [ Tuple "purs.json" pursJson, Tuple "spago.yaml" "[" ] <> legacyFiles, origin: API.FromPursJson } + , { label: "spago.yaml despite legacy files", files: [ Tuple "spago.yaml" spagoYaml ] <> legacyFiles, origin: API.FromSpagoYaml } + ] + \testCase -> + Spec.it ("Accepts " <> testCase.label) do + parse testCase.files >>= case _ of + Left err -> Assert.fail $ "Expected a modern manifest, but got: " <> err + Right { manifest, origin } -> do + Assert.shouldSatisfy origin (_ == testCase.origin) + let expected = if testCase.origin == API.FromPursJson then pursJson else String.replace (Pattern "registry-lib") (Replacement "yaml-package") pursJson + let expectedManifest = unsafeFromRight $ parseJson Manifest.codec expected + Assert.shouldEqual (stringifyJson Manifest.codec manifest) (stringifyJson Manifest.codec expectedManifest) + + for_ + [ { label: "no manifest", files: [] } + , { label: "bower.json only", files: [ Tuple "bower.json" bowerJson ] } + , { label: "spago.dhall without packages.dhall", files: [ Tuple "spago.dhall" spagoDhall ] } + , { label: "spago.dhall with packages.dhall", files: [ Tuple "spago.dhall" spagoDhall, Tuple "packages.dhall" "{}" ] } + , { label: "all legacy manifests without a modern manifest", files: legacyFiles } + ] + \testCase -> + Spec.it ("Rejects " <> testCase.label <> " with migration guidance") do + result <- parse testCase.files + expectError "migrate to purs.json or spago.yaml" result + + for_ + [ { label: "malformed purs.json before valid YAML and legacy files", files: [ Tuple "purs.json" "{", Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "does not typecheck" } + , { label: "ill-typed purs.json before valid YAML and legacy files", files: [ Tuple "purs.json" "{}", Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "does not typecheck" } + , { label: "deprecated SPDX identifiers in purs.json", files: [ Tuple "purs.json" (String.replace (Pattern "BSD-3-Clause") (Replacement "AGPL-3.0") pursJson), Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "license field is not canonical" } + , { label: "malformed spago.yaml before legacy files", files: [ Tuple "spago.yaml" "[" ] <> legacyFiles, error: "not possible to read it" } + , { label: "incomplete spago.yaml before legacy files", files: [ Tuple "spago.yaml" "{}" ] <> legacyFiles, error: "No 'package' key" } + , { label: "deprecated SPDX identifiers in spago.yaml", files: [ Tuple "spago.yaml" (String.replace (Pattern "BSD-3-Clause") (Replacement "AGPL-3.0") spagoYaml) ] <> legacyFiles, error: "not possible to read it" } + ] + \testCase -> + Spec.it ("Rejects " <> testCase.label <> " without fallback") do + result <- parse testCase.files + expectError testCase.error result + where + pursJson = """{"name":"registry-lib","version":"0.0.1","license":"BSD-3-Clause","location":{"githubOwner":"purescript","githubRepo":"registry-dev"},"ref":"v0.0.1","dependencies":{"prelude":">=6.0.0 <7.0.0"}}""" + + spagoYaml = + """ +package: + name: yaml-package + dependencies: + - prelude: ">=6.0.0 <7.0.0" + publish: + version: 0.0.1 + license: BSD-3-Clause + location: + githubOwner: purescript + githubRepo: registry-dev +""" + + bowerJson = """{"license":"BSD-3-Clause","dependencies":{"purescript-prelude":"^6.0.0"}}""" + spagoDhall = """{ license = "BSD-3-Clause", dependencies = [] : List Text, packages = ./packages.dhall }""" + legacyFiles = [ Tuple "bower.json" bowerJson, Tuple "spago.dhall" spagoDhall, Tuple "packages.dhall" "{}" ] + + parse :: Array (Tuple FilePath String) -> Aff (Either String API.ParsedManifest) + parse files = do resourceEnv <- liftEffect Env.lookupResourceEnv Aff.bracket Tmp.mkTmpDir FS.Extra.remove \packageDir -> do - let - manifestPath = Path.concat [ packageDir, "purs.json" ] - args = - { packageDir - , name: Utils.unsafePackageName "registry-lib" - , version: Utils.unsafeVersion "0.0.1" - , ref: "v0.0.1" - , location: GitHub { owner: "purescript", repo: "registry-dev", subdir: Nothing } - } - - FS.Aff.writeTextFile UTF8 manifestPath - """{"name":"registry-lib","version":"0.0.1","license":"AGPL-3.0","location":{"githubOwner":"purescript","githubRepo":"registry-dev"},"ref":"v0.0.1","dependencies":{"prelude":">=6.0.0 <7.0.0"}}""" - - result <- - API.parseSourceManifest args - # Env.runResourceEnv resourceEnv - # Log.interpret (\(Log.Log _ _ next) -> pure next) - # Except.runExcept - # Run.runBaseAff' + for_ files \(Tuple path contents) -> FS.Aff.writeTextFile UTF8 (Path.concat [ packageDir, path ]) contents + API.parseSourceManifest { packageDir, ref: "v0.0.1" } + # Env.runResourceEnv resourceEnv + # Log.interpret (\(Log.Log _ _ next) -> pure next) + # Except.runExcept + # Run.runBaseAff' - case result of - Left err -> - unless (String.contains (Pattern "license field is not canonical") err) do - Assert.fail $ "Expected a canonical license error, but got: " <> err - Right _ -> - Assert.fail "Expected parseSourceManifest to reject deprecated SPDX identifiers" + expectError :: String -> Either String API.ParsedManifest -> Aff Unit + expectError expected = case _ of + Left err -> unless (String.contains (Pattern expected) err) do + Assert.fail $ "Expected error containing '" <> expected <> "', but got: " <> err + Right _ -> Assert.fail $ "Expected parseSourceManifest to fail with: " <> expected removeIgnoredTarballFiles :: Spec.Spec Unit removeIgnoredTarballFiles = Spec.before runBefore do diff --git a/app/test/App/Legacy/LenientRange.purs b/app/test/App/Legacy/LenientRange.purs deleted file mode 100644 index 5990babdd..000000000 --- a/app/test/App/Legacy/LenientRange.purs +++ /dev/null @@ -1,64 +0,0 @@ -module Test.Registry.App.Legacy.LenientRange (spec) where - -import Registry.App.Prelude - -import Data.Either as Either -import Registry.App.Legacy.LenientRange as LenientRange -import Registry.Test.Assert as Assert -import Test.Spec as Spec - -spec :: Spec.Spec Unit -spec = do - Spec.describe "Parses malformed ranges with fixable errors" do - for_ fixable \{ raw, range } -> - Spec.it raw do - let parsed = LenientRange.parse raw - range `Assert.shouldEqualRight` map LenientRange.print parsed - - Spec.describe "Fails to parse unfixable malformed ranges" do - for_ unfixable \{ raw, label } -> - Spec.it label do - let parsed = LenientRange.parse raw - parsed `Assert.shouldSatisfy` Either.isLeft - -fixable :: Array { raw :: String, range :: String } -fixable = - [ { raw: "1", range: ">=1.0.0 <2.0.0" } - , { raw: "2.3", range: ">=2.3.0 <2.4.0" } - , { raw: "1 - 2", range: ">=1.0.0 <3.0.0" } - , { raw: "1.0 - 2.0", range: ">=1.0.0 <2.1.0" } - , { raw: "1.x.x", range: ">=1.0.0 <2.0.0" } - , { raw: "1.2.x", range: ">=1.2.0 <1.3.0" } - , { raw: "1.*.*", range: ">=1.0.0 <2.0.0" } - , { raw: ">=\t1.0.0 <2.0.0", range: ">=1.0.0 <2.0.0" } - , { raw: "~1", range: ">=1.0.0 <2.0.0" } - , { raw: "~> 1", range: ">=1.0.0 <2.0.0" } - , { raw: "~1.0", range: ">=1.0.0 <1.1.0" } - , { raw: "~>3.2.1", range: ">=3.2.1 <3.3.0" } - , { raw: "^ 1", range: ">=1.0.0 <2.0.0" } - , { raw: "^0.1", range: ">=0.1.0 <0.2.0" } - , { raw: "^1.0", range: ">=1.0.0 <2.0.0" } - , { raw: "^1.2", range: ">=1.2.0 <2.0.0" } - , { raw: "^0.0.1", range: ">=0.0.1 <0.0.2" } - , { raw: ">=01.02.03 <01.02.10", range: ">=1.2.3 <1.2.10" } - , { raw: "1.2.3 - 3.4", range: ">=1.2.3 <3.5.0" } - , { raw: "1.2 - 3.4", range: ">=1.2.0 <3.5.0" } - , { raw: "1.0.0 - 2.0.0", range: ">=1.0.0 <2.0.1" } - , { raw: "1.0.0", range: ">=1.0.0 <1.0.1" } - , { raw: ">1.0.0 <2.0.0", range: ">=1.0.1 <2.0.0" } - , { raw: "<1.0.0", range: ">=0.0.0 <1.0.0" } - , { raw: "<=1.0.0", range: ">=0.0.0 <1.0.1" } - , { raw: "^0", range: ">=0.0.0 <1.0.0" } - , { raw: ">01.02.03 <01.02.10", range: ">=1.2.4 <1.2.10" } - , { raw: "^0.0.1-beta", range: ">=0.0.1 <0.0.2" } - , { raw: ">=1.0.0-rc.1 <1.0.0-rc.5", range: ">=1.0.0 <1.0.1" } - , { raw: "0.0.1+build", range: ">=0.0.1 <0.0.2" } - ] - -unfixable :: Array { raw :: String, label :: String } -unfixable = - [ { raw: "*", label: "Results in *" } - , { raw: ">=*", label: "Results in *" } - , { raw: ">1.0.0", label: "Unbounded upper range" } - , { raw: ">=1.0.0", label: "Unbounded upper range" } - ] diff --git a/app/test/App/Legacy/Manifest.purs b/app/test/App/Legacy/Manifest.purs deleted file mode 100644 index efd4a884a..000000000 --- a/app/test/App/Legacy/Manifest.purs +++ /dev/null @@ -1,311 +0,0 @@ -module Test.Registry.App.Legacy.Manifest (spec) where - -import Registry.App.Prelude - -import Codec.JSON.DecodeError as CJ.DecodeError -import Data.Array as Array -import Data.Codec.JSON as CJ -import Registry.App.Legacy.Manifest as Legacy.Manifest -import Registry.License as License -import Registry.Manifest (Manifest(..)) -import Registry.Test.Assert as Assert -import Test.Spec (Spec) -import Test.Spec as Spec - -spec :: Spec Unit -spec = do - Spec.describe "Bowerfile" bowerfileSpec - Spec.describe "bowerfileToPursJson" bowerfileToPursJsonSpec - Spec.describe "spagoDhallToPursJson" spagoDhallToPursJsonSpec - Spec.describe "Legacy manifest codec" legacyManifestCodecSpec - -bowerfileSpec :: Spec Unit -bowerfileSpec = do - Spec.describe "Parses acceptable Bowerfiles" do - let - shouldParse :: String -> String -> Spec Unit - shouldParse label input = Spec.it label do - case parseJson Legacy.Manifest.bowerfileCodec input of - Left err -> Assert.fail $ Array.fold - [ "Failed to parse:\n" - , input - , "due to an error:\n" - , CJ.DecodeError.print err - ] - Right _ -> pure unit - - shouldParse "Version and license only" - """ - { - "version": "v1.0.0", - "license": "MIT" - } - """ - - shouldParse "Empty dependencies and license" - """ - { "version": "v1.0.0" - , "license": "" - , "dependencies": {} - } - """ - - shouldParse "Contains extra fields" - """ - { "extra": "value" - , "license": "not a license" - , "version": "v1.1.1" - } - """ - - shouldParse "Has non-SemVer dependenices" - """ - { "version": "notsemver" - , "license": "" - , "dependencies": { "also": "not semver" } - , "devDependencies": { "lastly": "🍝" } - } - """ - - shouldParse "Complete bowerfile" - """ - { "version": "v1.0.1" - , "license": [ "license" ] - , "dependencies": - { "other-package": "v0.0.1" - , "another-package": "v10.0.1-rc1" - } - , "devDependencies": - { "dev-dep": "v2.0.0" } - } - """ - - Spec.describe "Does not parse unacceptable Bowerfiles" do - let - shouldNotParse :: String -> String -> Spec Unit - shouldNotParse label input = Spec.it label do - case parseJson Legacy.Manifest.bowerfileCodec input of - Left _ -> pure unit - Right _ -> Assert.fail $ "Parsed input that should have failed:\n" <> input - - shouldNotParse "Wrong license format" - """ - { "version": "" - , "license": true - } - """ - - shouldNotParse "Wrong dependency format" - """ - { "version": "" - , "license": "" - , "dependencies": [] - } - """ - -bowerfileToPursJsonSpec :: Spec Unit -bowerfileToPursJsonSpec = do - Spec.describe "Converts valid Bowerfiles" do - Spec.it "Converts a simple bowerfile with MIT license" do - let - input = - """ - { "license": "MIT" - , "dependencies": { "purescript-prelude": "^6.0.0" } - } - """ - case parseJson Legacy.Manifest.bowerfileCodec input of - Left err -> Assert.fail $ "Failed to parse bowerfile:\n" <> CJ.DecodeError.print err - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left err -> Assert.fail $ "Failed to convert bowerfile:\n" <> err - Right result -> do - result.description `Assert.shouldEqual` Nothing - - Spec.it "Strips purescript- prefix from dependency names" do - let - input = - """ - { "license": "BSD-3-Clause" - , "dependencies": { "purescript-effect": "^4.0.0" } - } - """ - case parseJson Legacy.Manifest.bowerfileCodec input of - Left err -> Assert.fail $ "Failed to parse bowerfile:\n" <> CJ.DecodeError.print err - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left err -> Assert.fail $ "Failed to convert bowerfile:\n" <> err - Right _ -> pure unit - - Spec.it "Drops invalid SPDX identifiers when valid licenses remain" do - let - input = - """ - { "license": [ "MIT", "AGPL-3.0" ] - , "dependencies": { "purescript-prelude": "^6.0.0" } - } - """ - case parseJson Legacy.Manifest.bowerfileCodec input of - Left err -> Assert.fail $ "Failed to parse bowerfile:\n" <> CJ.DecodeError.print err - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left err -> Assert.fail $ "Failed to convert bowerfile:\n" <> err - Right result -> - Assert.shouldEqual "MIT" (License.print result.license) - - Spec.describe "Rejects invalid Bowerfiles" do - Spec.it "Fails on missing license" do - let - input = - """ - { "dependencies": { "purescript-prelude": "^6.0.0" } - } - """ - case parseJson Legacy.Manifest.bowerfileCodec input of - Left _ -> pure unit - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left _ -> pure unit - Right _ -> Assert.fail "Expected conversion to fail for missing license" - - Spec.it "Fails when the description exceeds the manifest limit" do - let - description = Array.fold $ Array.replicate 501 "a" - input = "{ \"license\": \"MIT\", \"description\": \"" <> description <> "\" }" - case parseJson Legacy.Manifest.bowerfileCodec input of - Left err -> Assert.fail $ "Failed to parse bowerfile:\n" <> CJ.DecodeError.print err - Right bowerfile -> case Legacy.Manifest.bowerfileToPursJson bowerfile of - Left _ -> pure unit - Right _ -> Assert.fail "Expected conversion to fail for an overlong description" - -spagoDhallToPursJsonSpec :: Spec Unit -spagoDhallToPursJsonSpec = do - Spec.describe "Converts valid SpagoDhallJson" do - Spec.it "Converts spago.dhall with license and dependencies" do - let - input = - """ - { "license": "MIT" - , "dependencies": [ "prelude", "effect" ] - , "packages": { "prelude": { "version": "v6.0.0" }, "effect": { "version": "v4.0.0" } } - } - """ - case parseJson Legacy.Manifest.spagoDhallJsonCodec input of - Left err -> Assert.fail $ "Failed to parse spago.dhall JSON:\n" <> CJ.DecodeError.print err - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left err -> Assert.fail $ "Failed to convert spago.dhall:\n" <> err - Right result -> do - result.description `Assert.shouldEqual` Nothing - - Spec.it "Handles purescript- prefix in package names" do - let - input = - """ - { "license": "BSD-3-Clause" - , "dependencies": [ "purescript-prelude" ] - , "packages": { "purescript-prelude": { "version": "v6.0.0" } } - } - """ - case parseJson Legacy.Manifest.spagoDhallJsonCodec input of - Left err -> Assert.fail $ "Failed to parse:\n" <> CJ.DecodeError.print err - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left err -> Assert.fail $ "Failed to convert:\n" <> err - Right _ -> pure unit - - Spec.it "Handles versions without v prefix" do - let - input = - """ - { "license": "Apache-2.0" - , "dependencies": [ "prelude" ] - , "packages": { "prelude": { "version": "6.0.0" } } - } - """ - case parseJson Legacy.Manifest.spagoDhallJsonCodec input of - Left err -> Assert.fail $ "Failed to parse:\n" <> CJ.DecodeError.print err - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left err -> Assert.fail $ "Failed to convert:\n" <> err - Right _ -> pure unit - - Spec.describe "Rejects invalid SpagoDhallJson" do - Spec.it "Fails on missing license" do - let - input = - """ - { "dependencies": [ "prelude" ] - , "packages": { "prelude": { "version": "v6.0.0" } } - } - """ - case parseJson Legacy.Manifest.spagoDhallJsonCodec input of - Left _ -> pure unit - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left _ -> pure unit - Right _ -> Assert.fail "Expected conversion to fail for missing license" - - Spec.it "Fails when dependency is not in packages" do - let - input = - """ - { "license": "MIT" - , "dependencies": [ "prelude", "missing" ] - , "packages": { "prelude": { "version": "v6.0.0" } } - } - """ - case parseJson Legacy.Manifest.spagoDhallJsonCodec input of - Left err -> Assert.fail $ "Failed to parse:\n" <> CJ.DecodeError.print err - Right spagoDhall -> case Legacy.Manifest.spagoDhallToPursJson spagoDhall of - Left _ -> pure unit - Right _ -> Assert.fail "Expected conversion to fail for missing package" - -legacyManifestCodecSpec :: Spec Unit -legacyManifestCodecSpec = do - Spec.describe "Parses manifests with optional ref" do - Spec.it "Parses manifest without ref, using fallback" do - let - input = - """ - { "name": "prelude" - , "version": "1.0.0" - , "license": "MIT" - , "location": { "githubOwner": "purescript", "githubRepo": "purescript-prelude" } - , "dependencies": {} - } - """ - fallbackRef = "v1.0.0" - case parseJson (Legacy.Manifest.legacyManifestCodec fallbackRef) input of - Left err -> Assert.fail $ "Failed to parse manifest without ref:\n" <> CJ.DecodeError.print err - Right (Manifest m) -> m.ref `Assert.shouldEqual` fallbackRef - - Spec.it "Parses manifest with ref, ignoring fallback" do - let - input = - """ - { "name": "prelude" - , "version": "1.0.0" - , "license": "MIT" - , "location": { "githubOwner": "purescript", "githubRepo": "purescript-prelude" } - , "ref": "abc123" - , "dependencies": {} - } - """ - fallbackRef = "v1.0.0" - case parseJson (Legacy.Manifest.legacyManifestCodec fallbackRef) input of - Left err -> Assert.fail $ "Failed to parse manifest with ref:\n" <> CJ.DecodeError.print err - Right (Manifest m) -> m.ref `Assert.shouldEqual` "abc123" - - Spec.it "Roundtrips manifest with ref" do - let - input = - """ - { "name": "prelude" - , "version": "1.0.0" - , "license": "MIT" - , "location": { "githubOwner": "purescript", "githubRepo": "purescript-prelude" } - , "ref": "v1.0.0" - , "dependencies": {} - } - """ - fallbackRef = "unused" - case parseJson (Legacy.Manifest.legacyManifestCodec fallbackRef) input of - Left err -> Assert.fail $ "Failed to parse:\n" <> CJ.DecodeError.print err - Right manifest -> do - let encoded = CJ.encode (Legacy.Manifest.legacyManifestCodec fallbackRef) manifest - case CJ.decode (Legacy.Manifest.legacyManifestCodec fallbackRef) encoded of - Left err -> Assert.fail $ "Failed to decode roundtripped manifest:\n" <> CJ.DecodeError.print err - Right (Manifest m) -> m.ref `Assert.shouldEqual` "v1.0.0" diff --git a/app/test/Main.purs b/app/test/Main.purs index 6cf1bb2f8..c17233538 100644 --- a/app/test/Main.purs +++ b/app/test/Main.purs @@ -12,9 +12,7 @@ import Test.Registry.App.CLI.Tar as Test.CLI.Tar import Test.Registry.App.Effect.PackageSets as Test.Effect.PackageSets import Test.Registry.App.Effect.Registry as Test.Effect.Registry import Test.Registry.App.GitHubIssue as Test.GitHubIssue -import Test.Registry.App.Legacy.LenientRange as Test.Legacy.LenientRange import Test.Registry.App.Legacy.LenientVersion as Test.Legacy.LenientVersion -import Test.Registry.App.Legacy.Manifest as Test.Legacy.Manifest import Test.Registry.App.Legacy.PackageSet as Test.Legacy.PackageSet import Test.Registry.App.Manifest.SpagoYaml as Test.Manifest.SpagoYaml import Test.Registry.App.SQLite as Test.SQLite @@ -47,8 +45,6 @@ main = runSpecAndExitProcess' config [ consoleReporter ] do Spec.describe "Registry.App.Legacy" do Spec.describe "Lenient Version" Test.Legacy.LenientVersion.spec - Spec.describe "Lenient Range" Test.Legacy.LenientRange.spec - Spec.describe "Legacy Manifest" Test.Legacy.Manifest.spec Spec.describe "Legacy PackageSet" Test.Legacy.PackageSet.spec Spec.describe "Registry.App.Manifest" do diff --git a/foreign/package.json b/foreign/package.json index bb3fca7a7..bcf5e0c66 100644 --- a/foreign/package.json +++ b/foreign/package.json @@ -14,7 +14,6 @@ "fuse.js": "^7.5.0", "jsonrepair": "^3.15.0", "registry-lib": "file:../lib", - "semver": "^7.8.5", "tar": "^7.5.22", "tmp": "^0.2.7", "yaml": "^2.9.1" diff --git a/foreign/src/Foreign/SemVer.js b/foreign/src/Foreign/SemVer.js deleted file mode 100644 index f205ba264..000000000 --- a/foreign/src/Foreign/SemVer.js +++ /dev/null @@ -1,26 +0,0 @@ -import semver from "semver"; - -export const parseRangeImpl = (rangeString) => { - // `validRange` cleans the input string (in loose mode) and converts it into a range - // using only comparative operators (no ^, ~, -, or other range operators), and then - // checks whether the result is a valid SemVer range. It then returns `null` if the - // range is not valid, and the converted range if it is. - // - // For example: - // - // > validRange("^1.0.0") - // ">=1.0.0 <2.0.0" - // - // In loose mode `validRange` will also fix common errors like including a 'v' prefix - // or extra spaces: - // - // > validRange(" = v 2.1.5-foo") - // "2.1.5-foo" - // - // We set these options because we should only be using this function to clean up ranges, - // not to validate them. - return semver.validRange(rangeString, { - loose: true, - includePrerelease: false, - }); -}; diff --git a/foreign/src/Foreign/SemVer.purs b/foreign/src/Foreign/SemVer.purs deleted file mode 100644 index 2b7643a83..000000000 --- a/foreign/src/Foreign/SemVer.purs +++ /dev/null @@ -1,25 +0,0 @@ -module Registry.Foreign.SemVer - ( parseRange - ) where - -import Prelude - -import Data.Maybe (Maybe(..)) -import Data.Nullable (Nullable, toMaybe) -import Data.String as String - -foreign import parseRangeImpl :: String -> Nullable String - -parseRange :: String -> Maybe String -parseRange original = do - converted <- case toMaybe (parseRangeImpl original) of - Just c -> pure c - Nothing -> - -- when parsing a version from a Bowerfile it could be that it's specified - -- in the https://giturl#version, or owner/repo#version, so we try to parse that here. - case String.split (String.Pattern "#") original of - [ _, v ] -> case toMaybe (parseRangeImpl v) of - Just c -> pure c - _ -> Nothing - _ -> Nothing - pure converted diff --git a/nix/overlay.nix b/nix/overlay.nix index c696a5969..6effc29a5 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -167,7 +167,7 @@ in npmFlags = [ "--omit=optional" ]; # To update: run `prefetch-npm-deps package-lock.json` in the dev shell. - npmDepsHash = "sha256-1PENsd/EzRYVZ6RxTssbSzPu7SVVGlWnP7Zjt6im5GI="; + npmDepsHash = "sha256-p/CDenuiSblZ8CunSkUt8v2I99D0fTQWNZKCRuRK78o="; installPhase = '' mkdir -p $out diff --git a/nix/test/config.nix b/nix/test/config.nix index d7fcc5b48..87eaa62bd 100644 --- a/nix/test/config.nix +++ b/nix/test/config.nix @@ -169,13 +169,27 @@ let }; }; - # effect@4.0.1 (same source as 4.0.0, used by publish E2E tests) + # effect@4.0.1 (updated manifest, same code as 4.0.0) effectV401Base64Response = fileName: base64Response { url = "/repos/purescript/purescript-effect/contents/${fileName}?ref=v4.0.1"; inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/effect-4.0.0/${fileName}"; + filePath = + if fileName == "purs.json" then + pkgs.writeText "effect-4.0.1-purs.json" ( + builtins.toJSON ( + builtins.fromJSON ( + builtins.readFile (rootPath + "/app/fixtures/github-packages/effect-4.0.0/purs.json") + ) + // { + version = "4.0.1"; + ref = "v4.0.1"; + } + ) + ) + else + rootPath + "/app/fixtures/github-packages/effect-4.0.0/${fileName}"; }; effectV401404Response = fileName: { @@ -248,7 +262,21 @@ let base64Response { url = "/repos/purescript/purescript-type-equality/contents/${fileName}?ref=v4.0.2"; inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/type-equality-4.0.1/${fileName}"; + filePath = + if fileName == "purs.json" then + pkgs.writeText "type-equality-4.0.2-purs.json" ( + builtins.toJSON ( + builtins.fromJSON ( + builtins.readFile (rootPath + "/app/fixtures/github-packages/type-equality-4.0.1/purs.json") + ) + // { + version = "4.0.2"; + ref = "v4.0.2"; + } + ) + ) + else + rootPath + "/app/fixtures/github-packages/type-equality-4.0.1/${fileName}"; }; typeEquality404Response = fileName: { @@ -266,7 +294,7 @@ let }; }; - # Slug package helpers (slug@3.0.0) - uses spago.dhall manifest format + # Slug package helpers (slug@3.0.0) - uses spago.yaml manifest format slugBase64Response = fileName: base64Response { @@ -292,47 +320,40 @@ let # GitHub API wiremock mappings githubMappings = [ + (effectBase64Response "purs.json") (effectBase64Response "bower.json") (effectBase64Response "LICENSE") # Expected 404s for manifest files that don't exist in the effect package (effect404Response "spago.yaml") - (effect404Response "spago.dhall") - (effect404Response "purs.json") (effect404Response "package.json") - # effect@4.0.1 (same source, different ref — used by publish E2E tests) + # effect@4.0.1 (updated manifest — used by publish E2E tests) + (effectV401Base64Response "purs.json") (effectV401Base64Response "bower.json") (effectV401Base64Response "LICENSE") (effectV401404Response "spago.yaml") - (effectV401404Response "spago.dhall") - (effectV401404Response "purs.json") (effectV401404Response "package.json") # Console package (console@6.1.0) + (consoleBase64Response "purs.json") (consoleBase64Response "bower.json") (consoleBase64Response "LICENSE") (console404Response "spago.yaml") - (console404Response "spago.dhall") - (console404Response "purs.json") (console404Response "package.json") # Unsafe-coerce package (unsafe-coerce@6.0.0) + (unsafeCoerceBase64Response "purs.json") (unsafeCoerceBase64Response "bower.json") (unsafeCoerce404Response "LICENSE") (unsafeCoerce404Response "spago.yaml") - (unsafeCoerce404Response "spago.dhall") - (unsafeCoerce404Response "purs.json") (unsafeCoerce404Response "package.json") - # Type-equality package (type-equality@4.0.2 for legacy imports test) + # Type-equality package (type-equality@4.0.2 for tag discovery test) + (typeEqualityBase64Response "purs.json") (typeEqualityBase64Response "bower.json") (typeEqualityBase64Response "LICENSE") (typeEquality404Response "spago.yaml") - (typeEquality404Response "spago.dhall") - (typeEquality404Response "purs.json") (typeEquality404Response "package.json") - # Slug package (slug@3.0.0) - uses spago.dhall manifest format - (slugBase64Response "spago.dhall") - (slugBase64Response "packages.dhall") + # Slug package (slug@3.0.0) - uses spago.yaml manifest format + (slugBase64Response "spago.yaml") (slugBase64Response "LICENSE") (slug404Response "purs.json") - (slug404Response "spago.yaml") (slug404Response "bower.json") (slug404Response "package.json") { @@ -440,7 +461,7 @@ let } # Tags for type-equality package (used by two scheduler tests): # 1. Transfer detection: metadata says purescript, commit URLs point to new-owner - # 2. Legacy imports: v4.0.2 is a new version not yet published + # 2. Tag discovery: v4.0.2 is a new version not yet published { request = { method = "GET"; @@ -462,7 +483,7 @@ let name = "v4.0.2"; commit = { sha = "type-eq-sha-402"; - # New version not yet published - scheduler detects for legacy import + # New version not yet published - discovered by the daily importer url = "https://api.github.com/repos/new-owner/purescript-type-equality/commits/type-eq-sha-402"; }; } @@ -1103,15 +1124,23 @@ let gitbot -C "$FIXTURES_DIR/purescript/package-sets" tag -m "psc-0.15.9-20230105" psc-0.15.9-20230105 gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" tag -m "v4.0.0" v4.0.0 - # Create a new commit for v4.0.1 so it's on a different commit than v4.0.0 - gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" commit --allow-empty -m "v4.0.1 release" + # Each source tag must carry a manifest with the matching version and ref. + jq '.version = "4.0.1" | .ref = "v4.0.1"' \ + "$FIXTURES_DIR/purescript/purescript-effect/purs.json" > temp.json + mv temp.json "$FIXTURES_DIR/purescript/purescript-effect/purs.json" + gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" add purs.json + gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" commit -m "v4.0.1 release" gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" tag -m "v4.0.1" v4.0.1 gitbot -C "$FIXTURES_DIR/purescript/purescript-console" tag -m "v6.1.0" v6.1.0 gitbot -C "$FIXTURES_DIR/purescript/purescript-unsafe-coerce" tag -m "v6.0.0" v6.0.0 gitbot -C "$FIXTURES_DIR/purescript/purescript-type-equality" tag -m "v4.0.1" v4.0.1 # Create a new commit for v4.0.2 so it's on a different commit than v4.0.1 # (the registry rejects publishing when multiple version tags point to the same commit) - gitbot -C "$FIXTURES_DIR/purescript/purescript-type-equality" commit --allow-empty -m "v4.0.2 release" + jq '.version = "4.0.2" | .ref = "v4.0.2"' \ + "$FIXTURES_DIR/purescript/purescript-type-equality/purs.json" > temp.json + mv temp.json "$FIXTURES_DIR/purescript/purescript-type-equality/purs.json" + gitbot -C "$FIXTURES_DIR/purescript/purescript-type-equality" add purs.json + gitbot -C "$FIXTURES_DIR/purescript/purescript-type-equality" commit -m "v4.0.2 release" gitbot -C "$FIXTURES_DIR/purescript/purescript-type-equality" tag -m "v4.0.2" v4.0.2 gitbot -C "$FIXTURES_DIR/purescript/purescript-slug" tag -m "v3.0.0" v3.0.0 ''; diff --git a/package-lock.json b/package-lock.json index ef16e7d86..4b2064a0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "fuse.js": "^7.5.0", "jsonrepair": "^3.15.0", "registry-lib": "file:../lib", - "semver": "^7.8.5", "tar": "^7.5.22", "tmp": "^0.2.7", "yaml": "^2.9.1" @@ -1120,18 +1119,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", From 1cc1123eed1c594c4de58036d845947276c89542 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Fri, 23 Oct 2026 00:09:24 +0000 Subject: [PATCH 2/3] Stop mirroring registry sets to legacy package sets Remove legacy repository writes, JSON and Dhall conversion, compatibility maps, and release tags. Retain modern registry package sets and assert they publish without a legacy checkout or API requests. --- SPEC.md | 2 + .../src/Test/E2E/Endpoint/PackageSets.purs | 21 +- .../package-sets/latest-compatible-sets.json | 3 - app/fixtures/package-sets/packages.json | 12 - app/fixtures/package-sets/src/packages.dhall | 11 - app/src/App/API.purs | 4 +- app/src/App/Effect/Registry.purs | 181 +------------- app/src/App/Legacy/PackageSet.purs | 225 ------------------ app/src/App/Legacy/Types.purs | 46 ---- app/test/App/Effect/Registry.purs | 1 - app/test/App/Legacy/PackageSet.purs | 193 --------------- app/test/Main.purs | 2 - app/test/Test/Assert/Run.purs | 4 - nix/test/config.nix | 20 +- 14 files changed, 25 insertions(+), 700 deletions(-) delete mode 100644 app/fixtures/package-sets/latest-compatible-sets.json delete mode 100644 app/fixtures/package-sets/packages.json delete mode 100644 app/fixtures/package-sets/src/packages.dhall delete mode 100644 app/src/App/Legacy/PackageSet.purs delete mode 100644 app/test/App/Legacy/PackageSet.purs diff --git a/SPEC.md b/SPEC.md index f30ffcfd2..8f1b7b1b6 100644 --- a/SPEC.md +++ b/SPEC.md @@ -482,6 +482,8 @@ If there are no published or unpublished versions then these fields contain empt **[Source](./lib/src/Registry/PackageSet.purs)** +The registry publishes JSON package sets only. It no longer mirrors sets to `purescript/package-sets`: no new `packages.dhall`, legacy `packages.json`, compatibility-map updates, or legacy release tags are produced. Previously published legacy sets and release assets are not modified by this retirement. + Package sets are stored in the `registry` repository under the `package-sets` directory. For example, the first package set was published at https://github.com/purescript/registry/blob/main/package-sets/0.0.1.json. Each package set is stored as a JSON file with the following fields: 1. `version`, which is a string containing the package set [`Version`](#version) for this set, which follows the version rules described in [Releasing the Package Set](#releasing-the-package-set). diff --git a/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs b/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs index 14b85abec..2061d88de 100644 --- a/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs +++ b/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs @@ -3,19 +3,25 @@ module Test.E2E.Endpoint.PackageSets (spec) where import Registry.App.Prelude import Control.Monad.Reader (ask) +import Data.Array as Array +import Data.Map as Map import Effect.Aff as Aff +import Node.FS.Sync as FS.Sync +import Node.Path as Path import Registry.API.V1 as V1 +import Registry.PackageSet as PackageSet import Registry.Test.Assert as Assert import Test.E2E.Support.Client as Client import Test.E2E.Support.Env (E2ESpec) import Test.E2E.Support.Env as Env import Test.E2E.Support.Fixtures as Fixtures +import Test.E2E.Support.WireMock as WireMock import Test.Spec as Spec spec :: E2ESpec spec = do Spec.describe "Package Sets endpoint" do - Spec.it "accepts unauthenticated add/upgrade requests" do + Spec.it "releases a registry package set without accessing legacy sets" do -- First publish unsafe-coerce to create the tarball in storage { jobId: publishJobId } <- Client.publish Fixtures.unsafeCoercePublishData _ <- Env.pollJobOrFail publishJobId @@ -24,6 +30,19 @@ spec = do job <- Env.pollJobOrFail jobId Assert.shouldSatisfy (V1.jobInfo job).finishedAt isJust + { stateDir } <- ask + let setPath = Path.concat [ stateDir, "scratch", "registry", "package-sets", "0.1.0.json" ] + liftAff (readJsonFile PackageSet.codec setPath) >>= case _ of + Left err -> Assert.fail $ "Expected a released registry package set: " <> err + Right (PackageSet set) -> do + Map.lookup Fixtures.unsafeCoerce.name set.packages `Assert.shouldEqual` Just Fixtures.unsafeCoerce.version + Map.lookup Fixtures.prelude.name set.packages `Assert.shouldEqual` Just Fixtures.prelude.version + + legacyCheckout <- liftEffect $ FS.Sync.exists $ Path.concat [ stateDir, "scratch", "package-sets" ] + legacyCheckout `Assert.shouldEqual` false + requests <- WireMock.getGithubRequests + Array.null (WireMock.filterByUrlContaining "/repos/purescript/package-sets" requests) `Assert.shouldEqual` true + Spec.it "rejects unauthenticated compiler change requests" do result <- Client.tryPackageSets Fixtures.packageSetCompilerChangeRequest case result of diff --git a/app/fixtures/package-sets/latest-compatible-sets.json b/app/fixtures/package-sets/latest-compatible-sets.json deleted file mode 100644 index 5cdbbb9c2..000000000 --- a/app/fixtures/package-sets/latest-compatible-sets.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "0.15.10": "psc-0.15.10-20230105" -} diff --git a/app/fixtures/package-sets/packages.json b/app/fixtures/package-sets/packages.json deleted file mode 100644 index dab6427a3..000000000 --- a/app/fixtures/package-sets/packages.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "metadata": { - "dependencies": [], - "repo": "https://github.com/purescript/purescript-metadata.git", - "version": "v0.15.9" - }, - "prelude": { - "dependencies": [], - "repo": "https://github.com/purescript/purescript-prelude.git", - "version": "v6.0.1" - } -} diff --git a/app/fixtures/package-sets/src/packages.dhall b/app/fixtures/package-sets/src/packages.dhall deleted file mode 100644 index 352fa3e2a..000000000 --- a/app/fixtures/package-sets/src/packages.dhall +++ /dev/null @@ -1,11 +0,0 @@ -{ metadata = - { dependencies = [] : List Text - , repo = "https://github.com/purescript/purescript-metadata.git" - , version = "v0.15.10" - } -, prelude = - { dependencies = [] : List Text - , repo = "https://github.com/purescript/purescript-prelude.git" - , version = "v6.0.1" - } -} diff --git a/app/src/App/API.purs b/app/src/App/API.purs index 9a52cad0e..4b48774a5 100644 --- a/app/src/App/API.purs +++ b/app/src/App/API.purs @@ -237,9 +237,7 @@ packageSetUpdate details = do Log.warn $ "Some packages could not be added to the set:\n" <> formatFailed let commitMessage = PackageSets.commitMessage latestPackageSet succeeded (un PackageSet packageSet).version Registry.writePackageSet packageSet commitMessage - Log.notice "Built and released a new package set! Now mirroring to the package-sets repo..." - Registry.mirrorPackageSet packageSet - Log.notice "Mirrored a new legacy package set." + Log.notice "Built and released a new package set!" type AuthenticatedEffects r = (REGISTRY + STORAGE + GITHUB + PACCHETTIBOTTI_ENV + LOG + EXCEPT String + AFF + EFFECT + r) diff --git a/app/src/App/Effect/Registry.purs b/app/src/App/Effect/Registry.purs index 4314990f7..e5419d3f0 100644 --- a/app/src/App/Effect/Registry.purs +++ b/app/src/App/Effect/Registry.purs @@ -26,17 +26,11 @@ import Registry.App.CLI.Git (GitResult) import Registry.App.CLI.Git as Git import Registry.App.Effect.Cache (class MemoryEncodable, Cache, CacheRef, MemoryEncoding(..)) import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.GitHub (GITHUB) -import Registry.App.Effect.GitHub as GitHub import Registry.App.Effect.Log (LOG) import Registry.App.Effect.Log as Log -import Registry.App.Legacy.PackageSet (PscTag(..)) -import Registry.App.Legacy.PackageSet as Legacy.PackageSet -import Registry.App.Legacy.Types (legacyPackageSetCodec) import Registry.Constants as Constants import Registry.Foreign.FastGlob as FastGlob import Registry.Foreign.Octokit (Address) -import Registry.Foreign.Octokit as Octokit import Registry.Internal.Codec as Internal.Codec import Registry.Manifest as Manifest import Registry.ManifestIndex as ManifestIndex @@ -83,7 +77,6 @@ data RegistryWrite a | DeleteManifest PackageName Version (Either String Unit -> a) | WriteMetadata PackageName Metadata (Either String Unit -> a) | WritePackageSet PackageSet String (Either String Unit -> a) - | MirrorPackageSet PackageSet (Either String Unit -> a) derive instance Functor RegistryWrite @@ -144,10 +137,6 @@ writePackageSet set message = Except.rethrow =<< Run.lift _registryWrite (WriteP readAllPackageSets :: forall r. Run (REGISTRY_READ + EXCEPT String + r) (Map Version PackageSet) readAllPackageSets = Except.rethrow =<< Run.lift _registryRead (ReadAllPackageSets identity) --- | Mirror a package set to the legacy package-sets repo -mirrorPackageSet :: forall r. PackageSet -> Run (REGISTRY_WRITE + EXCEPT String + r) Unit -mirrorPackageSet set = Except.rethrow =<< Run.lift _registryWrite (MirrorPackageSet set identity) - interpretRead :: forall r a. (RegistryRead ~> Run r) -> Run (REGISTRY_READ + r) a -> Run r a interpretRead handler = Run.interpret (Run.on _registryRead handler Run.send) @@ -158,7 +147,6 @@ interpretWrite handler = Run.interpret (Run.on _registryWrite handler Run.send) data RepoKey = RegistryRepo | ManifestIndexRepo - | LegacyPackageSetsRepo -- | A legend for values that can be committed. We know where each kind of value -- | ought to exist, so we can create a correct path for any given type ourselves. @@ -168,7 +156,6 @@ data CommitKey | CommitManifestIndex | CommitMetadataIndex | CommitPackageSet Version - | CommitLegacyPackageSets (Array FilePath) -- | Get the pattern representing the paths that should be committed for each -- | commit key, relative to the root of the repository. Suitable to be passed @@ -185,8 +172,6 @@ commitKeyToPaths = coerce <<< case _ of [ Constants.metadataDirectory <> Path.sep <> "*.json" ] CommitPackageSet version -> [ Path.concat [ Constants.packageSetsDirectory, Version.print version <> ".json" ] ] - CommitLegacyPackageSets paths -> - paths commitKeyToRepoKey :: CommitKey -> RepoKey commitKeyToRepoKey = case _ of @@ -195,7 +180,6 @@ commitKeyToRepoKey = case _ of CommitManifestIndex -> ManifestIndexRepo CommitMetadataIndex -> RegistryRepo CommitPackageSet _ -> RegistryRepo - CommitLegacyPackageSets _ -> LegacyPackageSetsRepo data WriteMode = ReadOnly | CommitAs Git.Committer @@ -219,7 +203,6 @@ newDebouncer = liftEffect $ Ref.new Map.empty type Repos = { registry :: Address , manifestIndex :: Address - , legacyPackageSets :: Address } -- | The default repos to use with the Registry effect handler @@ -227,7 +210,6 @@ defaultRepos :: Repos defaultRepos = { registry: Constants.registry , manifestIndex: Constants.manifestIndex - , legacyPackageSets: Legacy.PackageSet.legacyPackageSetsRepo } -- | Get the upstream address associated with a repository key. @@ -235,14 +217,12 @@ repoAddress :: RegistryEnv -> RepoKey -> Address repoAddress env = case _ of RegistryRepo -> env.repos.registry ManifestIndexRepo -> env.repos.manifestIndex - LegacyPackageSetsRepo -> env.repos.legacyPackageSets -- | Get the local path for the checkout associated with a repository key. repoPath :: RegistryEnv -> RepoKey -> FilePath repoPath env = case _ of RegistryRepo -> Path.concat [ env.workdir, "registry" ] ManifestIndexRepo -> Path.concat [ env.workdir, "registry-index" ] - LegacyPackageSetsRepo -> Path.concat [ env.workdir, "package-sets" ] -- | Get the repository at the given key, recording whether the pull or clone -- | had any effect (ie. if the repo was already up-to-date). @@ -471,7 +451,7 @@ handleRead env = Cache.interpret _registryCache (Cache.handleMemory env.cacheRef -- | Writes can optionally commit and push to the upstream Git repository. -- | -- | This handler uses the same memory-only cache as the read handler. -handleWrite :: forall r a. RegistryEnv -> RegistryWrite a -> Run (GITHUB + LOG + AFF + EFFECT + r) a +handleWrite :: forall r a. RegistryEnv -> RegistryWrite a -> Run (LOG + AFF + EFFECT + r) a handleWrite env = Cache.interpret _registryCache (Cache.handleMemory env.cacheRef) <<< case _ of WriteManifest manifest@(Manifest { name, version }) reply -> map (map reply) Except.runExcept do let formatted = formatPackageVersion name version @@ -558,110 +538,6 @@ handleWrite env = Cache.interpret _registryCache (Cache.handleMemory env.cacheRe Right Git.NoChange -> Log.info "Did not commit package set because it was unchanged." Right Git.Changed -> Log.info "Wrote and committed package set." - -- https://github.com/purescript/package-sets/blob/psc-0.15.4-20220829/release.sh - -- https://github.com/purescript/package-sets/blob/psc-0.15.4-20220829/update-latest-compatible-sets.sh - MirrorPackageSet set@(PackageSet { version }) reply -> map (map reply) Except.runExcept do - let name = Version.print version - Log.info $ "Mirroring legacy package set " <> name <> " to the legacy package sets repo" - - manifests <- Except.rethrow =<< handleRead env (ReadAllManifests identity) - - Log.debug $ "Converting package set..." - converted <- case Legacy.PackageSet.convertPackageSet manifests set of - Left error -> Except.throw $ "Failed to convert package set " <> name <> " to a legacy package set: " <> error - Right converted -> pure converted - - let printedTag = Legacy.PackageSet.printPscTag converted.tag - let legacyRepo = repoAddress env LegacyPackageSetsRepo - - packageSetsTags <- GitHub.listTags legacyRepo >>= case _ of - Left githubError -> - Except.throw $ Array.fold - [ "Could not mirror package set " <> name - , " because fetching tags from the legacy package-sets repo (" - , legacyRepo.owner <> "/" <> legacyRepo.repo - , ") failed: " <> Octokit.printGitHubError githubError - ] - Right tags -> pure $ Set.fromFoldable $ map _.name tags - - when (Set.member printedTag packageSetsTags) do - Except.throw $ "Could not mirror package set " <> name <> " because the tag " <> printedTag <> " already exists." - - -- We need to write three files to the package sets repository: - -- - -- * latest-compatible-sets.json - -- stores a mapping of compiler versions to their highest compatible tag - -- - -- * packages.json - -- stores the JSON representation of the latest package set - -- - -- * src/packages.dhall - -- stores the Dhall representation of the latest package set - let latestSetsPath = "latest-compatible-sets.json" - let packagesJsonPath = "packages.json" - let dhallPath = Path.concat [ "src", "packages.dhall" ] - let files = [ latestSetsPath, packagesJsonPath, dhallPath ] - let compilerKey = (un PscTag converted.tag).compiler - - commitFilesResult <- writeCommitPush (CommitLegacyPackageSets files) \legacyPath -> do - latestCompatibleSets <- do - latestSets <- Run.liftAff (readJsonFile Legacy.PackageSet.latestCompatibleSetsCodec (Path.concat [ legacyPath, latestSetsPath ])) >>= case _ of - Left err -> Except.throw $ "Could not mirror package set because reading the latest compatible sets file from " <> latestSetsPath <> " failed: " <> err - Right parsed -> pure parsed - - case Map.lookup compilerKey latestSets of - Just existingTag | existingTag == converted.tag -> do - Log.warn $ "Not updating latest-compatible-sets.json because the tag " <> printedTag <> " already exists." - pure latestSets - Just existingTag | existingTag > converted.tag -> do - Log.warn $ Array.fold - [ "Not updating latest-compatible-sets.json because an existing tag (" - , Legacy.PackageSet.printPscTag existingTag - , ") is higher than the tag we are pushing (" - , Legacy.PackageSet.printPscTag converted.tag - , ")." - ] - pure latestSets - _ -> - pure $ Map.insert compilerKey converted.tag latestSets - - -- Next we need to write the files that will be pushed to the package-sets repo - Log.debug $ "Writing " <> dhallPath - let fullDhallPath = Path.concat [ legacyPath, dhallPath ] - Run.liftAff $ FS.Aff.writeTextFile UTF8 fullDhallPath (Legacy.PackageSet.printDhall converted.packageSet <> "\n") - - Log.debug $ "Writing " <> packagesJsonPath - let fullPackagesJsonPath = Path.concat [ legacyPath, packagesJsonPath ] - Run.liftAff $ writeJsonFile legacyPackageSetCodec fullPackagesJsonPath converted.packageSet - - Log.debug $ "Writing " <> latestSetsPath - let fullLatestSetsPath = Path.concat [ legacyPath, latestSetsPath ] - Run.liftAff $ writeJsonFile Legacy.PackageSet.latestCompatibleSetsCodec fullLatestSetsPath latestCompatibleSets - - pure $ Just $ "Update to the " <> name <> " package set." - - case commitFilesResult of - Left error -> Except.throw $ "Failed to commit to legacy registry:" <> error - Right Git.NoChange -> Log.info "Did not commit legacy registry files because nothing has changed." - Right Git.Changed -> do - Log.info "Committed legacy registry files." - -- Now that we've written and pushed our commit, we also need to push some - -- tags to trigger the legacy package sets release workflow. - tagResult <- tagAndPush LegacyPackageSetsRepo do - -- We push the stable tag (ie. just a compiler version) if one does not yet - -- exist, and we always push the full tag. - let stable = Version.print compilerKey - Array.catMaybes - [ Just printedTag - , if Set.member stable packageSetsTags then Nothing else Just stable - ] - case tagResult of - Left error -> - Except.throw $ "Failed to push tags to legacy registry: " <> error - Right Git.NoChange -> - Log.warn $ "Tried to push tags to legacy registry, but there was no effect (they already existed)." - Right Git.Changed -> - Log.info "Pushed new tags to legacy registry." where -- | Write a file to the repository associated with the commit key, given a -- | callback that takes the file path of the repository on disk, writes the @@ -680,16 +556,6 @@ handleWrite env = Cache.interpret _registryCache (Cache.handleMemory env.cacheRe Left error -> pure (Left error) Right _ -> push repoKey - -- | Tag the repository with the given tags and push the result upstream. - tagAndPush :: RepoKey -> Array String -> Run _ (Either String GitResult) - tagAndPush key refs = do - results <- traverse (tag key) refs - let partition = partitionEithers results - case Array.uncons partition.fail of - Nothing | Array.any (_ == Git.Changed) partition.success -> pushTags key - Nothing -> pure (Right Git.NoChange) - Just { head } -> pure (Left head) - -- | Commit the file(s) indicated by the commit key with a commit message. commit :: CommitKey -> String -> Run _ (Either String GitResult) commit commitKey message' = do @@ -720,51 +586,6 @@ handleWrite env = Cache.interpret _registryCache (Cache.handleMemory env.cacheRe result <- Git.gitPush { address, committer } (repoPath env repoKey) pure result - -- | Tag the repository at the given key at its current commit with the tag - tag :: RepoKey -> String -> Run _ (Either String GitResult) - tag repoKey ref = do - let address = repoAddress env repoKey - let formatted = address.owner <> "/" <> address.repo - case env.write of - ReadOnly -> do - Log.info $ "Skipping push to repo " <> formatted <> " because write mode is 'ReadOnly'." - pure $ Right Git.NoChange - CommitAs committer -> do - result <- Except.runExcept do - let cwd = repoPath env repoKey - existingTags <- Git.withGit cwd [ "tag", "--list" ] \error -> - "Failed to list tags in local checkout " <> cwd <> ": " <> error - if Array.elem ref $ String.split (String.Pattern "\n") existingTags then do - Log.warn $ "Tag " <> ref <> " already exists." - pure Git.NoChange - else do - _ <- Git.withGit cwd [ "config", "user.name", committer.name ] \error -> - "Failed to configure git user name as " <> committer.name <> " in " <> cwd <> ": " <> error - _ <- Git.withGit cwd [ "config", "user.email", "<" <> committer.email <> ">" ] \error -> - "Failed to configure git user email as " <> committer.email <> " in " <> cwd <> ": " <> error - _ <- Git.withGit cwd [ "tag", ref ] \error -> - "Failed to create new tag " <> ref <> " in local checkout " <> cwd <> ": " <> error - pure Git.Changed - pure result - - -- | Push the repository tags at the given key to its upstream. - pushTags :: RepoKey -> Run _ (Either String GitResult) - pushTags repoKey = do - let address = repoAddress env repoKey - let formatted = address.owner <> "/" <> address.repo - case env.write of - ReadOnly -> do - Log.info $ "Skipping push to repo " <> formatted <> " because write mode is 'ReadOnly'." - pure $ Right Git.NoChange - CommitAs committer -> do - result <- Except.runExcept do - let cwd = repoPath env repoKey - let Git.AuthOrigin authOrigin = Git.mkAuthOrigin address committer - output <- Git.withGit cwd [ "push", "--tags", authOrigin ] \error -> - "Failed to push tags in local checkout " <> cwd <> ": " <> error - if String.contains (String.Pattern "Everything up-to-date") output then pure Git.NoChange else pure Git.Changed - pure result - appendJobIdToCommitMessage :: Maybe JobId -> String -> String appendJobIdToCommitMessage maybeJobId message = case maybeJobId of Nothing -> message diff --git a/app/src/App/Legacy/PackageSet.purs b/app/src/App/Legacy/PackageSet.purs deleted file mode 100644 index 62b718d7c..000000000 --- a/app/src/App/Legacy/PackageSet.purs +++ /dev/null @@ -1,225 +0,0 @@ -module Registry.App.Legacy.PackageSet - ( ConvertedLegacyPackageSet - , LatestCompatibleSets - , PscTag(..) - , filterLegacyPackageSets - , convertPackageSet - , legacyPackageSetsRepo - , parsePscTag - , printDhall - , printPscTag - , pscTagCodec - , latestCompatibleSetsCodec - ) where - -import Registry.App.Prelude - -import Codec.JSON.DecodeError as CJ.DecodeError -import Control.Monad.Error.Class as Error -import Data.Array as Array -import Data.Codec as Codec -import Data.Codec.JSON as CJ -import Data.Compactable (separate) -import Data.DateTime (Date, DateTime(..)) -import Data.DateTime as DateTime -import Data.Filterable (filterMap) -import Data.Formatter.DateTime (FormatterCommand(..)) -import Data.Formatter.DateTime as Format.DateTime -import Data.Formatter.DateTime as Formatter.DateTime -import Data.FunctorWithIndex (mapWithIndex) -import Data.List (List(..), (:)) -import Data.Map as Map -import Data.String as String -import Data.String.CodeUnits as String.CodeUnits -import Dodo as Dodo -import Dodo.Common as Dodo.Common -import Parsing as Parsing -import Parsing.Combinators.Array as Parsing.Combinators.Array -import Parsing.String as Parsing.String -import Registry.App.Legacy.Types (LegacyPackageSet(..), LegacyPackageSetEntry, RawVersion(..)) -import Registry.Foreign.Octokit (Address, Tag) -import Registry.Internal.Codec as Internal.Codec -import Registry.Internal.Format as Internal.Format -import Registry.ManifestIndex as ManifestIndex -import Registry.PackageName as PackageName -import Registry.Version as Version - -legacyPackageSetsRepo :: Address -legacyPackageSetsRepo = { owner: "purescript", repo: "package-sets" } - -type ConvertedLegacyPackageSet = - { tag :: PscTag - , upstream :: Version - , packageSet :: LegacyPackageSet - } - --- | A package set tag for the legacy package sets. -newtype PscTag = PscTag { compiler :: Version, date :: Date } - -derive instance Newtype PscTag _ -derive instance Eq PscTag -derive instance Ord PscTag - -pscTagCodec :: CJ.Codec PscTag -pscTagCodec = CJ.named "PscTag" $ Codec.codec' decode encode - where - decode json = do - tagStr <- Codec.decode CJ.string json - except $ lmap CJ.DecodeError.basic $ parsePscTag tagStr - - encode = - CJ.encode CJ.string <<< printPscTag - -pscDateFormat :: List FormatterCommand -pscDateFormat = YearFull : MonthTwoDigits : DayOfMonthTwoDigits : Nil - -parsePscTag :: String -> Either String PscTag -parsePscTag = lmap Parsing.parseErrorMessage <<< flip Parsing.runParser do - _ <- Parsing.String.string "psc-" - version <- parseVersion =<< charsUntilHyphen - date <- Parsing.String.rest - case Format.DateTime.unformat pscDateFormat date of - Left err -> - Parsing.fail $ "Expected an 8-digit date such as '20220101': " <> err - Right parsedDate -> - pure $ PscTag { compiler: version, date: DateTime.date parsedDate } - where - parseVersion = - Error.liftEither <<< flip Parsing.runParser Version.parser - - charsUntilHyphen = - map String.CodeUnits.fromCharArray - $ map fst - $ Parsing.Combinators.Array.manyTill_ Parsing.String.anyChar - $ Parsing.String.char '-' - -printPscTag :: PscTag -> String -printPscTag (PscTag { compiler, date }) = - Array.fold - [ "psc-" - , Version.print compiler - , "-" - , Format.DateTime.format pscDateFormat (DateTime date bottom) - ] - -convertPackageSet :: ManifestIndex -> PackageSet -> Either String ConvertedLegacyPackageSet -convertPackageSet index (PackageSet { compiler, packages, published, version }) = do - converted <- case separate $ mapWithIndex convertPackage packages of - { left, right } | Map.isEmpty left -> Right right - { left } -> do - let toValues = Array.fromFoldable <<< Map.values - Left $ String.joinWith "\n" $ Array.cons "Failed to convert packages:" $ toValues left - - let converted' = Map.insert (unsafeFromRight (PackageName.parse "metadata")) metadataPackage converted - pure { tag, upstream: version, packageSet: LegacyPackageSet converted' } - where - tag :: PscTag - tag = PscTag { compiler, date: published } - - -- Legacy package sets determine their compiler version by the version of - -- the 'metadata' package. - metadataPackage :: LegacyPackageSetEntry - metadataPackage = - { repo: "https://github.com/purescript/purescript-metadata.git" - , version: RawVersion ("v" <> Version.print compiler) - , dependencies: [] - } - - convertPackage :: PackageName -> Version -> Either String LegacyPackageSetEntry - convertPackage packageName packageVersion = do - versions <- note noIndexPackageError $ Map.lookup packageName $ ManifestIndex.toMap index - Manifest manifest <- note noIndexVersionError $ Map.lookup packageVersion versions - - repo <- case manifest.location of - GitHub { owner, repo, subdir: Nothing } -> Right $ "https://github.com/" <> owner <> "/" <> repo <> ".git" - Git { url, subdir: Nothing } -> Right url - GitHub _ -> Left usesSubdirError - Git _ -> Left usesSubdirError - - pure - { version: RawVersion manifest.ref - , dependencies: Array.fromFoldable $ Map.keys $ manifest.dependencies - , repo - } - where - nameStr = PackageName.print packageName - versionStr = Version.print packageVersion - noIndexPackageError = "No registry index entry found for " <> nameStr - noIndexVersionError = "Found registry index entry for " <> nameStr <> " but none for version " <> versionStr - usesSubdirError = "Package " <> nameStr <> " uses the 'subdir' key, which is not supported for legacy package sets." - -printDhall :: LegacyPackageSet -> String -printDhall (LegacyPackageSet entries) = do - let format = Dodo.twoSpaces { pageWidth = 80 } - let inputs = Map.toUnfoldable entries - Dodo.print Dodo.plainText format (printPackageSet inputs) - where - printPackageSet :: forall a. Array (Tuple PackageName LegacyPackageSetEntry) -> Dodo.Doc a - printPackageSet packages = - Dodo.Common.pursCurlies - $ Dodo.foldWithSeparator Dodo.Common.leadingComma - $ map printPackage packages - - quoteReserved :: String -> String - quoteReserved input = do - -- We can't use any reserved keywords: - -- https://docs.dhall-lang.org/references/Built-in-types.html#built-in-types-functions-and-operators - if input `Array.elem` [ "assert", "let", "using", "missing", "merge", "if", "then", "else" ] then - Array.fold [ "`", input, "`" ] - else - input - - printPackage :: forall a. Tuple PackageName LegacyPackageSetEntry -> Dodo.Doc a - printPackage (Tuple name entry) = - Array.fold - [ Dodo.text (quoteReserved (PackageName.print name)) - , Dodo.text " =" - , Dodo.break - , Dodo.indent (printEntry entry) - ] - - printEntry :: forall a. LegacyPackageSetEntry -> Dodo.Doc a - printEntry entry = - Dodo.Common.pursCurlies $ Dodo.foldWithSeparator Dodo.Common.leadingComma - [ Dodo.text "dependencies =" <> breakAlign (printDependencies entry.dependencies) - , Dodo.text "repo =" <> breakAlign (quoteString entry.repo) - , Dodo.text "version =" <> breakAlign (printVersion entry.version) - ] - - breakAlign :: forall a. Dodo.Doc a -> Dodo.Doc a - breakAlign doc = - Dodo.flexGroup (Dodo.spaceBreak <> Dodo.align 2 doc) - - printDependencies :: forall a. Array PackageName -> Dodo.Doc a - printDependencies dependencies = - if Array.null dependencies then - Dodo.text "[] : List Text" - else - Dodo.Common.pursSquares - $ Dodo.foldWithSeparator Dodo.Common.leadingComma - $ map (quoteString <<< PackageName.print) dependencies - - printVersion :: forall a. RawVersion -> Dodo.Doc a - printVersion (RawVersion version) = quoteString version - - quoteString :: forall a. String -> Dodo.Doc a - quoteString = Dodo.enclose (Dodo.text "\"") (Dodo.text "\"") <<< Dodo.text - -type LatestCompatibleSets = Map Version PscTag - -latestCompatibleSetsCodec :: CJ.Codec LatestCompatibleSets -latestCompatibleSetsCodec = Internal.Codec.versionMap pscTagCodec - --- | Filter the package sets to only those published before the registry was --- | launched. -filterLegacyPackageSets :: Array Tag -> Array String -filterLegacyPackageSets tags = do - let - -- Package sets after this date are published by the registry, and are - -- therefore not legacy package sets. - lastLegacyDate = unsafeFromRight $ Formatter.DateTime.unformat Internal.Format.iso8601Date "2022-09-01" - legacyTag { name } = case parsePscTag name of - Right (PscTag { date }) | date <= (DateTime.date lastLegacyDate) -> Just name - _ -> Nothing - - filterMap legacyTag tags diff --git a/app/src/App/Legacy/Types.purs b/app/src/App/Legacy/Types.purs index 6783bc0c5..ac184500b 100644 --- a/app/src/App/Legacy/Types.purs +++ b/app/src/App/Legacy/Types.purs @@ -3,48 +3,8 @@ module Registry.App.Legacy.Types where import Registry.App.Prelude import Data.Codec.JSON as CJ -import Data.Codec.JSON.Record as CJ.Record import Data.Profunctor as Profunctor import Registry.Internal.Codec as Internal.Codec -import Registry.PackageName as PackageName - --- | The format of a legacy packages.json package set file -newtype LegacyPackageSet = LegacyPackageSet (Map PackageName LegacyPackageSetEntry) - -derive instance Newtype LegacyPackageSet _ -derive newtype instance Eq LegacyPackageSet - -legacyPackageSetCodec :: CJ.Codec LegacyPackageSet -legacyPackageSetCodec = - Profunctor.wrapIso LegacyPackageSet - $ Internal.Codec.packageMap legacyPackageSetEntryCodec - --- | The union of all legacy package sets, with packages represented at all --- | versions they have in the package sets. -type LegacyPackageSetUnion = Map PackageName (Map RawVersion (Map PackageName { min :: RawVersion, max :: RawVersion })) - -legacyPackageSetUnionCodec :: CJ.Codec LegacyPackageSetUnion -legacyPackageSetUnionCodec = Internal.Codec.packageMap $ rawVersionMapCodec $ Internal.Codec.packageMap - $ CJ.named "LenientBounds" - $ CJ.Record.object - { min: rawVersionCodec - , max: rawVersionCodec - } - --- | The format of a legacy packages.json package set entry for an individual --- | package. -type LegacyPackageSetEntry = - { dependencies :: Array PackageName - , repo :: String - , version :: RawVersion - } - -legacyPackageSetEntryCodec :: CJ.Codec LegacyPackageSetEntry -legacyPackageSetEntryCodec = CJ.named "LegacyPackageSetEntry" $ CJ.Record.object - { dependencies: CJ.array PackageName.codec - , repo: CJ.string - , version: Profunctor.wrapIso RawVersion CJ.string - } -- | An unprocessed package name, which may possibly be malformed. newtype RawPackageName = RawPackageName String @@ -65,9 +25,3 @@ newtype RawVersion = RawVersion String derive instance Newtype RawVersion _ derive newtype instance Eq RawVersion derive newtype instance Ord RawVersion - -rawVersionCodec :: CJ.Codec RawVersion -rawVersionCodec = Profunctor.wrapIso RawVersion CJ.string - -rawVersionMapCodec :: forall a. CJ.Codec a -> CJ.Codec (Map RawVersion a) -rawVersionMapCodec = Internal.Codec.strMap "RawVersionMap" (Right <<< RawVersion) (un RawVersion) diff --git a/app/test/App/Effect/Registry.purs b/app/test/App/Effect/Registry.purs index 268cf71a2..cd1ab3bfc 100644 --- a/app/test/App/Effect/Registry.purs +++ b/app/test/App/Effect/Registry.purs @@ -72,7 +72,6 @@ spec = do , repos: { registry: { owner: "test", repo: "test" } , manifestIndex: { owner: "test", repo: "test" } - , legacyPackageSets: { owner: "test", repo: "test" } } , workdir: tmp , pull: Git.ForceClean diff --git a/app/test/App/Legacy/PackageSet.purs b/app/test/App/Legacy/PackageSet.purs deleted file mode 100644 index 2d4a7a2dc..000000000 --- a/app/test/App/Legacy/PackageSet.purs +++ /dev/null @@ -1,193 +0,0 @@ -module Test.Registry.App.Legacy.PackageSet (spec) where - -import Registry.App.Prelude - -import Data.Either as Either -import Data.Map as Map -import Data.Set as Set -import Registry.App.Legacy.LenientVersion (LenientVersion) -import Registry.App.Legacy.LenientVersion as LenientVersion -import Registry.App.Legacy.PackageSet (ConvertedLegacyPackageSet, LatestCompatibleSets, latestCompatibleSetsCodec, parsePscTag, printPscTag) -import Registry.App.Legacy.PackageSet as Legacy.PackageSet -import Registry.App.Legacy.Types (legacyPackageSetCodec) -import Registry.ManifestIndex as ManifestIndex -import Registry.PackageName as PackageName -import Registry.Test.Assert as Assert -import Registry.Test.Utils as Utils -import Registry.Version as Version -import Test.Spec as Spec - -spec :: Spec.Spec Unit -spec = do - Spec.it "Parses legacy package set tags" do - let valid = "psc-0.12.18-20220102" - let invalid = "psc-0.14.3.1-2022-01-02" - let roundtrip = map printPscTag <<< parsePscTag - roundtrip valid `Assert.shouldContain` valid - roundtrip invalid `Assert.shouldSatisfy` Either.isLeft - - Spec.it "Parses legacy 'latest compatible set' files" do - let - parsed :: Either _ LatestCompatibleSets - parsed = parseJson latestCompatibleSetsCodec latestCompatibleSets - - map (printJson latestCompatibleSetsCodec) parsed `Assert.shouldContain` latestCompatibleSets - - Spec.it "Produces correct legacy package set name" do - printPscTag convertedPackageSet.tag `Assert.shouldEqual` "psc-0.15.2-20220725" - - Spec.it "Decodes legacy package set" do - parseJson legacyPackageSetCodec legacyPackageSetJson `Assert.shouldContain` convertedPackageSet.packageSet - - Spec.it "Encodes legacy package set as JSON" do - printJson legacyPackageSetCodec convertedPackageSet.packageSet `Assert.shouldEqual` legacyPackageSetJson - - Spec.it "Encodes legacy package set as Dhall" do - Legacy.PackageSet.printDhall convertedPackageSet.packageSet `Assert.shouldEqual` legacyPackageSetDhall - -latestCompatibleSets :: String -latestCompatibleSets = - """{ - "0.12.0": "psc-0.12.0-20181024", - "0.12.1": "psc-0.12.1-20190107", - "0.12.2": "psc-0.12.2-20190210", - "0.12.3": "psc-0.12.3-20190409", - "0.12.4": "psc-0.12.4-20190418", - "0.12.5": "psc-0.12.5-20190525", - "0.13.0": "psc-0.13.0-20190713", - "0.13.2": "psc-0.13.2-20190815", - "0.13.3": "psc-0.13.3-20191005", - "0.13.4": "psc-0.13.4-20191125", - "0.13.5": "psc-0.13.5-20200103", - "0.13.6": "psc-0.13.6-20200507", - "0.13.8": "psc-0.13.8-20210226", - "0.14.0": "psc-0.14.0-20210409", - "0.14.1": "psc-0.14.1-20210613", - "0.14.2": "psc-0.14.2-20210713", - "0.14.3": "psc-0.14.3-20210825", - "0.14.4": "psc-0.14.4-20211109", - "0.14.5": "psc-0.14.5-20220224", - "0.14.6": "psc-0.14.6-20220228", - "0.14.7": "psc-0.14.7-20220418", - "0.15.0": "psc-0.15.0-20220527", - "0.15.2": "psc-0.15.2-20220706", - "0.15.3": "psc-0.15.3-20220712", - "0.15.4": "psc-0.15.4-20220829" -}""" - -packageSet :: PackageSet -packageSet = PackageSet - { compiler: Utils.unsafeVersion "0.15.2" - , published: Utils.unsafeDate "2022-07-25" - , version: Utils.unsafeVersion "4.2.0" - , packages: map LenientVersion.version $ Map.fromFoldable do - [ assert - , console - , effect - , prelude - ] - } - -convertedPackageSet :: ConvertedLegacyPackageSet -convertedPackageSet = - case Legacy.PackageSet.convertPackageSet index packageSet of - Left err -> unsafeCrashWith err - Right value -> value - where - index = unsafeFromRight $ ManifestIndex.fromSet ManifestIndex.ConsiderRanges $ Set.fromFoldable - [ mkManifest assert [ console, effect, prelude ] - , mkManifest console [ effect, prelude ] - , mkManifest effect [ prelude ] - , mkManifest prelude [] - ] - -legacyPackageSetJson :: String -legacyPackageSetJson = - """{ - "assert": { - "dependencies": [ - "console", - "effect", - "prelude" - ], - "repo": "https://github.com/purescript/purescript-assert.git", - "version": "v6.0.0" - }, - "console": { - "dependencies": [ - "effect", - "prelude" - ], - "repo": "https://github.com/purescript/purescript-console.git", - "version": "v6.0.0" - }, - "effect": { - "dependencies": [ - "prelude" - ], - "repo": "https://github.com/purescript/purescript-effect.git", - "version": "v4.0.0" - }, - "metadata": { - "dependencies": [], - "repo": "https://github.com/purescript/purescript-metadata.git", - "version": "v0.15.2" - }, - "prelude": { - "dependencies": [], - "repo": "https://github.com/purescript/purescript-prelude.git", - "version": "v6.0.0" - } -}""" - -legacyPackageSetDhall :: String -legacyPackageSetDhall = - """{ `assert` = - { dependencies = [ "console", "effect", "prelude" ] - , repo = "https://github.com/purescript/purescript-assert.git" - , version = "v6.0.0" - } -, console = - { dependencies = [ "effect", "prelude" ] - , repo = "https://github.com/purescript/purescript-console.git" - , version = "v6.0.0" - } -, effect = - { dependencies = [ "prelude" ] - , repo = "https://github.com/purescript/purescript-effect.git" - , version = "v4.0.0" - } -, metadata = - { dependencies = [] : List Text - , repo = "https://github.com/purescript/purescript-metadata.git" - , version = "v0.15.2" - } -, prelude = - { dependencies = [] : List Text - , repo = "https://github.com/purescript/purescript-prelude.git" - , version = "v6.0.0" - } -}""" - -assert :: Tuple PackageName LenientVersion -assert = Tuple (Utils.unsafePackageName "assert") (unsafeLenientVersion "v6.0.0") - -console :: Tuple PackageName LenientVersion -console = Tuple (Utils.unsafePackageName "console") (unsafeLenientVersion "v6.0.0") - -effect :: Tuple PackageName LenientVersion -effect = Tuple (Utils.unsafePackageName "effect") (unsafeLenientVersion "v4.0.0") - -prelude :: Tuple PackageName LenientVersion -prelude = Tuple (Utils.unsafePackageName "prelude") (unsafeLenientVersion "v6.0.0") - -unsafeLenientVersion :: String -> LenientVersion -unsafeLenientVersion = unsafeFromRight <<< LenientVersion.parse - -mkManifest :: Tuple PackageName LenientVersion -> Array (Tuple PackageName LenientVersion) -> Manifest -mkManifest (Tuple name version) deps = do - let toRange v = ">=" <> Version.print v <> " <" <> Version.print (Version.bumpHighest v) - Utils.unsafeManifest - (PackageName.print name) - (LenientVersion.print version) - (map (bimap PackageName.print (LenientVersion.version >>> toRange)) deps) diff --git a/app/test/Main.purs b/app/test/Main.purs index c17233538..54a3c66e0 100644 --- a/app/test/Main.purs +++ b/app/test/Main.purs @@ -13,7 +13,6 @@ import Test.Registry.App.Effect.PackageSets as Test.Effect.PackageSets import Test.Registry.App.Effect.Registry as Test.Effect.Registry import Test.Registry.App.GitHubIssue as Test.GitHubIssue import Test.Registry.App.Legacy.LenientVersion as Test.Legacy.LenientVersion -import Test.Registry.App.Legacy.PackageSet as Test.Legacy.PackageSet import Test.Registry.App.Manifest.SpagoYaml as Test.Manifest.SpagoYaml import Test.Registry.App.SQLite as Test.SQLite import Test.Registry.App.Server.MatrixBuilder as Test.Server.MatrixBuilder @@ -45,7 +44,6 @@ main = runSpecAndExitProcess' config [ consoleReporter ] do Spec.describe "Registry.App.Legacy" do Spec.describe "Lenient Version" Test.Legacy.LenientVersion.spec - Spec.describe "Legacy PackageSet" Test.Legacy.PackageSet.spec Spec.describe "Registry.App.Manifest" do Spec.describe "SpagoYaml" Test.Manifest.SpagoYaml.spec diff --git a/app/test/Test/Assert/Run.purs b/app/test/Test/Assert/Run.purs index a427bd841..45a0a5e2a 100644 --- a/app/test/Test/Assert/Run.purs +++ b/app/test/Test/Assert/Run.purs @@ -278,10 +278,6 @@ handleRegistryWriteMock env = case _ of WritePackageSet _packageSet _message reply -> pure $ reply $ Right unit - -- FIXME: Actually mirror the package set - MirrorPackageSet _packageSet reply -> - pure $ reply $ Right unit - handlePackageSetsMock :: forall r a. PackageSets a -> Run r a handlePackageSetsMock = case _ of -- FIXME: Actually reply with a package set with a pure upgrade diff --git a/nix/test/config.nix b/nix/test/config.nix index 87eaa62bd..832b666f5 100644 --- a/nix/test/config.nix +++ b/nix/test/config.nix @@ -356,23 +356,6 @@ let (slug404Response "purs.json") (slug404Response "bower.json") (slug404Response "package.json") - { - request = { - method = "GET"; - url = "/repos/purescript/package-sets/tags"; - }; - response = { - status = 200; - headers."Content-Type" = "application/json"; - jsonBody = { - name = "psc-0.15.10-20230105"; - commit = { - sha = "090897c992b2b310b1456506308db789672adac1"; - url = "https://api.github.com/repos/purescript/package-sets/commits/090897c992b2b310b1456506308db789672adac1"; - }; - }; - }; - } # Tags for prelude package (only v6.0.1 which is already published) { request = { @@ -1096,7 +1079,7 @@ let mkdir -p "$FIXTURES_DIR/purescript" # Copy fixtures and make writable (nix store files are read-only) - cp -r ${rootPath}/app/fixtures/{registry-index,registry,package-sets} "$FIXTURES_DIR/purescript/" + cp -r ${rootPath}/app/fixtures/{registry-index,registry} "$FIXTURES_DIR/purescript/" cp -r ${rootPath}/app/fixtures/github-packages/effect-4.0.0 "$FIXTURES_DIR/purescript/purescript-effect" cp -r ${rootPath}/app/fixtures/github-packages/console-6.1.0 "$FIXTURES_DIR/purescript/purescript-console" cp -r ${rootPath}/app/fixtures/github-packages/unsafe-coerce-6.0.0 "$FIXTURES_DIR/purescript/purescript-unsafe-coerce" @@ -1122,7 +1105,6 @@ let gitbot tag -m "initial-fixture" initial-fixture done - gitbot -C "$FIXTURES_DIR/purescript/package-sets" tag -m "psc-0.15.9-20230105" psc-0.15.9-20230105 gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" tag -m "v4.0.0" v4.0.0 # Each source tag must carry a manifest with the matching version and ref. jq '.version = "4.0.1" | .ref = "v4.0.1"' \ From ce73c96d6468dace6eb90f01cb1083cb20d7e149 Mon Sep 17 00:00:00 2001 From: Thomas Honeyman Date: Fri, 23 Oct 2026 00:35:56 +0000 Subject: [PATCH 3/3] Remove Dhall specification types and runtime validation Use the existing manifest codec and canonical-license check. Remove schema resources, Dhall tooling, obsolete fixtures, and unused GitHub and source-fetching paths with their dependencies. Keep version normalization in a small shared utility. Remove automatic inclusion of unsupported manifests and regenerate synthetic publication archives while preserving published fixture data. --- .env.example | 2 +- AGENTS.md | 3 +- CONTRIBUTING.md | 17 +- SPEC.md | 23 +- .../src/Test/E2E/Endpoint/PackageSets.purs | 10 +- app-e2e/src/Test/E2E/Support/Fixtures.purs | 6 +- .../github-packages/console-6.1.0/bower.json | 22 -- .../github-packages/effect-4.0.0/bower.json | 21 -- .../github-packages/slug-3.0.0/packages.dhall | 7 - .../github-packages/slug-3.0.0/spago.dhall | 7 - .../github-packages/slug-3.0.0/spago.yaml | 2 +- .../transitive-1.0.0/bower.json | 12 - .../transitive-1.0.0/src/Transitive.purs | 6 - .../type-equality-4.0.1/bower.json | 22 -- .../unsafe-coerce-6.0.0/bower.json | 21 -- .../licenses/halogen-hooks/bower.json | 20 -- .../licenses/halogen-hooks/package.json | 25 +- .../licenses/halogen-hooks/packages.dhall | 12 - .../licenses/halogen-hooks/spago.dhall | 7 - .../registry-storage/console-6.1.0.tar.gz | Bin 1727 -> 1562 bytes .../registry-storage/effect-4.0.1.tar.gz | Bin 5906 -> 5752 bytes .../registry-storage/slug-3.0.0.tar.gz | Bin 1347 -> 1274 bytes .../type-equality-4.0.2.tar.gz | Bin 1737 -> 1549 bytes .../unsafe-coerce-6.0.0.tar.gz | Bin 1094 -> 926 bytes app/fixtures/registry/bower-packages.json | 3 - app/fixtures/registry/new-packages.json | 1 - app/spago.yaml | 1 - app/src/App/API.purs | 64 ++--- app/src/App/Effect/Env.purs | 14 +- app/src/App/Effect/GitHub.purs | 55 ---- app/src/App/Effect/Pursuit.purs | 5 +- app/src/App/Effect/Source.purs | 220 +++++----------- app/src/App/Legacy/LenientVersion.purs | 72 ----- app/src/App/Legacy/Types.purs | 27 -- app/src/App/Prelude.purs | 11 - app/src/App/Version.purs | 21 ++ app/test/App/API.purs | 92 +++---- app/test/App/CLI/Licensee.purs | 20 +- .../LenientVersion.purs => Version.purs} | 13 +- app/test/Main.purs | 7 +- app/test/Test/Assert/Run.purs | 51 +--- flake.nix | 45 +--- foreign/package.json | 1 - foreign/spago.yaml | 3 - foreign/src/Foreign/Octokit.purs | 104 -------- foreign/src/Foreign/Tar.js | 18 -- foreign/src/Foreign/Tar.purs | 18 -- foreign/test/Foreign.purs | 2 - foreign/test/Foreign/Octokit.purs | 53 ---- .../packages/halogen-hooks-0.5.0/spago.dhall | 7 - lib/src/Constants.purs | 3 - lib/src/Location.purs | 3 - lib/src/Manifest.purs | 3 - lib/src/Metadata.purs | 3 - lib/src/Operation/Validation.purs | 3 - lib/src/Owner.purs | 3 - lib/test/Registry/Sha256.purs | 20 -- nix/overlay.nix | 7 +- nix/registry-server.nix | 4 +- nix/test/config.nix | 245 +----------------- nix/test/smoke.nix | 10 +- package-lock.json | 68 ----- scripts/src/DailyImporter.purs | 7 +- spago.lock | 4 - types/v1/Location.dhall | 7 - types/v1/Manifest.dhall | 24 -- types/v1/Metadata.dhall | 38 --- types/v1/Owner.dhall | 1 - types/v1/Prelude.dhall | 5 - types/v1/README.md | 8 - 70 files changed, 207 insertions(+), 1432 deletions(-) delete mode 100644 app/fixtures/github-packages/console-6.1.0/bower.json delete mode 100644 app/fixtures/github-packages/effect-4.0.0/bower.json delete mode 100644 app/fixtures/github-packages/slug-3.0.0/packages.dhall delete mode 100644 app/fixtures/github-packages/slug-3.0.0/spago.dhall delete mode 100644 app/fixtures/github-packages/transitive-1.0.0/bower.json delete mode 100644 app/fixtures/github-packages/transitive-1.0.0/src/Transitive.purs delete mode 100644 app/fixtures/github-packages/type-equality-4.0.1/bower.json delete mode 100644 app/fixtures/github-packages/unsafe-coerce-6.0.0/bower.json delete mode 100644 app/fixtures/licenses/halogen-hooks/bower.json delete mode 100644 app/fixtures/licenses/halogen-hooks/packages.dhall delete mode 100644 app/fixtures/licenses/halogen-hooks/spago.dhall delete mode 100644 app/fixtures/registry/bower-packages.json delete mode 100644 app/fixtures/registry/new-packages.json delete mode 100644 app/src/App/Legacy/LenientVersion.purs delete mode 100644 app/src/App/Legacy/Types.purs create mode 100644 app/src/App/Version.purs rename app/test/App/{Legacy/LenientVersion.purs => Version.purs} (77%) delete mode 100644 foreign/src/Foreign/Tar.js delete mode 100644 foreign/src/Foreign/Tar.purs delete mode 100644 foreign/test/Foreign/Octokit.purs delete mode 100644 lib/fixtures/packages/halogen-hooks-0.5.0/spago.dhall delete mode 100644 types/v1/Location.dhall delete mode 100644 types/v1/Manifest.dhall delete mode 100644 types/v1/Metadata.dhall delete mode 100644 types/v1/Owner.dhall delete mode 100644 types/v1/Prelude.dhall delete mode 100644 types/v1/README.md diff --git a/.env.example b/.env.example index 4e38b063b..680865ca3 100644 --- a/.env.example +++ b/.env.example @@ -45,7 +45,7 @@ SPACES_SECRET="digitalocean_spaces_secret" # READONLY=true # ----------------------------------------------------------------------------- -# Script-only Secrets (not used by server, used by scripts like daily-importer) +# Secrets for scripts such as daily-importer, not the server # ----------------------------------------------------------------------------- # Personal GitHub token for API requests when running scripts diff --git a/AGENTS.md b/AGENTS.md index eaefab606..61b55bd35 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,7 +94,6 @@ nixfmt *.nix nix/**/*.nix - `scripts/` — Runnable modules for registry tasks (PackageTransferrer, PackageSetUpdater, DailyImporter, etc.). Run via `nix run .#package-transferrer`, etc. - `test-utils/` — Shared test utilities. - `db/` — SQLite schemas and migrations (use `dbmate up` to initialize). -- `types/` — Dhall type specifications. - `nix/` — Nix build and deployment configuration. ## Scratch Directory & Caching @@ -104,7 +103,7 @@ The `scratch/` directory (gitignored) is used by scripts for: - `logs/` — Log files - `registry/`, `registry-index/` — Local clones for testing, also modified and optionally committed to by scripts -Caching is critical for the legacy importer due to the expense of downloading packages. The `Registry.App.Effect.Cache` module handles caching. +The `Registry.App.Effect.Cache` module caches API responses and package data to avoid repeated downloads. ## PureScript Conventions diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55d6b8312..1fb9b7f9d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,11 +47,10 @@ There is one additional directory for the web dashboard: - `dashboard` contains the static HTML/CSS/JS dashboard for monitoring registry jobs. It is deployed independently to GitHub Pages and requires no build step. -There are three more directories containing code for the registry. +There are two more directories containing code for the registry. - `db` contains schemas and migrations for the sqlite3 database used by the server. - `nix` contains Nix code for building and deploying the registry server to Digital Ocean. -- `types` contains Dhall specifications for the core registry types Finally, the `flake.nix` file orchestrates builds for the whole repository. @@ -182,7 +181,7 @@ The usual PureScript testing workflow applies in the registry — from within a spago test ``` -There are also a number of checks run by the Nix flake for non-PureScript code, such as verifying Dhall types. Run them: +The Nix flake also checks formatting, builds the dashboard, and runs integration and deployment tests. Run them: ```sh nix flake check -L @@ -212,7 +211,7 @@ The registry source code is defined such that most effectful code is abstracted We obviously don't want to perform these effects in our tests, but we still want to test that the `publish` function behaves as expected. If you are writing a test for a function written in `Run (SOME_EFFECT + r) a`, like `publish`, then you will need to mock effects when writing your tests. You can see the mock implementations in the [`Registry.Test.Assert.Run`](./app/test/Test/Assert/Run.purs) module. -The mock tests use fixtures to represent remote resources. For example, instead of a remote S3 bucket we have the [`registry-storage`](./app/fixtures/registry-storage/) fixtures; this directory is our 'storage backend' and we can 'download' tarballs from it and 'upload' tarballs to it. Instead of accessing arbitrary GitHub repositories we have the [`github-packages`](./app/fixtures/github-packages/) fixtures. Instead of the upstream registry, registry-index, and package-sets repositories, we have e.g. the [`registry-index`](./app/fixtures/registry-index) fixtures. +The mock tests use fixtures to represent remote resources. For example, instead of a remote S3 bucket we have the [`registry-storage`](./app/fixtures/registry-storage/) fixtures; this directory is our 'storage backend' and we can 'download' tarballs from it and 'upload' tarballs to it. Instead of accessing arbitrary GitHub repositories we have the [`github-packages`](./app/fixtures/github-packages/) fixtures. Instead of the upstream registry and registry-index repositories, we have the [`registry`](./app/fixtures/registry) and [`registry-index`](./app/fixtures/registry-index) fixtures. The function under test will only have access to data in these fixtures. @@ -256,18 +255,18 @@ services.wiremock-github-api = { { request = { method = "GET"; - url = "/repos/purescript/package-sets/tags"; + url = "/repos/purescript/purescript-prelude/tags"; }; response = { status = 200; headers."Content-Type" = "application/json"; - jsonBody = { - name = "psc-0.15.10-20230105"; + jsonBody = [{ + name = "v6.0.1"; commit = { sha = "090897c992b2b310b1456506308db789672adac1"; - url = "https://api.github.com/repos/purescript/package-sets/commits/090897c992b2b310b1456506308db789672adac1"; + url = "https://api.github.com/repos/purescript/purescript-prelude/commits/090897c992b2b310b1456506308db789672adac1"; }; - }; + }]; }; } ]; diff --git a/SPEC.md b/SPEC.md index 8f1b7b1b6..a1b63db5f 100644 --- a/SPEC.md +++ b/SPEC.md @@ -225,12 +225,11 @@ Changes that would break forward compatibility (such as removing required fields ### 3.2 Atomic Data -This section contains schemas for `PackageName`, `Version`, `Range`, `Location`, `Sha256`, `Owner`, `License`, and other simple data used in the registry. Each is a subsection which includes its rules, the representations in Dhall and JSON, and a link to the PureScript implementation in `Schema.purs`. +This section contains schemas for `PackageName`, `Version`, `Range`, `Location`, `Sha256`, `Owner`, `License`, and other simple data used in the registry. Each subsection includes its rules, JSON representation, and a link to its PureScript implementation. #### PackageName **[Source](./lib/src/Registry/PackageName.purs)** -**[Spec](./types/v1/Manifest.dhall)** Packages are uniquely identified by their `PackageName`. No two packages in the registry can share the same name. A package name represented as a `string`, with the following restrictions: @@ -247,7 +246,6 @@ We still encourage users to name their repositories with a `purescript-` prefix #### Version **[Source](./lib/src/Registry/Version.purs)** -**[Spec](./types/v1/Manifest.dhall)** Packages are associated with one or more versions, representing their source code at a point in time. The registry uses a restricted version of the SemVer spec which only allows versions with major, minor, and patch places (that means no build metadata and no prerelease identifiers). A version is represented as a `string` with the following restrictions: @@ -271,7 +269,6 @@ The `X.Y.Z` form maps on to these three kinds of change with the following rules #### Range **[Source](./lib/src/Registry/Range.purs)** -**[Spec](./types/v1/Manifest.dhall)** It is sometimes necessary to refer to a range of versions, such as when specifying dependency versions or supported compiler versions. The registry uses a restricted form of SemVer ranges; a version range is represented as a `string` with the following restrictions: @@ -290,7 +287,6 @@ For example: `">=1.0.1 <2.0.0"` #### Location **[Source](./lib/src/Registry/Location.purs)** -**[Spec](./types/v1/Location.dhall)** The registry operates on a pull-based model, in which package authors ask the registry to fetch their code and produce a package instead of uploading their package themselves. Accordingly, every package is associated with the location the registry will use to fetch it. A location is represented in JSON as an `object`. @@ -338,7 +334,6 @@ Or, using a monorepo: #### Owner **[Source](./lib/src/Registry/Owner.purs)** -**[Spec](./types/v1/Owner.dhall)** The registry relies on SSH key pairs to verify package ownership for the purposes of sensitive API operations like unpublishing versions or transferring packages. An `Owner` is made up of the three components of an SSH public key in text format ([RFC4253](https://www.rfc-editor.org/rfc/rfc4253#section-6.6)). That format looks like this: @@ -366,7 +361,6 @@ Alternately, this can be written without an id: #### License **[Source](./lib/src/Registry/License.purs)** -**[Spec](./types/v1/License.dhall)** All packages in the registry must have a license that grants permission for redistribution of the source code. Concretely, the registry requires that all packages use an SPDX license and specify an [SPDX license identifier](https://spdx.dev/ids/). `AND` and `OR` conjunctions are allowed, and licenses can contain exceptions using the `WITH` preposition. The SPDX specification describes [how licenses can be combined and exceptions applied](https://spdx.dev/ids#how). Newly submitted manifests must use current canonical SPDX identifiers, but registry readers should remain backward-compatible with historical stored manifests that still use deprecated SPDX spellings. @@ -377,7 +371,6 @@ A `License` is represented as a string, which must be a valid SPDX identifier. F #### Sha256 **[Source](./lib/src/Registry/Sha256.purs)** -**[Spec](./types/v1/Sha256.dhall)** The registry produces an archive file when publishing a package. We use [tarballs](https://wiki.debian.org/TarBall) as our archive format. The registry produces a tarball when publishing a package. The hash of this tarball is recorded so that package managers can verify the integrity of packages they download from the registry. The hash is stored in the [subresource integrity (SRI) format](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity). A Sha256 is represented as a `string` in JSON. For example: @@ -386,7 +379,6 @@ The registry produces an archive file when publishing a package. We use [tarball ### 3.3 Manifest **[Source](./lib/src/Registry/Manifest.purs)** -**[Spec](./types/v1/Manifest.dhall)** All packages in the registry contain a `purs.json` manifest file in their root directory. The manifest file specifies information necessary for the registry to package the source code, and it also serves as a lingua franca for package managers: all package managers are expected to support this manifest format. The manifest file is an object with the following fields: @@ -429,7 +421,6 @@ For example: ### 3.4 Metadata **[Source](./lib/src/Registry/Metadata.purs)** -**[Spec](./types/v1/Metadata.dhall)** All packages in the registry have an associated metadata file, which is located in the `metadata` directory of the `registry` repository under the package name. For example, the metadata for the `aff` package is located at: https://github.com/purescript/registry/blob/main/metadata/aff.json. Metadata files are the source of truth on all published and unpublished versions for a particular package for what there content is and where the package is located. Metadata files are produced by the registry, not by package authors, though they take some information from package manifests. @@ -482,8 +473,6 @@ If there are no published or unpublished versions then these fields contain empt **[Source](./lib/src/Registry/PackageSet.purs)** -The registry publishes JSON package sets only. It no longer mirrors sets to `purescript/package-sets`: no new `packages.dhall`, legacy `packages.json`, compatibility-map updates, or legacy release tags are produced. Previously published legacy sets and release assets are not modified by this retirement. - Package sets are stored in the `registry` repository under the `package-sets` directory. For example, the first package set was published at https://github.com/purescript/registry/blob/main/package-sets/0.0.1.json. Each package set is stored as a JSON file with the following fields: 1. `version`, which is a string containing the package set [`Version`](#version) for this set, which follows the version rules described in [Releasing the Package Set](#releasing-the-package-set). @@ -627,8 +616,6 @@ First, the registry uses the [`PackageName`](#packagename) listed in the JSON pa Next, the registry will use the provided [`Location`](#location) to fetch the package source at the provided `ref`. Packages must contain a `purs.json` manifest or `spago.yaml` manifest in the package root (in the presence of multiple manifest files, the `purs.json` file takes precedence and others are ignored). -Legacy `bower.json` and `spago.dhall` manifests are no longer accepted for publishing. Packages using these formats must migrate to `purs.json` or `spago.yaml`, including when retrying publication from an old source tag. Existing published archives remain available. Legacy files may still accompany a supported manifest and are subject to the normal packaging and license checks. - The registry will verify that the manifest matches with the JSON payload and metadata: 1. The manifest MUST be usable to produce a well-formed [`Manifest`](#33-manifest). @@ -638,7 +625,7 @@ The registry will verify that the manifest matches with the JSON payload and met 5. The manifest location MUST match the authoritative registry metadata location (and therefore the JSON payload location, when provided). 6. The manifest version MUST NOT have been unpublished before, according to the metadata file. A version already present in metadata, the manifest index, storage, and Pursuit is treated as an idempotent success; incomplete prior publications are reconciled. 7. Either a `resolutions` key was provided in the JSON payload, in which case the resolutions MUST include every dependency listed in the manifest dependencies at a version within the specified version bounds for each dependency, OR the manifest dependencies MUST be solvable by the registry solver. ALL package versions indicated in the resolutions MUST already be registered. -8. If the package source code includes a LICENSE file and/or a license listed in a bower.json or package.json manifest, then all licenses MUST match with the license specified in the PureScript [`Manifest`](#33-manifest). For example, if the package.json file specifies the "MIT" license and a LICENSE file specifies "BSD-3-Clause", then the PureScript license should admit at least "MIT AND BSD-3-Clause". +8. If the package source code includes a LICENSE file and/or a license listed in a package.json manifest, then all licenses MUST match with the license specified in the PureScript [`Manifest`](#33-manifest). For example, if the package.json file specifies the "MIT" license and a LICENSE file specifies "BSD-3-Clause", then the PureScript license should admit at least "MIT AND BSD-3-Clause". Next, the registry will verify the package source code. @@ -654,11 +641,7 @@ Finally, the registry will perform some processing on the source code, package t ###### Always-Included Files -These files are always included in the tarball, if present: - -- The full contents of the `src` directory. -- The `purs.json`, `spago.yaml`, `spago.dhall` and `packages.dhall`, `bower.json`, and `package.json` manifest formats (in the root of the package). -- Any README or LICENSE files (in the root of the package). +The registry always includes the full contents of `src`, package manifests, and root README and LICENSE files when present. The complete filename patterns are defined by `includedGlobs` and `includedInsensitiveGlobs` in [`Registry.Constants`](./lib/src/Constants.purs). ###### Always-Ignored Files diff --git a/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs b/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs index 2061d88de..404f8aea5 100644 --- a/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs +++ b/app-e2e/src/Test/E2E/Endpoint/PackageSets.purs @@ -3,10 +3,8 @@ module Test.E2E.Endpoint.PackageSets (spec) where import Registry.App.Prelude import Control.Monad.Reader (ask) -import Data.Array as Array import Data.Map as Map import Effect.Aff as Aff -import Node.FS.Sync as FS.Sync import Node.Path as Path import Registry.API.V1 as V1 import Registry.PackageSet as PackageSet @@ -15,13 +13,12 @@ import Test.E2E.Support.Client as Client import Test.E2E.Support.Env (E2ESpec) import Test.E2E.Support.Env as Env import Test.E2E.Support.Fixtures as Fixtures -import Test.E2E.Support.WireMock as WireMock import Test.Spec as Spec spec :: E2ESpec spec = do Spec.describe "Package Sets endpoint" do - Spec.it "releases a registry package set without accessing legacy sets" do + Spec.it "releases a registry package set" do -- First publish unsafe-coerce to create the tarball in storage { jobId: publishJobId } <- Client.publish Fixtures.unsafeCoercePublishData _ <- Env.pollJobOrFail publishJobId @@ -38,11 +35,6 @@ spec = do Map.lookup Fixtures.unsafeCoerce.name set.packages `Assert.shouldEqual` Just Fixtures.unsafeCoerce.version Map.lookup Fixtures.prelude.name set.packages `Assert.shouldEqual` Just Fixtures.prelude.version - legacyCheckout <- liftEffect $ FS.Sync.exists $ Path.concat [ stateDir, "scratch", "package-sets" ] - legacyCheckout `Assert.shouldEqual` false - requests <- WireMock.getGithubRequests - Array.null (WireMock.filterByUrlContaining "/repos/purescript/package-sets" requests) `Assert.shouldEqual` true - Spec.it "rejects unauthenticated compiler change requests" do result <- Client.tryPackageSets Fixtures.packageSetCompilerChangeRequest case result of diff --git a/app-e2e/src/Test/E2E/Support/Fixtures.purs b/app-e2e/src/Test/E2E/Support/Fixtures.purs index 89396babd..4eee2c669 100644 --- a/app-e2e/src/Test/E2E/Support/Fixtures.purs +++ b/app-e2e/src/Test/E2E/Support/Fixtures.purs @@ -56,12 +56,11 @@ console = { name: Utils.unsafePackageName "console", version: Utils.unsafeVersio prelude :: PackageFixture prelude = { name: Utils.unsafePackageName "prelude", version: Utils.unsafeVersion "6.0.1" } --- | slug@3.0.0 fixture package (uses spago.yaml manifest format) slug :: PackageFixture slug = { name: Utils.unsafePackageName "slug", version: Utils.unsafeVersion "3.0.0" } -- | Standard publish data for effect@4.0.1, used by E2E tests. --- | Generated from app/fixtures/github-packages/effect-4.0.0 with updated version/ref. +-- | The test setup copies effect-4.0.0 and updates its manifest for this release. effectPublishData :: Operation.PublishData effectPublishData = { name: effect.name @@ -116,9 +115,6 @@ consolePublishData = , version: console.version } --- | Publish data for slug@3.0.0, used for spago.yaml manifest format test. --- | Slug depends on prelude only and uses spago.yaml as its manifest format. --- | This matches the fixtures in app/fixtures/github-packages/slug-3.0.0 slugPublishData :: Operation.PublishData slugPublishData = { name: slug.name diff --git a/app/fixtures/github-packages/console-6.1.0/bower.json b/app/fixtures/github-packages/console-6.1.0/bower.json deleted file mode 100644 index 6145278d2..000000000 --- a/app/fixtures/github-packages/console-6.1.0/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "purescript-console", - "homepage": "https://github.com/purescript/purescript-console", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-console.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-effect": "^4.0.1", - "purescript-prelude": "^6.0.0" - } -} diff --git a/app/fixtures/github-packages/effect-4.0.0/bower.json b/app/fixtures/github-packages/effect-4.0.0/bower.json deleted file mode 100644 index 3b520e6ae..000000000 --- a/app/fixtures/github-packages/effect-4.0.0/bower.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "purescript-effect", - "homepage": "https://github.com/purescript/purescript-effect", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-effect.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-prelude": "^6.0.0" - } -} diff --git a/app/fixtures/github-packages/slug-3.0.0/packages.dhall b/app/fixtures/github-packages/slug-3.0.0/packages.dhall deleted file mode 100644 index 6368ba628..000000000 --- a/app/fixtures/github-packages/slug-3.0.0/packages.dhall +++ /dev/null @@ -1,7 +0,0 @@ --- Minimal packages.dhall for testing - defines only required dependencies inline -{ prelude = - { dependencies = [] : List Text - , repo = "https://github.com/purescript/purescript-prelude.git" - , version = "v6.0.1" - } -} diff --git a/app/fixtures/github-packages/slug-3.0.0/spago.dhall b/app/fixtures/github-packages/slug-3.0.0/spago.dhall deleted file mode 100644 index 3fb7f887f..000000000 --- a/app/fixtures/github-packages/slug-3.0.0/spago.dhall +++ /dev/null @@ -1,7 +0,0 @@ -{ name = "slug" -, license = "MIT" -, repository = "https://github.com/purescript/purescript-slug" -, dependencies = [ "prelude" ] -, packages = ./packages.dhall -, sources = [ "src/**/*.purs" ] -} diff --git a/app/fixtures/github-packages/slug-3.0.0/spago.yaml b/app/fixtures/github-packages/slug-3.0.0/spago.yaml index ed13278d8..2c03be0b6 100644 --- a/app/fixtures/github-packages/slug-3.0.0/spago.yaml +++ b/app/fixtures/github-packages/slug-3.0.0/spago.yaml @@ -9,5 +9,5 @@ package: githubOwner: purescript githubRepo: purescript-slug -# Keep this source fixture separate from the registry development workspace. +# Without a separate workspace, Spago treats this fixture as a registry-dev package. workspace: {} diff --git a/app/fixtures/github-packages/transitive-1.0.0/bower.json b/app/fixtures/github-packages/transitive-1.0.0/bower.json deleted file mode 100644 index d0d4d0bd1..000000000 --- a/app/fixtures/github-packages/transitive-1.0.0/bower.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "purescript-transitive", - "homepage": "https://github.com/purescript/purescript-transitive", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-transitive.git" - }, - "dependencies": { - "purescript-effect": "^4.0.0" - } -} diff --git a/app/fixtures/github-packages/transitive-1.0.0/src/Transitive.purs b/app/fixtures/github-packages/transitive-1.0.0/src/Transitive.purs deleted file mode 100644 index 71d771f62..000000000 --- a/app/fixtures/github-packages/transitive-1.0.0/src/Transitive.purs +++ /dev/null @@ -1,6 +0,0 @@ -module Transitive where - -import Prelude - -uno :: Int -uno = one diff --git a/app/fixtures/github-packages/type-equality-4.0.1/bower.json b/app/fixtures/github-packages/type-equality-4.0.1/bower.json deleted file mode 100644 index c427a0c0d..000000000 --- a/app/fixtures/github-packages/type-equality-4.0.1/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "purescript-type-equality", - "homepage": "https://github.com/purescript/purescript-type-equality", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-type-equality.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "devDependencies": { - "purescript-console": "^6.0.0", - "purescript-newtype": "^5.0.0" - } -} diff --git a/app/fixtures/github-packages/unsafe-coerce-6.0.0/bower.json b/app/fixtures/github-packages/unsafe-coerce-6.0.0/bower.json deleted file mode 100644 index eb6293c58..000000000 --- a/app/fixtures/github-packages/unsafe-coerce-6.0.0/bower.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "purescript-unsafe-coerce", - "homepage": "https://github.com/purescript/purescript-unsafe-coerce", - "license": "BSD-3-Clause", - "repository": { - "type": "git", - "url": "https://github.com/purescript/purescript-unsafe-coerce.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "output", - "test", - "bower.json", - "package.json" - ], - "devDependencies": { - "purescript-console": "^6.0.0" - } -} diff --git a/app/fixtures/licenses/halogen-hooks/bower.json b/app/fixtures/licenses/halogen-hooks/bower.json deleted file mode 100644 index 903e2ea63..000000000 --- a/app/fixtures/licenses/halogen-hooks/bower.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "purescript-halogen-hooks", - "license": [ - "MIT" - ], - "repository": { - "type": "git", - "url": "https://github.com/thomashoneyman/purescript-halogen-hooks" - }, - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "output" - ], - "dependencies": { - "purescript-halogen": "^v6.0.0" - } -} - diff --git a/app/fixtures/licenses/halogen-hooks/package.json b/app/fixtures/licenses/halogen-hooks/package.json index dfa084984..6d99c850a 100644 --- a/app/fixtures/licenses/halogen-hooks/package.json +++ b/app/fixtures/licenses/halogen-hooks/package.json @@ -1,27 +1,4 @@ { "name": "halogen-hooks", - "private": true, - "version": "0.0.1", - "description": "Hooks for Halogen", - "repository": "https://github.com/thomashoneyman/purescript-halogen-hooks", - "author": "Thomas Honeyman ", - "license": "Apache-2.0", - "scripts": { - "postinstall": "node node_modules/puppeteer/install.js", - "clean": "rm -rf .spago output node_modules *.lock", - "build": "spago build", - "build:examples": "spago -x examples/examples.dhall build", - "build:test": "spago -x test/test.dhall build", - "snapshot": "npm run bundle:performance && spago -x test/test.dhall run --main Performance.Snapshot.Write", - "test": "spago -x test/test.dhall test", - "test:performance": "npm run bundle:performance && spago -x test/test.dhall run --main Performance.Main", - "bundle:examples": "spago -x examples/examples.dhall bundle-app --main Example.Main --to examples/app.js", - "bundle:performance": "spago -x test/test.dhall bundle-app --main Performance.Test.App --to test/test.js --no-build" - }, - "devDependencies": { - "filter-console": "^0.1.1", - "headless-devtools": "^2.0.1", - "puppeteer": "^5.2.0", - "tracealyzer": "^0.10.3" - } + "license": "Apache-2.0" } diff --git a/app/fixtures/licenses/halogen-hooks/packages.dhall b/app/fixtures/licenses/halogen-hooks/packages.dhall deleted file mode 100644 index 3c70ca9cd..000000000 --- a/app/fixtures/licenses/halogen-hooks/packages.dhall +++ /dev/null @@ -1,12 +0,0 @@ -let upstream = - https://github.com/purescript/package-sets/releases/download/psc-0.14.0-20210313/packages.dhall sha256:ba6368b31902aad206851fec930e89465440ebf5a1fe0391f8be396e2d2f1d87 - -let additions = - { halogen-storybook = - { dependencies = [ "halogen", "routing", "foreign-object" ] - , repo = "https://github.com/rnons/purescript-halogen-storybook.git" - , version = "7327247aea379d4582dad9f93be9749556d26c99" - } - } - -in upstream // additions diff --git a/app/fixtures/licenses/halogen-hooks/spago.dhall b/app/fixtures/licenses/halogen-hooks/spago.dhall deleted file mode 100644 index b8f96ab21..000000000 --- a/app/fixtures/licenses/halogen-hooks/spago.dhall +++ /dev/null @@ -1,7 +0,0 @@ -{ name = "halogen-hooks" -, license = "MIT" -, repository = "https://github.com/thomashoneyman/purescript-halogen-hooks" -, dependencies = [ "halogen" ] -, packages = ./packages.dhall -, sources = [ "src/**/*.purs" ] -} diff --git a/app/fixtures/registry-storage/console-6.1.0.tar.gz b/app/fixtures/registry-storage/console-6.1.0.tar.gz index 8926e2815e98d214944e8d4d1337b0bf05dcaf36..7cb4a931170540fb782f921188b99101333c1d47 100644 GIT binary patch literal 1562 zcmV+#2Icu5iwFP!000001MOJdQ{qSv-{<@lT|Q|QvIq)Su548ZqfAvyxJ+2r$4Cav z6=bM<=xyEY|9;(*00MgAt?OOYr60&J{p*kE?&-;7tm0Hn1^cx zsL^hJfOpBs|2+Sentw~8+q(>!jiX;+m?u(OImDX1AiB4I9*S(Ifmgn6RwqYww# zU6A3AL?zRydWhm%z^V)*5=w!DW->c9jH5cNyOe^OEPNTO5OJ7HNMshEkEAfTQTN1J zEHPGDG?q2wMX51GO{CzltvK9p1kH`7K{S&|-7uQl4xph&5m-2YOAB*!>Ti57$Om|( z4^XZvRO5Um<1APt=A%jsg`5OtK_-(Zn5L`v>ZE9~N3P>?T6J(_q@{=(2Qx{&(0JwU zotlOSh}8>0AT*I`o(j;CjF>hc-uLsNFLi zpEFSd?pXGy&z*}JpaO7QA8dZfeT4aLO&cx6jFlL;=Wxk9Yk+^v9=ExFtsOb%zC*Uo z(U=KC)APACvP}<$BX8&m2DmJv&xK{1{F3$SXdNvA`@$R_#K5%eCs#?nudT7i&`Gmr zGqR*t+2J;qP5rU8jee=@1V8YmcAtaa+ zYvi#@@)_eGMm^zkf8;Z`aNWKRv|!#BJa@z=!*+#^)=01#TJTM67*$}X#3vf}MuO|0 zamQz#HyZlfbq;XlR~Q!b(L~X{j;`zIrDB9!@0yfS40P~oa5Z2!?@>T?q)ZBdz=&D? zV;I`Q0Q!&C2oAfj`2}+`_vHIazc>%N6v(Dj^~Vw$B|4|Hsxd@ z#~EQicaQ6h`VpL+;G?lHIZxy?4`l&p69>*Z8bG#;?{B}!{=>As|I=jr6YhZj;QrTc zwX6Hzd(d|NnchBcSq87qf3wkQR{4JqdN%)7!N9kHAGYz*`QPp|H}c==bUM}f{~jcN zpQ|LJs~p}$r|K5EkV5w}>>to?Ssis`?578xj5Rb~*y5+*#M#EnB*ClPlLDHxEfK{N z^|SzSw(+67$)8z)W^GGkc&&a~fH+U%?dPIYGM|=Wc&~p}3YptdPD~{ zE$3;!F9*w6y{T8Dfjk(;IJCTn(20i?zUh@jNgL z426ttV<`F5Fi%fG=!n4K=Y_y1jmxv$CZ?wm8ZIfJ7^-hji zh_A+rY!e!__uo6SHa4#`B$uOA%Y2A8v-5uKvaSds;q$|4@wiwzdiO)1git#@m0yJA z@9O%vR641>gVP^l<{r*a=bnHgx?9!ClC|t~FW5VsEU2l3G1Ec`ES=8$|Hq|j z^&M;_C;#*OH^uld^oI8_luO4yL3a{z%MHD8tSBb$dr=&Ecazu?0XRV)CJ_fOfJjV2 zmrKl@7dYV~3`97J3NZEJdk~@gib)J3(f0

yUtg;)EQ=JREs(%=<77#e>(!MSSnX z_=lqq-xpIa7y?#h-y^07h-t**GetQr!lFqLh{4PjSM-q%lL;Y@9kh`II(OoMwq`B{ zBKBNfz&$Tge3V22=5+=AB|}i1>pR|vhebsxuNr`g<{~gN0A1@((5Sz$K_MI9g*HIy zS6{f35f5T#mY5?EDgt*x7&$QyJ;#so_{yY6wmH+$ok|fp+>>0i8#p6Qw$M5^_g?sY zBnU)KB*X5-5m}wc6Jex`oJY9hR3T`;03P%a%c&U9kdcTvBvC;=6zDx5*#I|WE-;CM zcuJX03rC5@+@;DvCXebVq?!qm>WQMne!Hz%U>O(ojcPK$b=NemwH9kZ;}+~TgQn5F zHMPsO4Q)ejG1CII(?V>=Hnm32HW0C=TFALa8;aVw1@@_HGRp$P1nsJ;YbXq*n`+0_ zm{kC+)6{z{t#erb6aXE=23@<-Y$UUdf>fHaDLES$7jVVQW*h%i8=9`!x6+Uc&F+w{ z3sj~;S2b;|+0#`Mx;?XNSPalDrKMR-UDd8wtBBfBBd}}MvB7Gqy1v#+vVFnF216s& zhR#Tn^s=RytZ9=w`KpPLL;LgsSY6iCXu&=)bVfCA3#m+t{oF$$+=P~TrCu^i*?%S^ zn3HDDWLIP}#=+_}EL*dCHiJvUXvsiZ%)G{P$NHe?h9#rbvseK&*s4^FA~00e2U<6J zmL`Lyb!=vuy{@epodfjz2E&3js>s@s(KR~KRg92f-jYy?fed~DZrTj-CIwVRN~I83 z7_p{3Ploz1fcBh^&|#OlcF8(TM!N<{xzQ|kfVt8vB0!O(_C`gWJ?S##2(3z%nx)Jt zwjeVJ+6AbsYmLlKi81$i&%^S@N9EZ_gi zrD|z+{%-^RPyqI5s_&h_-WX5+NS^+OX*TkQxbeU^MXhUXjrYo%3;%Y@Tf>O@xT<3ho{IR?vp`?iyz058r4W5L`_2fH3k$( z1WNcOb$LTP%1H7riO~1&kBaYS8f5u@L5X7#;PD#G@Bv;fzTjPE;`0oLr_^|o*%tFC zUXUW2XoeX(?r#{r1c#_!r73+r##539u7^@H$LAd2g8_GA^7->gu~aN&;&PI4$o)xQ zM(T4_(vYXhw~YtIV*jJ$=*`>z+5SH%pDf@1Yn95b|F;1P{lCz%4?Luc7U5-aY|zNQ zNUr*MH78AKOamS+4wzXjCj=%=fF}k}*0~`c5Xr-wG3z@hZ<~*G**9>0y3js(&wQ*` z6N1`ndgs3Yi~S#k?hkkadd2%swNl-^|7-(R^Uq}ayk!}@JpbiVrM%1kHsHzpH`CkG z*XX--Ja_(AYvrZu4j;2s~lc15bGIeXO+Is?uSIf+-ACHD$^WzcPd^F1Q^zy?UoO>_@Bb&2)AFwWw*iNT@F&>x z2All8ojn^zFqi~x@+3XLwoQKS#`GRdkI2C=qF3JJtvD$ZB^8j=Pcf|@yi)JeT)}UF zzSg~%$~Dc1Y>{E*Ajnr4p87poWLVkM`6|OxOJxf^Ri5^lT+1fH)d5WJdB_#T8_CA& zCO-|b{}W$eKlvUg3WkDv!vNBK8klEiV9AKU;l~+6FYw}JS~)H^B~E7LrqpsyZc3aC z$xW%{fP4wD{(_Lq=HjOG$wWlzc^W0HD#9bZ>tTvXO48_UW$>S^g@(*Eh$wSyh( VU4*j%fVj2lbhPz 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 literal 5906 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 diff --git a/app/fixtures/registry-storage/slug-3.0.0.tar.gz b/app/fixtures/registry-storage/slug-3.0.0.tar.gz index e2984f5a47a648feffc775c9154576cc410f5cf9..d6a28e66a43bc7a076a2dc2031ba1aa9fe3c0848 100644 GIT binary patch literal 1274 zcmV(spm&XW@l&Snc0`wwRN^#wa+@;PWSbTAB8UBqTe@9 z#PRbh|1Zw^XD^`t!y-DsR+q6t%IDzv4?;i8|HK_SUg$hM2TkgEulKF_Kkc3$&HqXF ztbhIjo@Gw{=kq^xBbbPU=bAG%l*L|&)w+b2$tyVRp7tPG%T26dEOWl!#5rR#t~Nqz zA#)HK)?D$$9#$&OOP*S=RGfocLb8t4id#@hi1R%ZTxm3ri!v6u$X5_Uf?NzW%Qd>u za#`+T#ZfthSZkSx82v#ilkJA*Wn2>9Qe<4i%W}=371kE5SB50zamGXr)OXhfJ5jFX zwgkm>sYF6PED(8;ZBvTjt~(POQ8SRa834`D=~iO~6sH9nnTjPnxk;~PEw4U=anCT4Kq84+~Is;2QZrlGe2~YeuQp4 z*SiXk#hE%@)InC1f%5?y5RUE1gs7N3$LIr!4~G8iCUD=4BN+RWk%PiZ2P3mDCr(9$ zNew5qJGEeBPwjV(vGUPNz(}o%4z9-ziHOg}-!O80kJ1?WUKC){!YqU6-t^iH9SiKh z4QW8G0)J{T8cwwEjT5xDEma}t`x zQAz9Ljigv?Yne>U*@;5(g_0GXtJ{UY%ef*=Tvl8sN))9rxK{{xAs_d*4L|C9F`gA) zQvdBi04Xncp7K1w6X=1kP@GOEQhxj99JNp2S9+WpU+ha$w7!jl{jZC7B|H0glRYuV z_x69UcX7P`&+v7z+5gXgB2GTVD?Y%TLRDY@v>>o?xYGt^o3)|dXJ6fB^^Levt*gfX z%)&CFx^xTx3r8iW>qcb(a8NY{@K6&T^-{?=>@)>~v0vc2zRJgV<1S)_?+&n&>JzT$ z1TWelEo>+ik4s%$cbDS#a!aop8ch^m(Jg-8PWdg={Hqliq3b{pUGwH2)zsJ9H6zN{;W||N7_0_5b4Htf~LcfsIVJ8NROLyu*&Z<}fBU z^fC{#s@gNwZo{AWk~|M4Z5=PUIUzbM=eu%W)Ru3mM_4M6V?j>261Vi_fIbf-9QbF` zJs5!V*=YVvL#p&n#aC&iQxOkkj>id=cB4Qalxm(1D`oYM%uH|l?OhD_T`x_CeuXjW kpSO%RXwaZRg9Z&6G-%MEL4yVj8hjIf0T~($KL98I0Plx%{{R30 literal 1347 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 literal 1737 zcmV;)1~&O0iwFP!000001MOICPvb}up3nXjB|gC@7)XFQvByfrX~0sjv$iuVx_!a1 zJJ36h+w1NSMR)Vxx2m0xm(32hBgCC{T@tumU0wCmOS?OzXC>d~zfO`|=-K|UeQ382 z-n}v%B7HbHF;Aq;=kLcyNA33EJ2-i5Alqo7bW)-Dx3KkpNP0{Dd!Ebuh`pK(O6uwH z@gL>CeR}d?BmeDo=fgXAlbrm`^Y6-XR*8p44ei6De?Wgy@hGiCsVytynNYeC!-*EM zfTYM^qBw{Gl$=y4H=Lm;l4=GcS&dZ-rb0i0tjtqRG>l~?Mj}l}LCZ=i4kfR~LTjEu zS;;4n;Xpqo8vk)rlILp%tO7^~p+E<75UyP8EXqCGa9cE~jEZAY-XH)KS58s8Bqi*a#;~ zE-;BBJ*7;~3&&C=PpNWnk)V32sAh_~dX%d1$Jbs2QE(aGIw1ob_ru_$*JWL}xP$nb zfgAMiLhtH2hU=i$Wnl!4-$kw;hu*~?4v?|qM7VN?CM?Il1NMDCWKje`2;NP<=bAnG&MqXGM#p)*c+*P82$*mnaI!b#{lH_jD{ ztlbwvf;n*qA-f^W7>8(Z5yf6Sh#6c3LDvL2V&O+TaiVXmUJ#jR4I_g_xQY=9!aGt*F)+bz!R<9een*zY@?az%wJz1!!NCK5 zuO~x0mE(iu;)5^i$?^Ovlu>gLb@w~_Zl2(=QBUIGDV4wzWrp(v4tb=5q|ulWLe`|p zzaEB7icrsIV^KWd;YRxZRjYpg{=j~}uw%BuSQ1s=F+!7uQ-@iX3TCOLoR;ki{# zo)K!U^qLm4p%$DnNk3sIYaX(GG*B}B)a4}}oxDf|Dt?Z@iYIvJDw&&j{B(-j_3&Bc zQl{Y3Ma+IWF*4+>^?A$ZW83~mP3jf5|BL&-(>dAP|3`->&HjH2THF6yHtQ3w=(-OGnMLi^0wM1GFMfue_iV*SB>1!fDcjgM+snP74cfN< zRh7PK59rVLf7-a9>HoY1{Zam7i9G0gd%W$o|7e;}sD50&5C6#nr_t9d&R`3mlnd(#iQAKuq% zs6}Lo)i&=7MI1(xBBeV=lEdmn;JKToMDdXGT3!^G@&b_!c`m0g6?snGyLrD(Pi2iN zsUA#|P1UQkgh?SrGxD^6dEX3w6aaehsgToR6_PJCKSSfQr7>fs;3-!ssYE^liC3#U zDNW0(UUiSxn*6?o)dD4-7puB2hnDJ4sYf}HTB&xJ`x4bs6&{GVTC5RvaQV| z!r2+Qnjn-Lqn4YrAH+TYHR_P5<&m+qHNtyl%Z#w`Sk4<_P}R fpN57SYN(-x8fvJah8k+-^1bXXtV>9O*?WMYGauu^V_iX52C(>|Eks|{%n-okPS!b@pybE|D)052LGeM zaIgbg;p9K(|7#CmSF#d!PhhuG47+`|HAiiPl5W=*N4r`Gj?(xu=NB)cQS`iEwOJi0 z6lb@%M*IY`mLYboE z0*$kL*}Lrh?c_cJ+;;zs<{Rw+4|xBd43EP5zYW~Q{|CRmnKJl&{zt=6=>J>6J^VlS z>-1&dPi)-R|J|3pF8|}><4NfM+rZ%=lq#(YgqHERWxUtJWu>$QuB5ThR!O@BXOPvB zyABWg%LCuzHQHKBSf2JSPea#zO!&3??_OEP|+AsvlPMR^wU%b zrZE;v`k&cWCzDOPBaD|aG?V}{eqGUm_Pa5B%u$kFA!j5;vOg>f>&`Z+)~&{Ur<|7h zULR*gfqKsz&}(HXde%fD3Sk@P9t|@zb9iUGY)zAdE*i4YYSLKIA(Yk+HjpU|3Hjtb zGV2b`A}@x{ArKxM7Et~Z*sna$Z%PAQ4ZM8d@IlA^>V zE2{!e*20=jr#}X?b}7@UJXfkf(sFVFSARxN;k8lnXPL8ZbzP}lGxFYfxnND-(FqGB z_blXWj=mZsGuGuoS6Ud#e_Epp+;QM@r(GgkCKJ47Fcq24s&$eJ$FoyRO%A!mbe*ViDtUlqbw+1mz@$VmAi_@% literal 1094 zcmV-M1iAYkiwFP!000001MOICZyPrZ&S(D$B46?X`)tQ{3Z&?Orp18m%Z6sdJ`7td z>v%qOchW)9`DJVV`zXnFe6HJUnPIG`9w4zp@kol~a}rZa!!qn}g_>jMI_V|7?#Y4C zBfJ=m{3l`e^W*RRe*bE40;59*vJbVfOq2U#`1&8B-c$dHnxjtM8zm3p!xi;ObfYh9_{8ntbK;|EmRXDp`rAH*i|j8V%Q?vYkd>oVJ}-u1c)f)NAI}R_3PL zoeG=RlZ30X`}lD8^L7fsQ5tXN_U>(G(0Nm^+N>5eR>}yg^pe(Ha3r(K%4?G|uSB9+ z7taqPafZmwgZC(=QfX)Z*I@ecvYTA4{dg+elif;5l-8`}Ql)rbsP>_e zjcr)*gj!1~#{ZyH1{sNd^P!|Tdy5sy6eSmAc$0yxop2?MDttbE_jL6;X+5-_SO0zd zCvWC}^na57gTZK*|Gj=M^8XlU@P7|kXQ+*!9;MaLZBJ9UP@LKD%&L6@J%5->)Xmq- znhrtX9dZA&ub~ZRHD*q6wiQykL!9SR;;X_w{u|8?dIx;Q{m)$q;{ERk_*DIW_VUBD z!N0FR-T&hK|0sB({%^vC>Am?cKAt-N-LTiJ|M2>H6wm)-pxcFTjfGJ0BvicB!bPRD z1$W)F5V|BR!7IpW$=wML=Zgz}#%r{-maur)dUzR6-7g9MjQ?&Tc)pJ(_D3+OH((?$=weAef+dX= zJ%rNw!3Hv=VL~zah|G4tiPsMg-6D5GQ8P-Xj0>xo(y5S43(8?u;6h9aY_Ik&Gs?82 zw0koylT(e%lBTIMT1((NztdF-G;&pzZus%3PB9abqN$JzR){}+$Ocpv%r&FYEMzuk z>zq;&ndu0Vz_+wZT(Gh#@MddU&Rmv#pPL%~ybJwQa{g=@Q68c5;o5a&RA|fIpA|fIpA|fIpBI3)( M-&?o{^#CXU0DBKJGynhq diff --git a/app/fixtures/registry/bower-packages.json b/app/fixtures/registry/bower-packages.json deleted file mode 100644 index 59d4f8611..000000000 --- a/app/fixtures/registry/bower-packages.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "purescript-prelude": "https://github.com/purescript/purescript-prelude.git" -} diff --git a/app/fixtures/registry/new-packages.json b/app/fixtures/registry/new-packages.json deleted file mode 100644 index 0967ef424..000000000 --- a/app/fixtures/registry/new-packages.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/app/spago.yaml b/app/spago.yaml index 6c2b4bd55..92ef3f9a7 100644 --- a/app/spago.yaml +++ b/app/spago.yaml @@ -67,7 +67,6 @@ package: - transformers - tuples - typelevel-prelude - - unicode - unsafe-coerce - uuidv4 test: diff --git a/app/src/App/API.purs b/app/src/App/API.purs index 4b48774a5..d8fe7113f 100644 --- a/app/src/App/API.purs +++ b/app/src/App/API.purs @@ -50,11 +50,9 @@ import Data.String.Regex as Regex import Effect.Aff as Aff import Effect.Unsafe (unsafePerformEffect) import JSON as JSON -import Node.ChildProcess.Types (Exit(..)) import Node.FS.Aff as FS.Aff import Node.FS.Stats as FS.Stats import Node.FS.Sync as FS.Sync -import Node.Library.Execa as Execa import Node.Path as Path import Parsing as Parsing import Parsing.Combinators as Parsing.Combinators @@ -69,7 +67,7 @@ import Registry.App.CLI.PursVersions as PursVersions import Registry.App.CLI.Tar as Tar import Registry.App.Effect.Cache (class FsEncodable, Cache) import Registry.App.Effect.Cache as Cache -import Registry.App.Effect.Env (PACCHETTIBOTTI_ENV, RESOURCE_ENV) +import Registry.App.Effect.Env (PACCHETTIBOTTI_ENV) import Registry.App.Effect.Env as Env import Registry.App.Effect.GitHub (GITHUB) import Registry.App.Effect.Log (LOG) @@ -85,7 +83,6 @@ import Registry.App.Effect.Source (SOURCE) import Registry.App.Effect.Source as Source import Registry.App.Effect.Storage (STORAGE) import Registry.App.Effect.Storage as Storage -import Registry.App.Legacy.Types (RawPackageName(..), rawPackageNameMapCodec) import Registry.App.Manifest.SpagoYaml as SpagoYaml import Registry.App.Server.MatrixBuilder as MatrixBuilder import Registry.Constants (ignoredDirectories, ignoredFiles, ignoredGlobs, includedGlobs, includedInsensitiveGlobs) @@ -336,7 +333,7 @@ authenticated auth = case auth.payload of Registry.writeMetadata payload.name updated Log.notice "Successfully transferred your package!" -type PublishEffects r = (RESOURCE_ENV + PURSUIT + REGISTRY + STORAGE + SOURCE + GITHUB + COMPILER_CACHE + PURS_GRAPH_CACHE + LOG + EXCEPT String + AFF + EFFECT + r) +type PublishEffects r = (PURSUIT + REGISTRY + STORAGE + SOURCE + GITHUB + COMPILER_CACHE + PURS_GRAPH_CACHE + LOG + EXCEPT String + AFF + EFFECT + r) type PublishedPackage = { compiler :: Version @@ -410,7 +407,7 @@ resolveCompilerAndDeps compilerIndex manifest@(Manifest { dependencies }) maybeC ] -- | Detect which manifest format is present in the package directory. --- | Prefers purs.json over spago.yaml. Throws if neither is present. +-- | Use purs.json when both formats are present. Fail if neither is present. detectManifestOrigin :: forall r . FilePath @@ -426,14 +423,14 @@ detectManifestOrigin packageDir = do if hasPursJson then pure FromPursJson else if hasSpagoYaml then pure FromSpagoYaml else - Except.throw "Package source must contain a purs.json or spago.yaml manifest file. Publishing from bower.json or spago.dhall is no longer supported; migrate to purs.json or spago.yaml." + Except.throw "Package source must contain a purs.json or spago.yaml manifest file." -- | Parse a manifest from the package source directory. Returns the parsed -- | manifest along with which format it originated from. parseSourceManifest :: forall r . { packageDir :: FilePath, ref :: String } - -> Run (RESOURCE_ENV + LOG + EXCEPT String + AFF + EFFECT + r) ParsedManifest + -> Run (LOG + EXCEPT String + AFF + EFFECT + r) ParsedManifest parseSourceManifest { packageDir, ref } = do origin <- detectManifestOrigin packageDir manifest <- case origin of @@ -442,21 +439,18 @@ parseSourceManifest { packageDir, ref } = do string <- Run.liftAff (Aff.attempt (FS.Aff.readTextFile UTF8 pursJsonPath)) >>= case _ of Left error -> Except.throw $ "Could not read purs.json from path " <> pursJsonPath <> ": " <> Aff.message error Right s -> pure s - Env.askResourceEnv >>= \{ dhallTypes } -> Run.liftAff (jsonToDhallManifest dhallTypes string) >>= case _ of - Left error -> do - Log.error $ "Manifest does not typecheck: " <> error - Except.throw $ "Found a valid purs.json file in the package source, but it does not typecheck." - Right _ -> case validateCanonicalManifestLicense string of - Left err -> do - Log.error $ "Manifest license is not canonical: " <> err - Except.throw $ "Found a purs.json file in the package source, but its license field is not canonical." - Right _ -> case parseJson Manifest.codec string of + case parseJson Manifest.codec string of + Left err -> do + Log.error $ "Failed to parse manifest: " <> CJ.DecodeError.print err + Except.throw "Found a purs.json file in the package source, but it could not be decoded." + Right m -> do + case validateCanonicalManifestLicense string of Left err -> do - Log.error $ "Failed to parse manifest: " <> CJ.DecodeError.print err - Except.throw $ "Found a purs.json file in the package source, but it could not be decoded." - Right m -> do - Log.debug $ "Read a valid purs.json manifest from the package source:\n" <> stringifyJson Manifest.codec m - pure m + Log.error $ "Manifest license is not canonical: " <> err + Except.throw "Found a purs.json file in the package source, but its license field is not canonical." + Right _ -> pure unit + Log.debug $ "Read a valid purs.json manifest from the package source:\n" <> stringifyJson Manifest.codec m + pure m FromSpagoYaml -> do let spagoYamlPath = Path.concat [ packageDir, "spago.yaml" ] @@ -1157,23 +1151,20 @@ publishToPursuit { source, compiler, resolutions, installedResolutions } = Excep Right _ -> FS.Extra.remove tmp -type PursuitResolutions = Map RawPackageName { version :: Version, path :: FilePath } +type PursuitResolutions = Map String { version :: Version, path :: FilePath } pursuitResolutionsCodec :: CJ.Codec PursuitResolutions -pursuitResolutionsCodec = rawPackageNameMapCodec $ CJ.named "Resolution" $ CJ.Record.object { version: Version.codec, path: CJ.string } +pursuitResolutionsCodec = CJ.Common.strMap $ CJ.named "Resolution" $ CJ.Record.object { version: Version.codec, path: CJ.string } -- Resolutions format: https://github.com/purescript/purescript/pull/3565 --- --- Note: This interfaces with Pursuit, and therefore we must add purescript- --- prefixes to all package names for compatibility with the Bower naming format. formatPursuitResolutions :: { resolutions :: Map PackageName Version, installedResolutions :: FilePath } -> PursuitResolutions formatPursuitResolutions { resolutions, installedResolutions } = Map.fromFoldable do Tuple name version <- Map.toUnfoldable resolutions let - bowerPackageName = RawPackageName ("purescript-" <> PackageName.print name) + pursuitPackageName = "purescript-" <> PackageName.print name packagePath = Path.concat [ installedResolutions, PackageName.print name <> "-" <> Version.print version ] - [ Tuple bowerPackageName { path: packagePath, version } ] + [ Tuple pursuitPackageName { path: packagePath, version } ] -- | Copy files from the package source directory to the destination directory -- | for the tarball. This will copy all always-included files as well as files @@ -1255,19 +1246,6 @@ removeIgnoredTarballFiles path = do for_ (ignoredDirectories <> ignoredFiles <> globMatches.succeeded) \match -> FS.Extra.remove (Path.concat [ path, match ]) -jsonToDhallManifest :: FilePath -> String -> Aff (Either String String) -jsonToDhallManifest dhallTypes jsonStr = do - let cmd = "json-to-dhall" - -- Dhall requires that the path begin with './', but joining paths together with Node - -- will remove the './' prefix. We need to manually append this to the relative path. - let args = [ "--records-loose", "--unions-strict", Path.concat [ dhallTypes, "v1", "Manifest.dhall" ] ] - process <- Execa.execa cmd args identity - for_ process.stdin \{ writeUtf8End } -> writeUtf8End jsonStr - result <- process.getResult - pure case result.exit of - Normally 0 -> Right jsonStr - _ -> Left result.stderr - getPacchettiBotti :: forall r. Run (PACCHETTIBOTTI_ENV + r) Owner getPacchettiBotti = do { publicKey } <- Env.askPacchettiBotti @@ -1352,7 +1330,7 @@ printLicenseValidationError = case _ of ] -- | Validate that the license in the manifest is consistent with licenses --- | detected in the repository (LICENSE file, package.json, bower.json). +-- | detected in the repository (LICENSE file, package.json). -- | -- | This check ensures that the SPDX identifier asserted in the package -- | manifest accurately represents the licenses present in the repository. diff --git a/app/src/App/Effect/Env.purs b/app/src/App/Effect/Env.purs index 268ac4f38..4fc7aff7a 100644 --- a/app/src/App/Effect/Env.purs +++ b/app/src/App/Effect/Env.purs @@ -3,7 +3,7 @@ -- | Configuration Philosophy: -- | - Production infrastructure URLs are hardcoded here -- | - Secrets (tokens, keys) are required env vars with no defaults --- | - Local dev conveniences (DATABASE_URL, DHALL_TYPES) have sensible defaults +-- | - Local dev conveniences (e.g. DATABASE_URL) have sensible defaults module Registry.App.Effect.Env where import Registry.App.Prelude @@ -16,7 +16,6 @@ import Dotenv as Dotenv import Effect.Aff as Aff import Effect.Exception as Exception import Node.FS.Aff as FS.Aff -import Node.Path as Path import Node.Process as Process import Registry.Foreign.Octokit (GitHubToken(..), IssueNumber) import Run (Run) @@ -24,8 +23,7 @@ import Run.Reader (Reader) import Run.Reader as Run.Reader type ResourceEnv = - { dhallTypes :: FilePath - , databaseUrl :: DatabaseUrl + { databaseUrl :: DatabaseUrl , s3ApiUrl :: URL , s3BucketUrl :: URL , githubApiUrl :: URL @@ -49,7 +47,6 @@ runResourceEnv = Run.Reader.runReaderAt _resourceEnv lookupResourceEnv :: forall m. MonadEffect m => m ResourceEnv lookupResourceEnv = do - dhallTypesEnv <- liftEffect <<< Path.resolve [] =<< lookupWithDefault dhallTypes "./types" databaseUrlEnv <- lookupWithDefault databaseUrl { prefix: "sqlite:", path: "db/registry.sqlite3" } s3ApiUrlEnv <- lookupWithDefault s3ApiUrl productionS3ApiUrl @@ -61,8 +58,7 @@ lookupResourceEnv = do -- Optional - if not set, healthcheck pinging is disabled healthchecksUrlEnv <- lookupOptional healthchecksUrl pure - { dhallTypes: dhallTypesEnv - , databaseUrl: databaseUrlEnv + { databaseUrl: databaseUrlEnv , s3ApiUrl: s3ApiUrlEnv , s3BucketUrl: s3BucketUrlEnv , githubApiUrl: githubApiUrlEnv @@ -184,10 +180,6 @@ type DatabaseUrl = { prefix :: String, path :: FilePath } databaseUrl :: EnvKey DatabaseUrl databaseUrl = EnvKey { key: "DATABASE_URL", decode: decodeDatabaseUrl } --- | The location of the Dhall specifications directory. -dhallTypes :: EnvKey FilePath -dhallTypes = EnvKey { key: "DHALL_TYPES", decode: pure } - -- | Override for the S3 storage API URL. -- | If not set, uses productionS3ApiUrl. -- | Set this to point to mock services during testing. diff --git a/app/src/App/Effect/GitHub.purs b/app/src/App/Effect/GitHub.purs index 0caa8cc40..1cf9213b2 100644 --- a/app/src/App/Effect/GitHub.purs +++ b/app/src/App/Effect/GitHub.purs @@ -6,10 +6,6 @@ module Registry.App.Effect.GitHub , GitHubCache , _github , _githubCache - , getCommitDate - , getContent - , getJsonFile - , getRefCommit , handle , interpret , listCommitsSince @@ -29,15 +25,12 @@ import Data.Exists as Exists import Data.HTTP.Method (Method(..)) import Data.Time.Duration as Duration import Foreign.Object as Object -import JSON as JSON import Registry.App.Effect.Cache (class FsEncodable, class MemoryEncodable, Cache, CacheRef, FsEncoding(..), MemoryEncoding(..)) import Registry.App.Effect.Cache as Cache import Registry.App.Effect.Env (RESOURCE_ENV) import Registry.App.Effect.Env as Env import Registry.App.Effect.Log (LOG) import Registry.App.Effect.Log as Log -import Registry.App.Legacy.Types (RawVersion(..)) -import Registry.Foreign.JsonRepair as JsonRepair import Registry.Foreign.Octokit (Address, GitHubError(..), GitHubRoute(..), Octokit, Request, Tag, Team) import Registry.Foreign.Octokit as Octokit import Registry.Internal.Codec as Internal.Codec @@ -76,9 +69,6 @@ data GitHub a = ListTags Address (Either GitHubError (Array Tag) -> a) | ListCommitsSince Address DateTime (Either GitHubError (Array String) -> a) | ListTeamMembers Team (Either GitHubError (Array String) -> a) - | GetContent Address String FilePath (Either GitHubError String -> a) - | GetRefCommit Address String (Either GitHubError String -> a) - | GetCommitDate Address String (Either GitHubError DateTime -> a) derive instance Functor GitHub @@ -101,30 +91,6 @@ listCommitsSince address since = Run.lift _github (ListCommitsSince address sinc listTeamMembers :: forall r. Team -> Run (GITHUB + r) (Either GitHubError (Array String)) listTeamMembers team = Run.lift _github (ListTeamMembers team identity) --- | Read the content of a file in the provided repo. -getContent :: forall r. Address -> RawVersion -> FilePath -> Run (GITHUB + r) (Either GitHubError String) -getContent address (RawVersion ref) path = Run.lift _github (GetContent address ref path identity) - --- | Read the content of a JSON file in the provided repo, decoding its contents. -getJsonFile :: forall r a. Address -> RawVersion -> CJ.Codec a -> FilePath -> Run (GITHUB + r) (Either GitHubError a) -getJsonFile address ref codec path = do - content <- getContent address ref path - let - attemptDecode inner = case JSON.parse (JsonRepair.tryRepair inner) of - Left jsonError -> Left $ Octokit.DecodeError $ "Not Json: " <> jsonError - Right json -> case CJ.decode codec json of - Left decodeError -> Left $ Octokit.DecodeError $ CJ.DecodeError.print decodeError - Right decoded -> Right decoded - pure $ attemptDecode =<< content - --- | Get the commit SHA associated with a ref on the provided repo. -getRefCommit :: forall r. Address -> RawVersion -> Run (GITHUB + r) (Either GitHubError String) -getRefCommit address (RawVersion ref) = Run.lift _github (GetRefCommit address ref identity) - --- | Get the published date of a commit SHA -getCommitDate :: forall r. Address -> String -> Run (GITHUB + r) (Either GitHubError DateTime) -getCommitDate address commitSha = Run.lift _github (GetCommitDate address commitSha identity) - -- | Interpret the GITHUB effect, given a handler. interpret :: forall r a. (GitHub ~> Run r) -> Run (GITHUB + r) a -> Run r a interpret handler = Run.interpret (Run.on _github handler Run.send) @@ -155,27 +121,6 @@ handle env = Cache.interpret _githubCache (Cache.handleMemoryFs { cache: env.cac result <- request env.octokit (Octokit.listTeamMembersRequest team) pure $ reply $ map (map _.login) result - GetContent address ref path reply -> do - Log.debug $ "Fetching content from " <> address.owner <> "/" <> address.repo <> " at ref " <> ref <> " at path " <> path - request env.octokit (Octokit.getContentRequest { address, ref, path }) >>= case _ of - Left error -> pure $ reply $ Left error - Right result -> case Octokit.decodeBase64Content result of - Left base64Error -> do - Log.debug $ "Failed to decode base64-encoded file: " <> base64Error - pure $ reply $ Left $ DecodeError base64Error - Right decoded -> - pure $ reply $ Right decoded - - GetRefCommit address ref reply -> do - Log.debug $ "Fetching commit associated with ref " <> ref <> " on repository " <> address.owner <> "/" <> address.repo - result <- request env.octokit (Octokit.getRefCommitRequest { address, ref }) - pure $ reply result - - GetCommitDate address commitSha reply -> do - Log.debug $ "Fetching commit date associated with commit sha " <> commitSha <> " on repository " <> address.owner <> "/" <> address.repo - result <- request env.octokit (Octokit.getCommitDateRequest { address, commitSha }) - pure $ reply result - -- | A helper function for implementing GET requests to the GitHub API that -- | relies on the GitHub API to report whether there is any new data, and falls -- | back to the cache if there is not. diff --git a/app/src/App/Effect/Pursuit.purs b/app/src/App/Effect/Pursuit.purs index ea3cb61c4..ca4a8779c 100644 --- a/app/src/App/Effect/Pursuit.purs +++ b/app/src/App/Effect/Pursuit.purs @@ -17,8 +17,7 @@ import Registry.App.Effect.Env (RESOURCE_ENV) import Registry.App.Effect.Env as Env import Registry.App.Effect.Log (LOG) import Registry.App.Effect.Log as Log -import Registry.App.Legacy.LenientVersion (LenientVersion(..)) -import Registry.App.Legacy.LenientVersion as LenientVersion +import Registry.App.Version as App.Version import Registry.Foreign.Gzip as Gzip import Registry.Foreign.Octokit (GitHubToken(..)) import Registry.PackageName as PackageName @@ -139,6 +138,6 @@ availableVersionsCodec = Profunctor.dimap toRep fromRep (CJ.array (CJ.array CJ.s toRep = map (\(Tuple version url) -> [ Version.print version, url ]) <<< Map.toUnfoldable fromRep = Map.fromFoldable <<< Array.mapMaybe \array -> do rawVersion <- Array.index array 0 - LenientVersion { version } <- hush $ LenientVersion.parse rawVersion + version <- hush $ App.Version.parseLenient rawVersion url <- Array.index array 1 pure $ Tuple version url diff --git a/app/src/App/Effect/Source.purs b/app/src/App/Effect/Source.purs index 5926df7f1..b0a059c36 100644 --- a/app/src/App/Effect/Source.purs +++ b/app/src/App/Effect/Source.purs @@ -8,22 +8,13 @@ import Data.DateTime (DateTime) import Data.String as String import Effect.Aff (Milliseconds(..)) import Effect.Aff as Aff -import Effect.Exception as Exception import Effect.Now as Now -import Fetch.Retry as Fetch -import Node.Buffer as Buffer -import Node.FS.Aff as FS.Aff import Node.Path as Path import Registry.App.CLI.Git as Git -import Registry.App.CLI.Tar as Tar -import Registry.App.Effect.GitHub (GITHUB) -import Registry.App.Effect.GitHub as GitHub import Registry.App.Effect.Log (LOG) import Registry.App.Effect.Log as Log -import Registry.App.Legacy.Types (RawVersion(..)) import Registry.Foreign.FSExtra as FS.Extra import Registry.Foreign.Octokit as Octokit -import Registry.Foreign.Tar as Foreign.Tar import Registry.Location as Location import Run (AFF, EFFECT, Run) import Run as Run @@ -48,7 +39,6 @@ data FetchError | InaccessibleRepo Octokit.Address -- | A transient network error occurred (timeout, DNS failure, etc.). The caller should retry later. | TransientError { owner :: String, repo :: String, message :: String } - | NoToplevelDir | Fatal String printFetchError :: FetchError -> String @@ -57,7 +47,6 @@ printFetchError = case _ of NoSubdir -> "Monorepos and the `subdir` key are not supported yet. See issue #16." InaccessibleRepo { owner, repo } -> "Repository located at https://github.com/" <> owner <> "/" <> repo <> ".git is inaccessible or does not exist." TransientError { owner, repo, message } -> "Transient error accessing repository " <> owner <> "/" <> repo <> ": " <> message <> ". This may succeed on retry." - NoToplevelDir -> "Downloaded tarball has no top-level directory." Fatal err -> "Unrecoverable error. " <> err -- | Fetch the provided location to the provided destination path. @@ -73,7 +62,7 @@ interpret :: forall r a. (Source ~> Run r) -> Run (SOURCE + r) a -> Run r a interpret handler = Run.interpret (Run.on _source handler Run.send) -- | Handle the SOURCE effect by downloading package source to the file system. -handle :: forall r a. Source a -> Run (GITHUB + LOG + AFF + EFFECT + r) a +handle :: forall r a. Source a -> Run (LOG + AFF + EFFECT + r) a handle = case _ of Fetch destination location ref reply -> map (map reply) Except.runExcept do Log.info $ "Fetching " <> printJson Location.codec location @@ -86,139 +75,74 @@ handle = case _ of -- TODO: Support subdir. In the meantime, we verify subdir is not present. (See #16) when (isJust subdir) $ Except.throw NoSubdir - case pursPublishMethod of - -- This needs to be removed so that we can support non-GitHub packages (#15) - -- and monorepo packages (#16). - -- - -- However, the PureScript compiler requires packages to be a Git repo - -- with a tag checked out. Until we can replace using the compiler's - -- 'publish' command for docs we have to use this hacky checkout. - LegacyPursPublish -> do - Log.debug $ "Using legacy Git clone to fetch package source at tag: " <> show { owner, repo, ref } - - let - cloneUrl = - Array.fold [ "https://github.com/", owner, "/", repo ] - - Log.debug $ "Verifying repository is accessible: " <> cloneUrl - Run.liftAff (Git.gitRepoIsAccessibleWithRetry cloneUrl) >>= case _ of - Git.RepoAccessible -> pure unit - Git.RepoNotFound -> do - Log.error $ "Repository " <> owner <> "/" <> repo <> " does not exist or is private." - Except.throw $ InaccessibleRepo { owner, repo } - Git.RepoTransientError err -> do - Log.error $ "Repository " <> owner <> "/" <> repo <> " could not be reached due to a transient error: " <> err - Except.throw $ TransientError { owner, repo, message: err } - - let - repoDir = Path.concat [ destination, repo <> "-" <> ref ] - - -- If a git clone is cancelled by the timeout, but had partially-cloned, then it will - -- leave behind files that prevent a retry. - retryOpts = defaultRetry - { cleanupOnCancel = FS.Extra.remove repoDir - , timeout = Milliseconds 15_000.0 - } - - -- We disable Git LFS smudging because package sources should not - -- contain large binary files. This avoids downloading LFS objects - -- from misconfigured packages. - cloneArgs = - [ "-c" - , "filter.lfs.smudge=cat" - , "-c" - , "filter.lfs.process=cat" - , "clone" - , cloneUrl - , "--branch" - , ref - , "--single-branch" - , "-c" - , "advice.detachedHead=false" - , repoDir - ] - - clonePackageAtTag = - withRetry retryOpts (Git.gitCLI cloneArgs Nothing) >>= case _ of - Cancelled -> - Aff.throwError $ Aff.error $ "Timed out attempting to clone git tag: " <> cloneUrl <> " " <> ref - Failed err -> - Aff.throwError $ Aff.error err - Succeeded _ -> - pure unit - - Run.liftAff (Aff.attempt clonePackageAtTag) >>= case _ of - Right _ -> Log.debug $ "Cloned package source to " <> repoDir - Left error -> do - Log.warn $ "Git clone command failed:\n " <> String.joinWith " " (Array.cons "git" cloneArgs) - Log.error $ "Failed to clone git tag: " <> Aff.message error - - -- We'll receive this message if we try to clone a repo which doesn't - -- exist, which is interpreted as an attempt to fetch a private repo. - let missingRepoErr = "fatal: could not read Username for 'https://github.com': terminal prompts disabled" - - if String.contains (String.Pattern missingRepoErr) (Aff.message error) then - Except.throw $ InaccessibleRepo { owner, repo } - else - Except.throw $ Fatal $ "Failed to clone repository " <> owner <> "/" <> repo <> " at ref " <> ref <> ": " <> Aff.message error - - Log.debug $ "Getting published time..." - publishedTime <- Run.liftEffect Now.nowDateTime - pure { path: repoDir, published: publishedTime } - - -- This method is not currently used (see the comment on LegacyPursPublish), - -- but it's implemented here to demonstrate what we should do once we no - -- longer have to check out the repository. - PursPublish -> do - Log.debug $ "Using GitHub API to fetch package source at tag " <> show { owner, repo, ref } - commitDate <- do - let upstream = owner <> "/" <> repo - commit <- GitHub.getRefCommit { owner, repo } (RawVersion ref) >>= case _ of - Left githubError -> do - Log.error $ "Failed to fetch " <> upstream <> " at ref " <> ref <> ": " <> Octokit.printGitHubError githubError - Except.throw $ Fatal $ "Failed to fetch commit data associated with " <> upstream <> " at ref " <> ref - Right result -> pure result - GitHub.getCommitDate { owner, repo } commit >>= case _ of - Left githubError -> do - Log.error $ "Failed to fetch " <> upstream <> " at commit " <> commit <> ": " <> Octokit.printGitHubError githubError - Except.throw $ Fatal $ "Unable to get published time for commit " <> commit <> " associated with the given ref " <> ref - Right a -> pure a - - let tarballName = ref <> ".tar.gz" - let absoluteTarballPath = Path.concat [ destination, tarballName ] - let archiveUrl = "https://github.com/" <> owner <> "/" <> repo <> "/archive/" <> tarballName - Log.debug $ "Fetching tarball from GitHub: " <> archiveUrl - - response :: RetryResult Fetch.RetryRequestError Fetch.Response <- - Run.liftAff $ Fetch.withRetryRequest archiveUrl {} - - case response of - Cancelled -> Except.throw $ Fatal $ "Could not download " <> archiveUrl - Failed (Fetch.FetchError error) -> do - Log.error $ "Failed to download " <> archiveUrl <> " because of an HTTP error: " <> Exception.message error - Except.throw $ Fatal $ "Could not download " <> archiveUrl - Failed (Fetch.StatusError { status, arrayBuffer: arrayBufferAff }) -> do - arrayBuffer <- Run.liftAff arrayBufferAff - buffer <- Run.liftEffect $ Buffer.fromArrayBuffer arrayBuffer - bodyString <- Run.liftEffect $ Buffer.toString UTF8 (buffer :: Buffer) - Log.error $ "Failed to download " <> archiveUrl <> " because of a non-200 status code (" <> show status <> ") with body " <> bodyString - Except.throw $ Fatal $ "Could not download " <> archiveUrl - Succeeded { arrayBuffer: arrayBufferAff } -> do - arrayBuffer <- Run.liftAff arrayBufferAff - Log.debug $ "Successfully downloaded " <> archiveUrl <> " into a buffer." - buffer <- Run.liftEffect $ Buffer.fromArrayBuffer arrayBuffer - Run.liftAff (Aff.attempt (FS.Aff.writeFile absoluteTarballPath buffer)) >>= case _ of - Left error -> do - Log.error $ "Downloaded " <> archiveUrl <> " but failed to write it to the file at path " <> absoluteTarballPath <> ":\n" <> Aff.message error - Except.throw $ Fatal $ "Could not download " <> archiveUrl <> " due to an internal error." - Right _ -> - Log.debug $ "Tarball downloaded to " <> absoluteTarballPath - - Log.debug "Verifying tarball..." - Foreign.Tar.getToplevelDir absoluteTarballPath >>= case _ of - Nothing -> - Except.throw NoToplevelDir - Just path -> do - Log.debug "Extracting the tarball..." - Tar.extract { cwd: destination, archive: tarballName } - pure { path, published: commitDate } + Log.debug $ "Using Git clone to fetch package source at tag: " <> show { owner, repo, ref } + + let + cloneUrl = + Array.fold [ "https://github.com/", owner, "/", repo ] + + Log.debug $ "Verifying repository is accessible: " <> cloneUrl + Run.liftAff (Git.gitRepoIsAccessibleWithRetry cloneUrl) >>= case _ of + Git.RepoAccessible -> pure unit + Git.RepoNotFound -> do + Log.error $ "Repository " <> owner <> "/" <> repo <> " does not exist or is private." + Except.throw $ InaccessibleRepo { owner, repo } + Git.RepoTransientError err -> do + Log.error $ "Repository " <> owner <> "/" <> repo <> " could not be reached due to a transient error: " <> err + Except.throw $ TransientError { owner, repo, message: err } + + let + repoDir = Path.concat [ destination, repo <> "-" <> ref ] + + -- If a git clone is cancelled by the timeout, but had partially-cloned, then it will + -- leave behind files that prevent a retry. + retryOpts = defaultRetry + { cleanupOnCancel = FS.Extra.remove repoDir + , timeout = Milliseconds 15_000.0 + } + + -- We disable Git LFS smudging because package sources should not + -- contain large binary files. This avoids downloading LFS objects + -- from misconfigured packages. + cloneArgs = + [ "-c" + , "filter.lfs.smudge=cat" + , "-c" + , "filter.lfs.process=cat" + , "clone" + , cloneUrl + , "--branch" + , ref + , "--single-branch" + , "-c" + , "advice.detachedHead=false" + , repoDir + ] + + clonePackageAtTag = + withRetry retryOpts (Git.gitCLI cloneArgs Nothing) >>= case _ of + Cancelled -> + Aff.throwError $ Aff.error $ "Timed out attempting to clone git tag: " <> cloneUrl <> " " <> ref + Failed err -> + Aff.throwError $ Aff.error err + Succeeded _ -> + pure unit + + Run.liftAff (Aff.attempt clonePackageAtTag) >>= case _ of + Right _ -> Log.debug $ "Cloned package source to " <> repoDir + Left error -> do + Log.warn $ "Git clone command failed:\n " <> String.joinWith " " (Array.cons "git" cloneArgs) + Log.error $ "Failed to clone git tag: " <> Aff.message error + + -- We'll receive this message if we try to clone a repo which doesn't + -- exist, which is interpreted as an attempt to fetch a private repo. + let missingRepoErr = "fatal: could not read Username for 'https://github.com': terminal prompts disabled" + + if String.contains (String.Pattern missingRepoErr) (Aff.message error) then + Except.throw $ InaccessibleRepo { owner, repo } + else + Except.throw $ Fatal $ "Failed to clone repository " <> owner <> "/" <> repo <> " at ref " <> ref <> ": " <> Aff.message error + + Log.debug $ "Getting published time..." + publishedTime <- Run.liftEffect Now.nowDateTime + pure { path: repoDir, published: publishedTime } diff --git a/app/src/App/Legacy/LenientVersion.purs b/app/src/App/Legacy/LenientVersion.purs deleted file mode 100644 index 8097cd152..000000000 --- a/app/src/App/Legacy/LenientVersion.purs +++ /dev/null @@ -1,72 +0,0 @@ -module Registry.App.Legacy.LenientVersion - ( LenientVersion(..) - , parse - , parser - , print - , raw - , version - ) where - -import Registry.App.Prelude - -import Control.Monad.Error.Class as Error -import Data.Function (on) -import Data.Int as Int -import Data.String as String -import Parsing (Parser) -import Parsing as Parsing -import Registry.Version as Version - --- | A lenient variant on the `Registry.Version` type, which attempts to correct --- | issues with the input string while parsing and retains the raw input. -newtype LenientVersion = LenientVersion - { version :: Version - , raw :: String - } - -derive instance Newtype LenientVersion _ - -instance Eq LenientVersion where - eq = eq `on` (_.version <<< un LenientVersion) - -instance Ord LenientVersion where - compare = comparing (_.version <<< un LenientVersion) - --- | Print the `Version` within a lenient version -print :: LenientVersion -> String -print (LenientVersion v) = Version.print v.version - --- | Retrieve the parsed `Version` from a lenient version -version :: LenientVersion -> Version -version (LenientVersion v) = v.version - --- | Print the input string that was used to parse a lenient version -raw :: LenientVersion -> String -raw (LenientVersion v) = v.raw - --- | Parse a version in lenient mode, trimming spaces, prerelease identifiers, --- | and build metadata as needed. -parse :: String -> Either String LenientVersion -parse = lmap Parsing.parseErrorMessage <<< flip Parsing.runParser parser - -parser :: Parser String LenientVersion -parser = do - Parsing.ParseState unparsed _ _ <- Parsing.getParserT - parsed <- Error.liftEither $ Parsing.runParser (convertVersion unparsed) Version.parser - pure $ LenientVersion { raw: unparsed, version: parsed } - -convertVersion :: String -> String -convertVersion input = do - -- First we ensure there are no leading or trailing spaces. - String.trim input - -- Then we remove a 'v' prefix, if present. - # maybeIdentity (String.stripPrefix (String.Pattern "v")) - -- Then we group by where the version digits ought to be... - # String.split (String.Pattern ".") - -- ...so that we can trim any leading zeros - # map (maybeIdentity dropLeadingZeros) - -- and rejoin the string. - # String.joinWith "." - where - maybeIdentity k x = fromMaybe x (k x) - dropLeadingZeros = map (Int.toStringAs Int.decimal) <<< Int.fromString diff --git a/app/src/App/Legacy/Types.purs b/app/src/App/Legacy/Types.purs deleted file mode 100644 index ac184500b..000000000 --- a/app/src/App/Legacy/Types.purs +++ /dev/null @@ -1,27 +0,0 @@ -module Registry.App.Legacy.Types where - -import Registry.App.Prelude - -import Data.Codec.JSON as CJ -import Data.Profunctor as Profunctor -import Registry.Internal.Codec as Internal.Codec - --- | An unprocessed package name, which may possibly be malformed. -newtype RawPackageName = RawPackageName String - -derive instance Newtype RawPackageName _ -derive newtype instance Eq RawPackageName -derive newtype instance Ord RawPackageName - -rawPackageNameCodec :: CJ.Codec RawPackageName -rawPackageNameCodec = Profunctor.wrapIso RawPackageName CJ.string - -rawPackageNameMapCodec :: forall a. CJ.Codec a -> CJ.Codec (Map RawPackageName a) -rawPackageNameMapCodec = Internal.Codec.strMap "RawPackageMap" (Right <<< RawPackageName) (un RawPackageName) - --- | An unprocessed version -newtype RawVersion = RawVersion String - -derive instance Newtype RawVersion _ -derive newtype instance Eq RawVersion -derive newtype instance Ord RawVersion diff --git a/app/src/App/Prelude.purs b/app/src/App/Prelude.purs index 5e586ebae..cc771e102 100644 --- a/app/src/App/Prelude.purs +++ b/app/src/App/Prelude.purs @@ -1,6 +1,5 @@ module Registry.App.Prelude ( LogVerbosity(..) - , PursPublishMethod(..) , Retry , RetryResult(..) , URL @@ -22,7 +21,6 @@ module Registry.App.Prelude , parseYaml , partitionEithers , printJson - , pursPublishMethod , readJsonFile , readYamlFile , scratchDir @@ -252,12 +250,3 @@ data LogVerbosity = Quiet | Normal | Verbose derive instance Eq LogVerbosity derive instance Ord LogVerbosity - --- | A temporary flag that records whether we are using legacy purs publish --- | (which requires all packages to be a Git repository) or new purs publish --- | (which accepts any directory with package sources). -data PursPublishMethod = LegacyPursPublish | PursPublish - --- | The current purs publish method -pursPublishMethod :: PursPublishMethod -pursPublishMethod = LegacyPursPublish diff --git a/app/src/App/Version.purs b/app/src/App/Version.purs new file mode 100644 index 000000000..2dba22dd1 --- /dev/null +++ b/app/src/App/Version.purs @@ -0,0 +1,21 @@ +module Registry.App.Version + ( parseLenient + ) where + +import Registry.App.Prelude + +import Data.Int as Int +import Data.String as String +import Registry.Version as Version + +parseLenient :: String -> Either String Version +parseLenient input = do + String.trim input + # maybeIdentity (String.stripPrefix (String.Pattern "v")) + # String.split (String.Pattern ".") + # map (maybeIdentity dropLeadingZeros) + # String.joinWith "." + # Version.parse + where + maybeIdentity k x = fromMaybe x (k x) + dropLeadingZeros = map (Int.toStringAs Int.decimal) <<< Int.fromString diff --git a/app/test/App/API.purs b/app/test/App/API.purs index 205239d0e..9bec9c059 100644 --- a/app/test/App/API.purs +++ b/app/test/App/API.purs @@ -24,7 +24,6 @@ import Registry.App.Effect.Log as Log import Registry.App.Effect.Pursuit as Pursuit import Registry.App.Effect.Registry as Registry import Registry.App.Effect.Storage as Storage -import Registry.App.Legacy.Types (RawPackageName(..)) import Registry.Constants as Constants import Registry.Foreign.FSExtra as FS.Extra import Registry.Foreign.FastGlob as FastGlob @@ -162,8 +161,6 @@ spec = do } result <- Assert.Run.runTestEffects testEnv $ Except.runExcept do - -- We'll publish effect@4.0.0 from the fixtures/github-packages - -- directory, which has a purs.json manifest. let name = Utils.unsafePackageName "effect" version = Utils.unsafeVersion "4.0.0" @@ -337,11 +334,6 @@ spec = do enterCleanEnv workdir = do Env.loadEnvFile ".env.example" - -- FIXME: The publish pipeline probably shouldn't require this. But...the - -- publish pipeline requires that there be a 'types' directory containing - -- dhall types for the registry in the current working directory. - FS.Extra.copy { from: "types", to: Path.concat [ workdir, "types" ], preserveTimestamps: true } - testFixtures <- liftAff Tmp.mkTmpDir let copyFixture path = FS.Extra.copy { from: Path.concat [ "app", "fixtures", path ], to: Path.concat [ testFixtures, path ], preserveTimestamps: true } @@ -406,53 +398,43 @@ checkBuildPlanToResolutions = do expectedResolutions = Map.fromFoldable do packageName /\ version <- (Map.toUnfoldable resolutions :: Array _) let - bowerName = RawPackageName ("purescript-" <> PackageName.print packageName) + pursuitName = "purescript-" <> PackageName.print packageName path = Path.concat [ installedResolutions, PackageName.print packageName <> "-" <> Version.print version ] - pure $ Tuple bowerName { path, version } + pure $ Tuple pursuitName { path, version } parseSourceManifestSpec :: Spec.Spec Unit parseSourceManifestSpec = do - for_ - [ { label: "purs.json alone", files: [ Tuple "purs.json" pursJson ], origin: API.FromPursJson } - , { label: "spago.yaml alone", files: [ Tuple "spago.yaml" spagoYaml ], origin: API.FromSpagoYaml } - , { label: "purs.json before valid spago.yaml and legacy files", files: [ Tuple "purs.json" pursJson, Tuple "spago.yaml" spagoYaml ] <> legacyFiles, origin: API.FromPursJson } - , { label: "purs.json despite invalid spago.yaml and legacy files", files: [ Tuple "purs.json" pursJson, Tuple "spago.yaml" "[" ] <> legacyFiles, origin: API.FromPursJson } - , { label: "spago.yaml despite legacy files", files: [ Tuple "spago.yaml" spagoYaml ] <> legacyFiles, origin: API.FromSpagoYaml } - ] - \testCase -> - Spec.it ("Accepts " <> testCase.label) do - parse testCase.files >>= case _ of - Left err -> Assert.fail $ "Expected a modern manifest, but got: " <> err - Right { manifest, origin } -> do - Assert.shouldSatisfy origin (_ == testCase.origin) - let expected = if testCase.origin == API.FromPursJson then pursJson else String.replace (Pattern "registry-lib") (Replacement "yaml-package") pursJson - let expectedManifest = unsafeFromRight $ parseJson Manifest.codec expected - Assert.shouldEqual (stringifyJson Manifest.codec manifest) (stringifyJson Manifest.codec expectedManifest) - - for_ - [ { label: "no manifest", files: [] } - , { label: "bower.json only", files: [ Tuple "bower.json" bowerJson ] } - , { label: "spago.dhall without packages.dhall", files: [ Tuple "spago.dhall" spagoDhall ] } - , { label: "spago.dhall with packages.dhall", files: [ Tuple "spago.dhall" spagoDhall, Tuple "packages.dhall" "{}" ] } - , { label: "all legacy manifests without a modern manifest", files: legacyFiles } - ] - \testCase -> - Spec.it ("Rejects " <> testCase.label <> " with migration guidance") do - result <- parse testCase.files - expectError "migrate to purs.json or spago.yaml" result - - for_ - [ { label: "malformed purs.json before valid YAML and legacy files", files: [ Tuple "purs.json" "{", Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "does not typecheck" } - , { label: "ill-typed purs.json before valid YAML and legacy files", files: [ Tuple "purs.json" "{}", Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "does not typecheck" } - , { label: "deprecated SPDX identifiers in purs.json", files: [ Tuple "purs.json" (String.replace (Pattern "BSD-3-Clause") (Replacement "AGPL-3.0") pursJson), Tuple "spago.yaml" spagoYaml ] <> legacyFiles, error: "license field is not canonical" } - , { label: "malformed spago.yaml before legacy files", files: [ Tuple "spago.yaml" "[" ] <> legacyFiles, error: "not possible to read it" } - , { label: "incomplete spago.yaml before legacy files", files: [ Tuple "spago.yaml" "{}" ] <> legacyFiles, error: "No 'package' key" } - , { label: "deprecated SPDX identifiers in spago.yaml", files: [ Tuple "spago.yaml" (String.replace (Pattern "BSD-3-Clause") (Replacement "AGPL-3.0") spagoYaml) ] <> legacyFiles, error: "not possible to read it" } - ] - \testCase -> - Spec.it ("Rejects " <> testCase.label <> " without fallback") do - result <- parse testCase.files - expectError testCase.error result + Spec.it "Prefers purs.json over spago.yaml" do + result <- parse [ Tuple "purs.json" pursJson, Tuple "spago.yaml" spagoYaml ] + let + expected = + { manifest: unsafeFromRight $ parseJson Manifest.codec pursJson + , origin: API.FromPursJson + } + expected `Assert.shouldEqualRight` result + + Spec.it "Accepts spago.yaml" do + result <- parse [ Tuple "spago.yaml" spagoYaml ] + let + json = String.replace (Pattern "registry-lib") (Replacement "yaml-package") pursJson + expected = + { manifest: unsafeFromRight $ parseJson Manifest.codec json + , origin: API.FromSpagoYaml + } + expected `Assert.shouldEqualRight` result + + Spec.it "Rejects source without a manifest" do + result <- parse [] + expectError "must contain a purs.json or spago.yaml" result + + Spec.it "Rejects ill-typed purs.json without falling back to YAML" do + result <- parse [ Tuple "purs.json" "{}", Tuple "spago.yaml" spagoYaml ] + expectError "could not be decoded" result + + Spec.it "Rejects deprecated SPDX identifiers in purs.json" do + let input = String.replace (Pattern "BSD-3-Clause") (Replacement "AGPL-3.0") pursJson + result <- parse [ Tuple "purs.json" input, Tuple "spago.yaml" spagoYaml ] + expectError "license field is not canonical" result where pursJson = """{"name":"registry-lib","version":"0.0.1","license":"BSD-3-Clause","location":{"githubOwner":"purescript","githubRepo":"registry-dev"},"ref":"v0.0.1","dependencies":{"prelude":">=6.0.0 <7.0.0"}}""" @@ -470,17 +452,11 @@ package: githubRepo: registry-dev """ - bowerJson = """{"license":"BSD-3-Clause","dependencies":{"purescript-prelude":"^6.0.0"}}""" - spagoDhall = """{ license = "BSD-3-Clause", dependencies = [] : List Text, packages = ./packages.dhall }""" - legacyFiles = [ Tuple "bower.json" bowerJson, Tuple "spago.dhall" spagoDhall, Tuple "packages.dhall" "{}" ] - parse :: Array (Tuple FilePath String) -> Aff (Either String API.ParsedManifest) - parse files = do - resourceEnv <- liftEffect Env.lookupResourceEnv + parse files = Aff.bracket Tmp.mkTmpDir FS.Extra.remove \packageDir -> do for_ files \(Tuple path contents) -> FS.Aff.writeTextFile UTF8 (Path.concat [ packageDir, path ]) contents API.parseSourceManifest { packageDir, ref: "v0.0.1" } - # Env.runResourceEnv resourceEnv # Log.interpret (\(Log.Log _ _ next) -> pure next) # Except.runExcept # Run.runBaseAff' diff --git a/app/test/App/CLI/Licensee.purs b/app/test/App/CLI/Licensee.purs index b1c5042c8..0d5290a09 100644 --- a/app/test/App/CLI/Licensee.purs +++ b/app/test/App/CLI/Licensee.purs @@ -38,28 +38,16 @@ spec = do detected <- Licensee.detectFiles [ { name: "LICENSE", contents: files.license } ] Assert.shouldEqual (Right [ "MIT" ]) detected - Spec.it "Doesn't detect from Bower or Spago" do - files <- readFiles - detected <- Licensee.detectFiles - [ { name: "bower.json", contents: files.bowerJson } - , { name: "spago.dhall", contents: files.spagoDhall } - ] - Assert.shouldEqual (Right []) detected - type ManifestFiles = { license :: String , packageJson :: String - , spagoDhall :: String - , bowerJson :: String } readFiles :: Aff ManifestFiles readFiles = do license <- readLicense packageJson <- readPackageJson - spagoDhall <- readSpagoDhall - bowerJson <- readBowerJson - pure { license, packageJson, spagoDhall, bowerJson } + pure { license, packageJson } fixtureFile :: FilePath -> FilePath fixtureFile file = Path.concat [ "app", "fixtures", "licenses", "halogen-hooks", file ] @@ -69,9 +57,3 @@ readLicense = FS.Aff.readTextFile UTF8 $ fixtureFile "LICENSE" readPackageJson :: Aff String readPackageJson = FS.Aff.readTextFile UTF8 $ fixtureFile "package.json" - -readSpagoDhall :: Aff String -readSpagoDhall = FS.Aff.readTextFile UTF8 $ fixtureFile "spago.dhall" - -readBowerJson :: Aff String -readBowerJson = FS.Aff.readTextFile UTF8 $ fixtureFile "bower.json" diff --git a/app/test/App/Legacy/LenientVersion.purs b/app/test/App/Version.purs similarity index 77% rename from app/test/App/Legacy/LenientVersion.purs rename to app/test/App/Version.purs index 18c6b31eb..68016c23b 100644 --- a/app/test/App/Legacy/LenientVersion.purs +++ b/app/test/App/Version.purs @@ -1,10 +1,11 @@ -module Test.Registry.App.Legacy.LenientVersion (spec) where +module Test.Registry.App.Version (spec) where import Registry.App.Prelude import Data.Either as Either -import Registry.App.Legacy.LenientVersion as LenientVersion +import Registry.App.Version as App.Version import Registry.Test.Assert as Assert +import Registry.Version as Version import Test.Spec as Spec spec :: Spec.Spec Unit @@ -12,13 +13,13 @@ spec = do Spec.describe "Parses malformed versions with fixable errors" do for_ fixable \{ raw, version } -> Spec.it raw do - let parsed = LenientVersion.parse raw - version `Assert.shouldEqualRight` map LenientVersion.print parsed + let parsed = App.Version.parseLenient raw + version `Assert.shouldEqualRight` map Version.print parsed Spec.describe "Fails to parse unfixable malformed versions" do for_ unfixable \{ raw, label } -> Spec.it label do - let parsed = LenientVersion.parse raw + let parsed = App.Version.parseLenient raw parsed `Assert.shouldSatisfy` Either.isLeft fixable :: Array { raw :: String, version :: String } @@ -34,7 +35,7 @@ unfixable = [ { raw: ".12.0", label: "Malformed" } , { raw: "12.0", label: "Too few places" } , { raw: "0.12.12.1", label: "Too many places" } - , { raw: "1.0.1-rc.1", label: "Contains prerelease identiiers" } + , { raw: "1.0.1-rc.1", label: "Contains prerelease identifiers" } , { raw: "1.0.0+nightly", label: "Contains build metadata" } , { raw: "1.a.2", label: "Contains non-digit characters" } , { raw: "1.-1.2", label: "Contains non-digit characters" } diff --git a/app/test/Main.purs b/app/test/Main.purs index 54a3c66e0..f553cb7bc 100644 --- a/app/test/Main.purs +++ b/app/test/Main.purs @@ -12,10 +12,10 @@ import Test.Registry.App.CLI.Tar as Test.CLI.Tar import Test.Registry.App.Effect.PackageSets as Test.Effect.PackageSets import Test.Registry.App.Effect.Registry as Test.Effect.Registry import Test.Registry.App.GitHubIssue as Test.GitHubIssue -import Test.Registry.App.Legacy.LenientVersion as Test.Legacy.LenientVersion import Test.Registry.App.Manifest.SpagoYaml as Test.Manifest.SpagoYaml import Test.Registry.App.SQLite as Test.SQLite import Test.Registry.App.Server.MatrixBuilder as Test.Server.MatrixBuilder +import Test.Registry.App.Version as Test.Version import Test.Spec as Spec import Test.Spec.Reporter.Console (consoleReporter) import Test.Spec.Runner.Node (runSpecAndExitProcess') @@ -42,9 +42,6 @@ main = runSpecAndExitProcess' config [ consoleReporter ] do Spec.describe "Registry.App.GitHubIssue" do Test.GitHubIssue.spec - Spec.describe "Registry.App.Legacy" do - Spec.describe "Lenient Version" Test.Legacy.LenientVersion.spec - Spec.describe "Registry.App.Manifest" do Spec.describe "SpagoYaml" Test.Manifest.SpagoYaml.spec @@ -52,6 +49,8 @@ main = runSpecAndExitProcess' config [ consoleReporter ] do Spec.describe "MatrixBuilder" Test.Server.MatrixBuilder.spec Spec.describe "Registry.App.SQLite" Test.SQLite.spec + + Spec.describe "Registry.App.Version" Test.Version.spec where config = { defaultConfig: Cfg.defaultConfig { timeout = Just $ Milliseconds 300_000.0 } diff --git a/app/test/Test/Assert/Run.purs b/app/test/Test/Assert/Run.purs index 45a0a5e2a..bac6d4588 100644 --- a/app/test/Test/Assert/Run.purs +++ b/app/test/Test/Assert/Run.purs @@ -367,24 +367,17 @@ handleSourceMock env = case _ of Left _ -> pure $ reply $ Left $ InaccessibleRepo { owner, repo } Right _ -> do Run.liftAff $ FS.Extra.copy { from: localPath, to: destinationPath, preserveTimestamps: true } - case pursPublishMethod of - LegacyPursPublish -> do - -- When using the compiler and legacy 'purs publish' we have to be - -- in a clean git repository with the ref checked out. - Run.liftAff $ FS.Aff.rm' (Path.concat [ destinationPath, ".git" ]) { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 } - Run.liftAff $ FS.Aff.writeTextFile UTF8 (Path.concat [ destinationPath, ".gitignore" ]) "output" - let exec args = void (Git.withGit destinationPath args identity) - exec [ "init" ] - exec [ "config", "user.name", "test-user" ] - exec [ "config", "user.email", "test-user@aol.com" ] - exec [ "config", "commit.gpgSign", "false" ] - exec [ "config", "tag.gpgSign", "false" ] - exec [ "add", "." ] - exec [ "commit", "-m", "Initial commit" ] - exec [ "tag", "-m", ref, ref ] - - PursPublish -> - Except.throw "Tests are not set up for 'PursPublish' and must be fixed." + Run.liftAff $ FS.Aff.rm' (Path.concat [ destinationPath, ".git" ]) { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 } + Run.liftAff $ FS.Aff.writeTextFile UTF8 (Path.concat [ destinationPath, ".gitignore" ]) "output" + let exec args = void (Git.withGit destinationPath args identity) + exec [ "init" ] + exec [ "config", "user.name", "test-user" ] + exec [ "config", "user.email", "test-user@aol.com" ] + exec [ "config", "commit.gpgSign", "false" ] + exec [ "config", "tag.gpgSign", "false" ] + exec [ "add", "." ] + exec [ "commit", "-m", "Initial commit" ] + exec [ "tag", "-m", ref, ref ] pure $ reply $ Right { path: destinationPath, published: now } @@ -417,25 +410,3 @@ handleGitHubMock env = case _ of ListTeamMembers team reply -> pure $ reply $ case team of { org: "purescript", team: "packaging" } -> Right [ "pacchettibotti", "f-f", "thomashoneyman" ] _ -> Left $ APIError { statusCode: 404, message: "No fixture provided for team " <> team.org <> "/" <> team.team } - - GetContent address ref path reply -> do - let - name = stripPureScriptPrefix address.repo - fixedRef = fromMaybe ref $ String.stripPrefix (String.Pattern "v") ref - localPath = Path.concat [ env.github, name <> "-" <> fixedRef, path ] - - result <- Run.liftAff $ Aff.attempt (FS.Aff.readTextFile UTF8 localPath) >>= case _ of - Left _ -> pure $ Left $ APIError { statusCode: 404, message: "Not Found" } - Right contents -> pure $ Right contents - - pure $ reply result - - -- FIXME: Respond with an actual commit for specific input paths? This isn't - -- currently used in tests. - GetRefCommit _address _ref reply -> - pure $ reply $ Left $ UnexpectedError "Unimplemented" - - -- FIXME: Respond with an actual datetime for specific inputs? This isn't - -- currently used in tests. - GetCommitDate _address _ref reply -> - pure $ reply $ Left $ UnexpectedError "Unimplemented" diff --git a/flake.nix b/flake.nix index 318c6a0cb..fd97bb44b 100644 --- a/flake.nix +++ b/flake.nix @@ -45,7 +45,6 @@ (fileset.maybeMissing ./check) ./spago.lock ./spago.yaml - ./types ] ); @@ -53,15 +52,6 @@ fileset.fileFilter (f: f.name == "package.json" || f.name == "package-lock.json") ./. ); - # The location of the Dhall type specifications, used to type-check manifests. - DHALL_TYPES = ./types; - DHALL_PRELUDE = "${ - builtins.fetchGit { - url = "https://github.com/dhall-lang/dhall-lang"; - rev = "25cf020ab307cb2d66826b0d1ddac8bc89241e27"; - } - }/Prelude/package.dhall"; - # We disable git from entering interactive mode at any time, as there is no # one there to answer prompts. GIT_TERMINAL_PROMPT = 0; @@ -137,7 +127,6 @@ spago-test = pkgs.runCommand "spago-test" { - inherit DHALL_TYPES DHALL_PRELUDE; nativeBuildInputs = with pkgs; [ @@ -176,31 +165,6 @@ purs-tidy check $src && touch $out ''; - verify-dhall = - pkgs.runCommand "verify-dhall" - { - src = fileset.toSource { - root = ./.; - fileset = fileset.unions [ - ./types - ./lib/fixtures/manifests - ]; - }; - nativeBuildInputs = with pkgs; [ - dhall - dhall-json - parallel - ]; - inherit DHALL_PRELUDE; - } - '' - mkdir -p cache/dhall && export XDG_CACHE_HOME="$PWD/cache" - find $src/types/v1 -name "*.dhall" | parallel --will-cite 'dhall <<< {}' - find $src/lib/fixtures/manifests -name "*.json" | parallel --will-cite \ - 'json-to-dhall --plain --records-loose --unions-strict --file {} $src/types/v1/Manifest.dhall' - touch $out - ''; - # Integration test - exercises the server API integration = import ./nix/test/integration.nix { inherit pkgs spagoSrc; @@ -227,8 +191,7 @@ SERVER_PORT = envDefaults.SERVER_PORT; DATABASE_URL = envDefaults.DATABASE_URL; - # Dhall environment variables needed for manifest typechecking - inherit DHALL_TYPES DHALL_PRELUDE GIT_TERMINAL_PROMPT; + inherit GIT_TERMINAL_PROMPT; # NOTE: Test-specific env vars (REGISTRY_API_URL, GITHUB_API_URL, PACCHETTIBOTTI_*) # are NOT set here to avoid conflicting with .env files used by production scripts. @@ -299,11 +262,7 @@ # These env vars are known to Nix so we set them in advance. # Others, like credentials, must be set in a .env file in # the state directory, unless there are viable defaults. - inherit - DHALL_PRELUDE - GIT_TERMINAL_PROMPT - ; - DHALL_TYPES = "${pkgs.registry-server}/bin/types"; + inherit GIT_TERMINAL_PROMPT; }; }; system.stateVersion = "24.05"; diff --git a/foreign/package.json b/foreign/package.json index bcf5e0c66..24f457bd3 100644 --- a/foreign/package.json +++ b/foreign/package.json @@ -14,7 +14,6 @@ "fuse.js": "^7.5.0", "jsonrepair": "^3.15.0", "registry-lib": "file:../lib", - "tar": "^7.5.22", "tmp": "^0.2.7", "yaml": "^2.9.1" } diff --git a/foreign/spago.yaml b/foreign/spago.yaml index 92a611de5..8ce6fe634 100644 --- a/foreign/spago.yaml +++ b/foreign/spago.yaml @@ -7,7 +7,6 @@ package: - aff - aff-promise - arrays - - b64 - bifunctors - codec - codec-json @@ -30,13 +29,11 @@ package: - newtype - node-buffer - node-path - - nullable - ordered-collections - prelude - profunctor - registry-lib - strings - - transformers - tuples - unsafe-coerce - variant diff --git a/foreign/src/Foreign/Octokit.purs b/foreign/src/Foreign/Octokit.purs index 92637620b..5fa75f745 100644 --- a/foreign/src/Foreign/Octokit.purs +++ b/foreign/src/Foreign/Octokit.purs @@ -6,7 +6,6 @@ -- | logging effect that those requests rely on. module Registry.Foreign.Octokit ( Address - , Base64Content(..) , GitHubAPIError , GitHubError(..) , GitHubRoute(..) @@ -22,13 +21,8 @@ module Registry.Foreign.Octokit , atKey , closeIssueRequest , createCommentRequest - , decodeBase64Content - , getCommitDateRequest - , getContentRequest - , getRefCommitRequest , githubApiErrorCodec , githubErrorCodec - , isPermanentGitHubError , listCommitsSinceRequest , listTagsRequest , listTeamMembersRequest @@ -45,7 +39,6 @@ module Registry.Foreign.Octokit import Prelude import Codec.JSON.DecodeError as CJ.DecodeError -import Control.Monad.Except (except) import Control.Promise (Promise) import Control.Promise as Promise import Data.Array as Array @@ -67,7 +60,6 @@ import Data.Maybe as Maybe import Data.Newtype (class Newtype, unwrap) import Data.Profunctor as Profunctor import Data.String as String -import Data.String.Base64 as Base64 import Data.Tuple (Tuple(..)) import Data.Variant as Variant import Effect.Aff as Aff @@ -80,7 +72,6 @@ import JSON (JSON) import JSON as JSON import JSON.Object as JSON.Object import JSON.Path as JSON.Path -import Node.Path (FilePath) import Registry.Internal.Codec as Internal.Codec import Type.Proxy (Proxy(..)) import Unsafe.Coerce (unsafeCoerce) @@ -100,19 +91,6 @@ foreign import newOctokitImpl :: EffectFn2 GitHubToken String Octokit newOctokit :: forall m. MonadEffect m => GitHubToken -> String -> m Octokit newOctokit token baseUrl = liftEffect $ runEffectFn2 newOctokitImpl token baseUrl --- | A newline-delimited base64-encoded file retrieved from the GitHub API -newtype Base64Content = Base64Content String - -derive instance Newtype Base64Content _ - -decodeBase64Content :: Base64Content -> Either String String -decodeBase64Content (Base64Content string) = - -- Join base64 chunks before decoding to avoid splitting multi-byte UTF-8 - -- sequences. GitHub's API returns base64 with embedded newlines. - case Base64.decode $ String.replaceAll (String.Pattern "\n") (String.Replacement "") string of - Left error -> Left $ Aff.message error - Right value -> Right value - -- | The address of a GitHub repository as a owner/repo pair. type Address = { owner :: String, repo :: String } @@ -173,69 +151,6 @@ listCommitsSinceRequest { address, since } = , codec: CJ.array $ CJ.named "CommitSha" $ CJ.Record.object { sha: CJ.string } } --- | Fetch a specific file from the provided repository at the given ref and --- | filepath. Filepaths should lead to a single file from the root of the repo. --- | https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/v5.16.0/docs/repos/getContent.md -getContentRequest :: { address :: Address, ref :: String, path :: FilePath } -> Request Base64Content -getContentRequest { address, ref, path } = - { route: GitHubRoute GET [ "repos", address.owner, address.repo, "contents", path ] (Map.singleton "ref" ref) - , headers: Object.empty - , args: noArgs - , paginate: false - , codec: Profunctor.dimap toJsonRep fromJsonRep $ CJ.named "Content" $ CJ.Record.object - { data: CJ.Record.object - { type: value "file" - , encoding: value "base64" - , content: CJ.string - } - } - } - where - value :: String -> CJ.Codec String - value expected = Codec.codec' - ( \json -> except $ CJ.decode CJ.string json >>= \decoded -> case decoded == expected of - true -> pure expected - false -> Left (CJ.DecodeError.basic $ "Unexpected JSON value (expecting '" <> expected <> "'): " <> JSON.print json) - ) - (\_ -> CJ.encode CJ.string expected) - - toJsonRep (Base64Content str) = { data: { type: "file", encoding: "base64", content: str } } - fromJsonRep { data: { content } } = Base64Content content - --- | Fetch the commit SHA for a given ref on a GitHub repository --- | https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/v5.16.0/docs/git/getRef.md -getRefCommitRequest :: { address :: Address, ref :: String } -> Request String -getRefCommitRequest { address, ref } = - { route: GitHubRoute GET [ "repos", address.owner, address.repo, "git", "ref", ref ] Map.empty - , headers: Object.empty - , args: noArgs - , paginate: false - , codec: Profunctor.dimap toJsonRep fromJsonRep $ CJ.named "Ref" $ CJ.Record.object { object: CJ.Record.object { sha: CJ.string } } - } - where - toJsonRep sha = { object: { sha } } - fromJsonRep = _.object.sha - --- | Fetch the date associated with a given commit, in the RFC3339String format. --- | https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/v5.16.0/docs/git/getCommit.md -getCommitDateRequest :: { address :: Address, commitSha :: String } -> Request DateTime -getCommitDateRequest { address, commitSha } = - { route: GitHubRoute GET [ "repos", address.owner, address.repo, "git", "commits", commitSha ] Map.empty - , headers: Object.empty - , args: noArgs - , paginate: false - , codec: Profunctor.dimap toJsonRep fromJsonRep $ CJ.named "CommitData" $ CJ.Record.object - { data: CJ.named "Commit" $ CJ.Record.object - { committer: CJ.named "Commit.committer" $ CJ.Record.object - { date: Internal.Codec.iso8601DateTime - } - } - } - } - where - toJsonRep date = { data: { committer: { date } } } - fromJsonRep = _.data.committer.date - -- | Create a comment on an issue. Requires authentication. -- | https://github.com/octokit/plugin-rest-endpoint-methods.js/blob/v5.16.0/docs/issues/createComment.md createCommentRequest :: { address :: Address, issue :: IssueNumber, body :: String } -> Request Unit @@ -407,25 +322,6 @@ printGitHubError = case _ of , error ] --- | Returns true if the error represents a permanent failure that is safe to --- | cache across runs. Transient errors (rate limits, network issues, server --- | errors) return false and should be retried. --- | --- | Permanent errors: --- | - 404 Not Found: Resource doesn't exist at this ref/path --- | - DecodeError: Content exists but is malformed (immutable at a given tag) --- | --- | Transient errors (should NOT be cached): --- | - UnexpectedError: Network issues, DNS, TLS problems --- | - 401/403: Auth or rate limit issues --- | - 5xx: Server-side problems --- | - Any other status codes -isPermanentGitHubError :: GitHubError -> Boolean -isPermanentGitHubError = case _ of - APIError { statusCode: 404 } -> true - DecodeError _ -> true - _ -> false - atKey :: forall a. String -> CJ.Codec a -> JSON.JObject -> Either CJ.DecodeError a atKey key codec object = Maybe.maybe diff --git a/foreign/src/Foreign/Tar.js b/foreign/src/Foreign/Tar.js deleted file mode 100644 index 8496a9e8c..000000000 --- a/foreign/src/Foreign/Tar.js +++ /dev/null @@ -1,18 +0,0 @@ -import { list } from "tar"; - -// get toplevel directory in tar -export const getToplevelDirImpl = (filename) => () => { - var entries = []; - list({ - file: filename, - sync: true, - filter: (path, _) => { - var topLevel = /^[^\/]+\/$/; - return topLevel.exec(path); - }, - onReadEntry: (entry) => { - entries.push(entry.path); - }, - }); - return entries; -}; diff --git a/foreign/src/Foreign/Tar.purs b/foreign/src/Foreign/Tar.purs deleted file mode 100644 index 2087a78c9..000000000 --- a/foreign/src/Foreign/Tar.purs +++ /dev/null @@ -1,18 +0,0 @@ -module Registry.Foreign.Tar - ( getToplevelDir - ) where - -import Prelude - -import Data.Array as Array -import Data.Function.Uncurried (Fn1, runFn1) -import Data.Maybe (Maybe) -import Effect (Effect) -import Effect.Class (class MonadEffect, liftEffect) - -foreign import getToplevelDirImpl :: Fn1 String (Effect (Array String)) - -getToplevelDir :: forall m. MonadEffect m => String -> m (Maybe String) -getToplevelDir filename = do - paths <- liftEffect $ runFn1 getToplevelDirImpl filename - pure $ Array.head paths diff --git a/foreign/test/Foreign.purs b/foreign/test/Foreign.purs index 8bf36f4a9..ed7de88fa 100644 --- a/foreign/test/Foreign.purs +++ b/foreign/test/Foreign.purs @@ -6,7 +6,6 @@ import Effect (Effect) import Test.Registry.Foreign.FastGlob as Foreign.FastGlob import Test.Registry.Foreign.Gzip as Foreign.Gzip import Test.Registry.Foreign.JsonRepair as Foreign.JsonRepair -import Test.Registry.Foreign.Octokit as Foreign.Octokit import Test.Registry.Foreign.SPDX as Foreign.SPDX import Test.Spec as Spec import Test.Spec.Reporter.Console (consoleReporter) @@ -19,4 +18,3 @@ main = runSpecAndExitProcess [ consoleReporter ] do Spec.describe "JsonRepair" Foreign.JsonRepair.spec Spec.describe "FastGlob" Foreign.FastGlob.spec Spec.describe "Gzip" Foreign.Gzip.spec - Spec.describe "Octokit" Foreign.Octokit.spec diff --git a/foreign/test/Foreign/Octokit.purs b/foreign/test/Foreign/Octokit.purs deleted file mode 100644 index 4062be9a4..000000000 --- a/foreign/test/Foreign/Octokit.purs +++ /dev/null @@ -1,53 +0,0 @@ -module Test.Registry.Foreign.Octokit (spec) where - -import Prelude - -import Data.Either (Either(..)) -import Registry.Foreign.Octokit (Base64Content(..), decodeBase64Content) -import Registry.Test.Assert as Assert -import Test.Spec as Spec - -spec :: Spec.Spec Unit -spec = do - Spec.describe "decodeBase64Content" do - Spec.it "Decodes simple ASCII content" do - -- "hello world" in base64 - let content = Base64Content "aGVsbG8gd29ybGQ=" - decodeBase64Content content `Assert.shouldEqual` Right "hello world" - - Spec.it "Decodes content with embedded newlines" do - -- "hello world" split across lines (GitHub API style) - let content = Base64Content "aGVs\nbG8g\nd29ybGQ=" - decodeBase64Content content `Assert.shouldEqual` Right "hello world" - - Spec.it "Decodes UTF-8 with multi-byte characters" do - -- "hello — world" (with em-dash) in base64 - let content = Base64Content "aGVsbG8g4oCUIHdvcmxk" - decodeBase64Content content `Assert.shouldEqual` Right "hello — world" - - Spec.it "Decodes UTF-8 when multi-byte character is split across newline boundary" do - -- This is the gesso bug: "PureScript — Compatible" where the em-dash's - -- UTF-8 bytes (E2 80 94) get split by a newline in the base64 encoding. - -- The base64 "IOGA\nlC" decodes to bytes [20 E1 80] + [94 ...] which is - -- an incomplete UTF-8 sequence if decoded separately. - -- - -- Real example from gesso@1.0.1 spago.yaml (truncated): - -- "...in PureScript — Compatible..." - let - content = Base64Content - "aW4gUHVyZVNjcmlwdCDigJQgQ29tcGF0aWJsZQ==" - decodeBase64Content content `Assert.shouldEqual` Right "in PureScript — Compatible" - - Spec.it "Decodes the actual gesso content that was failing" do - -- The exact base64 from gesso's spago.yaml cache that caused the failure. - -- The newlines split the em-dash's UTF-8 bytes (E2 80 94) such that - -- line 2 ends with "IOGA" which decodes to bytes [20 E2 80] - an incomplete - -- UTF-8 sequence. Line 3 starts with "lC" which has the missing byte 94. - -- The old code decoded each line separately, failing on the incomplete UTF-8. - let - content = Base64Content $ - "cGFja2FnZToKICBuYW1lOiBnZXNzbwogIGRlc2NyaXB0aW9uOiAiRWFzaWx5\n" - <> "IGJ1aWxkIDxjYW52YXM+IGFwcGxpY2F0aW9ucyBpbiBQdXJlU2NyaXB0IOKA\n" - <> "lCBDb21wYXRpYmxlIHdpdGggSGFsb2dlbiI=" - decodeBase64Content content `Assert.shouldEqual` - Right "package:\n name: gesso\n description: \"Easily build applications in PureScript \x2014 Compatible with Halogen\"" diff --git a/lib/fixtures/packages/halogen-hooks-0.5.0/spago.dhall b/lib/fixtures/packages/halogen-hooks-0.5.0/spago.dhall deleted file mode 100644 index b8f96ab21..000000000 --- a/lib/fixtures/packages/halogen-hooks-0.5.0/spago.dhall +++ /dev/null @@ -1,7 +0,0 @@ -{ name = "halogen-hooks" -, license = "MIT" -, repository = "https://github.com/thomashoneyman/purescript-halogen-hooks" -, dependencies = [ "halogen" ] -, packages = ./packages.dhall -, sources = [ "src/**/*.purs" ] -} diff --git a/lib/src/Constants.purs b/lib/src/Constants.purs index c170367d6..b18e4d15b 100644 --- a/lib/src/Constants.purs +++ b/lib/src/Constants.purs @@ -73,9 +73,6 @@ includedGlobs :: Array String includedGlobs = [ "src/" , "purs.json" - , "spago.dhall" - , "packages.dhall" - , "bower.json" , "package.json" , "spago.yaml" ] diff --git a/lib/src/Location.purs b/lib/src/Location.purs index 64685bf33..fddf26156 100644 --- a/lib/src/Location.purs +++ b/lib/src/Location.purs @@ -1,8 +1,5 @@ -- | Implementation of the `Location` data type from the registry spec. -- | https://github.com/purescript/registry-dev/blob/master/SPEC.md#location --- | --- | There is also a Dhall spec for this data type: --- | https://github.com/purescript/registry-dev/blob/master/specs/v1/Location.dhall module Registry.Location ( GitData , GitHubData diff --git a/lib/src/Manifest.purs b/lib/src/Manifest.purs index d90511d8f..85fc948d6 100644 --- a/lib/src/Manifest.purs +++ b/lib/src/Manifest.purs @@ -5,9 +5,6 @@ -- | dependencies, and more. -- | https://github.com/purescript/registry-dev/blob/master/SPEC.md#manifest -- | --- | There is also a Dhall spec for this data type: --- | https://github.com/purescript/registry-dev/blob/master/specs/v1/Manifest.dhall --- | -- | Package manifests can be found in the tarball for each package, and they -- | are also cached in the registry index: -- | https://github.com/purescript/registry-index diff --git a/lib/src/Metadata.purs b/lib/src/Metadata.purs index 5c2798dcd..92d67ffe0 100644 --- a/lib/src/Metadata.purs +++ b/lib/src/Metadata.purs @@ -4,9 +4,6 @@ -- | used location of the source code. -- | https://github.com/purescript/registry-dev/blob/master/SPEC.md#metadata -- | --- | There is also a Dhall spec for this data type: --- | https://github.com/purescript/registry-dev/blob/master/specs/v1/Metadata.dhall --- | -- | Package metadata can be found at in the registry repository: -- | https://github.com/purescript/registry/blob/main/metadata module Registry.Metadata diff --git a/lib/src/Operation/Validation.purs b/lib/src/Operation/Validation.purs index c842145d9..9cd200b72 100644 --- a/lib/src/Operation/Validation.purs +++ b/lib/src/Operation/Validation.purs @@ -266,9 +266,6 @@ alwaysIncludedFilesIfPresent :: Set FilePath alwaysIncludedFilesIfPresent = Set.fromFoldable [ "purs.json" , "spago.yaml" - , "spago.dhall" - , "packages.dhall" - , "bower.json" , "package.json" ] diff --git a/lib/src/Owner.purs b/lib/src/Owner.purs index f02abfedc..1acd39f0f 100644 --- a/lib/src/Owner.purs +++ b/lib/src/Owner.purs @@ -1,9 +1,6 @@ -- | Implementation of the `Owner` data type from the registry spec. An Owner -- | identifies who can take authenticated operations for a given package. -- | https://github.com/purescript/registry-dev/blob/master/SPEC.md#owner --- | --- | There is also a Dhall spec for this data type: --- | https://github.com/purescript/registry-dev/blob/master/specs/v1/Owner.dhall module Registry.Owner ( Owner(..) , codec diff --git a/lib/test/Registry/Sha256.purs b/lib/test/Registry/Sha256.purs index 41c1f5cdb..58c2f8e09 100644 --- a/lib/test/Registry/Sha256.purs +++ b/lib/test/Registry/Sha256.purs @@ -18,38 +18,18 @@ import Test.Spec as Spec spec :: Spec.Spec Unit spec = do - Spec.it "Hashes a spago.dhall file the same as openssl" do - hash <- Sha256.hashFile hooksSpago - hash `Assert.shouldEqual` hooksSpagoHash - Spec.it "Hashes a LICENSE file the same as openssl" do hash <- Sha256.hashFile hooksLicense hash `Assert.shouldEqual` hooksLicenseHash - Spec.it "spago.dhall hash matches the nix hash" do - hash <- Sha256.hashFile hooksSpago - nix <- Except.runExceptT $ sha256Nix hooksSpago - hash `Assert.shouldEqualRight` nix - Spec.it "LICENSE hash matches the nix hash" do hash <- Sha256.hashFile hooksLicense nix <- Except.runExceptT $ sha256Nix hooksLicense hash `Assert.shouldEqualRight` nix - Spec.it "Round-trips spago.dhall file" do - hooksSpagoHash `Assert.shouldEqualRight` Sha256.parse (Sha256.print hooksSpagoHash) - Spec.it "Round-trips LICENSE file" do hooksLicenseHash `Assert.shouldEqualRight` Sha256.parse (Sha256.print hooksLicenseHash) --- Test hash produced by `openssl`: --- openssl dgst -sha256 -binary < fixtures/packages/halogen-hooks-0.5.0/spago.dhall | openssl base64 -A -hooksSpagoHash :: Sha256.Sha256 -hooksSpagoHash = Utils.fromRight "Failed to parse Sha256" $ Sha256.parse "sha256-fN9RUAzN21ZY4Y0UwqUSxwUPVz1g7/pcqoDvbJZoT04=" - -hooksSpago :: FilePath -hooksSpago = Path.concat [ "lib", "fixtures", "packages", "halogen-hooks-0.5.0", "spago.dhall" ] - -- Test hash produced by `openssl`: -- openssl dgst -sha256 -binary < fixtures/packages/halogen-hooks-0.5.0/LICENSE | openssl base64 -A hooksLicenseHash :: Sha256.Sha256 diff --git a/nix/overlay.nix b/nix/overlay.nix index 6effc29a5..cf35bcd9c 100644 --- a/nix/overlay.nix +++ b/nix/overlay.nix @@ -167,7 +167,7 @@ in npmFlags = [ "--omit=optional" ]; # To update: run `prefetch-npm-deps package-lock.json` in the dev shell. - npmDepsHash = "sha256-p/CDenuiSblZ8CunSkUt8v2I99D0fTQWNZKCRuRK78o="; + npmDepsHash = "sha256-SkkTzmtacCliKT5N3bO9kzd0OxjNYowXVkvO7TgNSaY="; installPhase = '' mkdir -p $out @@ -220,8 +220,6 @@ in registry-compilers registry-purs-versions - dhall - dhall-json licensee git git-lfs @@ -238,11 +236,8 @@ in description = "PureScript Registry API server"; src = ../app; spagoLock = app; - # Keep runtime schemas in the server closure rather than relying on a - # separately referenced source path in the deployed environment. extraInstall = '' cp -r ${../db} $out/bin/db - cp -r ${../types} $out/bin/types ''; }) { }; diff --git a/nix/registry-server.nix b/nix/registry-server.nix index 9f0583172..7a862823c 100644 --- a/nix/registry-server.nix +++ b/nix/registry-server.nix @@ -17,8 +17,8 @@ let set -o allexport [ -f ${cfg.stateDir}/.env ] && source ${cfg.stateDir}/.env - # Deployment-managed values must override state-dir values. Otherwise a - # stale non-secret setting such as DHALL_TYPES can survive a deployment. + # Load deployment settings last so they override old values in the + # persistent .env file. source ${envFile} set +o allexport diff --git a/nix/test/config.nix b/nix/test/config.nix index 832b666f5..81e70463c 100644 --- a/nix/test/config.nix +++ b/nix/test/config.nix @@ -115,247 +115,8 @@ let setupGitFixtures ]; - # Helper to create GitHub contents API response, as it returns base64-encoded content - base64Response = - { - url, - fileName, - filePath, - }: - { - request = { - method = "GET"; - inherit url; - }; - response = { - status = 200; - headers."Content-Type" = "application/json"; - jsonBody = { - type = "file"; - encoding = "base64"; - name = fileName; - path = fileName; - # Base64 encode the file content using Nix builtins - content = builtins.readFile ( - pkgs.runCommand "base64-${fileName}" { } '' - base64 -w 0 ${filePath} > $out - '' - ); - }; - }; - }; - - effectBase64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-effect/contents/${fileName}?ref=v4.0.0"; - inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/effect-4.0.0/${fileName}"; - }; - - # Helper to create a 404 response for files we expect not to exist - effect404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-effect/contents/${fileName}?ref=v4.0.0"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - - # effect@4.0.1 (updated manifest, same code as 4.0.0) - effectV401Base64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-effect/contents/${fileName}?ref=v4.0.1"; - inherit fileName; - filePath = - if fileName == "purs.json" then - pkgs.writeText "effect-4.0.1-purs.json" ( - builtins.toJSON ( - builtins.fromJSON ( - builtins.readFile (rootPath + "/app/fixtures/github-packages/effect-4.0.0/purs.json") - ) - // { - version = "4.0.1"; - ref = "v4.0.1"; - } - ) - ) - else - rootPath + "/app/fixtures/github-packages/effect-4.0.0/${fileName}"; - }; - - effectV401404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-effect/contents/${fileName}?ref=v4.0.1"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - - # Console package helpers (console@6.1.0) - consoleBase64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-console/contents/${fileName}?ref=v6.1.0"; - inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/console-6.1.0/${fileName}"; - }; - - console404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-console/contents/${fileName}?ref=v6.1.0"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - - # Unsafe-coerce package helpers (unsafe-coerce@6.0.0) - unsafeCoerceBase64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-unsafe-coerce/contents/${fileName}?ref=v6.0.0"; - inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/unsafe-coerce-6.0.0/${fileName}"; - }; - - unsafeCoerce404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-unsafe-coerce/contents/${fileName}?ref=v6.0.0"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - - # Type-equality package helpers (type-equality@4.0.2) - # Note: Uses purescript owner (actual location) not old-owner (metadata location) - typeEqualityBase64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-type-equality/contents/${fileName}?ref=v4.0.2"; - inherit fileName; - filePath = - if fileName == "purs.json" then - pkgs.writeText "type-equality-4.0.2-purs.json" ( - builtins.toJSON ( - builtins.fromJSON ( - builtins.readFile (rootPath + "/app/fixtures/github-packages/type-equality-4.0.1/purs.json") - ) - // { - version = "4.0.2"; - ref = "v4.0.2"; - } - ) - ) - else - rootPath + "/app/fixtures/github-packages/type-equality-4.0.1/${fileName}"; - }; - - typeEquality404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-type-equality/contents/${fileName}?ref=v4.0.2"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - - # Slug package helpers (slug@3.0.0) - uses spago.yaml manifest format - slugBase64Response = - fileName: - base64Response { - url = "/repos/purescript/purescript-slug/contents/${fileName}?ref=v3.0.0"; - inherit fileName; - filePath = rootPath + "/app/fixtures/github-packages/slug-3.0.0/${fileName}"; - }; - - slug404Response = fileName: { - request = { - method = "GET"; - url = "/repos/purescript/purescript-slug/contents/${fileName}?ref=v3.0.0"; - }; - response = { - status = 404; - headers."Content-Type" = "application/json"; - jsonBody = { - message = "Not Found"; - documentation_url = "https://docs.github.com/rest/repos/contents#get-repository-content"; - }; - }; - }; - # GitHub API wiremock mappings githubMappings = [ - (effectBase64Response "purs.json") - (effectBase64Response "bower.json") - (effectBase64Response "LICENSE") - # Expected 404s for manifest files that don't exist in the effect package - (effect404Response "spago.yaml") - (effect404Response "package.json") - # effect@4.0.1 (updated manifest — used by publish E2E tests) - (effectV401Base64Response "purs.json") - (effectV401Base64Response "bower.json") - (effectV401Base64Response "LICENSE") - (effectV401404Response "spago.yaml") - (effectV401404Response "package.json") - # Console package (console@6.1.0) - (consoleBase64Response "purs.json") - (consoleBase64Response "bower.json") - (consoleBase64Response "LICENSE") - (console404Response "spago.yaml") - (console404Response "package.json") - # Unsafe-coerce package (unsafe-coerce@6.0.0) - (unsafeCoerceBase64Response "purs.json") - (unsafeCoerceBase64Response "bower.json") - (unsafeCoerce404Response "LICENSE") - (unsafeCoerce404Response "spago.yaml") - (unsafeCoerce404Response "package.json") - # Type-equality package (type-equality@4.0.2 for tag discovery test) - (typeEqualityBase64Response "purs.json") - (typeEqualityBase64Response "bower.json") - (typeEqualityBase64Response "LICENSE") - (typeEquality404Response "spago.yaml") - (typeEquality404Response "package.json") - # Slug package (slug@3.0.0) - uses spago.yaml manifest format - (slugBase64Response "spago.yaml") - (slugBase64Response "LICENSE") - (slug404Response "purs.json") - (slug404Response "bower.json") - (slug404Response "package.json") # Tags for prelude package (only v6.0.1 which is already published) { request = { @@ -444,7 +205,7 @@ let } # Tags for type-equality package (used by two scheduler tests): # 1. Transfer detection: metadata says purescript, commit URLs point to new-owner - # 2. Tag discovery: v4.0.2 is a new version not yet published + # 2. Release discovery: v4.0.2 lets the importer find a release missing from metadata { request = { method = "GET"; @@ -466,7 +227,6 @@ let name = "v4.0.2"; commit = { sha = "type-eq-sha-402"; - # New version not yet published - discovered by the daily importer url = "https://api.github.com/repos/new-owner/purescript-type-equality/commits/type-eq-sha-402"; }; } @@ -1106,7 +866,8 @@ let done gitbot -C "$FIXTURES_DIR/purescript/purescript-effect" tag -m "v4.0.0" v4.0.0 - # Each source tag must carry a manifest with the matching version and ref. + # Update the manifest before tagging; publishing rejects a version or ref + # that doesn't match the request. jq '.version = "4.0.1" | .ref = "v4.0.1"' \ "$FIXTURES_DIR/purescript/purescript-effect/purs.json" > temp.json mv temp.json "$FIXTURES_DIR/purescript/purescript-effect/purs.json" diff --git a/nix/test/smoke.nix b/nix/test/smoke.nix index 855aa3700..2a03f0aff 100644 --- a/nix/test/smoke.nix +++ b/nix/test/smoke.nix @@ -27,7 +27,6 @@ else envVars = testConfig.testEnv; stateDir = "/var/lib/registry-server"; repoFixturesDir = "${stateDir}/repo-fixtures"; - dhallTypes = "${pkgs.registry-server}/bin/types"; in pkgs.testers.nixosTest { name = "registry-smoke"; @@ -62,12 +61,11 @@ else # Check that the service is still running (didn't crash) registry.succeed("systemctl is-active server.service") - # A stale state-dir value must not override deployment-managed schema paths. + # The .env file sets this to 1; the deployed setting of 0 must win. pid = registry.succeed("systemctl show --value --property MainPID server.service").strip() registry.succeed( - f"tr '\\0' '\\n' < /proc/{pid}/environ | grep -Fx 'DHALL_TYPES=${dhallTypes}'" + f"tr '\\0' '\\n' < /proc/{pid}/environ | grep -Fx 'GIT_TERMINAL_PROMPT=0'" ) - registry.succeed("test -f ${dhallTypes}/v1/Manifest.dhall") # Give the job executor a moment to start and potentially fail time.sleep(2) @@ -105,7 +103,7 @@ else }; script = '' mkdir -p ${stateDir} - echo 'DHALL_TYPES=/defunct/types' > ${stateDir}/.env + echo 'GIT_TERMINAL_PROMPT=1' > ${stateDir}/.env ${testConfig.setupGitFixtures}/bin/setup-git-fixtures ${repoFixturesDir} ''; }; @@ -118,7 +116,7 @@ else inherit stateDir; envVars = envVars // { REPO_FIXTURES_DIR = repoFixturesDir; - DHALL_TYPES = dhallTypes; + GIT_TERMINAL_PROMPT = 0; }; }; }; diff --git a/package-lock.json b/package-lock.json index 4b2064a0b..809e16e6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "fuse.js": "^7.5.0", "jsonrepair": "^3.15.0", "registry-lib": "file:../lib", - "tar": "^7.5.22", "tmp": "^0.2.7", "yaml": "^2.9.1" } @@ -357,18 +356,6 @@ "node": ">=18.0.0" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -802,15 +789,6 @@ "node": ">=10.0.0" } }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/content-type": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/content-type/-/content-type-3.1.1.tgz", @@ -999,27 +977,6 @@ "node": ">=8.6" } }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/nan": { "version": "2.29.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.29.0.tgz", @@ -1158,22 +1115,6 @@ "nan": "^2.23.0" } }, - "node_modules/tar": { - "version": "7.5.22", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", - "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -1222,15 +1163,6 @@ "node": ">= 10.0.0" } }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/yaml": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", diff --git a/scripts/src/DailyImporter.purs b/scripts/src/DailyImporter.purs index cf53e37ee..b7bf06d04 100644 --- a/scripts/src/DailyImporter.purs +++ b/scripts/src/DailyImporter.purs @@ -42,7 +42,7 @@ import Registry.App.Effect.Log (LOG) import Registry.App.Effect.Log as Log import Registry.App.Effect.Registry (REGISTRY_READ) import Registry.App.Effect.Registry as Registry -import Registry.App.Legacy.LenientVersion as LenientVersion +import Registry.App.Version as App.Version import Registry.Foreign.Octokit as Octokit import Registry.Location (Location(..)) import Registry.Operation as Operation @@ -168,10 +168,9 @@ combinedPublishedVersions metadata = Set.fromFoldable $ Map.keys metadata.publis -- | Find new version tags that point to recent commits and haven't been published findNewVersions :: Array Octokit.Tag -> Set String -> Set Version -> Array { version :: Version, ref :: String } findNewVersions tags recentShas publishedVersions = Array.catMaybes $ tags <#> \tag -> - case LenientVersion.parse tag.name of + case App.Version.parseLenient tag.name of Left _ -> Nothing -- Not a valid version tag - Right result -> do - let version = LenientVersion.version result + Right version -> do if not (Set.member tag.sha recentShas) then Nothing -- Tag doesn't point to a recent commit else if Set.member version publishedVersions then Nothing -- Already published else Just { version, ref: tag.name } diff --git a/spago.lock b/spago.lock index 7709b4840..570bc42f0 100644 --- a/spago.lock +++ b/spago.lock @@ -66,7 +66,6 @@ "transformers", "tuples", "typelevel-prelude", - "unicode", "unsafe-coerce", "uuidv4" ] @@ -161,7 +160,6 @@ "aff", "aff-promise", "arrays", - "b64", "bifunctors", "codec", "codec-json", @@ -184,13 +182,11 @@ "newtype", "node-buffer", "node-path", - "nullable", "ordered-collections", "prelude", "profunctor", "registry-lib", "strings", - "transformers", "tuples", "unsafe-coerce", "variant" diff --git a/types/v1/Location.dhall b/types/v1/Location.dhall deleted file mode 100644 index 2ffde0e13..000000000 --- a/types/v1/Location.dhall +++ /dev/null @@ -1,7 +0,0 @@ -let Subdir = { subdir : Optional Text } - -let GitHub = Subdir //\\ { githubOwner : Text, githubRepo : Text } - -let Git = Subdir //\\ { gitUrl : Text } - -in < GitHub : GitHub | Git : Git > diff --git a/types/v1/Manifest.dhall b/types/v1/Manifest.dhall deleted file mode 100644 index 2f1a6fa5b..000000000 --- a/types/v1/Manifest.dhall +++ /dev/null @@ -1,24 +0,0 @@ -let Map = (./Prelude.dhall).Map.Type - -let PackageName = Text - -let License = Text - -let Version = Text - -let Range = Text - -let Manifest = - { name : PackageName - , license : License - , version : Version - , location : ./Location.dhall - , ref : Text - , owners : Optional (List ./Owner.dhall) - , description : Optional Text - , includeFiles : Optional (List Text) - , excludeFiles : Optional (List Text) - , dependencies : Optional (Map PackageName Range) - } - -in Manifest diff --git a/types/v1/Metadata.dhall b/types/v1/Metadata.dhall deleted file mode 100644 index 508964e38..000000000 --- a/types/v1/Metadata.dhall +++ /dev/null @@ -1,38 +0,0 @@ -let Map = (./Prelude.dhall).Map.Type -let NonEmpty = (./Prelude.dhall).NonEmpty.Type - -let Owner = ./Owner.dhall - -let Location = ./Location.dhall - -let Version = Text - -let Sha256 = Text - -let ISO8601String = Text - --- NOTE: The `ref` field is DEPRECATED and WILL BE REMOVED after 2027-01-31. --- It is only present for backwards compatibility with older package managers. --- Do not rely on its presence! -let PublishedMetadata = - { hash : Sha256 - , bytes : Natural - , publishedTime : ISO8601String - , compilers : NonEmpty Version - , ref : Optional Text - } - -let UnpublishedMetadata = - { reason : Text - , publishedTime : ISO8601String - , unpublishedTime : ISO8601String - } - -let Metadata = - { location : Location - , owners : Optional (List Owner) - , published : Map Version PublishedMetadata - , unpublished : Map Version UnpublishedMetadata - } - -in Metadata diff --git a/types/v1/Owner.dhall b/types/v1/Owner.dhall deleted file mode 100644 index 4ec19f148..000000000 --- a/types/v1/Owner.dhall +++ /dev/null @@ -1 +0,0 @@ -let Owner = { id : Optional Text, keytype : Text, public : Text } in Owner diff --git a/types/v1/Prelude.dhall b/types/v1/Prelude.dhall deleted file mode 100644 index d86e105e1..000000000 --- a/types/v1/Prelude.dhall +++ /dev/null @@ -1,5 +0,0 @@ --- We read DHALL_PRELUDE from the environment because we cannot make requests to --- remote hosts in an offline environment (such as Nix in CI). DHALL_PRELUDE is --- automatically set in your Nix shell, but if you are not using a Nix shell and --- want to run this locally then the URL will be used instead. -env:DHALL_PRELUDE diff --git a/types/v1/README.md b/types/v1/README.md deleted file mode 100644 index 71bb41334..000000000 --- a/types/v1/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Specs V1 - -The Dhall types in this directory represent core data types for the registry which the registry guarantees forwards compatibility for. Those include: - -- `Manifest.dhall`: the `purs.json` manifest format -- `Metadata.dhall`: the package metadata file format - -as well as other types required by these two file formats. To learn more, please see the [SPEC.md](../../SPEC.md).