From b686611ea98cb4493418b53b7135f74cbd59f98d Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Mon, 19 Jan 2026 11:45:45 +0200 Subject: [PATCH 1/5] Always add dependency ranges in solver-based projects --- src/Spago/Command/Fetch.purs | 13 ++++++++++--- test-fixtures/list-packages-registry.txt | 1 + test-fixtures/spago-install-solver-ranges.yaml | 12 ++++++++++++ test/Spago/Install.purs | 5 +++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test-fixtures/spago-install-solver-ranges.yaml diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index 768f21e32..f29796b75 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -176,10 +176,17 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do doc <- justOrDieWith yamlDoc Config.configDocMissingErrorMessage liftAff $ Config.addPackagesToConfig configPath doc isTest actualPackagesToInstall - -- if the flag is selected, we kick off the process of adding ranges to the config - when ensureRanges do + -- For solver-based projects, always ensure ranges (they're required for the solver to work) + -- For package-set projects, only add ranges if explicitly requested + let + isSolverBuild = case currentWorkspace.packageSet.buildType of + RegistrySolverBuild _ -> true + PackageSetBuild _ _ -> false + shouldEnsureRanges = ensureRanges || isSolverBuild + + when shouldEnsureRanges do { configPath, package, yamlDoc } <- getPackageConfigPath "in which to add ranges." - logInfo $ "Adding ranges to core dependencies to the config in " <> Path.quote configPath + logInfo $ "Adding dependency ranges to the config in " <> Path.quote configPath packageDeps <- (Map.lookup package.name dependencies) `justOrDieWith` "Impossible: package dependencies must be in dependencies map" let rangeMap = map getRangeFromPackage packageDeps.core diff --git a/test-fixtures/list-packages-registry.txt b/test-fixtures/list-packages-registry.txt index adf7d5412..114e79f31 100644 --- a/test-fixtures/list-packages-registry.txt +++ b/test-fixtures/list-packages-registry.txt @@ -2,6 +2,7 @@ Reading Spago workspace configuration... ✓ Selecting package to build: aaa +Adding dependency ranges to the config in "spago.yaml" Downloading dependencies... No lockfile found, generating it... Lockfile written to spago.lock. Please commit this file. diff --git a/test-fixtures/spago-install-solver-ranges.yaml b/test-fixtures/spago-install-solver-ranges.yaml new file mode 100644 index 000000000..ed3527ffc --- /dev/null +++ b/test-fixtures/spago-install-solver-ranges.yaml @@ -0,0 +1,12 @@ +package: + name: aaa + dependencies: + - console: ">=6.1.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - either: ">=6.1.0 <7.0.0" + - prelude: ">=6.0.2 <7.0.0" + test: + main: Test.Main + dependencies: [] +workspace: + extraPackages: {} diff --git a/test/Spago/Install.purs b/test/Spago/Install.purs index a2eb2b044..d8f6ce3f0 100644 --- a/test/Spago/Install.purs +++ b/test/Spago/Install.purs @@ -262,6 +262,11 @@ spec = Spec.around withTempDir do -- Check that the lockfile is back to the original checkFixture (testCwd "spago.lock") (fixture "spago.lock") + Spec.it "adds version ranges automatically for solver-based projects" \{ spago, fixture, testCwd } -> do + spago [ "init", "--name", "aaa", "--use-solver" ] >>= shouldBeSuccess + spago [ "install", "either" ] >>= shouldBeSuccess + checkFixture (testCwd "spago.yaml") (fixture "spago-install-solver-ranges.yaml") + insertConfigDependencies :: Config -> Dependencies -> Dependencies -> Config insertConfigDependencies config core test = From 4490c371eaa1559cf466544a85056d0c9eefc9da Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Mon, 19 Jan 2026 11:54:43 +0200 Subject: [PATCH 2/5] Make the offline flag a little more offline --- src/Spago/Command/Fetch.purs | 10 ++++++++++ src/Spago/Git.purs | 12 +++++++++--- src/Spago/Registry.purs | 27 ++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index f29796b75..0cadab6f9 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -515,6 +515,16 @@ getGitPackageInLocalCache name package = do getPackageDependencies :: forall a. PackageName -> Package -> Spago (FetchEnv a) (Maybe (ByEnv (Map PackageName Range))) getPackageDependencies packageName package = case package of RegistryVersion v -> do + { offline } <- ask + -- Check if registry-index exists when offline + when (offline == Offline) do + indexExists <- FS.exists Paths.registryIndexPath + unless indexExists do + die + [ "You are offline and the Registry Index is not cached locally." + , "Cannot look up dependencies for " <> PackageName.print packageName <> "@" <> Version.print v + , "Please connect to the internet and run 'spago install' first." + ] maybeManifest <- Registry.getManifestFromIndex packageName v pure $ maybeManifest <#> \(Manifest m) -> { core: m.dependencies, test: Map.empty } GitPackage p -> do diff --git a/src/Spago/Git.purs b/src/Spago/Git.purs index 140136176..a68ca5514 100644 --- a/src/Spago/Git.purs +++ b/src/Spago/Git.purs @@ -97,9 +97,15 @@ checkout { repo, ref } = Except.runExceptT $ void $ runGit [ "checkout", ref ] ( fetch :: ∀ a path. Path.IsPath path => { repo :: path, remote :: String } -> Spago (GitEnv a) (Either String Unit) fetch { repo, remote } = do - remoteUrl <- runGit [ "remote", "get-url", remote ] (Just $ Path.toGlobal repo) # Except.runExceptT >>= rightOrDie - logInfo $ "Fetching from " <> remoteUrl - Except.runExceptT $ runGit_ [ "fetch", remote, "--tags" ] (Just $ Path.toGlobal repo) + { offline } <- ask + case offline of + Offline -> do + logDebug $ "Skipping fetch from remote '" <> remote <> "' because we are offline" + pure $ Left "Cannot fetch from remote while offline." + _ -> do + remoteUrl <- runGit [ "remote", "get-url", remote ] (Just $ Path.toGlobal repo) # Except.runExceptT >>= rightOrDie + logInfo $ "Fetching from " <> remoteUrl + Except.runExceptT $ runGit_ [ "fetch", remote, "--tags" ] (Just $ Path.toGlobal repo) getRefType :: ∀ a path. Path.IsPath path => { repo :: path, ref :: String } -> Spago (GitEnv a) (Either String String) getRefType { repo, ref } = Except.runExceptT $ runGit [ "cat-file", "-t", ref ] (Just $ Path.toGlobal repo) diff --git a/src/Spago/Registry.purs b/src/Spago/Registry.purs index c9ef7ed29..6037200c1 100644 --- a/src/Spago/Registry.purs +++ b/src/Spago/Registry.purs @@ -188,6 +188,18 @@ getRegistryFns registryBox registryLock = do -- Now that we are up to date with the Registry we init/refresh the database updatePackageSetsDb db + + -- Check if registry directories exist when offline - the build should have already failed by now, but just in case.. + case offline of + Offline -> do + registryExists <- FS.exists Paths.registryPath + registryIndexExists <- FS.exists Paths.registryIndexPath + unless registryExists do + die "You are offline and the Registry is not cached locally. Please connect to the internet and run 'spago install' to cache the registry." + unless registryIndexExists do + die "You are offline and the Registry Index is not cached locally. Please connect to the internet and run 'spago install' to cache the registry index." + _ -> pure unit + pure fetchingFreshRegistry -- | Update the database with the latest package sets @@ -211,12 +223,17 @@ getRegistryFns registryBox registryLock = do -- | List all the package sets versions available in the Registry repo getAvailablePackageSets :: ∀ a. Spago (LogEnv a) (Array Version) getAvailablePackageSets = do - { success: setVersions, fail: parseFailures } <- map (partitionEithers <<< map parseSetVersion) $ FS.ls Paths.packageSetsPath + packageSetsExists <- FS.exists Paths.packageSetsPath + if packageSetsExists then do + { success: setVersions, fail: parseFailures } <- map (partitionEithers <<< map parseSetVersion) $ FS.ls Paths.packageSetsPath - unless (Array.null parseFailures) do - logDebug $ [ toDoc "Failed to parse some package-sets versions:" ] <> map (indent <<< toDoc <<< show) parseFailures + unless (Array.null parseFailures) do + logDebug $ [ toDoc "Failed to parse some package-sets versions:" ] <> map (indent <<< toDoc <<< show) parseFailures - pure setVersions + pure setVersions + else do + logDebug $ "Package sets directory does not exist at " <> Path.quote Paths.packageSetsPath + pure [] where parseSetVersion str = Version.parse case String.stripSuffix (Pattern ".json") str of Nothing -> str @@ -286,7 +303,7 @@ getManifestFromIndexImpl db name version = do manifests <- map (map (\m@(Manifest m') -> Tuple m'.version m)) case maybeManifests of Right ms -> pure $ NonEmptyArray.toUnfoldable ms Left err -> do - logWarn $ "Could not read package manifests from index, proceeding anyways. Error: " <> err + logWarn $ "Could not read package manifests for '" <> PackageName.print name <> "' from index. Error: " <> err pure [] let versions = Map.fromFoldable manifests -- and memoize it From c886ba6b58dce2c48a52854e92581cf4f91cab56 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Mon, 19 Jan 2026 12:45:54 +0200 Subject: [PATCH 3/5] Revert "Always add dependency ranges in solver-based projects" This reverts commit b686611ea98cb4493418b53b7135f74cbd59f98d. --- src/Spago/Command/Fetch.purs | 13 +++---------- test-fixtures/list-packages-registry.txt | 1 - test-fixtures/spago-install-solver-ranges.yaml | 12 ------------ test/Spago/Install.purs | 5 ----- 4 files changed, 3 insertions(+), 28 deletions(-) delete mode 100644 test-fixtures/spago-install-solver-ranges.yaml diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index 0cadab6f9..5e2eda094 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -176,17 +176,10 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do doc <- justOrDieWith yamlDoc Config.configDocMissingErrorMessage liftAff $ Config.addPackagesToConfig configPath doc isTest actualPackagesToInstall - -- For solver-based projects, always ensure ranges (they're required for the solver to work) - -- For package-set projects, only add ranges if explicitly requested - let - isSolverBuild = case currentWorkspace.packageSet.buildType of - RegistrySolverBuild _ -> true - PackageSetBuild _ _ -> false - shouldEnsureRanges = ensureRanges || isSolverBuild - - when shouldEnsureRanges do + -- if the flag is selected, we kick off the process of adding ranges to the config + when ensureRanges do { configPath, package, yamlDoc } <- getPackageConfigPath "in which to add ranges." - logInfo $ "Adding dependency ranges to the config in " <> Path.quote configPath + logInfo $ "Adding ranges to core dependencies to the config in " <> Path.quote configPath packageDeps <- (Map.lookup package.name dependencies) `justOrDieWith` "Impossible: package dependencies must be in dependencies map" let rangeMap = map getRangeFromPackage packageDeps.core diff --git a/test-fixtures/list-packages-registry.txt b/test-fixtures/list-packages-registry.txt index 114e79f31..adf7d5412 100644 --- a/test-fixtures/list-packages-registry.txt +++ b/test-fixtures/list-packages-registry.txt @@ -2,7 +2,6 @@ Reading Spago workspace configuration... ✓ Selecting package to build: aaa -Adding dependency ranges to the config in "spago.yaml" Downloading dependencies... No lockfile found, generating it... Lockfile written to spago.lock. Please commit this file. diff --git a/test-fixtures/spago-install-solver-ranges.yaml b/test-fixtures/spago-install-solver-ranges.yaml deleted file mode 100644 index ed3527ffc..000000000 --- a/test-fixtures/spago-install-solver-ranges.yaml +++ /dev/null @@ -1,12 +0,0 @@ -package: - name: aaa - dependencies: - - console: ">=6.1.0 <7.0.0" - - effect: ">=4.0.0 <5.0.0" - - either: ">=6.1.0 <7.0.0" - - prelude: ">=6.0.2 <7.0.0" - test: - main: Test.Main - dependencies: [] -workspace: - extraPackages: {} diff --git a/test/Spago/Install.purs b/test/Spago/Install.purs index d8f6ce3f0..a2eb2b044 100644 --- a/test/Spago/Install.purs +++ b/test/Spago/Install.purs @@ -262,11 +262,6 @@ spec = Spec.around withTempDir do -- Check that the lockfile is back to the original checkFixture (testCwd "spago.lock") (fixture "spago.lock") - Spec.it "adds version ranges automatically for solver-based projects" \{ spago, fixture, testCwd } -> do - spago [ "init", "--name", "aaa", "--use-solver" ] >>= shouldBeSuccess - spago [ "install", "either" ] >>= shouldBeSuccess - checkFixture (testCwd "spago.yaml") (fixture "spago-install-solver-ranges.yaml") - insertConfigDependencies :: Config -> Dependencies -> Dependencies -> Config insertConfigDependencies config core test = From af80f65996aefb66b093206521c30a6997084d8a Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Thu, 22 Jan 2026 10:32:20 +0200 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Fyodor Soikin --- src/Spago/Command/Fetch.purs | 6 ++---- src/Spago/Registry.purs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index 5e2eda094..9f0c0a64e 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -508,11 +508,9 @@ getGitPackageInLocalCache name package = do getPackageDependencies :: forall a. PackageName -> Package -> Spago (FetchEnv a) (Maybe (ByEnv (Map PackageName Range))) getPackageDependencies packageName package = case package of RegistryVersion v -> do - { offline } <- ask -- Check if registry-index exists when offline - when (offline == Offline) do - indexExists <- FS.exists Paths.registryIndexPath - unless indexExists do + whenM (asks _.offline <#> eq Offline) do + unlessM (FS.exists Paths.registryIndexPath) do die [ "You are offline and the Registry Index is not cached locally." , "Cannot look up dependencies for " <> PackageName.print packageName <> "@" <> Version.print v diff --git a/src/Spago/Registry.purs b/src/Spago/Registry.purs index 6037200c1..84ac4e1d5 100644 --- a/src/Spago/Registry.purs +++ b/src/Spago/Registry.purs @@ -192,11 +192,9 @@ getRegistryFns registryBox registryLock = do -- Check if registry directories exist when offline - the build should have already failed by now, but just in case.. case offline of Offline -> do - registryExists <- FS.exists Paths.registryPath - registryIndexExists <- FS.exists Paths.registryIndexPath - unless registryExists do + unlessM (FS.exists Paths.registryPath) $ die "You are offline and the Registry is not cached locally. Please connect to the internet and run 'spago install' to cache the registry." - unless registryIndexExists do + unlessM (FS.exists Paths.registryIndexPath) $ die "You are offline and the Registry Index is not cached locally. Please connect to the internet and run 'spago install' to cache the registry index." _ -> pure unit From fe0734189abb5a88542b922259e973e1f4c9a597 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferrai Date: Thu, 22 Jan 2026 20:56:45 +0200 Subject: [PATCH 5/5] Fail on package sets not found --- src/Spago/Registry.purs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Spago/Registry.purs b/src/Spago/Registry.purs index 84ac4e1d5..b70b31f5b 100644 --- a/src/Spago/Registry.purs +++ b/src/Spago/Registry.purs @@ -230,8 +230,10 @@ getRegistryFns registryBox registryLock = do pure setVersions else do - logDebug $ "Package sets directory does not exist at " <> Path.quote Paths.packageSetsPath - pure [] + die + [ "Package sets directory does not exist at " <> Path.quote Paths.packageSetsPath + , "Please connect to the internet and run 'spago install' to populate the registry cache." + ] where parseSetVersion str = Version.parse case String.stripSuffix (Pattern ".json") str of Nothing -> str