From b84981dd086219bc1157a440667aca92ea23efb4 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Fri, 25 Oct 2024 00:48:10 -0400 Subject: [PATCH 01/30] Stricter path types for stricter path-related logic --- CHANGELOG.md | 1 + bin/src/Main.purs | 94 +++++----- core/src/Config.purs | 14 +- core/src/FS.purs | 95 +++++----- core/src/Path.purs | 165 +++++++++++++++++ core/src/Prelude.purs | 2 +- spago.lock | 12 ++ spago.yaml | 1 + src/Spago/BuildInfo.purs | 14 +- src/Spago/Cmd.purs | 18 +- src/Spago/Command/Build.purs | 41 ++--- src/Spago/Command/Bundle.purs | 32 ++-- src/Spago/Command/Docs.purs | 8 +- src/Spago/Command/Fetch.purs | 100 ++++++----- src/Spago/Command/Graph.purs | 11 +- src/Spago/Command/Init.purs | 51 +++--- src/Spago/Command/Ls.purs | 32 ++-- src/Spago/Command/Publish.purs | 35 ++-- src/Spago/Command/Repl.purs | 13 +- src/Spago/Command/Run.purs | 30 ++-- src/Spago/Command/Script.purs | 1 - src/Spago/Command/Sources.purs | 9 +- src/Spago/Command/Test.purs | 9 +- src/Spago/Command/Uninstall.purs | 21 ++- src/Spago/Command/Upgrade.purs | 6 +- src/Spago/Config.purs | 168 ++++++++++-------- src/Spago/Db.purs | 6 +- src/Spago/Esbuild.purs | 2 +- src/Spago/Git.purs | 102 ++++------- src/Spago/Glob.purs | 49 ++--- src/Spago/Lock.purs | 6 +- src/Spago/Paths.js | 2 +- src/Spago/Paths.purs | 93 +++++----- src/Spago/Prelude.purs | 22 ++- src/Spago/Psa.purs | 42 ++--- src/Spago/Psa/Output.purs | 37 ++-- src/Spago/Psa/Types.purs | 13 +- src/Spago/Purs.purs | 35 ++-- src/Spago/Purs/Graph.purs | 30 ++-- src/Spago/Registry.purs | 22 +-- src/Spago/Tar.purs | 8 +- .../build/migrate-config/migrating-output.txt | 2 +- .../migrate-config/unmigrated-warning.txt | 2 +- .../bundle-refuse-overwrite-output.txt | 2 +- ...antic-instructions-installation-result.txt | 2 +- test-fixtures/spago-yml-check-stderr.txt | 4 +- test/Prelude.purs | 73 ++++---- test/Spago/Build.purs | 115 ++++++------ test/Spago/Build/BuildInfo.purs | 21 +-- test/Spago/Build/Monorepo.purs | 100 ++++++----- test/Spago/Build/Pedantic.purs | 49 +++-- test/Spago/Bundle.purs | 54 +++--- test/Spago/Docs.purs | 12 +- test/Spago/Errors.purs | 10 +- test/Spago/Glob.purs | 57 +++--- test/Spago/Init.purs | 13 +- test/Spago/InitSubpackage.purs | 24 +-- test/Spago/Install.purs | 78 ++++---- test/Spago/Lock.purs | 6 +- test/Spago/Ls.purs | 23 +-- test/Spago/Publish.purs | 59 +++--- test/Spago/Repl.purs | 14 +- test/Spago/Run.purs | 25 ++- test/Spago/Sources.purs | 13 +- test/Spago/Test.purs | 61 ++++--- test/Spago/Uninstall.purs | 31 ++-- test/Spago/Unit.purs | 2 + test/Spago/Unit/Path.purs | 36 ++++ test/Spago/Upgrade.purs | 16 +- 69 files changed, 1328 insertions(+), 1028 deletions(-) create mode 100644 core/src/Path.purs delete mode 100644 src/Spago/Command/Script.purs create mode 100644 test/Spago/Unit/Path.purs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c0fc05be..23304dbd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ Other improvements: before trying to build with them. - Spago no longer ignores config fields that it doesn't recognize. This should help catch typos in field names. +- Internally Spago uses stricter-typed file paths. ## [0.21.0] - 2023-05-04 diff --git a/bin/src/Main.purs b/bin/src/Main.purs index 74009d649..c2809ed7d 100644 --- a/bin/src/Main.purs +++ b/bin/src/Main.purs @@ -15,7 +15,6 @@ import Data.Set as Set import Effect.Aff as Aff import Effect.Aff.AVar as AVar import Effect.Now as Now -import Node.Process as Process import Options.Applicative (CommandFields, Mod, Parser, ParserPrefs(..)) import Options.Applicative as O import Options.Applicative.Types (Backtracking(..)) @@ -51,6 +50,7 @@ import Spago.Generated.BuildInfo as BuildInfo import Spago.Git as Git import Spago.Json as Json import Spago.Log (LogVerbosity(..)) +import Spago.Path as Path import Spago.Paths as Paths import Spago.Purs as Purs import Spago.Registry as Registry @@ -162,7 +162,7 @@ type BundleArgs = { minify :: Boolean , sourceMaps :: Boolean , module :: Maybe String - , outfile :: Maybe FilePath + , outfile :: Maybe String , platform :: Maybe String , selectedPackage :: Maybe String , pursArgs :: List String @@ -529,7 +529,8 @@ main = do \c -> Aff.launchAff_ case c of Cmd'SpagoCmd (SpagoCmd globalArgs@{ offline, migrateConfig } command) -> do logOptions <- mkLogOptions startingTime globalArgs - runSpago { logOptions } case command of + rootPath <- Path.mkRoot =<< Paths.cwd + runSpago { logOptions, rootPath } case command of Sources args -> do { env } <- mkFetchEnv { packages: mempty @@ -544,7 +545,7 @@ main = do void $ runSpago env (Sources.run { json: args.json }) Init args@{ useSolver } -> do -- Fetch the registry here so we can select the right package set later - env <- mkRegistryEnv offline + env <- mkRegistryEnv offline <#> Record.union { rootPath } setVersion <- parseSetVersion args.setVersion void $ runSpago env $ Init.run { mode: args.mode, setVersion, useSolver } Fetch args -> do @@ -588,7 +589,7 @@ main = do void $ runSpago publishEnv (Publish.publish {}) Repl args@{ selectedPackage } -> do - packages <- FS.exists "spago.yaml" >>= case _ of + packages <- FS.exists (rootPath "spago.yaml") >>= case _ of true -> do -- if we have a config then we assume it's a workspace, and we can run a repl in the project pure mempty -- TODO newPackages @@ -597,9 +598,10 @@ main = do logWarn "No configuration found, creating a temporary project to run a repl in..." tmpDir <- mkTemp FS.mkdirp tmpDir - logDebug $ "Creating repl project in temp dir: " <> tmpDir - liftEffect $ Process.chdir tmpDir - env <- mkRegistryEnv offline + logDebug $ "Creating repl project in temp dir: " <> Path.quote tmpDir + Paths.chdir tmpDir + tmpRootPath <- Path.mkRoot tmpDir + env <- mkRegistryEnv offline <#> Record.union { rootPath: tmpRootPath } void $ runSpago env $ Init.run { setVersion: Nothing , mode: Init.InitWorkspace { packageName: Just "repl" } @@ -649,12 +651,12 @@ main = do testEnv <- runSpago env (mkTestEnv args buildEnv) runSpago testEnv Test.run LsPaths args -> do - runSpago { logOptions } $ Ls.listPaths args + runSpago { logOptions, rootPath } $ Ls.listPaths args LsPackages args@{ pure } -> do let fetchArgs = { packages: mempty, selectedPackage: Nothing, pure, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline } { env: env@{ workspace }, fetchOpts } <- mkFetchEnv fetchArgs dependencies <- runSpago env (Fetch.run fetchOpts) - let lsEnv = { workspace, dependencies, logOptions } + let lsEnv = { workspace, dependencies, logOptions, rootPath } runSpago lsEnv (Ls.listPackageSet args) LsDeps { selectedPackage, json, transitive, pure } -> do let fetchArgs = { packages: mempty, selectedPackage, pure, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline } @@ -676,12 +678,12 @@ main = do { env, fetchOpts } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline } dependencies <- runSpago env (Fetch.run fetchOpts) purs <- Purs.getPurs - runSpago { dependencies, logOptions, purs, workspace: env.workspace } (Graph.graphModules args) + runSpago { dependencies, logOptions, rootPath, purs, workspace: env.workspace } (Graph.graphModules args) GraphPackages args -> do { env, fetchOpts } <- mkFetchEnv { packages: mempty, selectedPackage: Nothing, pure: false, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline } dependencies <- runSpago env (Fetch.run fetchOpts) purs <- Purs.getPurs - runSpago { dependencies, logOptions, purs, workspace: env.workspace } (Graph.graphPackages args) + runSpago { dependencies, logOptions, rootPath, purs, workspace: env.workspace } (Graph.graphPackages args) Cmd'VersionCmd v -> when v do output (OutputLines [ BuildInfo.packages."spago-bin" ]) @@ -706,7 +708,7 @@ main = do mkBundleEnv :: forall a. BundleArgs -> Spago (Fetch.FetchEnv a) (Bundle.BundleEnv ()) mkBundleEnv bundleArgs = do - { workspace, logOptions } <- ask + { workspace, logOptions, rootPath } <- ask logDebug $ "Bundle args: " <> show bundleArgs selected <- case workspace.selected of @@ -755,18 +757,19 @@ mkBundleEnv bundleArgs = do , sourceMaps: bundleArgs.sourceMaps , extraArgs } + argsOutput = bundleArgs.output <#> (rootPath _) newWorkspace = workspace { buildOptions - { output = bundleArgs.output <|> workspace.buildOptions.output + { output = argsOutput <|> workspace.buildOptions.output } } esbuild <- Esbuild.getEsbuild - let bundleEnv = { esbuild, logOptions, workspace: newWorkspace, selected, bundleOptions } + let bundleEnv = { esbuild, logOptions, rootPath, workspace: newWorkspace, selected, bundleOptions } pure bundleEnv mkRunEnv :: forall a b. RunArgs -> Build.BuildEnv b -> Spago (Fetch.FetchEnv a) (Run.RunEnv ()) mkRunEnv runArgs { dependencies, purs } = do - { workspace, logOptions } <- ask + { workspace, logOptions, rootPath } <- ask logDebug $ "Run args: " <> show runArgs node <- Run.getNode @@ -801,17 +804,18 @@ mkRunEnv runArgs { dependencies, purs } = do runOptions = { moduleName , execArgs - , executeDir: Paths.cwd + , executeDir: Path.toGlobal rootPath , successMessage: Nothing , failureMessage: "Running failed." } - let newWorkspace = workspace { buildOptions { output = runArgs.output <|> workspace.buildOptions.output } } - let runEnv = { logOptions, workspace: newWorkspace, selected, node, runOptions, dependencies, purs } + let argsOutput = runArgs.output <#> (rootPath _) + let newWorkspace = workspace { buildOptions { output = argsOutput <|> workspace.buildOptions.output } } + let runEnv = { logOptions, rootPath, workspace: newWorkspace, selected, node, runOptions, dependencies, purs } pure runEnv mkTestEnv :: forall a b. TestArgs -> Build.BuildEnv b -> Spago (Fetch.FetchEnv a) (Test.TestEnv ()) mkTestEnv testArgs { dependencies, purs } = do - { workspace, logOptions } <- ask + { workspace, logOptions, rootPath } <- ask logDebug $ "Test args: " <> show testArgs node <- Run.getNode @@ -845,8 +849,9 @@ mkTestEnv testArgs { dependencies, purs } = do logDebug $ "Selected packages to test: " <> Json.stringifyJson (CJ.Common.nonEmptyArray PackageName.codec) (map _.selected.package.name selectedPackages) - let newWorkspace = workspace { buildOptions { output = testArgs.output <|> workspace.buildOptions.output } } - let testEnv = { logOptions, workspace: newWorkspace, selectedPackages, node, dependencies, purs } + let argsOutput = testArgs.output <#> (rootPath _) + let newWorkspace = workspace { buildOptions { output = argsOutput <|> workspace.buildOptions.output } } + let testEnv = { logOptions, rootPath, workspace: newWorkspace, selectedPackages, node, dependencies, purs } pure testEnv mkBuildEnv @@ -861,12 +866,13 @@ mkBuildEnv -> Fetch.PackageTransitiveDeps -> Spago (Fetch.FetchEnv ()) (Build.BuildEnv ()) mkBuildEnv buildArgs dependencies = do - { logOptions, workspace, git } <- ask + { logOptions, rootPath, workspace, git } <- ask purs <- Purs.getPurs let + argsOutput = buildArgs.output <#> (rootPath _) newWorkspace = workspace { buildOptions - { output = buildArgs.output <|> workspace.buildOptions.output + { output = argsOutput <|> workspace.buildOptions.output , statVerbosity = buildArgs.statVerbosity <|> workspace.buildOptions.statVerbosity } -- Override the backend args from the config if they are passed in through a flag @@ -880,6 +886,7 @@ mkBuildEnv buildArgs dependencies = do pure { logOptions + , rootPath , purs , git , dependencies @@ -910,7 +917,7 @@ mkPublishEnv dependencies = do mkReplEnv :: forall a. ReplArgs -> Fetch.PackageTransitiveDeps -> PackageMap -> Spago (Fetch.FetchEnv a) (Repl.ReplEnv ()) mkReplEnv replArgs dependencies supportPackage = do - { workspace, logOptions } <- ask + { workspace, logOptions, rootPath } <- ask logDebug $ "Repl args: " <> show replArgs purs <- Purs.getPurs @@ -926,16 +933,17 @@ mkReplEnv replArgs dependencies supportPackage = do , supportPackage , depsOnly: false , logOptions + , rootPath , pursArgs: Array.fromFoldable replArgs.pursArgs , selected } -mkFetchEnv :: forall a b. { offline :: OnlineStatus, migrateConfig :: Boolean, isRepl :: Boolean | FetchArgsRow b } -> Spago (LogEnv a) { env :: Fetch.FetchEnv (), fetchOpts :: Fetch.FetchOpts } +mkFetchEnv :: forall a b. { offline :: OnlineStatus, migrateConfig :: Boolean, isRepl :: Boolean | FetchArgsRow b } -> Spago (SpagoBaseEnv a) { env :: Fetch.FetchEnv (), fetchOpts :: Fetch.FetchOpts } mkFetchEnv args@{ migrateConfig, offline } = do let - parsePackageName p = case PackageName.parse p of - Right pkg -> Right pkg - Left err -> Left ("- Could not parse package " <> show p <> ": " <> err) + parsePackageName p = + PackageName.parse p + # lmap \err -> "- Could not parse package " <> show p <> ": " <> err let { right: packageNames, left: failedPackageNames } = partitionMap parsePackageName (Array.fromFoldable args.packages) unless (Array.null failedPackageNames) do die $ [ toDoc "Failed to parse some package name: " ] <> map (indent <<< toDoc) failedPackageNames @@ -945,25 +953,28 @@ mkFetchEnv args@{ migrateConfig, offline } = do Left _err -> die $ "Failed to parse selected package name, was: " <> show args.selectedPackage env <- mkRegistryEnv offline - workspace <- runSpago env (Config.readWorkspace { maybeSelectedPackage, pureBuild: args.pure, migrateConfig }) + { rootPath } <- ask + workspace <- + runSpago (Record.union env { rootPath }) + (Config.readWorkspace { maybeSelectedPackage, pureBuild: args.pure, migrateConfig }) let fetchOpts = { packages: packageNames, ensureRanges: args.ensureRanges, isTest: args.testDeps, isRepl: args.isRepl } - pure { fetchOpts, env: Record.union { workspace } env } + pure { fetchOpts, env: Record.union { workspace, rootPath } env } -mkRegistryEnv :: forall a. OnlineStatus -> Spago (LogEnv a) (Registry.RegistryEnv ()) +mkRegistryEnv :: forall a. OnlineStatus -> Spago (SpagoBaseEnv a) (Registry.RegistryEnv ()) mkRegistryEnv offline = do - logDebug $ "CWD: " <> Paths.cwd + { logOptions, rootPath } <- ask -- Take care of the caches FS.mkdirp Paths.globalCachePath - FS.mkdirp Paths.localCachePath - FS.mkdirp Paths.localCachePackagesPath - logDebug $ "Global cache: " <> show Paths.globalCachePath - logDebug $ "Local cache: " <> show Paths.localCachePath + FS.mkdirp $ rootPath Paths.localCachePath + FS.mkdirp $ rootPath Paths.localCachePackagesPath + logDebug $ "Workspace root path: " <> Path.quote rootPath + logDebug $ "Global cache: " <> Path.quote Paths.globalCachePath + logDebug $ "Local cache: " <> Paths.localCachePath -- Make sure we have git and purs git <- Git.getGit purs <- Purs.getPurs - { logOptions } <- ask db <- liftEffect $ Db.connect { database: Paths.databasePath , logger: \str -> Reader.runReaderT (logDebug $ "DB: " <> str) { logOptions } @@ -982,7 +993,7 @@ mkRegistryEnv offline = do mkLsEnv :: forall a. Fetch.PackageTransitiveDeps -> Spago (Fetch.FetchEnv a) Ls.LsEnv mkLsEnv dependencies = do - { logOptions, workspace } <- ask + { logOptions, workspace, rootPath } <- ask selected <- case workspace.selected of Just s -> pure s Nothing -> @@ -998,15 +1009,16 @@ mkLsEnv dependencies = do [ toDoc "No package was selected. Please select (with -p) one of the following packages:" , indent (toDoc $ map _.package.name workspacePackages) ] - pure { logOptions, workspace, dependencies, selected } + pure { logOptions, workspace, dependencies, selected, rootPath } mkDocsEnv :: ∀ a. DocsArgs -> Fetch.PackageTransitiveDeps -> Spago (Fetch.FetchEnv a) (Docs.DocsEnv ()) mkDocsEnv args dependencies = do - { logOptions, workspace } <- ask + { logOptions, rootPath, workspace } <- ask purs <- Purs.getPurs pure { purs , logOptions + , rootPath , workspace , dependencies , depsOnly: args.depsOnly diff --git a/core/src/Config.purs b/core/src/Config.purs index 5dd95970e..753b2d850 100644 --- a/core/src/Config.purs +++ b/core/src/Config.purs @@ -97,8 +97,8 @@ type PublishConfig = { version :: Version , license :: License , location :: Maybe Location - , include :: Maybe (Array FilePath) - , exclude :: Maybe (Array FilePath) + , include :: Maybe (Array AdHocFilePath) + , exclude :: Maybe (Array AdHocFilePath) } publishConfigCodec :: CJ.Codec PublishConfig @@ -205,7 +205,7 @@ packageBuildOptionsCodec = CJ.named "PackageBuildOptionsInput" $ CJS.objectStric type BundleConfig = { minify :: Maybe Boolean , module :: Maybe String - , outfile :: Maybe FilePath + , outfile :: Maybe AdHocFilePath , platform :: Maybe BundlePlatform , type :: Maybe BundleType , extraArgs :: Maybe (Array String) @@ -342,7 +342,7 @@ workspaceConfigCodec = CJ.named "WorkspaceConfig" $ CJS.objectStrict $ CJS.record type WorkspaceBuildOptionsInput = - { output :: Maybe FilePath + { output :: Maybe AdHocFilePath , censorLibraryWarnings :: Maybe CensorBuildWarnings , statVerbosity :: Maybe StatVerbosity } @@ -435,7 +435,7 @@ statVerbosityCodec = CJ.Sum.enumSum print parse data SetAddress = SetFromRegistry { registry :: Version } | SetFromUrl { url :: String, hash :: Maybe Sha256 } - | SetFromPath { path :: FilePath } + | SetFromPath { path :: AdHocFilePath } derive instance Eq SetAddress @@ -469,7 +469,7 @@ extraPackageCodec = Codec.codec' decode encode decode json = map ExtraLocalPackage (Codec.decode localPackageCodec json) <|> map ExtraRemotePackage (Codec.decode remotePackageCodec json) -type LocalPackage = { path :: FilePath } +type LocalPackage = { path :: AdHocFilePath } localPackageCodec :: CJ.Codec LocalPackage localPackageCodec = CJ.named "LocalPackage" $ CJ.Record.objectStrict { path: CJ.string } @@ -495,7 +495,7 @@ remotePackageCodec = Codec.codec' decode encode type GitPackage = { git :: String , ref :: String - , subdir :: Maybe FilePath + , subdir :: Maybe AdHocFilePath , dependencies :: Maybe Dependencies } diff --git a/core/src/FS.purs b/core/src/FS.purs index e1f688a54..f877338a8 100644 --- a/core/src/FS.purs +++ b/core/src/FS.purs @@ -13,6 +13,7 @@ module Spago.FS , moveSync , readJsonFile , readTextFile + , readTextFileSync , readYamlDocFile , readYamlFile , stat @@ -37,21 +38,24 @@ import Node.FS.Stats (Stats) import Node.FS.Stats as Stats import Node.FS.Sync as FS.Sync import Spago.Json as Json +import Spago.Path (toRaw) +import Spago.Path as Path import Spago.Yaml as Yaml -mkdirp :: forall m. MonadAff m => FilePath -> m Unit -mkdirp = liftAff <<< flip FS.Aff.mkdir' { recursive: true, mode: Perms.mkPerms Perms.all Perms.all Perms.all } +mkdirp :: forall m path. Path.IsPath path => MonadAff m => path -> m Unit +mkdirp path = liftAff $ + FS.Aff.mkdir' (toRaw path) { recursive: true, mode: Perms.mkPerms Perms.all Perms.all Perms.all } foreign import moveSyncImpl :: String -> String -> Effect Unit -moveSync :: forall m. MonadEffect m => { src :: FilePath, dst :: FilePath } -> m Unit -moveSync { src, dst } = liftEffect $ moveSyncImpl src dst +moveSync :: ∀ m src dst. Path.IsPath src => Path.IsPath dst => MonadEffect m => { src :: src, dst :: dst } -> m Unit +moveSync { src, dst } = liftEffect $ moveSyncImpl (toRaw src) (toRaw dst) -copyFileSync :: forall m. MonadEffect m => { src :: FilePath, dst :: FilePath } -> m Unit -copyFileSync { src, dst } = liftEffect $ FS.Sync.copyFile src dst +copyFileSync :: ∀ m src dst. Path.IsPath src => Path.IsPath dst => MonadEffect m => { src :: src, dst :: dst } -> m Unit +copyFileSync { src, dst } = liftEffect $ FS.Sync.copyFile (toRaw src) (toRaw dst) -copyFile :: forall m. MonadAff m => { src :: FilePath, dst :: FilePath } -> m Unit -copyFile { src, dst } = liftAff $ FS.Aff.copyFile src dst +copyFile :: ∀ m src dst. Path.IsPath src => Path.IsPath dst => MonadAff m => { src :: src, dst :: dst } -> m Unit +copyFile { src, dst } = liftAff $ FS.Aff.copyFile (toRaw src) (toRaw dst) foreign import cpImpl :: String -> String -> Effect Unit @@ -59,72 +63,75 @@ foreign import cpImpl :: String -> String -> Effect Unit -- | Note: if `src` is a directory it will copy everything inside of this directory, -- | not the entire directory itself. -- | Note: if `src` is a file, `dst` cannot be a directory -copyTree :: forall m. MonadEffect m => { src :: FilePath, dst :: FilePath } -> m Unit -copyTree { src, dst } = liftEffect $ cpImpl src dst +copyTree :: ∀ m src dst. Path.IsPath src => Path.IsPath dst => MonadEffect m => { src :: src, dst :: dst } -> m Unit +copyTree { src, dst } = liftEffect $ cpImpl (toRaw src) (toRaw dst) foreign import ensureFileSyncImpl :: String -> Effect Unit -ensureFileSync :: forall m. MonadEffect m => FilePath -> m Unit -ensureFileSync file = liftEffect $ ensureFileSyncImpl file +ensureFileSync :: forall m path. Path.IsPath path => MonadEffect m => path -> m Unit +ensureFileSync file = liftEffect $ ensureFileSyncImpl $ toRaw file -exists :: forall m. MonadEffect m => String -> m Boolean -exists = liftEffect <<< FS.Sync.exists +exists :: forall m path. Path.IsPath path => MonadEffect m => path -> m Boolean +exists = liftEffect <<< FS.Sync.exists <<< toRaw -unlink :: ∀ m. MonadAff m => String -> m Unit -unlink = liftAff <<< FS.Aff.unlink +unlink :: ∀ m path. Path.IsPath path => MonadAff m => path -> m Unit +unlink = liftAff <<< FS.Aff.unlink <<< toRaw -writeTextFile :: forall m. MonadAff m => FilePath -> String -> m Unit -writeTextFile path text = liftAff $ FS.Aff.writeTextFile UTF8 path text +writeTextFile :: forall m path. Path.IsPath path => MonadAff m => path -> String -> m Unit +writeTextFile path text = liftAff $ FS.Aff.writeTextFile UTF8 (toRaw path) text -readTextFile :: forall m. MonadAff m => FilePath -> m String -readTextFile path = liftAff $ FS.Aff.readTextFile UTF8 path +readTextFile :: forall m path. Path.IsPath path => MonadAff m => path -> m String +readTextFile path = liftAff $ FS.Aff.readTextFile UTF8 (toRaw path) -writeFile :: forall m. MonadAff m => FilePath -> Buffer -> m Unit -writeFile path buf = liftAff $ FS.Aff.writeFile path buf +readTextFileSync :: ∀ m path. Path.IsPath path => MonadEffect m => path -> m String +readTextFileSync path = liftEffect $ FS.Sync.readTextFile UTF8 (toRaw path) -ls :: forall m. MonadAff m => FilePath -> m (Array FilePath) -ls = liftAff <<< FS.Aff.readdir +writeFile :: forall m path. Path.IsPath path => MonadAff m => path -> Buffer -> m Unit +writeFile path buf = liftAff $ FS.Aff.writeFile (toRaw path) buf -chmod :: forall m. MonadAff m => FilePath -> Perms -> m Unit -chmod path perms = liftAff $ FS.Aff.chmod path perms +ls :: forall m path. Path.IsPath path => MonadAff m => path -> m (Array AdHocFilePath) +ls = liftAff <<< FS.Aff.readdir <<< toRaw + +chmod :: forall m path. Path.IsPath path => MonadAff m => path -> Perms -> m Unit +chmod path perms = liftAff $ FS.Aff.chmod (toRaw path) perms -- | Encode data as formatted JSON and write it to the provided filepath -writeJsonFile :: forall a. CJ.Codec a -> FilePath -> a -> Aff Unit -writeJsonFile codec path = FS.Aff.writeTextFile UTF8 path <<< (_ <> "\n") <<< Json.printJson codec +writeJsonFile :: forall a path. Path.IsPath path => CJ.Codec a -> path -> a -> Aff Unit +writeJsonFile codec path = FS.Aff.writeTextFile UTF8 (toRaw path) <<< (_ <> "\n") <<< Json.printJson codec -- | Decode data from a JSON file at the provided filepath -readJsonFile :: forall a. CJ.Codec a -> FilePath -> Aff (Either String a) +readJsonFile :: forall a path. Path.IsPath path => CJ.Codec a -> path -> Aff (Either String a) readJsonFile codec path = do - result <- Aff.attempt $ FS.Aff.readTextFile UTF8 path + result <- Aff.attempt $ FS.Aff.readTextFile UTF8 (toRaw path) pure (lmap Aff.message result >>= Json.parseJson codec >>> lmap CJ.DecodeError.print) -- | Encode data as formatted YAML and write it to the provided filepath -writeYamlFile :: forall a. CJ.Codec a -> FilePath -> a -> Aff Unit -writeYamlFile codec path = FS.Aff.writeTextFile UTF8 path <<< (_ <> "\n") <<< String.trim <<< Yaml.printYaml codec +writeYamlFile :: forall a path. Path.IsPath path => CJ.Codec a -> path -> a -> Aff Unit +writeYamlFile codec path = FS.Aff.writeTextFile UTF8 (toRaw path) <<< (_ <> "\n") <<< String.trim <<< Yaml.printYaml codec -- | Decode data from a YAML file at the provided filepath -readYamlFile :: forall a. CJ.Codec a -> FilePath -> Aff (Either String a) +readYamlFile :: forall a path. Path.IsPath path => CJ.Codec a -> path -> Aff (Either String a) readYamlFile codec path = do - result <- Aff.attempt $ FS.Aff.readTextFile UTF8 path + result <- Aff.attempt $ FS.Aff.readTextFile UTF8 (toRaw path) pure (lmap Aff.message result >>= Yaml.parseYaml codec >>> lmap CJ.DecodeError.print) -writeYamlDocFile :: forall a. FilePath -> Yaml.YamlDoc a -> Aff Unit -writeYamlDocFile path = FS.Aff.writeTextFile UTF8 path <<< (_ <> "\n") <<< String.trim <<< Yaml.toString +writeYamlDocFile :: forall a path. Path.IsPath path => path -> Yaml.YamlDoc a -> Aff Unit +writeYamlDocFile path = FS.Aff.writeTextFile UTF8 (toRaw path) <<< (_ <> "\n") <<< String.trim <<< Yaml.toString -readYamlDocFile :: forall a. CJ.Codec a -> FilePath -> Aff (Either String { doc :: Yaml.YamlDoc a, yaml :: a }) +readYamlDocFile :: forall a path. Path.IsPath path => CJ.Codec a -> path -> Aff (Either String { doc :: Yaml.YamlDoc a, yaml :: a }) readYamlDocFile codec path = do - result <- Aff.attempt $ FS.Aff.readTextFile UTF8 path + result <- Aff.attempt $ FS.Aff.readTextFile UTF8 (toRaw path) pure (lmap Aff.message result >>= Yaml.parseYamlDoc codec >>> lmap CJ.DecodeError.print) -stat :: forall m. MonadAff m => FilePath -> m (Either Error Stats) -stat path = liftAff $ try (FS.Aff.stat path) +stat :: forall m path. Path.IsPath path => MonadAff m => path -> m (Either Error Stats) +stat path = liftAff $ try (FS.Aff.stat $ toRaw path) -isLink :: forall m. MonadEffect m => FilePath -> m Boolean -isLink path = liftEffect $ try (FS.Sync.lstat path) >>= case _ of +isLink :: forall m path. Path.IsPath path => MonadEffect m => path -> m Boolean +isLink path = liftEffect $ try (FS.Sync.lstat $ toRaw path) >>= case _ of Left _err -> pure true -- TODO: we should bubble this up instead Right stats -> pure $ Stats.isSymbolicLink stats -foreign import getInBetweenPathsImpl :: EffectFn2 FilePath FilePath (Array FilePath) +foreign import getInBetweenPathsImpl :: EffectFn2 GlobalPath GlobalPath (Array GlobalPath) -getInBetweenPaths :: forall m. MonadEffect m => FilePath -> FilePath -> m (Array FilePath) +getInBetweenPaths :: forall m. MonadEffect m => GlobalPath -> GlobalPath -> m (Array GlobalPath) getInBetweenPaths a b = liftEffect $ runEffectFn2 getInBetweenPathsImpl a b diff --git a/core/src/Path.purs b/core/src/Path.purs new file mode 100644 index 000000000..0ed5f2abb --- /dev/null +++ b/core/src/Path.purs @@ -0,0 +1,165 @@ +module Spago.Path + ( () + , AdHocFilePath + , GlobalPath + , LocalPath + , RootPath + , appendPath + , basename + , class AppendPath + , class IsPath + , dirname + , isPrefixOf + , localPart + , localPathCodec + , global + , mkRoot + , printLocalPath + , quote + , relativeTo + , replaceExtension + , rootPart + , toAbsolute + , toGlobal + , toRaw + , withForwardSlashes + , withForwardSlashes' + ) where + +import Prelude + +import Data.Codec.JSON as CJ +import Data.Function (on) +import Data.Maybe (Maybe, isJust) +import Data.Profunctor (dimap) +import Data.String as String +import Effect.Class (class MonadEffect, liftEffect) +import Node.Path as Node.Path +import Node.Path as Path + +-- | Normally this represents the root directory of the workspace. All Spago +-- | Workspace-scoped paths are relative to a `RootPath`. +newtype RootPath = RootPath String +derive newtype instance Show RootPath +derive newtype instance Eq RootPath +derive newtype instance Ord RootPath + +-- | A Spago Workspace-scoped path, consists of two parts: `RootPath` and local +-- | part, relative to the root. This lets us both have the full path for +-- | actually working with files and the local part for user-facing output. +newtype LocalPath = LocalPath { root :: RootPath, local :: AdHocFilePath } +instance Show LocalPath where show (LocalPath p) = p.local +instance Eq LocalPath where eq = eq `on` toGlobal +instance Ord LocalPath where compare = compare `on` toGlobal + +-- | A part that is logically not part of the Spago Workspace, but points to +-- | something "global", such as registry cache, temp directory, and so on. +newtype GlobalPath = GlobalPath String +instance Show GlobalPath where show (GlobalPath p) = p +derive newtype instance Eq GlobalPath +derive newtype instance Ord GlobalPath + +type AdHocFilePath = String + +class (Show path, Eq path, Ord path) <= IsPath path where + toGlobal :: path -> GlobalPath + relativeTo :: path -> RootPath -> LocalPath + quote :: path -> String + replaceExtension :: String.Pattern -> String.Replacement -> path -> Maybe path + withForwardSlashes :: path -> path + +instance IsPath LocalPath where + toGlobal (LocalPath { root: RootPath root, local }) = + GlobalPath $ Path.concat [ root, local ] + relativeTo path root + | rootPart path == root = path + | otherwise = toGlobal path `relativeTo` root + quote (LocalPath path) + | path.local == "" = "\".\"" + | otherwise = "\"" <> path.local <> "\"" + replaceExtension p r (LocalPath path) = + LocalPath <<< path { local = _ } <$> replaceExtension_ p r path.local + withForwardSlashes (LocalPath path) = + LocalPath { root: withForwardSlashes path.root, local: withForwardSlashes' path.local } + +instance IsPath GlobalPath where + toGlobal = identity + relativeTo (GlobalPath path) (RootPath root) = + LocalPath { root: RootPath root, local: Path.relative root path } + quote (GlobalPath path) = + "\"" <> path <> "\"" + replaceExtension p r (GlobalPath path) = + GlobalPath <$> replaceExtension_ p r path + withForwardSlashes (GlobalPath path) = + GlobalPath $ withForwardSlashes' path + +instance IsPath RootPath where + toGlobal (RootPath path) = GlobalPath path + relativeTo path root = toGlobal path `relativeTo` root + quote (RootPath path) = "\"" <> path <> "\"" + replaceExtension p r (RootPath path) = RootPath <$> replaceExtension_ p r path + withForwardSlashes (RootPath path) = RootPath $ withForwardSlashes' path + +class AppendPath base path result | base path -> result where + appendPath :: base -> path -> result +instance AppendPath RootPath AdHocFilePath LocalPath where + appendPath root local + | Path.isAbsolute local = global local `relativeTo` root + | otherwise = LocalPath { root, local } +instance AppendPath LocalPath AdHocFilePath LocalPath where + appendPath (LocalPath { root, local }) path + | Path.isAbsolute path = global path `relativeTo` root + | otherwise = LocalPath { root, local: Path.concat [ local, path ] } +instance AppendPath GlobalPath AdHocFilePath GlobalPath where + appendPath (GlobalPath path) p + | Path.isAbsolute p = GlobalPath p + | otherwise = GlobalPath $ Path.concat [ path, p ] + +infixl 5 appendPath as + +-- | The only publicly available way to create a root path. This function is +-- | intentionally made effectful, even though it doesn't have to be, to make it +-- | difficult to use it accidentally. +mkRoot :: ∀ m path. MonadEffect m => IsPath path => path -> m RootPath +mkRoot path = pure $ RootPath $ toRaw path + +global :: String -> GlobalPath +global = GlobalPath + +rootPart :: LocalPath -> RootPath +rootPart (LocalPath { root }) = root + +localPart :: LocalPath -> AdHocFilePath +localPart (LocalPath { local }) = local + +dirname :: ∀ path. IsPath path => path -> GlobalPath +dirname path = global $ Node.Path.dirname $ toRaw path + +replaceExtension_ :: String.Pattern -> String.Replacement -> String -> Maybe String +replaceExtension_ p (String.Replacement r) = map (_ <> r) <<< String.stripSuffix p + +basename :: ∀ path. IsPath path => path -> String +basename path = Node.Path.basename $ toRaw path + +isPrefixOf :: ∀ path1 path2. IsPath path1 => IsPath path2 => path1 -> path2 -> Boolean +isPrefixOf prefix whole = isJust $ String.stripPrefix (String.Pattern $ toRaw prefix) (toRaw whole) + +toAbsolute :: ∀ m path. IsPath path => MonadEffect m => path -> m GlobalPath +toAbsolute path = liftEffect $ GlobalPath <$> Node.Path.resolve [] (toRaw path) + +toRaw :: ∀ path. IsPath path => path -> String +toRaw p = let (GlobalPath g) = toGlobal p in g + +withForwardSlashes' :: String -> String +withForwardSlashes' = String.replaceAll (String.Pattern "\\") (String.Replacement "/") + +localPathCodec :: RootPath -> CJ.Codec LocalPath +localPathCodec root = CJ.string # dimap printLocalPath (root _) + +-- | Formats the local part of the path for user-friendly printing. The only +-- | difference (for now) is that the "root" path is represented to the user as +-- | "./" so as not to get them confused by a seeming absence of output. +printLocalPath :: LocalPath -> String +printLocalPath p = + let l = localPart p + in if l == "" then "./" else l diff --git a/core/src/Prelude.purs b/core/src/Prelude.purs index e41556bdc..fb3c35cab 100644 --- a/core/src/Prelude.purs +++ b/core/src/Prelude.purs @@ -43,12 +43,12 @@ import Effect.Exception.Unsafe (unsafeThrow) as Extra import Effect.Ref (Ref) as Extra import Node.Buffer (Buffer) as Extra import Node.Encoding (Encoding(..)) as Extra -import Node.Path (FilePath) as Extra import Partial.Unsafe (unsafeCrashWith) import Registry.ManifestIndex (ManifestIndex) as Extra import Registry.Types (PackageName, Version, Range, Location, License, Manifest(..), Metadata(..), Sha256) as Extra import Spago.Json (printJson, parseJson) as Extra import Spago.Log (logDebug, logError, logInfo, Docc, logSuccess, logWarn, die, die', justOrDieWith, justOrDieWith', rightOrDie, rightOrDie_, rightOrDieWith, rightOrDieWith', toDoc, indent, indent2, output, LogEnv, LogOptions, OutputFormat(..)) as Extra +import Spago.Path (AdHocFilePath, GlobalPath, LocalPath, RootPath, class AppendPath, appendPath, ()) as Extra import Spago.Yaml (YamlDoc, printYaml, parseYaml) as Extra newtype Spago env a = Spago (ReaderT env Extra.Aff a) diff --git a/spago.lock b/spago.lock index b7968ba07..8c6c8a9eb 100644 --- a/spago.lock +++ b/spago.lock @@ -474,6 +474,7 @@ "console", "control", "datetime", + "debug", "docs-search-common", "docs-search-index", "dodo-printer", @@ -539,6 +540,7 @@ "contravariant", "control", "datetime", + "debug", "distributive", "docs-search-common", "docs-search-index", @@ -768,6 +770,7 @@ "contravariant", "control", "datetime", + "debug", "distributive", "docs-search-common", "docs-search-index", @@ -2071,6 +2074,15 @@ "tuples" ] }, + "debug": { + "type": "registry", + "version": "6.0.2", + "integrity": "sha256-vmkYFuXYuELBzeauvgHG6E6Kf/Hp1dAnxwE9ByHfwSg=", + "dependencies": [ + "functions", + "prelude" + ] + }, "distributive": { "type": "registry", "version": "6.0.0", diff --git a/spago.yaml b/spago.yaml index e0b81aa32..306846476 100644 --- a/spago.yaml +++ b/spago.yaml @@ -12,6 +12,7 @@ package: - WildcardInferredType - ImplicitQualifiedImportReExport dependencies: + - debug - aff - aff-promise - affjax diff --git a/src/Spago/BuildInfo.purs b/src/Spago/BuildInfo.purs index 9f40af61a..706c77442 100644 --- a/src/Spago/BuildInfo.purs +++ b/src/Spago/BuildInfo.purs @@ -4,7 +4,6 @@ import Spago.Prelude import Data.Array.NonEmpty as NEA import Data.String as String -import Node.Path as Path import Registry.PackageName as PackageName import Registry.Version as Version import Spago.Config (Workspace, WorkspacePackage) @@ -30,25 +29,26 @@ type BuildInfo = type BuildInfoEnv a = { workspace :: Workspace , logOptions :: LogOptions + , rootPath :: RootPath , purs :: Purs | a } writeBuildInfo :: forall a. Spago (BuildInfoEnv a) Unit writeBuildInfo = do - { workspace, purs } <- ask + { workspace, purs, rootPath } <- ask let buildInfo = { pursVersion: Version.print purs.version , packages: map mkPackageBuildInfo $ NEA.toUnfoldable $ Config.getWorkspacePackages workspace.packageSet } buildInfoString = mkBuildInfo buildInfo - writeIt = FS.writeTextFile buildInfoPath buildInfoString + writeIt = FS.writeTextFile (buildInfoPath rootPath) buildInfoString -- try to write the new build info only if necessary - FS.exists buildInfoPath >>= case _ of + FS.exists (buildInfoPath rootPath) >>= case _ of false -> writeIt true -> do - currentContent <- FS.readTextFile buildInfoPath + currentContent <- FS.readTextFile (buildInfoPath rootPath) when (currentContent /= buildInfoString) do writeIt @@ -77,8 +77,8 @@ mkBuildInfo { packages, pursVersion } = String.joinWith "\n" renderPackageType p = "\"" <> p.name <> "\" :: String" currentSpagoVersion = BuildInfo.packages."spago-bin" -buildInfoPath ∷ FilePath -buildInfoPath = Path.concat [ Paths.localCachePath, "BuildInfo.purs" ] +buildInfoPath ∷ RootPath -> LocalPath +buildInfoPath root = root Paths.localCachePath "BuildInfo.purs" mkPackageBuildInfo :: WorkspacePackage -> { name :: String, version :: String } mkPackageBuildInfo { package } = diff --git a/src/Spago/Cmd.purs b/src/Spago/Cmd.purs index c7ce8a7be..bcf860634 100644 --- a/src/Spago/Cmd.purs +++ b/src/Spago/Cmd.purs @@ -13,6 +13,7 @@ import Node.Library.Execa as Execa import Node.Platform as Platform import Node.Process as Process import Partial.Unsafe (unsafeCrashWith, unsafePartial) +import Spago.Path as Path data StdinConfig = StdinPipeParent @@ -68,7 +69,7 @@ type ExecOptions = { pipeStdin :: StdinConfig , pipeStdout :: Boolean , pipeStderr :: Boolean - , cwd :: Maybe FilePath + , cwd :: Maybe GlobalPath } defaultExecOptions :: ExecOptions @@ -86,7 +87,12 @@ spawn cmd args opts = liftAff do StdinPipeParent -> Just inherit StdinWrite _ -> Just pipe StdinNewPipe -> Just pipe - subprocess <- Execa.execa cmd args (_ { cwd = opts.cwd, stdin = stdinOpt, stdout = Just pipe, stderr = Just pipe }) + subprocess <- Execa.execa cmd args _ + { cwd = Path.toRaw <$> opts.cwd + , stdin = stdinOpt + , stdout = Just pipe + , stderr = Just pipe + } case opts.pipeStdin of StdinWrite s | Just { writeUtf8End } <- subprocess.stdin -> writeUtf8End s @@ -106,9 +112,9 @@ joinProcess cp = do Normally 0 -> pure $ Right result _ -> pure $ Left result -exec :: forall m. MonadAff m => String -> Array String -> ExecOptions -> m (Either ExecResult ExecResult) +exec :: forall m. MonadAff m => GlobalPath -> Array String -> ExecOptions -> m (Either ExecResult ExecResult) exec cmd args opts = liftAff do - result <- _.getResult =<< spawn cmd args opts + result <- _.getResult =<< spawn (Path.toRaw cmd) args opts case result.exit of Normally 0 -> pure $ Right result _ -> pure $ Left result @@ -175,7 +181,7 @@ findFlag { flags, args } = if argsLen == 0 then Nothing else go 0 isSingleCharFlag = eq (Just 1) <<< map String.length <<< String.stripPrefix (Pattern "-") -getExecutable :: forall a. String -> Spago (LogEnv a) { cmd :: String, output :: String } +getExecutable :: ∀ a. String -> Spago (LogEnv a) { cmd :: GlobalPath, output :: String } getExecutable command = case Process.platform of Just Platform.Win32 -> do @@ -198,7 +204,7 @@ getExecutable command = where askVersion cmd = exec cmd [ "--version" ] defaultExecOptions { pipeStdout = false, pipeStderr = false } - mkCmd cmd maybeExtension = cmd <> maybe "" (append ".") maybeExtension + mkCmd cmd maybeExtension = Path.global $ cmd <> maybe "" (append ".") maybeExtension complain err = do logDebug $ printExecResult err diff --git a/src/Spago/Command/Build.purs b/src/Spago/Command/Build.purs index 9bdabea50..307203bf5 100644 --- a/src/Spago/Command/Build.purs +++ b/src/Spago/Command/Build.purs @@ -22,6 +22,7 @@ import Spago.Config (Package(..), PackageMap, WithTestGlobs(..), Workspace, Work import Spago.Config as Config import Spago.Git (Git) import Spago.Log (prepareToDie) +import Spago.Path as Path import Spago.Psa as Psa import Spago.Purs (Purs) import Spago.Purs.Graph as Graph @@ -31,6 +32,7 @@ type BuildEnv a = , git :: Git , dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions + , rootPath :: RootPath , workspace :: Workspace , strictWarnings :: Maybe Boolean , pedanticPackages :: Boolean @@ -49,6 +51,7 @@ run opts = do { dependencies , workspace , logOptions + , rootPath , strictWarnings , pedanticPackages } <- ask @@ -64,7 +67,7 @@ run opts = do let addOutputArgs args = case workspace.buildOptions.output of Nothing -> args - Just output -> args <> [ "--output", output ] + Just output -> args <> [ "--output", Path.toRaw output ] -- find the `--json-errors` flag and die if it's there - Spago handles it when (isJust $ Cmd.findFlag { flags: [ "--json-errors" ], args: opts.pursArgs }) do @@ -73,15 +76,6 @@ run opts = do , "Use the --json-errors flag for Spago." ] - {- - TODO: before, then, else - buildAction globs = do - let action = buildBackend globs >> (fromMaybe (pure ()) maybePostBuild) - runCommands "Before" beforeCommands - action `onException` (runCommands "Else" elseCommands) - runCommands "Then" thenCommands - -} - when (isJust $ Cmd.findFlag { flags: [ "-g", "--codegen" ], args: opts.pursArgs }) do die [ "Can't pass the `--codegen` option to purs, Spago already does that for you." @@ -103,7 +97,8 @@ run opts = do Just p -> NEA.singleton p Nothing -> Config.getWorkspacePackages workspace.packageSet globs = getBuildGlobs - { dependencies: case workspace.selected of + { rootPath + , dependencies: case workspace.selected of Just p -> let { core, test } = unsafeFromJust $ Map.lookup p.package.name dependencies @@ -116,7 +111,8 @@ run opts = do , selected: selectedPackages } pathDecisions <- liftEffect $ sequence $ Psa.toPathDecisions - { allDependencies + { rootPath + , allDependencies , selectedPackages: NEA.toArray selectedPackages , psaCliFlags: { strict: strictWarnings, statVerbosity: workspace.buildOptions.statVerbosity } , censorLibWarnings: workspace.buildOptions.censorLibWarnings @@ -129,7 +125,7 @@ run opts = do , statVerbosity: fromMaybe Psa.defaultStatVerbosity workspace.buildOptions.statVerbosity } - built <- Psa.psaCompile globs args psaArgs + built <- Psa.psaCompile rootPath globs args psaArgs backendBuilt <- case workspace.backend of _ | not built -> pure false Nothing -> pure true @@ -140,7 +136,7 @@ run opts = do moreBackendArgs = case backend.args of Just as | Array.length as > 0 -> as _ -> [] - Cmd.exec backend.cmd (addOutputArgs moreBackendArgs) Cmd.defaultExecOptions >>= case _ of + Cmd.exec (Path.global backend.cmd) (addOutputArgs moreBackendArgs) Cmd.defaultExecOptions >>= case _ of Left r -> do logDebug $ Cmd.printExecResult r prepareToDie [ "Failed to build with backend " <> backend.cmd ] $> false @@ -178,15 +174,16 @@ run opts = do -- then we could use the graph to remove outdated modules from `output`! type BuildGlobsOptions = - { withTests :: Boolean + { rootPath :: RootPath + , withTests :: Boolean , depsOnly :: Boolean , selected :: NonEmptyArray WorkspacePackage , dependencies :: PackageMap } -getBuildGlobs :: BuildGlobsOptions -> Set FilePath -getBuildGlobs { selected, dependencies, withTests, depsOnly } = - Set.fromFoldable $ projectGlobs <> monorepoPkgGlobs <> dependencyGlobs <> [ BuildInfo.buildInfoPath ] +getBuildGlobs :: BuildGlobsOptions -> Set LocalPath +getBuildGlobs { rootPath, selected, dependencies, withTests, depsOnly } = + Set.fromFoldable $ projectGlobs <> monorepoPkgGlobs <> dependencyGlobs <> [ BuildInfo.buildInfoPath rootPath ] where -- Here we select the right globs for a monorepo setup with a bunch of packages projectGlobs = case depsOnly of @@ -199,15 +196,15 @@ getBuildGlobs { selected, dependencies, withTests, depsOnly } = true -> WithTestGlobs false -> NoTestGlobs - workspacePackageGlob :: WorkspacePackage -> Array String - workspacePackageGlob p = Config.sourceGlob testGlobs p.package.name (WorkspacePackage p) + workspacePackageGlob :: WorkspacePackage -> Array LocalPath + workspacePackageGlob p = Config.sourceGlob rootPath testGlobs p.package.name (WorkspacePackage p) { yes: monorepoPkgs, no: dependencyPkgs } = partition isWorkspacePackage $ Map.toUnfoldable dependencies -- depsOnly means "no packages from the monorepo", so we filter out the workspace packages - dependencyGlobs = (Tuple.uncurry $ Config.sourceGlob NoTestGlobs) =<< dependencyPkgs + dependencyGlobs = (Tuple.uncurry $ Config.sourceGlob rootPath NoTestGlobs) =<< dependencyPkgs monorepoPkgGlobs | depsOnly = [] - | otherwise = (Tuple.uncurry $ Config.sourceGlob NoTestGlobs) =<< monorepoPkgs + | otherwise = (Tuple.uncurry $ Config.sourceGlob rootPath NoTestGlobs) =<< monorepoPkgs isWorkspacePackage :: Tuple PackageName Package -> Boolean isWorkspacePackage = case _ of diff --git a/src/Spago/Command/Bundle.purs b/src/Spago/Command/Bundle.purs index ff8f80f65..4e9d76684 100644 --- a/src/Spago/Command/Bundle.purs +++ b/src/Spago/Command/Bundle.purs @@ -5,16 +5,17 @@ import Spago.Prelude import Data.Array (all, fold, take) import Data.String as Str import Data.String.Utils (startsWith) -import Node.Path as Path import Spago.Cmd as Cmd import Spago.Config (BundlePlatform(..), BundleType(..), Workspace, WorkspacePackage) import Spago.Esbuild (Esbuild) import Spago.FS as FS import Spago.Generated.BuildInfo as BuildInfo +import Spago.Path as Path type BundleEnv a = { esbuild :: Esbuild , logOptions :: LogOptions + , rootPath :: RootPath , bundleOptions :: BundleOptions , workspace :: Workspace , selected :: WorkspacePackage @@ -25,30 +26,21 @@ type BundleOptions = { minify :: Boolean , sourceMaps :: Boolean , module :: String - , outfile :: FilePath + , outfile :: AdHocFilePath , force :: Boolean , platform :: BundlePlatform , type :: BundleType , extraArgs :: Array String } -type RawBundleOptions = - { minify :: Boolean - , module :: String - , outfile :: FilePath - , platform :: String - , type :: String - , extraArgs :: Array String - } - run :: ∀ a. Spago (BundleEnv a) Unit run = do - { esbuild, selected, workspace, bundleOptions: opts } <- ask + ({ rootPath, esbuild, selected, workspace, bundleOptions: opts } :: BundleEnv a) <- ask logDebug $ "Bundle options: " <> show opts let minify = if opts.minify then [ "--minify" ] else [] sourceMap = if opts.sourceMaps then [ "--sourcemap" ] else [] - outfile = Path.concat [ selected.path, opts.outfile ] + outfile = selected.path opts.outfile format = case opts.platform, opts.type of BundleBrowser, BundleApp -> "--format=iife" _, _ -> "--format=esm" @@ -57,9 +49,9 @@ run = do BundleNode -> s BundleBrowser -> "" - output = workspace.buildOptions.output # fromMaybe "output" + output = workspace.buildOptions.output # fromMaybe (rootPath "output") -- TODO: we might need to use `Path.relative selected.path output` instead of just output there - mainPath = withForwardSlashes $ Path.concat [ output, opts.module, "index.js" ] + mainPath = Path.localPart $ output opts.module "index.js" { input, entrypoint } = case opts.type of BundleApp -> @@ -71,7 +63,7 @@ run = do , input: Cmd.StdinNewPipe } - execOptions = Cmd.defaultExecOptions { pipeStdin = input } + execOptions = Cmd.defaultExecOptions { pipeStdin = input, cwd = Just (Path.toGlobal rootPath) } banner = fold [ bundleWatermarkPrefix @@ -82,7 +74,7 @@ run = do args = fold [ [ "--bundle" - , "--outfile=" <> outfile + , "--outfile=" <> (Path.toRaw outfile) , "--platform=" <> show opts.platform , "--banner:js=" <> banner , "--loader:.node=file" -- See https://github.com/evanw/esbuild/issues/1051 @@ -95,10 +87,10 @@ run = do ] -- FIXME: remove this after 2024-12-01 - whenM (FS.exists checkWatermarkMarkerFileName) + whenM (FS.exists $ rootPath checkWatermarkMarkerFileName) $ unless opts.force $ whenM (isNotSpagoGeneratedFile outfile) - $ die [ "Target file " <> opts.outfile <> " was not previously generated by Spago. Use --force to overwrite anyway." ] + $ die [ "Target file " <> Path.quote outfile <> " was not previously generated by Spago. Use --force to overwrite anyway." ] logInfo "Bundling..." logDebug $ "Running esbuild: " <> show args @@ -108,7 +100,7 @@ run = do logDebug $ Cmd.printExecResult r die [ "Failed to bundle." ] -isNotSpagoGeneratedFile :: ∀ a. String -> Spago (BundleEnv a) Boolean +isNotSpagoGeneratedFile :: ∀ a. LocalPath -> Spago (BundleEnv a) Boolean isNotSpagoGeneratedFile path = do exists <- FS.exists path if not exists then diff --git a/src/Spago/Command/Docs.purs b/src/Spago/Command/Docs.purs index 0ea06a5b3..dc0007cc8 100644 --- a/src/Spago/Command/Docs.purs +++ b/src/Spago/Command/Docs.purs @@ -23,20 +23,22 @@ type DocsEnv a = , workspace :: Workspace , dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions + , rootPath :: RootPath , docsFormat :: DocsFormat , depsOnly :: Boolean , open :: Boolean | a } -run :: Spago (DocsEnv _) Unit +run :: ∀ a. Spago (DocsEnv a) Unit run = do logDebug "Running `spago docs`" logInfo "Generating documentation for the project. This might take a while..." - { workspace, dependencies, docsFormat, depsOnly, open } <- ask + { rootPath, workspace, dependencies, docsFormat, depsOnly, open } <- ask let globs = Build.getBuildGlobs - { withTests: true + { rootPath + , withTests: true , selected: Config.getWorkspacePackages workspace.packageSet , dependencies: Fetch.toAllDependencies dependencies , depsOnly diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index 704f41adf..b76e39b1d 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -38,7 +38,6 @@ import Effect.Aff.AVar as AVar import Effect.Ref as Ref import Node.Buffer as Buffer import Node.Encoding as Encoding -import Node.Path as Path import Registry.Internal.Codec as Internal.Codec import Registry.Metadata as Metadata import Registry.PackageName as PackageName @@ -54,6 +53,7 @@ import Spago.FS as FS import Spago.Git as Git import Spago.Lock (LockEntry(..)) import Spago.Lock as Lock +import Spago.Path as Path import Spago.Paths as Paths import Spago.Purs as Purs import Spago.Registry as Registry @@ -66,6 +66,7 @@ type FetchEnvRow a = ( getRegistry :: Spago (Registry.PreRegistryEnv ()) Registry.RegistryFunctions , workspace :: Workspace , logOptions :: LogOptions + , rootPath :: Path.RootPath , offline :: OnlineStatus , purs :: Purs.Purs , git :: Git.Git @@ -86,20 +87,23 @@ run :: forall a. FetchOpts -> Spago (FetchEnv a) PackageTransitiveDeps run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do logDebug $ "Requested to install these packages: " <> printJson (CJ.array PackageName.codec) packagesRequestedToInstall - { workspace: currentWorkspace } <- ask + { workspace: currentWorkspace, rootPath } <- ask let getPackageConfigPath errorMessageEnd = do - case currentWorkspace.selected, currentWorkspace.rootPackage of + res <- case currentWorkspace.selected, currentWorkspace.rootPackage of Just { path, doc, package }, _ -> - pure { configPath: Path.concat [ path, "spago.yaml" ], yamlDoc: doc, package } + pure { configPath: path "spago.yaml", yamlDoc: doc, package } _, Just rootPackage -> - pure { configPath: "spago.yaml", yamlDoc: currentWorkspace.doc, package: rootPackage } + pure { configPath: rootPath "spago.yaml", yamlDoc: currentWorkspace.doc, package: rootPackage } Nothing, Nothing -> die [ "No package found in the root configuration." , "Please use the `-p` flag to select a package " <> errorMessageEnd ] + doc <- justOrDieWith res.yamlDoc Config.configDocMissingErrorMessage + pure res { yamlDoc = doc } + installingPackagesData <- do case packagesRequestedToInstall of [] -> @@ -162,13 +166,13 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do countString = case Array.length actualPackagesToInstall of 1 -> "1 package" n -> show n <> " packages" - logInfo $ "Adding " <> countString <> " to the config in " <> configPath + logInfo $ "Adding " <> countString <> " to the config in " <> Path.quote configPath liftAff $ Config.addPackagesToConfig configPath yamlDoc isTest actualPackagesToInstall -- 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 ranges to core dependencies to the config in " <> 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 @@ -212,7 +216,8 @@ fetchPackagesToLocalCache packages = do GitPackage gitPackage -> (Just <<< Tuple gitPackage.git) <$> AVar.new unit _ -> pure Nothing parallelise $ packages # Map.toUnfoldable <#> \(Tuple name package) -> do - let localPackageLocation = Config.getPackageLocation name package + { rootPath } <- ask + let localPackageLocation = Config.getLocalPackageLocation rootPath name package -- first of all, we check if we have the package in the local cache. If so, we don't even do the work unlessM (FS.exists localPackageLocation) case package of GitPackage gitPackage -> do @@ -234,8 +239,8 @@ fetchPackagesToLocalCache packages = do Right versionMetadata -> do logDebug $ "Metadata read: " <> printJson Metadata.publishedMetadataCodec versionMetadata -- then check if we have a tarball cached. If not, download it - let globalCachePackagePath = Path.concat [ Paths.globalCachePath, "packages", PackageName.print name ] - let archivePath = Path.concat [ globalCachePackagePath, versionString <> ".tar.gz" ] + let globalCachePackagePath = Paths.globalCachePath "packages" PackageName.print name + let archivePath = globalCachePackagePath (versionString <> ".tar.gz") FS.mkdirp globalCachePackagePath -- We need to see if the tarball is there, and if we can decompress it. -- This is because if Spago is killed while it's writing the tar, then it might leave it corrupted. @@ -247,7 +252,7 @@ fetchPackagesToLocalCache packages = do FS.mkdirp tempDir tarIsGood <- if tarExists then do - logDebug $ "Trying to unpack archive to temp folder: " <> tempDir + logDebug $ "Trying to unpack archive to temp folder: " <> Path.quote tempDir map (either (const false) (const true)) $ liftEffect $ Tar.extract { filename: archivePath, cwd: tempDir } else pure false @@ -288,14 +293,14 @@ fetchPackagesToLocalCache packages = do unless (archiveSha == versionMetadata.hash) do die $ "Archive fetched for " <> packageVersion <> " has a different hash (" <> Sha256.print archiveSha <> ") than expected (" <> Sha256.print versionMetadata.hash <> ")" -- if everything's alright we stash the tar in the global cache - logDebug $ "Fetched archive for " <> packageVersion <> ", saving it in the global cache: " <> archivePath + logDebug $ "Fetched archive for " <> packageVersion <> ", saving it in the global cache: " <> Path.quote archivePath FS.writeFile archivePath archiveBuffer - logDebug $ "Unpacking archive to temp folder: " <> tempDir + logDebug $ "Unpacking archive to temp folder: " <> Path.quote tempDir (liftEffect $ Tar.extract { filename: archivePath, cwd: tempDir }) >>= case _ of Right _ -> pure unit Left err -> die [ "Failed to decode downloaded package " <> packageVersion <> ", error:", show err ] - logDebug $ "Moving extracted file to local cache:" <> localPackageLocation - FS.moveSync { src: (Path.concat [ tempDir, tarInnerFolder ]), dst: localPackageLocation } + logDebug $ "Moving extracted file to local cache:" <> Path.quote localPackageLocation + FS.moveSync { src: tempDir tarInnerFolder, dst: Path.toGlobal localPackageLocation } -- Local package, no work to be done LocalPackage _ -> pure unit WorkspacePackage _ -> pure unit @@ -319,7 +324,7 @@ updateCache key value cacheRef = liftEffect $ Ref.modify_ (Map.insert key value) writeNewLockfile :: ∀ a. String -> PackageTransitiveDeps -> Spago (FetchEnv a) PackageTransitiveDeps writeNewLockfile reason allTransitiveDeps = do logInfo $ reason <> ", generating it..." - { workspace } <- ask + { workspace, rootPath } <- ask -- All these Refs are needed to memoise Db and file reads packageDependenciesCache <- liftEffect $ Ref.new Map.empty @@ -372,9 +377,9 @@ writeNewLockfile reason allTransitiveDeps = do WorkspacePackage _ -> Nothing GitPackage gitPackage -> Just do - let packageLocation = Config.getPackageLocation packageName package + let packageLocation = Config.getLocalPackageLocation rootPath packageName package withCache packageLocation gitRefCache do - Git.getRef (Just packageLocation) >>= case _ of + Git.getRef packageLocation >>= case _ of Left err -> die err -- TODO maybe not die here? Right rev -> do @@ -421,7 +426,7 @@ writeNewLockfile reason allTransitiveDeps = do , extra_packages: fromMaybe Map.empty workspace.workspaceConfig.extraPackages } } - liftAff $ FS.writeJsonFile Lock.lockfileCodec "spago.lock" lockfile + liftAff $ FS.writeJsonFile Lock.lockfileCodec (rootPath "spago.lock") lockfile logInfo "Lockfile written to spago.lock. Please commit this file." -- We update the dependencies here with the commit hashes that came from the getRef calls, @@ -446,15 +451,18 @@ toAllDependencies = >>> foldMap (\m -> [ m.core, m.test ]) >>> foldl Map.union Map.empty -getGitPackageInLocalCache :: forall a. PackageName -> GitPackage -> Spago (Git.GitEnv a) Unit +getGitPackageInLocalCache :: forall a. PackageName -> GitPackage -> Spago (FetchEnv a) Unit getGitPackageInLocalCache name package = do - ensureRepoCloned - ensureRefPresent + { rootPath } <- ask + FS.mkdirp $ rootPath Paths.localCachePackagesPath PackageName.print name + let repoCache = rootPath Paths.localCacheGitPath Config.fileSystemCharEscape package.git - let localPackageLocation = Config.getPackageLocation name (GitPackage package) - logDebug $ "Copying repo to " <> localPackageLocation - FS.mkdirp $ Path.concat [ Paths.localCachePackagesPath, PackageName.print name ] - FS.copyTree { src: repoCacheLocation, dst: localPackageLocation } + ensureRepoCloned repoCache + ensureRefPresent repoCache + + let localPackageLocation = Config.getLocalPackageLocation rootPath name (GitPackage package) + logDebug $ "Copying repo to " <> Path.quote localPackageLocation + FS.copyTree { src: repoCache, dst: localPackageLocation } logDebug $ "Checking out ref '" <> package.ref <> "'" Git.checkout { repo: localPackageLocation, ref: package.ref } >>= rightOrDie_ @@ -463,34 +471,31 @@ getGitPackageInLocalCache name package = do -- So we run getRef here and then do a copy if the ref is different than the original one -- (since it might be a commit to start with) logDebug $ "Checking if we need to copy the package to a commit hash location..." - commitHash <- Git.getRef (Just localPackageLocation) >>= rightOrDie + commitHash <- Git.getRef localPackageLocation >>= rightOrDie when (commitHash /= package.ref) do - let commitHashLocation = Config.getPackageLocation name (GitPackage $ package { ref = commitHash }) - logDebug $ "Copying the repo also to " <> commitHashLocation + let commitHashLocation = Config.getLocalPackageLocation rootPath name (GitPackage $ package { ref = commitHash }) + logDebug $ "Copying the repo also to " <> Path.quote commitHashLocation FS.copyTree { src: localPackageLocation, dst: commitHashLocation } where - repoCacheLocation = Path.concat [ Paths.localCacheGitPath, Config.fileSystemCharEscape package.git ] - - ensureRepoCloned = unlessM (FS.exists repoCacheLocation) do + ensureRepoCloned repoCache = unlessM (FS.exists repoCache) do tempDir <- mkTemp' (Just $ printJson Config.gitPackageCodec package) - logDebug $ "Cloning repo in " <> tempDir + logDebug $ "Cloning repo in " <> Path.quote tempDir Git.fetchRepo package tempDir >>= rightOrDie_ - logDebug $ "Repo cloned. Moving to " <> repoCacheLocation - FS.mkdirp $ Path.concat [ Paths.localCachePackagesPath, PackageName.print name ] - FS.moveSync { src: tempDir, dst: repoCacheLocation } + logDebug $ "Repo cloned. Moving to " <> Path.quote repoCache + FS.moveSync { src: tempDir, dst: Path.toGlobal repoCache } - ensureRefPresent = do + ensureRefPresent repoCache = do logDebug $ "Verifying ref " <> package.ref { offline } <- ask - Git.getRefType { repo: repoCacheLocation, ref: package.ref } >>= case _, offline of + Git.getRefType { repo: repoCache, ref: package.ref } >>= case _, offline of Right _, _ -> pure unit Left _, Offline -> die $ "Repo " <> package.git <> " does not have ref " <> package.ref <> " in local cache. Cannot pull from origin in offline mode." Left _, Online -> do logDebug $ "Ref " <> package.ref <> " is not present, trying to pull from origin" - Git.fetch { repo: repoCacheLocation, remote: "origin" } >>= rightOrDie_ + Git.fetch { repo: repoCache, remote: "origin" } >>= rightOrDie_ getPackageDependencies :: forall a. PackageName -> Package -> Spago (FetchEnv a) (Maybe (ByEnv (Map PackageName Range))) getPackageDependencies packageName package = case package of @@ -500,37 +505,36 @@ getPackageDependencies packageName package = case package of GitPackage p -> do -- Note: we get the package in local cache nonetheless, -- so we have guarantees about being able to fetch it - let packageLocation = Config.getPackageLocation packageName package + { rootPath } <- ask + let packageLocation = Config.getLocalPackageLocation rootPath packageName package unlessM (FS.exists packageLocation) do getGitPackageInLocalCache packageName p case p.dependencies of Just (Dependencies dependencies) -> pure $ Just { core: map (fromMaybe Config.widestRange) dependencies, test: Map.empty } Nothing -> do - readLocalDependencies case p.subdir of - Nothing -> packageLocation - Just s -> Path.concat [ packageLocation, s ] + readLocalDependencies $ Path.toGlobal $ maybe packageLocation (packageLocation _) p.subdir LocalPackage p -> do - readLocalDependencies p.path + readLocalDependencies $ Path.global p.path WorkspacePackage p -> pure $ Just $ (map (fromMaybe Config.widestRange) <<< unwrap) `onEachEnv` getWorkspacePackageDeps p where -- try to see if the package has a spago config, and if it's there we read it - readLocalDependencies :: FilePath -> Spago (FetchEnv a) (Maybe (ByEnv (Map PackageName Range))) + readLocalDependencies :: GlobalPath -> Spago (FetchEnv a) (Maybe (ByEnv (Map PackageName Range))) readLocalDependencies configLocation = do -- TODO: make this work with manifests - Config.readConfig (Path.concat [ configLocation, "spago.yaml" ]) >>= case _ of + Config.readConfig (configLocation "spago.yaml") >>= case _ of Right { yaml: { package: Just { dependencies: Dependencies deps, test } } } -> pure $ Just { core: fromMaybe Config.widestRange <$> deps , test: fromMaybe Config.widestRange <$> (test <#> _.dependencies <#> unwrap # fromMaybe Map.empty) } Right _ -> die - [ "Read the configuration at path " <> configLocation + [ "Read the configuration at path " <> Path.quote configLocation , "However, it didn't contain a `package` section." ] Left errLines -> die - [ toDoc $ "Could not read config at " <> configLocation + [ toDoc $ "Could not read config at " <> Path.quote configLocation , toDoc "Error: " , indent $ toDoc errLines ] diff --git a/src/Spago/Command/Graph.purs b/src/Spago/Command/Graph.purs index ba2481788..37b695152 100644 --- a/src/Spago/Command/Graph.purs +++ b/src/Spago/Command/Graph.purs @@ -22,6 +22,7 @@ import Spago.Purs.Graph as Graph type GraphEnv a = { dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions + , rootPath :: RootPath , workspace :: Workspace , purs :: Purs | a @@ -39,12 +40,12 @@ type GraphPackagesArgs = , topo :: Boolean } -graphModules :: forall a. GraphModulesArgs -> Spago (GraphEnv a) Unit +graphModules :: ∀ a. GraphModulesArgs -> Spago (GraphEnv a) Unit graphModules { dot, json, topo } = do - env@{ dependencies, workspace } <- ask + env@{ dependencies, workspace, rootPath } <- ask let allDependencies = Fetch.toAllDependencies dependencies let selected = Config.getWorkspacePackages workspace.packageSet - let globs = Build.getBuildGlobs { selected, withTests: false, dependencies: allDependencies, depsOnly: false } + let globs = Build.getBuildGlobs { rootPath, selected, withTests: false, dependencies: allDependencies, depsOnly: false } eitherGraph <- Graph.runGraph globs [] graph <- either die pure eitherGraph @@ -67,10 +68,10 @@ graphModules { dot, json, topo } = do graphPackages :: forall a. GraphPackagesArgs -> Spago (GraphEnv a) Unit graphPackages { dot, json, topo } = do - env@{ dependencies, workspace } <- ask + env@{ dependencies, workspace, rootPath } <- ask let allDependencies = Fetch.toAllDependencies dependencies let selected = Config.getWorkspacePackages workspace.packageSet - let globs = Build.getBuildGlobs { selected, withTests: false, dependencies: allDependencies, depsOnly: false } + let globs = Build.getBuildGlobs { rootPath, selected, withTests: false, dependencies: allDependencies, depsOnly: false } eitherGraph <- Graph.runGraph globs [] graph <- either die pure eitherGraph diff --git a/src/Spago/Command/Init.purs b/src/Spago/Command/Init.purs index 886281386..3078e309f 100644 --- a/src/Spago/Command/Init.purs +++ b/src/Spago/Command/Init.purs @@ -16,17 +16,18 @@ import Spago.Prelude import Data.Map as Map import Data.String as String -import Node.Path as Path import Registry.PackageName as PackageName import Registry.Version as Version import Spago.Config (Dependencies(..), SetAddress(..), Config) import Spago.Config as Config import Spago.FS as FS import Spago.Log as Log -import Spago.Paths as Paths +import Spago.Path as Path import Spago.Registry (RegistryEnv) import Spago.Registry as Registry +type InitEnv a = RegistryEnv ( rootPath :: RootPath | a ) + data InitMode = InitWorkspace { packageName :: Maybe String } | InitSubpackage { packageName :: String } @@ -40,7 +41,7 @@ type InitOptions = -- TODO run git init? Is that desirable? -run :: ∀ a. InitOptions -> Spago (RegistryEnv a) Config +run :: ∀ a. InitOptions -> Spago (InitEnv a) Config run opts = do -- Use the specified version of the package set (if specified). -- Otherwise, get the latest version of the package set for the given compiler @@ -57,9 +58,9 @@ run opts = do let mainModuleName = "Main" testModuleName = "Test.Main" - srcDir = Path.concat [ projectDir, "src" ] - testDir = Path.concat [ projectDir, "test" ] - configPath = Path.concat [ projectDir, "spago.yaml" ] + srcDir = projectDir "src" + testDir = projectDir "test" + configPath = projectDir "spago.yaml" config = defaultConfig { name: packageName, withWorkspace, testModuleName } -- Write config @@ -71,16 +72,16 @@ run opts = do -- Because you might want to just init a project with your own source files, -- or just migrate a psc-package project whenDirNotExists srcDir do - copyIfNotExists (Path.concat [ srcDir, mainModuleName <> ".purs" ]) (srcMainTemplate mainModuleName) + copyIfNotExists (srcDir (mainModuleName <> ".purs")) (srcMainTemplate mainModuleName) whenDirNotExists testDir $ do - FS.mkdirp (Path.concat [ testDir, "Test" ]) - copyIfNotExists (Path.concat [ testDir, "Test", "Main.purs" ]) (testMainTemplate testModuleName) + FS.mkdirp (testDir "Test") + copyIfNotExists (testDir "Test" "Main.purs") (testMainTemplate testModuleName) case opts.mode of InitWorkspace _ -> do - copyIfNotExists ".gitignore" gitignoreTemplate - copyIfNotExists pursReplFile.name pursReplFile.content + copyIfNotExists (projectDir ".gitignore") gitignoreTemplate + copyIfNotExists (projectDir pursReplFile.name) pursReplFile.content InitSubpackage _ -> pure unit @@ -102,14 +103,15 @@ run opts = do true -> logInfo $ foundExistingFile dest false -> FS.writeTextFile dest srcTemplate - getPackageName :: Spago (RegistryEnv a) PackageName + getPackageName :: Spago (InitEnv a) PackageName getPackageName = do + { rootPath } <- ask let candidateName = case opts.mode of - InitWorkspace { packageName: Nothing } -> String.take 150 $ Path.basename Paths.cwd + InitWorkspace { packageName: Nothing } -> String.take 150 $ Path.basename rootPath InitWorkspace { packageName: Just n } -> n InitSubpackage { packageName: n } -> n - logDebug [ show Paths.cwd, show candidateName ] + logDebug [ Path.quote rootPath, "\"" <> candidateName <> "\"" ] pname <- case PackageName.parse (PackageName.stripPureScriptPrefix candidateName) of Left err -> die [ toDoc "Could not figure out a name for the new package. Error:" @@ -120,7 +122,7 @@ run opts = do logDebug [ "Got packageName and setVersion:", PackageName.print pname, unsafeStringify opts.setVersion ] pure pname - getWithWorkspace :: Version -> Spago (RegistryEnv a) (Maybe { setVersion :: Maybe Version }) + getWithWorkspace :: Version -> Spago (InitEnv a) (Maybe { setVersion :: Maybe Version }) getWithWorkspace setVersion = case opts.mode of InitWorkspace _ -> pure $ Just @@ -133,12 +135,13 @@ run opts = do logWarn "The --package-set and --use-solver flags are ignored when initializing a subpackage" pure Nothing - getProjectDir :: PackageName -> Spago (RegistryEnv a) FilePath + getProjectDir :: PackageName -> Spago (InitEnv a) LocalPath getProjectDir packageName = case opts.mode of InitWorkspace _ -> - pure "." + ask <#> _.rootPath <#> (_ "") InitSubpackage _ -> do - let dirPath = PackageName.print packageName + { rootPath } <- ask + let dirPath = rootPath PackageName.print packageName unlessM (FS.exists dirPath) $ FS.mkdirp dirPath pure dirPath @@ -288,11 +291,11 @@ pursReplFile = { name: ".purs-repl", content: "import Prelude\n" } -- ERROR TEXTS ----------------------------------------------------------------- -foundExistingProject :: FilePath -> String -foundExistingProject path = "Found a \"" <> path <> "\" file, skipping copy." +foundExistingProject :: LocalPath -> String +foundExistingProject path = "Found a " <> Path.quote path <> " file, skipping copy." -foundExistingDirectory :: FilePath -> String -foundExistingDirectory dir = "Found existing directory \"" <> dir <> "\", skipping copy of sample sources" +foundExistingDirectory :: LocalPath -> String +foundExistingDirectory dir = "Found existing directory " <> Path.quote dir <> ", skipping copy of sample sources" -foundExistingFile :: FilePath -> String -foundExistingFile file = "Found existing file \"" <> file <> "\", not overwriting it" +foundExistingFile :: LocalPath -> String +foundExistingFile file = "Found existing file " <> Path.quote file <> ", not overwriting it" diff --git a/src/Spago/Command/Ls.purs b/src/Spago/Command/Ls.purs index c4f645cef..8f0b15056 100644 --- a/src/Spago/Command/Ls.purs +++ b/src/Spago/Command/Ls.purs @@ -25,6 +25,7 @@ import Registry.Version as Version import Spago.Command.Fetch as Fetch import Spago.Config (BuildType(..), Package(..), Workspace, WorkspacePackage) import Spago.Config as Config +import Spago.Path as Path import Spago.Paths as Paths import Type.Proxy (Proxy(..)) @@ -53,6 +54,7 @@ type LsSetEnv = { dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions , workspace :: Workspace + , rootPath :: RootPath } type LsEnv = @@ -60,28 +62,30 @@ type LsEnv = , logOptions :: LogOptions , workspace :: Workspace , selected :: WorkspacePackage + , rootPath :: RootPath } -listPaths :: LsPathsArgs -> Spago { logOptions :: LogOptions } Unit +listPaths :: LsPathsArgs -> Spago { logOptions :: LogOptions, rootPath :: RootPath } Unit listPaths { json } = do logDebug "Running `listPaths`" + { rootPath } <- ask case json of true -> - output $ OutputJson (CJ.Common.map CJ.string CJ.string) $ Map.fromFoldable keyValuePairs + output $ OutputJson (CJ.Common.map CJ.string CJ.string) $ Map.fromFoldable (keyValuePairs rootPath) false -> output $ OutputTable { titles: [ "Name", "Path" ] - , rows: (\(Tuple k v) -> [ k, v ]) <$> keyValuePairs + , rows: (\(Tuple k v) -> [ k, v ]) <$> keyValuePairs rootPath } where - keyValuePairs = + keyValuePairs root = rmap Path.toRaw <$> [ Tuple "Global cache path" Paths.globalCachePath , Tuple "Global registry path" Paths.registryPath , Tuple "Global registry index path" Paths.registryIndexPath , Tuple "Global package sets path" Paths.packageSetsPath , Tuple "Global database path" Paths.databasePath - , Tuple "Local cache path" Paths.localCachePath - , Tuple "Local cache packages path" Paths.localCachePackagesPath + , Tuple "Local cache path" $ Path.toGlobal $ root Paths.localCachePath + , Tuple "Local cache packages path" $ Path.toGlobal $ root Paths.localCachePackagesPath ] -- TODO: add LICENSE field @@ -89,19 +93,19 @@ listPaths { json } = do listPackageSet :: LsPackagesArgs -> Spago LsSetEnv Unit listPackageSet { json } = do logDebug "Running `listPackageSet`" - { workspace } <- ask + { workspace, rootPath } <- ask case workspace.packageSet.buildType of RegistrySolverBuild _extraPackages -> die "Cannot list the packages in the package set, as none is configured for the project." PackageSetBuild _info packageSet -> do let packages = Map.toUnfoldable packageSet case json of - true -> formatPackagesJson packages + true -> formatPackagesJson rootPath packages false -> formatPackagesTable packages listPackages :: LsDepsOpts -> Spago LsEnv Unit listPackages { transitive, json } = do logDebug "Running `listPackages`" - { dependencies, selected } <- ask + { dependencies, selected, rootPath } <- ask let allDependencies = Fetch.toAllDependencies dependencies direct = (Map.keys <<< unwrap <<< _.dependencies <<< _.package) selected @@ -111,11 +115,11 @@ listPackages { transitive, json } = do case packages of [] -> logWarn "There are no dependencies listed in your configuration" _ -> case json of - true -> formatPackagesJson packages + true -> formatPackagesJson rootPath packages false -> formatPackagesTable packages -formatPackagesJson :: forall m. MonadEffect m => Array (Tuple PackageName Package) -> m Unit -formatPackagesJson packages = output $ OutputJson (packageMap packageCodec) (map wrapPackage $ Map.fromFoldable packages) +formatPackagesJson :: forall m. MonadEffect m => RootPath -> Array (Tuple PackageName Package) -> m Unit +formatPackagesJson root packages = output $ OutputJson (packageMap packageCodec) (map wrapPackage $ Map.fromFoldable packages) where wrapPackage value = { value @@ -151,7 +155,7 @@ formatPackagesJson packages = output $ OutputJson (packageMap packageCodec) (map encode = CJ.encode ( CJ.named "WorkspacePackage" $ CJ.Record.object - { path: CJ.string + { path: Path.localPathCodec root , package: Config.packageConfigCodec , hasTests: CJ.boolean } @@ -175,7 +179,7 @@ formatPackagesTable pkgs = output $ OutputTable RegistryVersion _ -> "-" GitPackage { git } -> git LocalPackage { path } -> path - WorkspacePackage { path } -> path + WorkspacePackage { path } -> Path.printLocalPath path showVersion :: Package -> String showVersion = case _ of diff --git a/src/Spago/Command/Publish.purs b/src/Spago/Command/Publish.purs index f8b88207b..67169d767 100644 --- a/src/Spago/Command/Publish.purs +++ b/src/Spago/Command/Publish.purs @@ -20,7 +20,6 @@ import Effect.Aff (Milliseconds(..)) import Effect.Aff as Aff import Effect.Ref as Ref import JSON (JSON) -import Node.Path as Path import Node.Process as Process import Record as Record import Registry.API.V1 as V1 @@ -45,6 +44,7 @@ import Spago.Git as Git import Spago.Json as Json import Spago.Log (LogVerbosity(..)) import Spago.Log as Log +import Spago.Path as Path import Spago.Prelude as Effect import Spago.Purs (Purs) import Spago.Purs.Graph as Graph @@ -64,6 +64,7 @@ type PublishEnv a = { getRegistry :: Spago (PreRegistryEnv ()) Registry.RegistryFunctions , workspace :: Workspace , logOptions :: LogOptions + , rootPath :: RootPath , offline :: OnlineStatus , git :: Git , db :: Db @@ -97,7 +98,7 @@ publish _args = do ) resultRef - env@{ selected: selected', purs, dependencies } <- ask + env@{ selected: selected', purs, dependencies, rootPath } <- ask let (selected :: WorkspacePackage) = selected' { hasTests = false } let name = selected.package.name let strName = PackageName.print name @@ -118,7 +119,7 @@ publish _args = do -- We then need to check that the dependency graph is accurate. If not, queue the errors let allCoreDependencies = Fetch.toAllDependencies $ dependencies <#> _ { test = Map.empty } - let globs = Build.getBuildGlobs { selected: NEA.singleton selected, withTests: false, dependencies: allCoreDependencies, depsOnly: false } + let globs = Build.getBuildGlobs { rootPath, selected: NEA.singleton selected, withTests: false, dependencies: allCoreDependencies, depsOnly: false } eitherGraph <- Graph.runGraph globs [] case eitherGraph of Right graph -> do @@ -192,7 +193,7 @@ publish _args = do , "submit a transfer operation." ] - unlessM (locationIsInGitRemotes location) $ addError $ toDoc + unlessM (locationIsInGitRemotes rootPath location) $ addError $ toDoc [ "The location specified in the manifest file" , "(" <> Json.stringifyJson Location.codec location <> ")" , " is not one of the remotes in the git repository." @@ -221,19 +222,24 @@ publish _args = do -- All dependencies come from the registry so we can trust the build plan. -- We can then try to build with the dependencies from there. - Internal.Path.readPursFiles (Path.concat [ selected.path, "src" ]) >>= case _ of + Internal.Path.readPursFiles (Path.toRaw $ selected.path "src") >>= case _ of Nothing -> addError $ toDoc [ "This package has no PureScript files in its `src` directory. " , "All package sources must be in the `src` directory, with any additional " , "sources indicated by the `files` key in your manifest." ] Just files -> do + let rootPathPrefix = + Path.toRaw rootPath + # String.stripSuffix (String.Pattern "/") + # fromMaybe (Path.toRaw rootPath) + # (_ <> "/") Operation.Validation.validatePursModules files >>= case _ of Left formattedError -> addError $ toDoc [ "This package has either malformed or disallowed PureScript module names" , "in its `src` directory. All package sources must be in the `src` directory," , "with any additional sources indicated by the `files` key in your manifest." - , formattedError + , formattedError # String.replaceAll (String.Pattern rootPathPrefix) (String.Replacement "") ] Right _ -> pure unit @@ -265,7 +271,7 @@ publish _args = do -- 2) input any login credentials as there are other errors to fix -- before doing that. -- The "hard" git tag checks will occur only if these succeed. - Git.getStatus Nothing >>= case _ of + Git.getStatus rootPath >>= case _ of Left _err -> do die $ toDoc [ toDoc "Could not verify whether the git tree is clean due to the below error:" @@ -280,7 +286,7 @@ publish _args = do | otherwise -> do -- TODO: once we ditch `purs publish`, we don't have a requirement for a tag anymore, -- but we can use any ref. We can then use `getRef` here instead of `tagCheckedOut` - maybeCurrentTag <- hush <$> Git.tagCheckedOut Nothing + maybeCurrentTag <- hush <$> Git.tagCheckedOut rootPath case maybeCurrentTag of Just currentTag -> when (currentTag /= expectedTag) $ addError $ toDoc @@ -294,7 +300,7 @@ publish _args = do Nothing -> -- Current commit does not refer to a git tag. -- We should see whether the expected tag was already defined - Git.listTags Nothing >>= case _ of + Git.listTags rootPath >>= case _ of Left err -> die $ toDoc [ toDoc "Cannot check whether publish config's `version` matches any existing git tags due to the below error:" @@ -341,7 +347,7 @@ publish _args = do Right { expectedVersion, publishingData: publishingData@{ resolutions } } -> do logInfo "Passed preliminary checks." -- This requires login credentials. - Git.pushTag Nothing expectedVersion >>= case _ of + Git.pushTag rootPath expectedVersion >>= case _ of Left err -> die $ toDoc [ err , toDoc "You can try to push the tag manually by running:" @@ -390,6 +396,7 @@ publish _args = do , git: env.git , dependencies , logOptions: env.logOptions + , rootPath: env.rootPath , workspace: env.workspace { selected = Just selected } , strictWarnings: Nothing , pedanticPackages: false @@ -462,13 +469,13 @@ waitForJobFinish jobId = go Nothing true -> logSuccess $ "Registry finished processing the package. Your package was published successfully!" false -> die $ "Registry finished processing the package, but it failed. Please fix it and try again." -locationIsInGitRemotes :: ∀ a. Location -> Spago (PublishEnv a) Boolean -locationIsInGitRemotes location = do - isGitRepo <- FS.exists ".git" +locationIsInGitRemotes :: ∀ a. RootPath -> Location -> Spago (PublishEnv a) Boolean +locationIsInGitRemotes root location = do + isGitRepo <- FS.exists $ root ".git" if not isGitRepo then pure false else - Git.getRemotes Nothing >>= case _ of + Git.getRemotes root >>= case _ of Left err -> die $ toDoc err Right remotes -> diff --git a/src/Spago/Command/Repl.purs b/src/Spago/Command/Repl.purs index 8f0a56350..68df68ce5 100644 --- a/src/Spago/Command/Repl.purs +++ b/src/Spago/Command/Repl.purs @@ -20,6 +20,7 @@ type ReplEnv a = , supportPackage :: PackageMap , depsOnly :: Boolean , logOptions :: LogOptions + , rootPath :: RootPath , pursArgs :: Array String , selected :: NonEmptyArray WorkspacePackage | a @@ -27,17 +28,19 @@ type ReplEnv a = run :: ∀ a. Spago (ReplEnv a) Unit run = do - { dependencies, pursArgs, selected, depsOnly, supportPackage } <- ask + { rootPath, dependencies, pursArgs, selected, depsOnly, supportPackage } <- ask - unlessM (FS.exists pursReplFile.name) $ - FS.writeTextFile pursReplFile.name pursReplFile.content + let replFile = rootPath pursReplFile.name + unlessM (FS.exists replFile) $ + FS.writeTextFile replFile pursReplFile.content let allDependencies = Map.unionWith (\l _ -> l) supportPackage $ Fetch.toAllDependencies dependencies globs = Build.getBuildGlobs - { selected + { rootPath + , selected , dependencies: allDependencies , depsOnly , withTests: true } - void $ Purs.repl globs pursArgs + void $ Purs.repl rootPath globs pursArgs diff --git a/src/Spago/Command/Run.purs b/src/Spago/Command/Run.purs index 1d9bd713b..e033e36d1 100644 --- a/src/Spago/Command/Run.purs +++ b/src/Spago/Command/Run.purs @@ -13,19 +13,20 @@ import Data.Array as Array import Data.Array.NonEmpty as NEA import Data.Map as Map import Node.FS.Perms as Perms -import Node.Path as Path import Registry.Version as Version import Spago.Cmd as Cmd import Spago.Command.Build as Build import Spago.Command.Fetch as Fetch import Spago.Config (Workspace, WorkspacePackage) import Spago.FS as FS +import Spago.Path as Path import Spago.Paths as Paths import Spago.Purs (Purs, ModuleGraph(..)) import Spago.Purs as Purs type RunEnv a = { logOptions :: LogOptions + , rootPath :: RootPath , workspace :: Workspace , runOptions :: RunOptions , selected :: WorkspacePackage @@ -38,16 +39,16 @@ type RunEnv a = type RunOptions = { execArgs :: Array String , moduleName :: String - , executeDir :: FilePath + , executeDir :: GlobalPath , successMessage :: Maybe String , failureMessage :: String } -type Node = { cmd :: String, version :: Version } +type Node = { cmd :: GlobalPath, version :: Version } nodeVersion :: forall a. Spago (LogEnv a) Version nodeVersion = - Cmd.exec "node" [ "--version" ] Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } >>= case _ of + Cmd.exec (Path.global "node") [ "--version" ] Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } >>= case _ of Left r -> do logDebug $ Cmd.printExecResult r die [ "Failed to find node. Have you installed it, and is it in your PATH?" ] @@ -62,30 +63,30 @@ nodeVersion = getNode :: forall a. Spago (LogEnv a) Node getNode = do version <- nodeVersion - pure { cmd: "node", version } + pure { cmd: Path.global "node", version } run :: forall a. Spago (RunEnv a) Unit run = do - { workspace, node, runOptions: opts, dependencies, selected } <- ask + { workspace, node, runOptions: opts, dependencies, selected, rootPath } <- ask let execOptions = Cmd.defaultExecOptions { pipeStdin = Cmd.StdinPipeParent } case workspace.backend of Nothing -> do logDebug "Running with backend: nodejs" - let runDir = Path.concat [ Paths.localCachePath, "run" ] + let runDir = rootPath Paths.localCachePath "run" FS.mkdirp runDir - absOutput <- liftEffect $ Path.resolve [] $ fromMaybe "output" workspace.buildOptions.output + absOutput <- liftEffect $ Path.toAbsolute $ fromMaybe (rootPath "output") workspace.buildOptions.output let - runJsPath = Path.concat [ runDir, "run.js" ] - packageJsonPath = Path.concat [ runDir, "package.json" ] + runJsPath = runDir "run.js" + packageJsonPath = runDir "package.json" packageJsonContents = "{\"type\":\"module\" }" - nodeArgs = [ runJsPath ] <> opts.execArgs + nodeArgs = [ Path.toRaw runJsPath ] <> opts.execArgs nodeContents = Array.fold [ "import { main } from 'file://" - , withForwardSlashes absOutput + , Path.toRaw (withForwardSlashes absOutput) , "/" , opts.moduleName , "/" @@ -97,7 +98,8 @@ run = do -- We check that the module we're about to run is included in the build and spit out a nice error if it isn't (see #383) let globs = Build.getBuildGlobs - { dependencies: Fetch.toAllDependencies dependencies + { rootPath + , dependencies: Fetch.toAllDependencies dependencies , depsOnly: false -- Here we include tests as well, because we use this code for `spago run` and `spago test` , withTests: true @@ -126,7 +128,7 @@ run = do Just backend -> do let args = [ "--run", opts.moduleName <> ".main" ] <> opts.execArgs logDebug $ "Running command `" <> backend.cmd <> " " <> show args <> "`" - Cmd.exec backend.cmd args execOptions >>= case _ of + Cmd.exec (Path.global backend.cmd) args execOptions >>= case _ of Right _ -> case opts.successMessage of Just m -> logSuccess m Nothing -> pure unit diff --git a/src/Spago/Command/Script.purs b/src/Spago/Command/Script.purs deleted file mode 100644 index 9d579a2b4..000000000 --- a/src/Spago/Command/Script.purs +++ /dev/null @@ -1 +0,0 @@ -module Spago.Command.Script where diff --git a/src/Spago/Command/Sources.purs b/src/Spago/Command/Sources.purs index 8ad3474f0..3324fd1a3 100644 --- a/src/Spago/Command/Sources.purs +++ b/src/Spago/Command/Sources.purs @@ -10,12 +10,13 @@ import Spago.Command.Fetch (FetchEnv) import Spago.Command.Fetch as Fetch import Spago.Config (Package(..), WithTestGlobs(..)) import Spago.Config as Config +import Spago.Path as Path type SourcesOpts = { json :: Boolean } run :: forall a. SourcesOpts -> Spago (FetchEnv a) Unit run { json } = do - { workspace } <- ask + { workspace, rootPath } <- ask -- lookup the dependencies in the package set, so we get their version numbers let selectedPackages = case workspace.selected of @@ -30,9 +31,9 @@ run { json } = do let globs = Array.foldMap - (\(Tuple packageName package) -> Config.sourceGlob WithTestGlobs packageName package) + (\(Tuple packageName package) -> Config.sourceGlob rootPath WithTestGlobs packageName package) (Map.toUnfoldable transitivePackages :: Array (Tuple PackageName Package)) output case json of - true -> OutputJson (CJ.array CJ.string) globs - false -> OutputLines globs + true -> OutputJson (CJ.array $ Path.localPathCodec rootPath) globs + false -> OutputLines $ Path.localPart <$> globs diff --git a/src/Spago/Command/Test.purs b/src/Spago/Command/Test.purs index 11df0d0ff..a63538077 100644 --- a/src/Spago/Command/Test.purs +++ b/src/Spago/Command/Test.purs @@ -7,11 +7,12 @@ import Spago.Command.Fetch as Fetch import Spago.Command.Run (Node) import Spago.Command.Run as Run import Spago.Config (Workspace, WorkspacePackage) -import Spago.Paths as Paths +import Spago.Path as Path import Spago.Purs (Purs) type TestEnv a = { logOptions :: LogOptions + , rootPath :: RootPath , workspace :: Workspace , selectedPackages :: NonEmptyArray SelectedTest , dependencies :: Fetch.PackageTransitiveDeps @@ -28,7 +29,7 @@ type SelectedTest = run :: forall a. Spago (TestEnv a) Unit run = do - { workspace, logOptions, node, selectedPackages, dependencies, purs } <- ask + { workspace, logOptions, rootPath, node, selectedPackages, dependencies, purs } <- ask void $ for selectedPackages \{ execArgs, moduleName, selected } -> do let @@ -36,12 +37,12 @@ run = do runOptions = { successMessage: Just $ "Test succeeded for package \"" <> PackageName.print name <> "\"." , failureMessage: "Tests failed for package \"" <> PackageName.print name <> "\"." - , executeDir: Paths.cwd + , executeDir: Path.toGlobal rootPath , execArgs , moduleName } - runEnv = { logOptions, workspace, selected, node, runOptions, dependencies, purs } + runEnv = { logOptions, rootPath, workspace, selected, node, runOptions, dependencies, purs } logInfo $ "Running tests for package: " <> PackageName.print name runSpago runEnv Run.run diff --git a/src/Spago/Command/Uninstall.purs b/src/Spago/Command/Uninstall.purs index d07c4c1fd..e8ab8c240 100644 --- a/src/Spago/Command/Uninstall.purs +++ b/src/Spago/Command/Uninstall.purs @@ -12,7 +12,6 @@ import Data.Newtype (wrap) import Data.Set as Set import Data.Set.NonEmpty as NonEmptySet import Data.String as String -import Node.Path as Path import Node.Process as Process import Registry.PackageName as PackageName import Spago.Command.Fetch (FetchEnv) @@ -21,21 +20,25 @@ import Spago.Config (BuildType(..), Dependencies, Package(..), PackageConfig) import Spago.Config as Config import Spago.Config as Core import Spago.FS as FS +import Spago.Path as Path type UninstallArgs = { dependenciesToRemove :: Set PackageName , testDeps :: Boolean } -run :: UninstallArgs -> Spago (FetchEnv _) Unit +run :: ∀ a. UninstallArgs -> Spago (FetchEnv a) Unit run args = do logDebug "Running `spago uninstall`" - { workspace } <- ask + { workspace, rootPath } <- ask - { sourceOrTestString, deps, configPath, yamlDoc, name } <- case workspace.selected, workspace.rootPackage of - Just p, _ -> toContext (Path.concat [ p.path, "spago.yaml" ]) p.doc p.package - Nothing, Just rootPackage -> toContext "spago.yaml" workspace.doc rootPackage + { sourceOrTestString, deps, configPath, yamlDoc: doc', name } <- case workspace.selected, workspace.rootPackage of + Just p, _ -> toContext (p.path "spago.yaml") p.doc p.package + Nothing, Just rootPackage -> toContext (rootPath "spago.yaml") workspace.doc rootPackage Nothing, Nothing -> die "No package was selected. Please select a package." + + yamlDoc <- justOrDieWith doc' Config.configDocMissingErrorMessage + let { warn, removed: removedSet } = separate deps warnAbout = NEA.fromFoldable warn @@ -89,7 +92,7 @@ run args = do $ Config.getWorkspacePackages workspace.packageSet Fetch.writeNewLockfile reason dependencies - toContext :: FilePath -> YamlDoc Core.Config -> PackageConfig -> Spago _ (_ _) + toContext :: LocalPath -> Maybe (YamlDoc Core.Config) -> PackageConfig -> Spago _ (_ _) toContext configPath yamlDoc pkgConfig = case args.testDeps of true -> case pkgConfig.test of Nothing -> do @@ -118,10 +121,10 @@ run args = do true -> acc { removed = Set.insert next acc.removed } false -> acc { warn = Set.insert next acc.warn } - modifyConfig :: FilePath -> YamlDoc Core.Config -> String -> NonEmptyArray PackageName -> Spago (FetchEnv _) Unit + modifyConfig :: LocalPath -> YamlDoc Core.Config -> String -> NonEmptyArray PackageName -> Spago (FetchEnv _) Unit modifyConfig configPath yamlDoc sourceOrTestString = \removedPackages -> do logInfo $ "Removing the following " <> sourceOrTestString <> " dependencies: " <> (String.joinWith ", " $ map PackageName.print $ Array.fromFoldable removedPackages) - logDebug $ "Editing config file at path: " <> configPath + logDebug $ "Editing config file at path: " <> Path.quote configPath liftEffect $ Config.removePackagesFromConfig yamlDoc args.testDeps $ NonEmptySet.fromFoldable1 removedPackages liftAff $ FS.writeYamlDocFile configPath yamlDoc diff --git a/src/Spago/Command/Upgrade.purs b/src/Spago/Command/Upgrade.purs index d5000e148..19631654c 100644 --- a/src/Spago/Command/Upgrade.purs +++ b/src/Spago/Command/Upgrade.purs @@ -14,7 +14,9 @@ type UpgradeArgs = run :: ∀ a. UpgradeArgs -> Spago (FetchEnv a) Unit run args = do - { workspace } <- ask + { workspace, rootPath } <- ask + doc <- justOrDieWith workspace.doc Config.configDocMissingErrorMessage + case workspace.workspaceConfig.packageSet of Just (Core.SetFromRegistry { registry: currentPackageSet }) -> do latestPackageSet <- Registry.findPackageSet args.setVersion @@ -25,7 +27,7 @@ run args = do true -> logSuccess $ "Nothing to upgrade, you already have the " <> whichVersion <> " package set." false -> do logInfo $ "Upgrading the package set to the " <> whichVersion <> " version: " <> Version.print latestPackageSet - Config.setPackageSetVersionInConfig workspace.doc latestPackageSet + Config.setPackageSetVersionInConfig rootPath doc latestPackageSet logSuccess "Upgrade successful!" Just _ -> die "This command is not yet implemented for projects using a custom package set." Nothing -> die "This command is not yet implemented for projects using a solver. See https://github.com/purescript/spago/issues/1001" diff --git a/src/Spago/Config.purs b/src/Spago/Config.purs index 0f510f0fa..f2e5117ae 100644 --- a/src/Spago/Config.purs +++ b/src/Spago/Config.purs @@ -1,27 +1,29 @@ module Spago.Config ( BuildType(..) , Package(..) - , PackageSet(..) , PackageMap + , PackageSet(..) , WithTestGlobs(..) , Workspace , WorkspaceBuildOptions , WorkspacePackage , addPackagesToConfig , addRangesToConfig - , removePackagesFromConfig - , rootPackageToWorkspacePackage - , getPackageLocation + , configDocMissingErrorMessage , fileSystemCharEscape - , getWorkspacePackages + , getLocalPackageLocation , getTopologicallySortedWorkspacePackages + , getWorkspacePackages , module Core + , readConfig , readWorkspace - , sourceGlob + , removePackagesFromConfig + , rootPackageToWorkspacePackage , setPackageSetVersionInConfig + , sourceGlob , workspacePackageToLockfilePackage - , readConfig - ) where + ) + where import Spago.Prelude @@ -45,13 +47,13 @@ import Data.Profunctor as Profunctor import Data.Set as Set import Data.Set.NonEmpty (NonEmptySet) import Data.Set.NonEmpty as NonEmptySet -import Data.String (CodePoint, Pattern(..)) +import Data.String (CodePoint) import Data.String as String import Dodo as Log import Effect.Aff as Aff import Effect.Uncurried (EffectFn2, EffectFn3, runEffectFn2, runEffectFn3) import Foreign.Object as Foreign -import Node.Path as Path +import Node.Path as Node.Path import Registry.Internal.Codec as Internal.Codec import Registry.PackageName as PackageName import Registry.PackageSet as Registry.PackageSet @@ -63,6 +65,7 @@ import Spago.Glob as Glob import Spago.Json as Json import Spago.Lock (Lockfile, PackageSetInfo) import Spago.Lock as Lock +import Spago.Path as Path import Spago.Paths as Paths import Spago.Registry as Registry import Spago.Yaml as Yaml @@ -73,13 +76,13 @@ type Workspace = , compatibleCompiler :: Range , backend :: Maybe Core.BackendConfig , buildOptions :: WorkspaceBuildOptions - , doc :: YamlDoc Core.Config + , doc :: Maybe (YamlDoc Core.Config) , workspaceConfig :: Core.WorkspaceConfig , rootPackage :: Maybe Core.PackageConfig } type WorkspaceBuildOptions = - { output :: Maybe FilePath + { output :: Maybe LocalPath , censorLibWarnings :: Maybe Core.CensorBuildWarnings , statVerbosity :: Maybe Core.StatVerbosity } @@ -139,9 +142,9 @@ type PackageSet = } type WorkspacePackage = - { path :: FilePath + { path :: LocalPath , package :: Core.PackageConfig - , doc :: YamlDoc Core.Config + , doc :: Maybe (YamlDoc Core.Config) , hasTests :: Boolean } @@ -154,8 +157,8 @@ data Package type ReadWorkspaceConfigResult = { config :: ReadConfigResult , hasTests :: Boolean - , configPath :: FilePath - , packagePath :: FilePath + , configPath :: LocalPath + , packagePath :: LocalPath } type ReadWorkspaceOptions = @@ -166,23 +169,26 @@ type ReadWorkspaceOptions = -- | Reads all the configurations in the tree and builds up the Map of local -- | packages to be integrated in the package set -readWorkspace :: ∀ a. ReadWorkspaceOptions -> Spago (Registry.RegistryEnv a) Workspace +readWorkspace :: ∀ a. ReadWorkspaceOptions -> Spago (Registry.RegistryEnv ( rootPath :: RootPath | a )) Workspace readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do + { rootPath } <- ask logInfo "Reading Spago workspace configuration..." let - doMigrateConfig :: FilePath -> _ -> Spago (Registry.RegistryEnv _) Unit + doMigrateConfig :: ∀ path. Path.IsPath path => path -> _ -> Spago (Registry.RegistryEnv _) Unit doMigrateConfig path config = do case migrateConfig, config.wasMigrated of true, true -> do - logInfo $ "Migrating your " <> path <> " to the latest version..." + logInfo $ "Migrating your " <> Path.quote path <> " to the latest version..." liftAff $ FS.writeYamlDocFile path config.doc - false, true -> logWarn $ "Your " <> path <> " is using an outdated format. Run Spago with the --migrate flag to update it to the latest version." + false, true -> logWarn $ "Your " <> Path.quote path <> " is using an outdated format. Run Spago with the --migrate flag to update it to the latest version." _, false -> pure unit + rootConfigPath = rootPath "spago.yaml" + -- First try to read the config in the root. It _has_ to contain a workspace -- configuration, or we fail early. - { workspace, package: maybePackage, workspaceDoc } <- readConfig "spago.yaml" >>= case _ of + { workspace, package: maybePackage, workspaceDoc } <- readConfig rootConfigPath >>= case _ of Left errLines -> die [ toDoc "Couldn't parse Spago config, error:" @@ -196,64 +202,72 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do , "See the relevant documentation here: https://github.com/purescript/spago#the-workspace" ] Right config@{ yaml: { workspace: Just workspace, package }, doc } -> do - doMigrateConfig "spago.yaml" config + doMigrateConfig (rootPath "spago.yaml") config pure { workspace, package, workspaceDoc: doc } logDebug "Gathering all the spago configs in the tree..." - otherConfigPaths <- liftAff $ Glob.gitignoringGlob Paths.cwd [ "**/spago.yaml" ] + otherConfigPaths <- liftAff $ Glob.gitignoringGlob rootPath [ "**/spago.yaml" ] <#> Array.delete rootConfigPath unless (Array.null otherConfigPaths) do - logDebug $ [ toDoc "Found packages at these paths:", Log.indent $ Log.lines (map toDoc otherConfigPaths) ] + logDebug $ [ toDoc "Found packages at these paths:", Log.indent $ Log.lines (map (toDoc <<< Path.quote) otherConfigPaths) ] -- We read all of them in, and only read the package section, if any. let - readWorkspaceConfig :: FilePath -> Spago (Registry.RegistryEnv _) (Either Docc ReadWorkspaceConfigResult) + readWorkspaceConfig :: LocalPath -> Spago (Registry.RegistryEnv _) (Either Docc ReadWorkspaceConfigResult) readWorkspaceConfig path = do maybeConfig <- readConfig path -- We try to figure out if this package has tests - look for test sources - hasTests <- FS.exists (Path.concat [ Path.dirname path, "test" ]) + hasTests <- FS.exists (Path.dirname path "test") pure $ case maybeConfig of Left eLines -> Left $ toDoc - [ toDoc $ "Could not read config at path " <> path + [ toDoc $ "Could not read config at path " <> Path.quote path , toDoc "Error was: " , indent $ toDoc eLines ] - Right config -> do - Right { config, hasTests, configPath: path, packagePath: Path.dirname path } + Right config -> Right + { config + , hasTests + , configPath: path + , packagePath: Path.dirname path `Path.relativeTo` rootPath + } { right: otherPackages, left: failedPackages } <- partitionMap identity <$> traverse readWorkspaceConfig otherConfigPaths unless (Array.null failedPackages) do logWarn $ [ toDoc "Failed to read some configs:" ] <> failedPackages + -- We prune any configs that use a different workspace. -- For reasoning, see https://github.com/purescript/spago/issues/951 let configPathsWithWorkspaces = otherPackages # Array.mapMaybe \readResult -> readResult.packagePath <$ readResult.config.yaml.workspace unless (Array.null configPathsWithWorkspaces) do - logDebug $ "Found these paths with workspaces: " <> show configPathsWithWorkspaces + logDebug $ "Found these paths with workspaces: " <> String.joinWith ", " (Path.quote <$> configPathsWithWorkspaces) { right: configsNoWorkspaces, left: prunedConfigs } <- let fn { left, right } readResult@{ configPath, packagePath, hasTests, config } = do - if Array.any (\p -> isJust $ String.stripPrefix (Pattern p) packagePath) configPathsWithWorkspaces then + if Array.any (_ `Path.isPrefixOf` packagePath) configPathsWithWorkspaces then pure { right, left: Array.cons packagePath left } else case readResult.config.yaml.package of - Nothing -> pure { right, left: Array.cons packagePath left } + Nothing -> + pure { right, left: Array.cons packagePath left } Just package -> do -- Note: we migrate configs only at this point - this is because we read a whole lot of them but we are -- supposed to ignore any subtrees that contain a different workspace, and those we don't want to migrate doMigrateConfig configPath config -- We store the path of the package, so we can treat it basically as a LocalPackage - pure { left, right: Array.cons (Tuple package.name { package, hasTests, path: packagePath, doc: config.doc }) right } + pure { left, right: Array.cons (Tuple package.name { package, hasTests, path: packagePath, doc: Just config.doc }) right } in Array.foldM fn { right: [], left: [] } otherPackages unless (Array.null prunedConfigs) do - logDebug $ [ "Excluding configs that use a different workspace (directly or implicitly via parent directory's config):" ] <> Array.sort prunedConfigs + logDebug $ + [ "Excluding configs that use a different workspace (directly or implicitly via parent directory's config):" ] + <> Array.sort (Path.quote <$> prunedConfigs) rootPackage <- case maybePackage of Nothing -> pure [] Just rootPackage -> do - rootPackage' <- rootPackageToWorkspacePackage { rootPackage, workspaceDoc } + rootPackage' <- rootPackageToWorkspacePackage rootPath { rootPackage, workspaceDoc } pure [ Tuple rootPackage.name rootPackage' ] let workspacePackages = Map.fromFoldable $ configsNoWorkspaces <> rootPackage @@ -264,7 +278,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do Nothing -> die "No valid packages found in the current project, halting." -- If there's only one package and it's not in the root we still select that Just { head: (Tuple packageName package), tail: [] } -> do - logDebug $ "Selecting package " <> PackageName.print packageName <> " from " <> package.path + logDebug $ "Selecting package " <> PackageName.print packageName <> " from " <> Path.quote package.path pure (Just package) -- If no package has been selected and we have many packages, then we build all of them but select none _ -> pure Nothing @@ -274,7 +288,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do <> case (Array.fromFoldable $ Map.keys workspacePackages) of [] -> [ toDoc "No available packages." ] - pkgs -> + pkgs -> case typoSuggestions PackageName.print name pkgs of [] -> [ toDoc "All available packages:", indent (toDoc pkgs) ] suggestions -> [ toDoc "Did you mean:", indent (toDoc suggestions) ] @@ -282,9 +296,10 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do pure (Just p) logDebug "Parsing the lockfile..." - maybeLockfileContents <- FS.exists "spago.lock" >>= case _ of + let lockFilePath = rootPath "spago.lock" + maybeLockfileContents <- FS.exists lockFilePath >>= case _ of false -> pure (Left "No lockfile found") - true -> liftAff (FS.readJsonFile Lock.lockfileCodec "spago.lock") >>= case _ of + true -> liftAff (FS.readJsonFile Lock.lockfileCodec lockFilePath) >>= case _ of Left error -> do logWarn [ "Your project contains a spago.lock file, but it cannot be decoded. Spago will generate a new one." @@ -332,7 +347,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do Left reason, Just address@(Core.SetFromPath { path }) -> do logDebug reason logDebug $ "Reading the package set from local path: " <> path - liftAff (FS.readJsonFile remotePackageSetCodec path) >>= case _ of + liftAff (FS.readJsonFile remotePackageSetCodec (rootPath path)) >>= case _ of Left err -> die $ "Couldn't read the package set: " <> err Right (RemotePackageSet localPackageSet) -> do logInfo "Read the package set from local path" @@ -413,7 +428,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do case maybeSelected of Just selected -> do logSuccess $ "Selecting package to build: " <> PackageName.print selected.package.name - logDebug $ "Package path: " <> selected.path + logDebug $ "Package path: " <> Path.quote selected.path Nothing -> do logSuccess [ toDoc $ "Selecting " <> show (Map.size workspacePackages) <> " packages to build:" @@ -423,7 +438,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do let buildOptions :: WorkspaceBuildOptions buildOptions = - { output: _.output =<< workspace.buildOpts + { output: workspace.buildOpts >>= _.output <#> \o -> withForwardSlashes $ rootPath o , censorLibWarnings: _.censorLibraryWarnings =<< workspace.buildOpts , statVerbosity: _.statVerbosity =<< workspace.buildOpts } @@ -434,7 +449,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do , compatibleCompiler: fromMaybe Core.widestRange $ map _.compiler packageSetInfo , backend: workspace.backend , buildOptions - , doc: workspaceDoc + , doc: Just workspaceDoc , workspaceConfig: workspace , rootPackage: maybePackage } @@ -442,15 +457,18 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do rootPackageToWorkspacePackage :: forall m . MonadEffect m - => { rootPackage :: Core.PackageConfig, workspaceDoc :: YamlDoc Core.Config } + => RootPath + -> { rootPackage :: Core.PackageConfig, workspaceDoc :: YamlDoc Core.Config } -> m WorkspacePackage -rootPackageToWorkspacePackage { rootPackage, workspaceDoc } = do - hasTests <- liftEffect $ FS.exists "test" - pure { path: "./", doc: workspaceDoc, package: rootPackage, hasTests } +rootPackageToWorkspacePackage rootPath { rootPackage, workspaceDoc } = do + hasTests <- liftEffect $ FS.exists (rootPath "test") + pure { path: rootPath "", doc: Just workspaceDoc, package: rootPackage, hasTests } workspacePackageToLockfilePackage :: WorkspacePackage -> Tuple PackageName Lock.WorkspaceLockPackage workspacePackageToLockfilePackage { path, package } = Tuple package.name - { path: withForwardSlashes path + { path: case Path.localPart (withForwardSlashes path) of + "" -> "./" + p -> p , core: { dependencies: package.dependencies, build_plan: mempty } , test: { dependencies: foldMap _.dependencies package.test, build_plan: mempty } } @@ -472,11 +490,11 @@ shouldComputeNewLockfile { workspace, workspacePackages } workspaceLock = where eraseBuildPlan = _ { core { build_plan = mempty }, test { build_plan = mempty } } -getPackageLocation :: PackageName -> Package -> FilePath -getPackageLocation name = Paths.mkRelative <<< case _ of - RegistryVersion v -> Path.concat [ Paths.localCachePackagesPath, PackageName.print name <> "-" <> Version.print v ] - GitPackage p -> Path.concat [ Paths.localCachePackagesPath, PackageName.print name, fileSystemCharEscape p.ref ] - LocalPackage p -> p.path +getLocalPackageLocation :: RootPath -> PackageName -> Package -> LocalPath +getLocalPackageLocation root name = case _ of + RegistryVersion v -> root Paths.localCachePackagesPath (PackageName.print name <> "-" <> Version.print v) + GitPackage p -> root Paths.localCachePackagesPath PackageName.print name fileSystemCharEscape p.ref + LocalPackage p -> root p.path WorkspacePackage { path } -> path -- This function must be injective and must always produce valid directory @@ -505,8 +523,8 @@ data WithTestGlobs | NoTestGlobs | OnlyTestGlobs -sourceGlob :: WithTestGlobs -> PackageName -> Package -> Array String -sourceGlob withTestGlobs name package = map (\p -> Path.concat [ getPackageLocation name package, p ]) +sourceGlob :: RootPath -> WithTestGlobs -> PackageName -> Package -> Array LocalPath +sourceGlob root withTestGlobs name package = map (\p -> getLocalPackageLocation root name package p) case package of WorkspacePackage { hasTests } -> case hasTests, withTestGlobs of @@ -515,7 +533,7 @@ sourceGlob withTestGlobs name package = map (\p -> Path.concat [ getPackageLocat true, OnlyTestGlobs -> [ testGlob ] true, NoTestGlobs -> [ srcGlob ] true, WithTestGlobs -> [ srcGlob, testGlob ] - GitPackage { subdir: Just s } -> [ Path.concat [ s, srcGlob ] ] + GitPackage { subdir: Just s } -> [ Node.Path.concat [ s, srcGlob ] ] _ -> [ srcGlob ] srcGlob :: String @@ -553,32 +571,32 @@ getTopologicallySortedWorkspacePackages packageSet = do type ReadConfigResult = { doc :: YamlDoc Core.Config, yaml :: Core.Config, wasMigrated :: Boolean } -readConfig :: forall a. FilePath -> Spago (LogEnv a) (Either (Array String) ReadConfigResult) +readConfig :: ∀ a path. Path.IsPath path => path -> Spago (SpagoBaseEnv a) (Either (Array String) ReadConfigResult) readConfig path = do - logDebug $ "Reading config from " <> path + logDebug $ "Reading config from " <> Path.quote path try (FS.readTextFile path) >>= case _ of Left err -> do - logDebug $ "Could not read file " <> path <> ", error: " <> Aff.message err - let replaceExt = map (_ <> ".yml") <<< String.stripSuffix (String.Pattern ".yaml") - yml <- map join $ for (replaceExt path) \yml -> do + logDebug $ "Could not read file " <> Path.quote path <> ", error: " <> Aff.message err + let altConfigName = Path.replaceExtension (String.Pattern ".yaml") (String.Replacement ".yml") path + yml <- map join $ for altConfigName \yml -> do hasYml <- FS.exists yml pure $ if hasYml then Just yml else Nothing - pure $ Left $ case path, yml of + pure $ Left $ case Path.basename path, yml of "spago.yaml", Nothing -> - [ "Did not find `" <> path <> "`. Run `spago init` to initialize a new project." ] + [ "Did not find " <> Path.quote path <> ". Run `spago init` to initialize a new project." ] "spago.yaml", Just y -> - [ "Did not find `" <> path <> "`. Spago's configuration files must end with `.yaml`, not `.yml`." - , "Try renaming `" <> y <> "` to `" <> path <> "` or run `spago init` to initialize a new project." + [ "Did not find " <> Path.quote path <> ". Spago's configuration files must end with `.yaml`, not `.yml`." + , "Try renaming " <> Path.quote y <> " to " <> Path.quote path <> " or run `spago init` to initialize a new project." ] _, Nothing -> - [ "Did not find `" <> path <> "`." ] + [ "Did not find " <> Path.quote path <> "." ] _, Just y -> - [ "Did not find `" <> path <> "`. Spago's configuration files must end with `.yaml`, not `.yml`." - , "Try renaming `" <> y <> "` to `" <> path <> "`." + [ "Did not find " <> Path.quote path <> ". Spago's configuration files must end with `.yaml`, not `.yml`." + , "Try renaming " <> Path.quote y <> " to " <> Path.quote path <> "." ] Right yamlString -> do case lmap (\err -> CJ.DecodeError.basic ("YAML: " <> err)) (Yaml.parser yamlString) of @@ -600,12 +618,12 @@ readConfig path = do (\yaml -> { doc, yaml, wasMigrated: isJust maybeMigratedDoc }) (CJ.decode Core.configCodec (Yaml.toJson $ fromMaybe doc maybeMigratedDoc)) -setPackageSetVersionInConfig :: forall m. MonadAff m => MonadEffect m => YamlDoc Core.Config -> Version -> m Unit -setPackageSetVersionInConfig doc version = do +setPackageSetVersionInConfig :: forall m. MonadAff m => MonadEffect m => RootPath -> YamlDoc Core.Config -> Version -> m Unit +setPackageSetVersionInConfig root doc version = do liftEffect $ runEffectFn2 setPackageSetVersionInConfigImpl doc (Version.print version) - liftAff $ FS.writeYamlDocFile "spago.yaml" doc + liftAff $ FS.writeYamlDocFile (root "spago.yaml") doc -addPackagesToConfig :: forall m. MonadAff m => FilePath -> YamlDoc Core.Config -> Boolean -> Array PackageName -> m Unit +addPackagesToConfig :: forall m path. Path.IsPath path => MonadAff m => path -> YamlDoc Core.Config -> Boolean -> Array PackageName -> m Unit addPackagesToConfig configPath doc isTest pkgs = do liftEffect $ runEffectFn3 addPackagesToConfigImpl doc isTest (map PackageName.print pkgs) liftAff $ FS.writeYamlDocFile configPath doc @@ -619,6 +637,12 @@ addRangesToConfig doc = runEffectFn2 addRangesToConfigImpl doc <<< map (\(Tuple name range) -> Tuple (PackageName.print name) (Core.printSpagoRange range)) <<< (Map.toUnfoldable :: Map _ _ -> Array _) +configDocMissingErrorMessage :: String +configDocMissingErrorMessage = Array.fold + [ "This operation requires a YAML config document, but none was found in the environment. " + , "This is an internal error. Please open an issue at https://github.com/purescript/spago/issues" + ] + foreign import setPackageSetVersionInConfigImpl :: EffectFn2 (YamlDoc Core.Config) String Unit foreign import addPackagesToConfigImpl :: EffectFn3 (YamlDoc Core.Config) Boolean (Array String) Unit foreign import removePackagesFromConfigImpl :: EffectFn3 (YamlDoc Core.Config) Boolean (PackageName -> Boolean) Unit diff --git a/src/Spago/Db.purs b/src/Spago/Db.purs index 874df03ce..979ad6aaa 100644 --- a/src/Spago/Db.purs +++ b/src/Spago/Db.purs @@ -47,7 +47,7 @@ import Registry.Version as Version -- API type ConnectOptions = - { database :: FilePath + { database :: GlobalPath , logger :: String -> Effect Unit } @@ -136,7 +136,7 @@ insertMetadata db packageName metadata@(Metadata { unpublished }) = do -------------------------------------------------------------------------------- -- Table types and conversions --- Note: bump `Paths.databaseVersion` every time we change the database schema in a breaking way +-- Note: bump `Path.databaseVersion` every time we change the database schema in a breaking way data Db type PackageSetJs = @@ -232,7 +232,7 @@ packageSetCodec = CJ.named "PackageSet" $ CJ.Record.object -------------------------------------------------------------------------------- -- FFI -foreign import connectImpl :: EffectFn2 FilePath (EffectFn1 String Unit) Db +foreign import connectImpl :: EffectFn2 GlobalPath (EffectFn1 String Unit) Db foreign import insertPackageSetImpl :: EffectFn2 Db PackageSetJs Unit diff --git a/src/Spago/Esbuild.purs b/src/Spago/Esbuild.purs index e158ce85a..ca0a93244 100644 --- a/src/Spago/Esbuild.purs +++ b/src/Spago/Esbuild.purs @@ -5,7 +5,7 @@ import Spago.Prelude import Spago.Cmd as Cmd type Esbuild = - { cmd :: FilePath + { cmd :: GlobalPath , version :: String } diff --git a/src/Spago/Git.purs b/src/Spago/Git.purs index 713929328..9287ddc47 100644 --- a/src/Spago/Git.purs +++ b/src/Spago/Git.purs @@ -9,7 +9,6 @@ module Spago.Git , checkout , fetch , getRefType - , isIgnored , listTags , parseRemote , pushTag @@ -26,32 +25,31 @@ import Data.Maybe (fromJust) import Data.String (Pattern(..)) import Data.String as String import Data.String.Regex as Regex -import Node.ChildProcess.Types (Exit(..)) -import Node.Path as Path -import Node.Process as Process import Partial.Unsafe (unsafePartial) import Registry.Version as Version import Spago.Cmd as Cmd import Spago.FS as FS +import Spago.Path as Path -type Git = { cmd :: String, version :: String } +type Git = { cmd :: GlobalPath, version :: String } type GitEnv a = { git :: Git, logOptions :: LogOptions, offline :: OnlineStatus | a } type Remote = { name :: String, url :: String, owner :: String, repo :: String } -runGit_ :: forall a. Array String -> Maybe FilePath -> ExceptT String (Spago (GitEnv a)) Unit +runGit_ :: ∀ a. Array String -> Maybe GlobalPath -> ExceptT String (Spago (GitEnv a)) Unit runGit_ args cwd = void $ runGit args cwd -runGit :: forall a. Array String -> Maybe FilePath -> ExceptT String (Spago (GitEnv a)) String +runGit :: ∀ a. Array String -> Maybe GlobalPath -> ExceptT String (Spago (GitEnv a)) String runGit args cwd = ExceptT do { git } <- ask - result <- Cmd.exec git.cmd args (Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd }) + result <- Cmd.exec git.cmd args + (Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Path.toGlobal <$> cwd }) pure case result of Right r -> Right r.stdout Left r -> Left r.stderr -fetchRepo :: ∀ a b. { git :: String, ref :: String | a } -> FilePath -> Spago (GitEnv b) (Either (Array String) Unit) +fetchRepo :: ∀ a b path. Path.IsPath path => { git :: String, ref :: String | a } -> path -> Spago (GitEnv b) (Either (Array String) Unit) fetchRepo { git, ref } path = do repoExists <- FS.exists path { offline } <- ask @@ -64,16 +62,16 @@ fetchRepo { git, ref } path = do cloneOrFetchResult <- case repoExists of true -> do logDebug $ "Found " <> git <> " locally, pulling..." - Except.runExceptT $ runGit_ [ "fetch", "origin" ] (Just path) + Except.runExceptT $ runGit_ [ "fetch", "origin" ] (Just $ Path.toGlobal path) false -> do logInfo $ "Cloning " <> git -- For the reasoning on the filter options, see: -- https://github.com/purescript/spago/issues/701#issuecomment-1317192919 - Except.runExceptT $ runGit_ [ "clone", "--filter=tree:0", git, path ] Nothing + Except.runExceptT $ runGit_ [ "clone", "--filter=tree:0", git, Path.toRaw path ] Nothing result <- Except.runExceptT do Except.ExceptT $ pure cloneOrFetchResult logDebug $ "Checking out the requested ref for " <> git <> " : " <> ref - _ <- runGit [ "checkout", ref ] (Just path) + _ <- runGit [ "checkout", ref ] (Just $ Path.toGlobal path) -- if we are on a branch and not on a detached head, then we need to pull -- the following command will fail if on a detached head, and succeed if on a branch Except.mapExceptT @@ -81,9 +79,9 @@ fetchRepo { git, ref } path = do Left _err -> pure (Right unit) Right _ -> do logDebug "Pulling the latest changes" - Except.runExceptT $ runGit_ [ "pull", "--rebase", "--autostash" ] (Just path) + Except.runExceptT $ runGit_ [ "pull", "--rebase", "--autostash" ] (Just $ Path.toGlobal path) ) - (runGit_ [ "symbolic-ref", "-q", "HEAD" ] (Just path)) + (runGit_ [ "symbolic-ref", "-q", "HEAD" ] (Just $ Path.toGlobal path)) case result of Left err -> pure $ Left @@ -94,39 +92,39 @@ fetchRepo { git, ref } path = do logDebug $ "Successfully fetched the repo '" <> git <> "' at ref '" <> ref <> "'" pure $ Right unit -checkout :: ∀ a. { repo :: String, ref :: String } -> Spago (GitEnv a) (Either String Unit) -checkout { repo, ref } = Except.runExceptT $ void $ runGit [ "checkout", ref ] (Just repo) +checkout :: ∀ a path. Path.IsPath path => { repo :: path, ref :: String } -> Spago (GitEnv a) (Either String Unit) +checkout { repo, ref } = Except.runExceptT $ void $ runGit [ "checkout", ref ] (Just $ Path.toGlobal repo) -fetch :: ∀ a. { repo :: String, remote :: String } -> Spago (GitEnv a) (Either String Unit) +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 repo) # Except.runExceptT >>= rightOrDie + remoteUrl <- runGit [ "remote", "get-url", remote ] (Just $ Path.toGlobal repo) # Except.runExceptT >>= rightOrDie logInfo $ "Fetching from " <> remoteUrl - Except.runExceptT $ runGit_ [ "fetch", remote, "--tags" ] (Just repo) + Except.runExceptT $ runGit_ [ "fetch", remote, "--tags" ] (Just $ Path.toGlobal repo) -getRefType :: ∀ a. { repo :: String, ref :: String } -> Spago (GitEnv a) (Either String String) -getRefType { repo, ref } = Except.runExceptT $ runGit [ "cat-file", "-t", ref ] (Just 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) -listTags :: forall a. Maybe FilePath -> Spago (GitEnv a) (Either Docc (Array String)) +listTags :: ∀ a path. Path.IsPath path => path -> Spago (GitEnv a) (Either Docc (Array String)) listTags cwd = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git } <- ask Cmd.exec git.cmd [ "tag" ] opts >>= case _ of Left r -> do pure $ Left $ toDoc [ "Could not run `git tag`. Error:", r.message ] Right r -> pure $ Right $ String.split (Pattern "\n") r.stdout -getStatus :: forall a. Maybe FilePath -> Spago (GitEnv a) (Either Docc String) +getStatus :: ∀ a path. Path.IsPath path => path -> Spago (GitEnv a) (Either Docc String) getStatus cwd = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git } <- ask Cmd.exec git.cmd [ "status", "--porcelain" ] opts >>= case _ of Left r -> do pure $ Left $ toDoc [ "Could not run `git status`. Error:", r.message ] Right r -> pure $ Right r.stdout -getRef :: forall a. Maybe FilePath -> Spago (GitEnv a) (Either Docc String) +getRef :: ∀ a path. Path.IsPath path => path -> Spago (GitEnv a) (Either Docc String) getRef cwd = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git } <- ask Cmd.exec git.cmd [ "rev-parse", "HEAD" ] opts >>= case _ of Left r -> pure $ Left $ toDoc @@ -135,9 +133,9 @@ getRef cwd = do ] Right r -> pure $ Right r.stdout -getRemotes :: forall a. Maybe FilePath -> Spago (GitEnv a) (Either Docc (Array Remote)) +getRemotes :: ∀ a @path. Path.IsPath path => path -> Spago (GitEnv a) (Either Docc (Array Remote)) getRemotes = \cwd -> do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git } <- ask Cmd.exec git.cmd [ "remote", "--verbose" ] opts <#> case _ of Left r -> Left $ toDoc @@ -151,17 +149,17 @@ getRemotes = \cwd -> do [] -> Left $ toDoc "Could not parse any remotes from the output of `git remote --verbose`." remotes -> Right $ Array.nub remotes -tagCheckedOut :: forall a. Maybe FilePath -> Spago (GitEnv a) (Either Docc String) +tagCheckedOut :: ∀ a path. Path.IsPath path => path -> Spago (GitEnv a) (Either Docc String) tagCheckedOut cwd = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git } <- ask Cmd.exec git.cmd [ "describe", "--tags", "--exact-match" ] opts >>= case _ of Left _ -> pure $ Left $ toDoc "The git ref currently checked out is not a tag." Right r -> pure $ Right r.stdout -pushTag :: forall a. Maybe FilePath -> Version -> Spago (GitEnv a) (Either Docc Unit) +pushTag :: ∀ a path. Path.IsPath path => path -> Version -> Spago (GitEnv a) (Either Docc Unit) pushTag cwd version = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = cwd } + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } { git, offline } <- ask case offline of Offline -> do @@ -177,46 +175,18 @@ pushTag cwd version = do ] Right _ -> pure $ Right unit --- | Check if the path is ignored by git --- --- `git check-ignore` exits with 1 when path is not ignored, and 128 when --- a fatal error occurs (i.e. when not in a git repository). -isIgnored :: forall a. FilePath -> Spago (GitEnv a) Boolean -isIgnored path = do - { git } <- ask - result <- Cmd.exec git.cmd [ "check-ignore", "--quiet", path ] (Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false }) - case result of - -- Git is successful if it's an ignored file - Right _ -> pure true - -- Git will fail with exitCode 128 if this is not a git repo or if it's dealing with a link. - -- We ignore links - I mean, do we really want to deal with recursive links?!? - Left r - | Normally 128 <- r.exit -> do - -- Sigh. Even if something is behind a link Node will not tell us that, - -- so we need to check all the paths between the cwd and the provided path - -- Just beautiful - paths <- liftEffect do - cwd <- Process.cwd - absolutePath <- Path.resolve [] path - FS.getInBetweenPaths cwd absolutePath - Array.any identity <$> traverse FS.isLink paths - -- Git will fail with 1 when a file is just, like, normally ignored - | Normally 1 <- r.exit -> - pure false - | otherwise -> do - logDebug "IsIgnored encountered an interesting exitCode" - logDebug $ Cmd.printExecResult r - -- We still do not ignore it, just in case - pure false getGit :: forall a. Spago (LogEnv a) Git getGit = do - Cmd.exec "git" [ "--version" ] Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } >>= case _ of - Right r -> pure { cmd: "git", version: r.stdout } + Cmd.exec cmd [ "--version" ] Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } >>= case _ of + Right r -> pure { cmd, version: r.stdout } Left r -> do logDebug $ Cmd.printExecResult r die [ "Failed to find git. Have you installed it, and is it in your PATH?" ] + where + cmd = Path.global "git" + parseRemote :: String -> Maybe Remote parseRemote = \line -> case Regex.split tabOrSpaceRegex line of diff --git a/src/Spago/Glob.purs b/src/Spago/Glob.purs index 29838fcea..8f2ad34ef 100644 --- a/src/Spago/Glob.purs +++ b/src/Spago/Glob.purs @@ -17,20 +17,20 @@ import Data.String as String import Data.String as String.CodePoint import Effect.Aff as Aff import Effect.Ref as Ref -import Node.FS.Sync as SyncFS -import Node.Path as Path +import Spago.FS as FS +import Spago.Path as Path type Glob = { ignore :: Array String , include :: Array String } -foreign import testGlob :: Glob -> String -> Boolean +foreign import testGlob :: Glob -> AdHocFilePath -> Boolean splitGlob :: Glob -> Array Glob splitGlob { ignore, include } = (\a -> { ignore, include: [ a ] }) <$> include -type Entry = { name :: String, path :: String, dirent :: DirEnt } +type Entry = { name :: String, path :: GlobalPath, dirent :: DirEnt } type FsWalkOptions = { entryFilter :: Entry -> Effect Boolean, deepFilter :: Entry -> Effect Boolean } -- https://nodejs.org/api/fs.html#class-fsdirent @@ -58,17 +58,17 @@ foreign import fsWalkImpl -> (forall a b. b -> Either a b) -> (Either Error (Array Entry) -> Effect Unit) -> FsWalkOptions - -> String + -> RootPath -> Effect Unit -gitignoreFileToGlob :: FilePath -> String -> Glob -gitignoreFileToGlob base = +gitignoreFileToGlob :: LocalPath -> String -> Glob +gitignoreFileToGlob root = String.split (String.Pattern "\n") >>> map String.trim - >>> Array.filter (not <<< or [ String.null, isComment ]) + >>> Array.filter (not String.null && not isComment) >>> partitionMap ( \line -> do - let pattern lin = withForwardSlashes $ Path.concat [ base, gitignorePatternToGlobPattern lin ] + let pattern lin = Path.localPart $ withForwardSlashes $ root gitignorePatternToGlobPattern lin case String.stripPrefix (String.Pattern "!") line of Just negated -> Left $ pattern negated Nothing -> Right $ pattern line @@ -89,13 +89,13 @@ gitignoreFileToGlob base = | leadingSlash pattern = dropPrefixSlash pattern <> "/**" | otherwise = "**/" <> pattern <> "/**" -fsWalk :: String -> Array String -> Array String -> Aff (Array Entry) -fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do +fsWalk :: RootPath -> Array String -> Array String -> Aff (Array Entry) +fsWalk root ignorePatterns includePatterns = Aff.makeAff \cb -> do let includeMatcher = testGlob { ignore: [], include: includePatterns } -- Pattern for directories which can be outright ignored. -- This will be updated whenver a .gitignore is found. - ignoreMatcherRef :: Ref (String -> Boolean) <- Ref.new (testGlob { ignore: [], include: ignorePatterns }) + ignoreMatcherRef :: Ref (AdHocFilePath -> Boolean) <- Ref.new (testGlob { ignore: [], include: ignorePatterns }) -- If this Ref contains `true` because this Aff has been canceled, then deepFilter will always return false. canceled <- Ref.new false @@ -105,11 +105,10 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do updateIgnoreMatcherWithGitignore :: Entry -> Effect Unit updateIgnoreMatcherWithGitignore entry = do let - gitignorePath = entry.path -- directory of this .gitignore relative to the directory being globbed - base = Path.relative cwd (Path.dirname gitignorePath) + base = Path.dirname entry.path `Path.relativeTo` root - try (SyncFS.readTextFile UTF8 entry.path) >>= traverse_ \gitignore -> do + try (FS.readTextFileSync entry.path) >>= traverse_ \gitignore -> do let gitignored = testGlob <$> (splitGlob $ gitignoreFileToGlob base gitignore) @@ -175,18 +174,21 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do -- => patternBase is a prefix of relDirPath => the directory matches String.Pattern patternBase `isPrefix` relDirPath + relPath :: Entry -> String + relPath entry = Path.localPart $ withForwardSlashes entry.path `Path.relativeTo` root + -- Should `fsWalk` recurse into this directory? deepFilter :: Entry -> Effect Boolean deepFilter entry = fromMaybe false <$> runMaybeT do isCanceled <- lift $ Ref.read canceled guard $ not isCanceled - let relPath = withForwardSlashes $ Path.relative cwd entry.path shouldIgnore <- lift $ Ref.read ignoreMatcherRef - guard $ not $ shouldIgnore relPath + let path = relPath entry + guard $ not $ shouldIgnore path -- Only if the path of this directory matches any of the patterns base path, -- can anything in this directory possibly match the corresponding full pattern. - pure $ matchesAnyPatternBase relPath + pure $ matchesAnyPatternBase path -- Should `fsWalk` retain this entry for the result array? entryFilter :: Entry -> Effect Boolean @@ -194,16 +196,17 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do when (isFile entry.dirent && entry.name == ".gitignore") do updateIgnoreMatcherWithGitignore entry ignoreMatcher <- Ref.read ignoreMatcherRef - let path = withForwardSlashes $ Path.relative cwd entry.path + let path = relPath entry pure $ includeMatcher path && not (ignoreMatcher path) options = { entryFilter, deepFilter } - fsWalkImpl Left Right cb options cwd + fsWalkImpl Left Right cb options root pure $ Aff.Canceler \_ -> void $ liftEffect $ Ref.write true canceled -gitignoringGlob :: String -> Array String -> Aff (Array String) -gitignoringGlob dir patterns = map (withForwardSlashes <<< Path.relative dir <<< _.path) - <$> fsWalk dir [ ".git" ] patterns +gitignoringGlob :: RootPath -> Array String -> Aff (Array LocalPath) +gitignoringGlob root patterns = do + entries <- fsWalk root [ ".git" ] patterns + pure $ entries <#> \e -> e.path `Path.relativeTo` root diff --git a/src/Spago/Lock.purs b/src/Spago/Lock.purs index 3d0aa4013..1c9d9b4e6 100644 --- a/src/Spago/Lock.purs +++ b/src/Spago/Lock.purs @@ -57,7 +57,7 @@ type PackageSetInfo = type WorkspaceLockPackage = { core :: WorkspaceLockPackageEnv , test :: WorkspaceLockPackageEnv - , path :: FilePath + , path :: AdHocFilePath } type WorkspaceLockPackageEnv = @@ -119,7 +119,7 @@ registryLockType :: String registryLockType = "registry" type PathLock = - { path :: FilePath + { path :: AdHocFilePath , dependencies :: Array PackageName } @@ -136,7 +136,7 @@ pathLockCodec = Profunctor.dimap toRep fromRep $ CJ.named "PathLock" $ CJS.objec type GitLock = { url :: String , rev :: String - , subdir :: Maybe FilePath + , subdir :: Maybe AdHocFilePath , dependencies :: Array PackageName } diff --git a/src/Spago/Paths.js b/src/Spago/Paths.js index da219a5fa..3fc526971 100644 --- a/src/Spago/Paths.js +++ b/src/Spago/Paths.js @@ -1,3 +1,3 @@ import envPaths from 'env-paths'; -export const paths = envPaths('spago'); +export const paths_ = envPaths('spago'); diff --git a/src/Spago/Paths.purs b/src/Spago/Paths.purs index e2b3ccc8a..016786938 100644 --- a/src/Spago/Paths.purs +++ b/src/Spago/Paths.purs @@ -1,61 +1,74 @@ -module Spago.Paths where +module Spago.Paths + ( chdir + , cwd + , databasePath + , databaseVersion + , globalCachePath + , localCacheGitPath + , localCachePackagesPath + , localCachePath + , packageSetsPath + , paths + , registryIndexPath + , registryPath + ) where import Prelude -import Effect.Unsafe (unsafePerformEffect) -import Node.Path (FilePath) -import Node.Path as Path +import Effect.Class (class MonadEffect, liftEffect) +import Node.Path as Node.Path import Node.Process as Process - -type NodePaths = - { config :: FilePath - , data :: FilePath - , cache :: FilePath - , log :: FilePath - , temp :: FilePath +import Spago.Path (class IsPath, AdHocFilePath, GlobalPath, global, toRaw, ()) + +type NodePaths p = + { config :: p + , data :: p + , cache :: p + , log :: p + , temp :: p } -foreign import paths :: NodePaths - -cwd :: FilePath -cwd = unsafePerformEffect (Process.cwd) - -mkRelative :: FilePath -> FilePath -mkRelative = Path.relative cwd +foreign import paths_ :: NodePaths String -globalCachePath :: FilePath -globalCachePath = paths.cache +paths :: NodePaths GlobalPath +paths = + { config: global paths_.config + , data: global paths_.data + , cache: global paths_.cache + , log: global paths_.log + , temp: global paths_.temp + } -localCachePath :: FilePath -localCachePath = toLocalCachePath cwd +cwd :: ∀ m. MonadEffect m => m GlobalPath +cwd = global <$> liftEffect Process.cwd -localCachePackagesPath :: FilePath -localCachePackagesPath = toLocalCachePackagesPath cwd +chdir :: ∀ m path. MonadEffect m => IsPath path => path -> m Unit +chdir path = liftEffect $ Process.chdir (toRaw path) -localCacheGitPath :: FilePath -localCacheGitPath = toLocalCacheGitPath cwd +globalCachePath :: GlobalPath +globalCachePath = paths.cache -toLocalCachePath :: FilePath -> FilePath -toLocalCachePath rootDir = Path.concat [ rootDir, ".spago" ] +localCachePath :: AdHocFilePath +localCachePath = ".spago" -toLocalCachePackagesPath :: FilePath -> FilePath -toLocalCachePackagesPath rootDir = Path.concat [ toLocalCachePath rootDir, "p" ] +localCachePackagesPath :: AdHocFilePath +localCachePackagesPath = Node.Path.concat [ localCachePath, "p" ] -toLocalCacheGitPath :: FilePath -> FilePath -toLocalCacheGitPath rootDir = Path.concat [ toLocalCachePath rootDir, "g" ] +localCacheGitPath :: AdHocFilePath +localCacheGitPath = Node.Path.concat [ localCachePath, "g" ] -registryPath ∷ FilePath -registryPath = Path.concat [ globalCachePath, "registry" ] +registryPath ∷ GlobalPath +registryPath = globalCachePath "registry" -registryIndexPath ∷ FilePath -registryIndexPath = Path.concat [ globalCachePath, "registry-index" ] +registryIndexPath ∷ GlobalPath +registryIndexPath = globalCachePath "registry-index" -packageSetsPath :: FilePath -packageSetsPath = Path.concat [ registryPath, "package-sets" ] +packageSetsPath :: GlobalPath +packageSetsPath = registryPath "package-sets" -- | We should bump this number every time we change the database schema in a breaking way databaseVersion :: Int databaseVersion = 2 -databasePath :: FilePath -databasePath = Path.concat [ globalCachePath, "spago.v" <> show databaseVersion <> ".sqlite" ] +databasePath :: GlobalPath +databasePath = globalCachePath ("spago.v" <> show databaseVersion <> ".sqlite") diff --git a/src/Spago/Prelude.purs b/src/Spago/Prelude.purs index cda29c1c3..bb87a3311 100644 --- a/src/Spago/Prelude.purs +++ b/src/Spago/Prelude.purs @@ -1,9 +1,11 @@ module Spago.Prelude ( HexString(..) , OnlineStatus(..) + , SpagoBaseEnv , isPrefix , mkTemp , mkTemp' + , module Spago.Path , module Spago.Core.Prelude , parTraverseSpago , parallelise @@ -16,7 +18,6 @@ module Spago.Prelude , unsafeLog , unsafeStringify , withBackoff' - , withForwardSlashes ) where import Spago.Core.Prelude @@ -28,7 +29,6 @@ import Data.Foldable as Foldable import Data.Function.Uncurried (Fn3, runFn3) import Data.Int as Int import Data.Maybe as Maybe -import Data.String (Pattern(..), Replacement(..)) import Data.String as String import Data.String.Extra (levenshtein) import Data.Traversable (class Traversable) @@ -37,16 +37,23 @@ import Effect.Now as Now import JSON (JSON) import JSON as JSON import Node.Buffer as Buffer -import Node.Path as Path import Partial.Unsafe (unsafeCrashWith) import Registry.Sha256 as Registry.Sha256 import Registry.Sha256 as Sha256 import Registry.Version as Version +import Spago.Path (class IsPath, AdHocFilePath, GlobalPath, LocalPath, RootPath, (), withForwardSlashes) +import Spago.Path as Path import Spago.Paths as Paths import Unsafe.Coerce (unsafeCoerce) data OnlineStatus = Offline | Online +type SpagoBaseEnv a = + { rootPath :: Path.RootPath + , logOptions :: LogOptions + | a + } + unsafeFromRight :: forall e a. Either e a -> a unsafeFromRight v = Either.fromRight' (\_ -> unsafeCrashWith $ "Unexpected Left: " <> unsafeStringify v) v @@ -151,7 +158,7 @@ withBackoff { delay: Aff.Milliseconds timeout, action, shouldCancel, shouldRetry maybeResult <- runAction 0 action (Int.floor timeout) loop 1 maybeResult -mkTemp' :: forall m. MonadAff m => Maybe String -> m FilePath +mkTemp' :: forall m. MonadAff m => Maybe String -> m Path.GlobalPath mkTemp' maybeSuffix = liftAff do -- Get a random string (HexString random) <- liftEffect do @@ -159,15 +166,12 @@ mkTemp' maybeSuffix = liftAff do sha <- Sha256.hashString $ show now <> fromMaybe "" maybeSuffix shaToHex sha -- Return the dir, but don't make it - that's the responsibility of the client - let tempDirPath = Path.concat [ Paths.paths.temp, String.drop 50 random ] + let tempDirPath = Paths.paths.temp String.drop 50 random pure tempDirPath -mkTemp :: forall m. MonadAff m => m FilePath +mkTemp :: forall m. MonadAff m => m Path.GlobalPath mkTemp = mkTemp' Nothing -withForwardSlashes :: String -> String -withForwardSlashes = String.replaceAll (Pattern "\\") (Replacement "/") - isPrefix :: String.Pattern -> String -> Boolean isPrefix p = isJust <<< String.stripPrefix p diff --git a/src/Spago/Psa.purs b/src/Spago/Psa.purs index 5ac0b5c18..179272aa5 100644 --- a/src/Spago/Psa.purs +++ b/src/Spago/Psa.purs @@ -1,6 +1,6 @@ -- A majority of this code was copied from -- - https://github.com/natefaubion/purescript-psa --- +-- -- To fullfil license requirements -- Copyright © Nathan Faubion -- https://opensource.org/license/mit/ @@ -23,24 +23,24 @@ import Foreign.Object as FO import JSON as JSON import Node.Encoding as Encoding import Node.FS.Aff as FSA -import Node.Path as Path import Spago.Cmd as Cmd import Spago.Config (Package(..), PackageMap, WorkspacePackage) import Spago.Config as Config import Spago.Core.Config (CensorBuildWarnings(..), WarningCensorTest(..)) import Spago.Core.Config as Core import Spago.Log (prepareToDie) +import Spago.Path as Path import Spago.Psa.Output (buildOutput) import Spago.Psa.Printer (printDefaultOutputToErr, printJsonOutputToOut) -import Spago.Psa.Types (ErrorCode, PathDecision, PsaArgs, PsaOutputOptions, PsaPathType(..), psaResultCodec) +import Spago.Psa.Types (ErrorCode, PathDecision, PsaArgs, PsaOutputOptions, PsaPathType(..), PsaEnv, psaResultCodec) import Spago.Purs as Purs defaultStatVerbosity :: Core.StatVerbosity defaultStatVerbosity = Core.CompactStats -psaCompile :: forall a. Set.Set FilePath -> Array String -> PsaArgs -> Spago (Purs.PursEnv a) Boolean -psaCompile globs pursArgs psaArgs = do - result <- Purs.compile globs (Array.snoc pursArgs "--json-errors") +psaCompile :: ∀ a. RootPath -> Set.Set LocalPath -> Array String -> PsaArgs -> Spago (PsaEnv a) Boolean +psaCompile cwd globs pursArgs psaArgs = do + result <- Purs.compile cwd globs (Array.snoc pursArgs "--json-errors") let resultStdout = Cmd.getStdout result arrErrorsIsEmpty <- forWithIndex (Str.split (Str.Pattern "\n") resultStdout) \idx err -> case JSON.parse err >>= CJ.decode psaResultCodec >>> lmap CJ.DecodeError.print of @@ -92,13 +92,14 @@ psaCompile globs pursArgs psaArgs = do either (const (pure Nothing)) pure result toPathDecisions - :: { allDependencies :: PackageMap + :: { rootPath :: RootPath + , allDependencies :: PackageMap , selectedPackages :: Array WorkspacePackage , psaCliFlags :: PsaOutputOptions , censorLibWarnings :: Maybe Core.CensorBuildWarnings } - -> Array (Effect (Array (String -> Maybe PathDecision))) -toPathDecisions { allDependencies, selectedPackages, psaCliFlags, censorLibWarnings } = + -> Array (Effect (Array (LocalPath -> Maybe PathDecision))) +toPathDecisions { rootPath, allDependencies, selectedPackages, psaCliFlags, censorLibWarnings } = projectDecisions <> dependencyDecisions where projectDecisions = selectedPackages <#> \selected -> toWorkspacePackagePathDecision { selected, psaCliFlags } @@ -113,7 +114,7 @@ toPathDecisions { allDependencies, selectedPackages, psaCliFlags, censorLibWarni pkgsInProject :: Set PackageName pkgsInProject = foldMap (\p -> Set.singleton p.package.name) selectedPackages - toDependencyDecision :: Tuple PackageName Package -> Effect (Array (String -> Maybe PathDecision)) + toDependencyDecision :: Tuple PackageName Package -> Effect (Array (LocalPath -> Maybe PathDecision)) toDependencyDecision dep = case snd dep of WorkspacePackage p -> toWorkspacePackagePathDecision @@ -121,10 +122,10 @@ toPathDecisions { allDependencies, selectedPackages, psaCliFlags, censorLibWarni , psaCliFlags } _ -> do - pkgLocation <- Path.resolve [] $ Tuple.uncurry Config.getPackageLocation dep + let pkgLocation = Tuple.uncurry (Config.getLocalPackageLocation rootPath) dep pure [ toPathDecision - { pathIsFromPackage: isJust <<< String.stripPrefix (String.Pattern pkgLocation) + { pathIsFromPackage: (pkgLocation `Path.isPrefixOf` _) , pathType: IsLib , strict: false , censorWarnings: censorLibWarnings @@ -135,20 +136,19 @@ toWorkspacePackagePathDecision :: { selected :: WorkspacePackage , psaCliFlags :: PsaOutputOptions } - -> Effect (Array (String -> Maybe PathDecision)) + -> Effect (Array (LocalPath -> Maybe PathDecision)) toWorkspacePackagePathDecision { selected: { path, package }, psaCliFlags } = do - pkgPath <- Path.resolve [] path - let srcPath = Path.concat [ pkgPath, "src" ] - let testPath = Path.concat [ pkgPath, "test" ] + let srcPath = path "src" + let testPath = path "test" pure [ toPathDecision - { pathIsFromPackage: isJust <<< String.stripPrefix (String.Pattern srcPath) + { pathIsFromPackage: (srcPath `Path.isPrefixOf` _) , pathType: IsSrc , strict: fromMaybe false $ psaCliFlags.strict <|> (package.build >>= _.strict) , censorWarnings: package.build >>= _.censorProjectWarnings } , toPathDecision - { pathIsFromPackage: isJust <<< String.stripPrefix (String.Pattern testPath) + { pathIsFromPackage: (testPath `Path.isPrefixOf` _) , pathType: IsSrc , strict: fromMaybe false $ psaCliFlags.strict <|> (package.test >>= _.strict) , censorWarnings: package.test >>= _.censorTestWarnings @@ -156,12 +156,12 @@ toWorkspacePackagePathDecision { selected: { path, package }, psaCliFlags } = do ] toPathDecision - :: { pathIsFromPackage :: String -> Boolean + :: { pathIsFromPackage :: LocalPath -> Boolean , pathType :: PsaPathType , strict :: Boolean , censorWarnings :: Maybe Config.CensorBuildWarnings } - -> String + -> LocalPath -> Maybe PathDecision toPathDecision options pathToFile = do Alternative.guard $ options.pathIsFromPackage pathToFile @@ -182,6 +182,6 @@ shouldPrintWarning = case _ of ByCode c -> \code _ -> c == code ByMessagePrefix prefix -> \_ msg -> isJust $ String.stripPrefix (String.Pattern $ String.trim prefix) (String.trim msg) -- We return `true` to print the warning. - -- If an element was found (i.e. `Just` is returned), then one of the tests succeeded, + -- If an element was found (i.e. `Just` is returned), then one of the tests succeeded, -- so we should not print the warning and return false here. \code msg -> isNothing $ NonEmptyArray.find (\f -> f code msg) tests diff --git a/src/Spago/Psa/Output.purs b/src/Spago/Psa/Output.purs index f87604cad..906b54bc2 100644 --- a/src/Spago/Psa/Output.purs +++ b/src/Spago/Psa/Output.purs @@ -14,19 +14,13 @@ module Spago.Psa.Output , trimPosition ) where -import Prelude +import Spago.Core.Prelude import Data.Array as Array -import Data.Foldable (foldl) -import Data.Maybe (Maybe(..), fromMaybe) import Data.String as Str -import Data.Tuple (Tuple(..)) import Foreign.Object as FO -import Node.Path as Path -import Spago.Core.Prelude (Spago) -import Spago.Paths as Paths -import Spago.Psa.Types (Filename, Lines, PathDecision, PathInfo, Position, PsaAnnotedError, PsaError, PsaArgs, PsaPath(..), PsaPathType(..), PsaResult, compareByLocation) -import Spago.Purs as Purs +import Spago.Path as Path +import Spago.Psa.Types (Lines, PathDecision, PathInfo, Position, PsaAnnotedError, PsaArgs, PsaEnv, PsaError, PsaPath(..), PsaPathType(..), PsaResult, Filename, compareByLocation) data ErrorTag = Error | Warning @@ -68,15 +62,16 @@ initialStats = -- | position information. buildOutput :: forall a - . (Filename -> Position -> Spago (Purs.PursEnv a) (Maybe Lines)) + . (Filename -> Position -> Spago (PsaEnv a) (Maybe Lines)) -> PsaArgs -> PsaResult - -> Spago (Purs.PursEnv a) Output + -> Spago (PsaEnv a) Output buildOutput loadLines options result = do + { rootPath } <- ask let result' = - { warnings: pathOf <$> result.warnings - , errors: pathOf <$> result.errors + { warnings: pathOf rootPath <$> result.warnings + , errors: pathOf rootPath <$> result.errors } initialState = { warnings: [] @@ -91,15 +86,13 @@ buildOutput loadLines options result = do } where - pathOf :: PsaError -> Tuple PathInfo PsaError - pathOf x = Tuple pathDecision x + pathOf :: Path.RootPath -> PsaError -> Tuple PathInfo PsaError + pathOf root x = Tuple pathDecision x where pathDecision = case x.filename of - Just short | short /= "" -> do - let - path - | Path.isAbsolute short = short - | otherwise = Path.concat [ Paths.cwd, short ] + Just filename | filename /= "" -> do + let path = root filename + short = Path.localPart path fromMaybe unknownPathInfo $ Array.findMap (\p -> map (toPathInfo short) $ p path) options.decisions _ -> unknownPathInfo @@ -120,7 +113,7 @@ buildOutput loadLines options result = do , shouldShowError: \_ _ -> true } - onError :: ErrorTag -> Output -> Tuple PathInfo PsaError -> Spago (Purs.PursEnv a) Output + onError :: ErrorTag -> Output -> Tuple PathInfo PsaError -> Spago (PsaEnv a) Output onError tag state (Tuple pathInfo error) = if shouldShowError then do source <- fromMaybe (pure Nothing) (loadLines <$> error.filename <*> error.position) @@ -134,7 +127,7 @@ buildOutput loadLines options result = do Error -> true Warning -> pathInfo.shouldShowError error.errorCode error.message - update :: Array PsaAnnotedError -> Spago (Purs.PursEnv a) Output + update :: Array PsaAnnotedError -> Spago (PsaEnv a) Output update log = pure $ onTag (_ { stats = stats, errors = state.errors <> log }) diff --git a/src/Spago/Psa/Types.purs b/src/Spago/Psa/Types.purs index 3ee0b0ae3..345c4682d 100644 --- a/src/Spago/Psa/Types.purs +++ b/src/Spago/Psa/Types.purs @@ -1,6 +1,6 @@ -- A majority of this code was copied from -- - https://github.com/natefaubion/purescript-psa-utils --- +-- -- To fullfil license requirements -- Copyright © Nathan Faubion -- https://opensource.org/license/mit/ @@ -18,6 +18,7 @@ module Spago.Psa.Types , PsaOutputOptions , PathInfo , Position + , PsaEnv , Suggestion , Lines , psaResultCodec @@ -33,6 +34,10 @@ import Data.Codec.JSON.Record as CJ.Record import Data.Maybe (Maybe(..)) import Data.Tuple (Tuple(..)) import Spago.Core.Config as Core +import Spago.Path (LocalPath, RootPath) +import Spago.Purs (PursEnv) + +type PsaEnv a = PursEnv ( rootPath :: RootPath | a ) type ErrorCode = String type ModuleName = String @@ -52,8 +57,8 @@ derive instance Eq PsaPath derive instance Ord PsaPath instance Show PsaPath where show = case _ of - Src s -> "(Src " <> s <> ")" - Lib s -> "(Lib " <> s <> ")" + Src s -> "(Src " <> show s <> ")" + Lib s -> "(Lib " <> show s <> ")" Unknown -> "Unknown" data PsaPathType @@ -82,7 +87,7 @@ type PsaArgs = { jsonErrors :: Boolean , color :: Boolean , statVerbosity :: Core.StatVerbosity - , decisions :: Array (String -> Maybe PathDecision) + , decisions :: Array (LocalPath -> Maybe PathDecision) } type PsaOutputOptions = diff --git a/src/Spago/Purs.purs b/src/Spago/Purs.purs index e28918fe6..2d730812b 100644 --- a/src/Spago/Purs.purs +++ b/src/Spago/Purs.purs @@ -12,6 +12,7 @@ import Node.Library.Execa (ExecaResult) import Registry.Internal.Codec as Internal.Codec import Registry.Version as Version import Spago.Cmd as Cmd +import Spago.Path as Path type PursEnv a = { purs :: Purs @@ -20,16 +21,16 @@ type PursEnv a = } type Purs = - { cmd :: FilePath + { cmd :: GlobalPath , version :: Version } -getPurs :: forall a. Spago (LogEnv a) Purs +getPurs :: ∀ a. Spago (LogEnv a) Purs getPurs = Cmd.getExecutable "purs" >>= parseVersionOutput -- Drop the stuff after a space: dev builds look like this: 0.15.6 [development build; commit: 8da7e96005f717f03d6eee3c12b1f1416659a919] -- Drop the stuff after a hyphen: prerelease builds look like this: 0.15.6-2 -parseVersionOutput :: forall a. { cmd :: String, output :: String } -> Spago (LogEnv a) Purs +parseVersionOutput :: ∀ a. { cmd :: GlobalPath, output :: String } -> Spago (LogEnv a) Purs parseVersionOutput { cmd, output: stdout } = case parseLenientVersion (dropStuff "-" $ dropStuff " " stdout) of Left _err -> die $ "Failed to parse purs version. Was: " <> stdout -- Fail if Purs is lower than 0.15.4 @@ -41,10 +42,10 @@ parseVersionOutput { cmd, output: stdout } = case parseLenientVersion (dropStuff where dropStuff pattern = fromMaybe "" <<< Array.head <<< String.split (String.Pattern pattern) -compile :: forall a. Set FilePath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) -compile globs pursArgs = do +compile :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) +compile cwd globs pursArgs = do { purs } <- ask - let args = [ "compile" ] <> pursArgs <> Set.toUnfoldable globs + let args = [ "compile" ] <> pursArgs <> (globsToArgs cwd globs) logDebug [ "Running command:", "purs " <> String.joinWith " " args ] -- PureScript (as of v0.14.0) outputs the compiler errors/warnings to `stdout` -- and outputs "Compiling..." messages to `stderr` @@ -52,18 +53,24 @@ compile globs pursArgs = do -- However, we do not pipe `stdout` to the parent, so that we don't see the errors reported twice: -- once via `purs` and once via spago's pretty-printing of the same errors/warnings. Cmd.exec purs.cmd args $ Cmd.defaultExecOptions - { pipeStdout = false } + { pipeStdout = false + , cwd = Just $ Path.toGlobal cwd + } -repl :: forall a. Set FilePath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) -repl globs pursArgs = do +repl :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) +repl cwd globs pursArgs = do { purs } <- ask - let args = [ "repl" ] <> pursArgs <> Set.toUnfoldable globs + let args = [ "repl" ] <> pursArgs <> (globsToArgs cwd globs) Cmd.exec purs.cmd args $ Cmd.defaultExecOptions { pipeStdout = true , pipeStderr = true , pipeStdin = Cmd.StdinPipeParent + , cwd = Just $ Path.toGlobal cwd } +globsToArgs :: RootPath -> Set LocalPath -> Array String +globsToArgs cwd globs = Path.localPart <<< (_ `Path.relativeTo` cwd) <$> Set.toUnfoldable globs + data DocsFormat = Html | Markdown @@ -87,10 +94,10 @@ printDocsFormat = case _ of Ctags -> "ctags" Etags -> "etags" -docs :: forall a. Set FilePath -> DocsFormat -> Spago (PursEnv a) (Either ExecaResult ExecaResult) +docs :: ∀ a. Set LocalPath -> DocsFormat -> Spago (PursEnv a) (Either ExecaResult ExecaResult) docs globs format = do { purs } <- ask - let args = [ "docs", "--format", printDocsFormat format ] <> Set.toUnfoldable globs + let args = [ "docs", "--format", printDocsFormat format ] <> (Path.toRaw <$> Set.toUnfoldable globs) Cmd.exec purs.cmd args $ Cmd.defaultExecOptions { pipeStdout = true , pipeStderr = true @@ -120,10 +127,10 @@ moduleGraphNodeCodec = CJ.named "ModuleGraphNode" $ CJ.Record.object , depends: CJ.array CJ.string } -graph :: forall a. Set FilePath -> Array String -> Spago (PursEnv a) (Either CJ.DecodeError ModuleGraph) +graph :: ∀ a. Set LocalPath -> Array String -> Spago (PursEnv a) (Either CJ.DecodeError ModuleGraph) graph globs pursArgs = do { purs } <- ask - let args = [ "graph" ] <> pursArgs <> Set.toUnfoldable globs + let args = [ "graph" ] <> pursArgs <> (Path.toRaw <$> Set.toUnfoldable globs) logDebug [ "Running command:", "purs " <> String.joinWith " " args ] let execOpts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } Cmd.exec purs.cmd args execOpts >>= case _ of diff --git a/src/Spago/Purs/Graph.purs b/src/Spago/Purs/Graph.purs index b881d8369..51b6c9347 100644 --- a/src/Spago/Purs/Graph.purs +++ b/src/Spago/Purs/Graph.purs @@ -33,7 +33,7 @@ import Spago.Config (Package(..), WithTestGlobs(..), WorkspacePackage) import Spago.Config as Config import Spago.Glob as Glob import Spago.Log as Log -import Spago.Paths as Paths +import Spago.Path as Path import Spago.Purs (ModuleGraph(..), ModuleGraphNode, ModuleName, Purs) import Spago.Purs as Purs import Unsafe.Coerce (unsafeCoerce) @@ -48,7 +48,7 @@ type PreGraphEnv a = | a } -runGraph :: forall a. Set FilePath -> Array String -> Spago (PreGraphEnv a) (Either String Purs.ModuleGraph) +runGraph :: forall a. Set LocalPath -> Array String -> Spago (PreGraphEnv a) (Either String Purs.ModuleGraph) runGraph globs pursArgs = map (lmap toErrorMessage) $ Purs.graph globs pursArgs where toErrorMessage = append "Could not decode the output of `purs graph`, error: " <<< CJ.DecodeError.print @@ -60,6 +60,7 @@ type PackageGraphEnv a = { selected :: NonEmptyArray WorkspacePackage , dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions + , rootPath :: RootPath | a } @@ -83,7 +84,7 @@ moduleGraphCodec = Internal.Codec.strMap "ModuleGraphWithPackage" Right identity getModuleGraphWithPackage :: forall a. Purs.ModuleGraph -> Spago (PackageGraphEnv a) ModuleGraphWithPackage getModuleGraphWithPackage (ModuleGraph graph) = do - { selected, dependencies } <- ask + { selected, dependencies, rootPath } <- ask -- First compile the globs for each package, we get out a set of the modules that a package contains -- and we can have a map from path to a PackageName @@ -100,13 +101,13 @@ getModuleGraphWithPackage (ModuleGraph graph) = do -- We should memoise them, so that when we get the graph for a monorepo we don't evaluate the globs again. -- Each call is a few milliseconds, but there are potentially hundreds of those, and it adds up. logDebug "Calling pathToPackage..." - pathToPackage :: Map FilePath PackageName <- map (Map.fromFoldable <<< Array.fold) + pathToPackage :: Map LocalPath PackageName <- map (Map.fromFoldable <<< Array.fold) $ for (Map.toUnfoldable allPackages) \(Tuple name package) -> do -- Basically partition the modules of the current package by in src and test packages let withTestGlobs = if (Set.member name (Map.keys testPackages)) then OnlyTestGlobs else NoTestGlobs logDebug $ "Getting globs for package " <> PackageName.print name - globMatches :: Array FilePath <- map Array.fold $ traverse compileGlob (Config.sourceGlob withTestGlobs name package) + globMatches :: Array LocalPath <- map Array.fold $ traverse compileGlob (Config.sourceGlob rootPath withTestGlobs name package) pure $ map (\p -> Tuple p name) globMatches logDebug "Got the pathToPackage map, calling packageGraph" @@ -116,18 +117,20 @@ getModuleGraphWithPackage (ModuleGraph graph) = do addPackageInfo pkgGraph (Tuple moduleName { path, depends }) = let -- Windows paths will need a conversion to forward slashes to be matched to globs - newPath = withForwardSlashes path + newPath = withForwardSlashes $ rootPath path newVal = do package <- Map.lookup newPath pathToPackage - pure { path: newPath, depends, package } + pure { path: Path.localPart newPath, depends, package } in maybe pkgGraph (\v -> Map.insert moduleName v pkgGraph) newVal packageGraph = foldl addPackageInfo Map.empty (Map.toUnfoldable graph :: Array _) pure packageGraph -compileGlob :: forall a. FilePath -> Spago a (Array FilePath) -compileGlob sourcePath = liftAff $ Glob.gitignoringGlob Paths.cwd [ withForwardSlashes sourcePath ] +compileGlob :: ∀ a. LocalPath -> Spago { rootPath :: RootPath | a } (Array LocalPath) +compileGlob sourcePath = do + { rootPath } <- ask + liftAff $ Glob.gitignoringGlob rootPath [ Path.localPart $ withForwardSlashes sourcePath ] -------------------------------------------------------------------------------- -- Package graph @@ -181,6 +184,7 @@ type ImportsGraphEnv a = , workspacePackages :: NonEmptyArray WorkspacePackage , dependencies :: Fetch.PackageTransitiveDeps , logOptions :: LogOptions + , rootPath :: RootPath | a } @@ -231,8 +235,12 @@ checkImports graph = do | otherwise = OnlyTestGlobs -- Compile the globs for the project, we get the set of source files in the project - glob :: Set FilePath <- map Set.fromFoldable do - map Array.fold $ traverse compileGlob (Config.sourceGlob testGlobOption packageName (WorkspacePackage selected)) + { rootPath } <- ask + glob :: Set String <- + map Set.fromFoldable + $ map (map Path.localPart) + $ map Array.fold + $ traverse compileGlob (Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected)) let -- Filter this improved graph to only have the project modules diff --git a/src/Spago/Registry.purs b/src/Spago/Registry.purs index 2c2341c39..7eb03f262 100644 --- a/src/Spago/Registry.purs +++ b/src/Spago/Registry.purs @@ -27,7 +27,6 @@ import Data.Time.Duration (Minutes(..)) import Effect.AVar (AVar) import Effect.Aff.AVar as AVar import Effect.Now as Now -import Node.Path as Path import Registry.Constants as Registry.Constants import Registry.ManifestIndex as ManifestIndex import Registry.Metadata as Metadata @@ -39,6 +38,7 @@ import Spago.Db (Db) import Spago.Db as Db import Spago.FS as FS import Spago.Git as Git +import Spago.Path as Path import Spago.Paths as Paths import Spago.Purs as Purs @@ -112,7 +112,7 @@ readPackageSet version = do { readPackageSet: fn } <- runSpago { logOptions, db, git, purs, offline } getRegistry runSpago { logOptions } (fn version) -getRegistryFns :: AVar RegistryFunctions -> AVar Unit -> Spago (PreRegistryEnv _) RegistryFunctions +getRegistryFns :: AVar RegistryFunctions -> AVar Unit -> Spago (PreRegistryEnv ()) RegistryFunctions getRegistryFns registryBox registryLock = do -- The Box AVar will be empty until the first time we fetch the Registry, then -- we can just use the value that is cached. @@ -132,7 +132,7 @@ getRegistryFns registryBox registryLock = do { getManifestFromIndex: getManifestFromIndexImpl db , getMetadata: getMetadataImpl db , getMetadataForPackages: getMetadataForPackagesImpl db - , listMetadataFiles: FS.ls (Path.concat [ Paths.registryPath, Registry.Constants.metadataDirectory ]) + , listMetadataFiles: FS.ls (Paths.registryPath Registry.Constants.metadataDirectory) , listPackageSets: listPackageSetsImpl , findPackageSet: findPackageSetImpl , readPackageSet: readPackageSetImpl @@ -142,7 +142,7 @@ getRegistryFns registryBox registryLock = do pure registryFns where - fetchRegistry :: Spago (PreRegistryEnv _) Boolean + fetchRegistry :: Spago (PreRegistryEnv ()) Boolean fetchRegistry = do -- we keep track of how old the latest pull was - if the last pull was recent enough -- we just move on, otherwise run the fibers @@ -165,7 +165,7 @@ getRegistryFns registryBox registryLock = do pure fetchingFreshRegistry -- | Update the database with the latest package sets - updatePackageSetsDb :: Db -> Spago (LogEnv _) Unit + updatePackageSetsDb :: ∀ a. Db -> Spago (LogEnv a) Unit updatePackageSetsDb db = do { logOptions } <- ask setsAvailable <- map Set.fromFoldable getAvailablePackageSets @@ -183,7 +183,7 @@ getRegistryFns registryBox registryLock = do liftEffect $ Db.insertPackageSetEntry db { packageName: name, packageVersion: version, packageSetVersion: set.version } -- | List all the package sets versions available in the Registry repo - getAvailablePackageSets :: Spago (LogEnv _) (Array Version) + getAvailablePackageSets :: ∀ a. Spago (LogEnv a) (Array Version) getAvailablePackageSets = do { success: setVersions, fail: parseFailures } <- map (partitionEithers <<< map parseSetVersion) $ FS.ls Paths.packageSetsPath @@ -199,7 +199,7 @@ getRegistryFns registryBox registryLock = do readPackageSetImpl :: Version -> Spago (LogEnv ()) PackageSet readPackageSetImpl setVersion = do logDebug "Reading the package set from the Registry repo..." - let packageSetPath = Path.concat [ Paths.packageSetsPath, Version.print setVersion <> ".json" ] + let packageSetPath = Paths.packageSetsPath (Version.print setVersion <> ".json") liftAff (FS.readJsonFile PackageSet.codec packageSetPath) >>= case _ of Left err -> die $ "Couldn't read the package set: " <> err Right registryPackageSet -> do @@ -242,8 +242,8 @@ getMetadataForPackagesImpl db names = do where metadataFromFile pkgName = do - let metadataFilePath = Path.concat [ Paths.registryPath, Registry.Constants.metadataDirectory, PackageName.print pkgName <> ".json" ] - logDebug $ "Reading metadata from file: " <> metadataFilePath + let metadataFilePath = Paths.registryPath Registry.Constants.metadataDirectory (PackageName.print pkgName <> ".json") + logDebug $ "Reading metadata from file: " <> Path.quote metadataFilePath liftAff (FS.readJsonFile Metadata.codec metadataFilePath) -- Manifests are immutable so we can just lookup in the DB or read from file if not there @@ -255,7 +255,7 @@ getManifestFromIndexImpl db name version = do -- if we don't have it we need to read it from file -- (note that we have all the versions of a package in the same file) logDebug $ "Reading package from Index: " <> PackageName.print name - maybeManifests <- liftAff $ ManifestIndex.readEntryFile Paths.registryIndexPath name + maybeManifests <- liftAff $ ManifestIndex.readEntryFile (Path.toRaw Paths.registryIndexPath) name manifests <- map (map (\m@(Manifest m') -> Tuple m'.version m)) case maybeManifests of Right ms -> pure $ NonEmptyArray.toUnfoldable ms Left err -> do @@ -323,7 +323,7 @@ isVersionCompatible installedVersion minVersion = _, _ -> false -- | Check if we have fetched the registry recently enough, so we don't hit the net all the time -shouldFetchRegistryRepos :: forall a. Db -> Spago (LogEnv a) Boolean +shouldFetchRegistryRepos :: ∀ a. Db -> Spago (LogEnv a) Boolean shouldFetchRegistryRepos db = do now <- liftEffect $ Now.nowDateTime let registryKey = "registry" diff --git a/src/Spago/Tar.purs b/src/Spago/Tar.purs index 81ba18347..0b18cef9e 100644 --- a/src/Spago/Tar.purs +++ b/src/Spago/Tar.purs @@ -4,12 +4,12 @@ import Spago.Prelude import Data.Function.Uncurried (Fn4, runFn4) -foreign import extractImpl :: forall r. Fn4 (TarErr -> r) (Unit -> r) String String (Effect r) +foreign import extractImpl :: forall r. Fn4 (TarErr -> r) (Unit -> r) GlobalPath GlobalPath (Effect r) type TarErr = { recoverable :: Boolean - , file :: FilePath - , cwd :: FilePath + , file :: GlobalPath + , cwd :: GlobalPath , code :: String , tarCode :: String } @@ -21,4 +21,4 @@ type TarErr = extract :: ExtractArgs -> Effect (Either TarErr Unit) extract { cwd, filename } = runFn4 extractImpl Left Right cwd filename -type ExtractArgs = { cwd :: String, filename :: String } +type ExtractArgs = { cwd :: GlobalPath, filename :: GlobalPath } diff --git a/test-fixtures/build/migrate-config/migrating-output.txt b/test-fixtures/build/migrate-config/migrating-output.txt index d1f3676ba..a5f03da20 100644 --- a/test-fixtures/build/migrate-config/migrating-output.txt +++ b/test-fixtures/build/migrate-config/migrating-output.txt @@ -1,5 +1,5 @@ Reading Spago workspace configuration... -Migrating your spago.yaml to the latest version... +Migrating your "spago.yaml" to the latest version... ✓ Selecting package to build: migrate-config diff --git a/test-fixtures/build/migrate-config/unmigrated-warning.txt b/test-fixtures/build/migrate-config/unmigrated-warning.txt index 7586bd046..e9e7e0518 100644 --- a/test-fixtures/build/migrate-config/unmigrated-warning.txt +++ b/test-fixtures/build/migrate-config/unmigrated-warning.txt @@ -1,5 +1,5 @@ Reading Spago workspace configuration... -‼ Your spago.yaml is using an outdated format. Run Spago with the --migrate flag to update it to the latest version. +‼ Your "spago.yaml" is using an outdated format. Run Spago with the --migrate flag to update it to the latest version. ✓ Selecting package to build: migrate-config diff --git a/test-fixtures/bundle-refuse-overwrite-output.txt b/test-fixtures/bundle-refuse-overwrite-output.txt index 5ef5140e3..af3f6a80c 100644 --- a/test-fixtures/bundle-refuse-overwrite-output.txt +++ b/test-fixtures/bundle-refuse-overwrite-output.txt @@ -11,4 +11,4 @@ Errors 0 0 0 ✓ Build succeeded. -✘ Target file index.js was not previously generated by Spago. Use --force to overwrite anyway. +✘ Target file "index.js" was not previously generated by Spago. Use --force to overwrite anyway. diff --git a/test-fixtures/pedantic/pedantic-instructions-installation-result.txt b/test-fixtures/pedantic/pedantic-instructions-installation-result.txt index 5c545401a..e8389c1ec 100644 --- a/test-fixtures/pedantic/pedantic-instructions-installation-result.txt +++ b/test-fixtures/pedantic/pedantic-instructions-installation-result.txt @@ -2,7 +2,7 @@ Reading Spago workspace configuration... ✓ Selecting package to build: follow-instructions -Adding 1 package to the config in spago.yaml +Adding 1 package to the config in "spago.yaml" Downloading dependencies... Lockfile is out of date (installing new packages), generating it... Lockfile written to spago.lock. Please commit this file. diff --git a/test-fixtures/spago-yml-check-stderr.txt b/test-fixtures/spago-yml-check-stderr.txt index 23110a41c..ccd6509e4 100644 --- a/test-fixtures/spago-yml-check-stderr.txt +++ b/test-fixtures/spago-yml-check-stderr.txt @@ -3,8 +3,8 @@ Reading Spago workspace configuration... ✘ Couldn't parse Spago config, error: - Did not find `spago.yaml`. Spago's configuration files must end with `.yaml`, not `.yml`. - Try renaming `spago.yml` to `spago.yaml` or run `spago init` to initialize a new project. + Did not find "spago.yaml". Spago's configuration files must end with `.yaml`, not `.yml`. + Try renaming "spago.yml" to "spago.yaml" or run `spago init` to initialize a new project. The configuration file help can be found here https://github.com/purescript/spago#the-configuration-file diff --git a/test/Prelude.purs b/test/Prelude.purs index 9477ac586..d4db8524b 100644 --- a/test/Prelude.purs +++ b/test/Prelude.purs @@ -14,8 +14,6 @@ import Effect.Class.Console (log) import Effect.Class.Console as Console import Node.FS.Aff as FS.Aff import Node.Library.Execa (ExecaResult) -import Node.Path (dirname) -import Node.Path as Path import Node.Platform as Platform import Node.Process as Process import Record (merge) @@ -28,39 +26,44 @@ import Spago.Command.Init as Init import Spago.Core.Config (Dependencies(..), Config) import Spago.Core.Config as Config import Spago.FS as FS +import Spago.Path (toRaw) +import Spago.Path as Path +import Spago.Paths as Paths import Spago.Prelude as X import Test.Spec.Assertions (fail) import Test.Spec.Assertions as Assert +type FixturePath = GlobalPath + type TestDirs = { spago :: Array String -> Aff (Either ExecResult ExecResult) , spago' :: StdinConfig -> Array String -> Aff (Either ExecResult ExecResult) - , fixture :: FilePath -> FilePath - , oldCwd :: FilePath - , testCwd :: FilePath + , fixture :: AdHocFilePath -> FixturePath + , oldCwd :: GlobalPath + , testCwd :: RootPath } withTempDir :: (TestDirs -> Aff Unit) -> Aff Unit withTempDir = Aff.bracket createTempDir cleanupTempDir where createTempDir = do - oldCwd <- liftEffect $ Process.cwd - temp <- mkTemp' $ Just "spago-test-" + oldCwd <- Paths.cwd + temp <- Path.mkRoot =<< mkTemp' (Just "spago-test-") FS.mkdirp temp - liftEffect $ Process.chdir temp + Paths.chdir temp isDebug <- liftEffect $ map isJust $ Process.lookupEnv "SPAGO_TEST_DEBUG" when isDebug do - log $ "Running test in " <> temp + log $ "Running test in " <> Path.quote temp let - fixturesPath = oldCwd <> Path.sep <> "test-fixtures" + fixturesPath = oldCwd "test-fixtures" - fixture path = Path.concat [ fixturesPath, path ] + fixture path = fixturesPath path spago' :: StdinConfig -> Array String -> Aff (Either ExecResult ExecResult) spago' stdin args = Cmd.exec - "node" - ([ Path.concat [ oldCwd, "bin", "index.dev.js" ] ] <> args) + (Path.global "node") + ([ Path.toRaw $ oldCwd "bin" "index.dev.js" ] <> args) $ Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, pipeStdin = stdin } spago = spago' StdinNewPipe @@ -74,10 +77,10 @@ withTempDir = Aff.bracket createTempDir cleanupTempDir } cleanupTempDir { oldCwd } = do - liftEffect $ Process.chdir oldCwd + Paths.chdir oldCwd -rmRf :: ∀ m. MonadAff m => FilePath -> m Unit -rmRf dir = liftAff $ FS.Aff.rm' dir { force: true, recursive: true, maxRetries: 5, retryDelay: 1000 } +rmRf :: ∀ m path. MonadAff m => IsPath path => path -> m Unit +rmRf dir = liftAff $ FS.Aff.rm' (toRaw dir) { force: true, recursive: true, maxRetries: 5, retryDelay: 1000 } shouldEqual :: forall m t @@ -111,16 +114,16 @@ shouldEqualStr v1 v2 = , "" ] -checkFixture :: String -> String -> Aff Unit +checkFixture :: ∀ path. IsPath path => path -> FixturePath -> Aff Unit checkFixture filepath fixturePath = checkFixture' filepath fixturePath (shouldEqualStr `on` String.trim) -checkFixture' :: String -> String -> (String -> String -> Aff Unit) -> Aff Unit +checkFixture' :: ∀ path. IsPath path => path -> FixturePath -> (String -> String -> Aff Unit) -> Aff Unit checkFixture' filepath fixturePath assertEqual = do filecontent <- FS.readTextFile filepath overwriteSpecFile <- liftEffect $ map isJust $ Process.lookupEnv "SPAGO_TEST_ACCEPT" if overwriteSpecFile then do - Console.log $ "Overwriting fixture at path: " <> fixturePath - let parentDir = dirname fixturePath + Console.log $ "Overwriting fixture at path: " <> Path.quote fixturePath + let parentDir = Path.dirname fixturePath unlessM (FS.exists parentDir) $ FS.mkdirp parentDir FS.writeTextFile fixturePath (String.trim filecontent <> "\n") else do @@ -174,8 +177,8 @@ checkOutputsStr checkers = } checkOutputs - :: { stdoutFile :: Maybe FilePath - , stderrFile :: Maybe FilePath + :: { stdoutFile :: Maybe FixturePath + , stderrFile :: Maybe FixturePath , result :: (Either ExecResult ExecResult) -> Boolean } -> Either ExecResult ExecResult @@ -183,8 +186,8 @@ checkOutputs checkOutputs args = checkOutputs' $ args `merge` { sanitize: String.trim } checkOutputs' - :: { stdoutFile :: Maybe FilePath - , stderrFile :: Maybe FilePath + :: { stdoutFile :: Maybe FixturePath + , stderrFile :: Maybe FixturePath , result :: (Either ExecResult ExecResult) -> Boolean , sanitize :: String -> String } @@ -198,8 +201,8 @@ checkOutputs' checkers execResult = do let actual = checkers.sanitize actual' overwriteSpecFile <- liftEffect $ map isJust $ Process.lookupEnv "SPAGO_TEST_ACCEPT" if overwriteSpecFile then do - Console.log $ "Overwriting fixture at path: " <> fixtureFileExpected - let parentDir = dirname fixtureFileExpected + Console.log $ "Overwriting fixture at path: " <> Path.quote fixtureFileExpected + let parentDir = Path.dirname fixtureFileExpected unlessM (FS.exists parentDir) $ FS.mkdirp parentDir FS.writeTextFile fixtureFileExpected (actual <> "\n") else do @@ -215,25 +218,25 @@ checkOutputs' checkers execResult = do shouldBeSuccess :: Either ExecaResult ExecaResult -> Aff Unit shouldBeSuccess = checkOutputs { stdoutFile: Nothing, stderrFile: Nothing, result: isRight } -shouldBeSuccessOutput :: FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeSuccessOutput :: FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeSuccessOutput outFixture = checkOutputs { stdoutFile: Just outFixture, stderrFile: Nothing, result: isRight } -shouldBeSuccessErr :: FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeSuccessErr :: FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeSuccessErr errFixture = checkOutputs { stdoutFile: Nothing, stderrFile: Just errFixture, result: isRight } -shouldBeSuccessOutputWithErr :: FilePath -> FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeSuccessOutputWithErr :: FixturePath -> FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeSuccessOutputWithErr outFixture errFixture = checkOutputs { stdoutFile: Just outFixture, stderrFile: Just errFixture, result: isRight } shouldBeFailure :: Either ExecaResult ExecaResult -> Aff Unit shouldBeFailure = checkOutputs { stdoutFile: Nothing, stderrFile: Nothing, result: isLeft } -shouldBeFailureOutput :: FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeFailureOutput :: FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeFailureOutput outFixture = checkOutputs { stdoutFile: Just outFixture, stderrFile: Nothing, result: isLeft } -shouldBeFailureErr :: FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeFailureErr :: FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeFailureErr errFixture = checkOutputs { stdoutFile: Nothing, stderrFile: Just errFixture, result: isLeft } -shouldBeFailureOutputWithErr :: FilePath -> FilePath -> Either ExecaResult ExecaResult -> Aff Unit +shouldBeFailureOutputWithErr :: FixturePath -> FixturePath -> Either ExecaResult ExecaResult -> Aff Unit shouldBeFailureOutputWithErr outFixture errFixture = checkOutputs { stdoutFile: Just outFixture, stderrFile: Just errFixture, result: isLeft } mkPackageName :: String -> PackageName @@ -255,14 +258,14 @@ writePursFile { moduleName, rest } = modNameLine = "module " <> moduleName <> " where" editSpagoYaml :: (Config -> Config) -> Aff Unit -editSpagoYaml = editSpagoYaml' "spago.yaml" +editSpagoYaml = editSpagoYaml' $ Path.global "spago.yaml" -editSpagoYaml' :: FilePath -> (Config -> Config) -> Aff Unit +editSpagoYaml' :: ∀ path. IsPath path => path -> (Config -> Config) -> Aff Unit editSpagoYaml' configPath f = do content <- liftAff $ FS.readYamlDocFile Config.configCodec configPath case content of Left err -> - Assert.fail $ "Failed to decode spago.yaml file at path " <> configPath <> "\n" <> err + Assert.fail $ "Failed to decode spago.yaml file at path " <> Path.quote configPath <> "\n" <> err Right { yaml: config } -> liftAff $ FS.writeYamlFile Config.configCodec configPath $ f config diff --git a/test/Spago/Build.purs b/test/Spago/Build.purs index fd47bb0ed..e37be5035 100644 --- a/test/Spago/Build.purs +++ b/test/Spago/Build.purs @@ -4,8 +4,6 @@ import Test.Prelude import Data.Foldable (fold) import Data.String as String -import Node.FS.Aff as FSA -import Node.Path as Path import Node.Platform as Platform import Node.Process as Process import Spago.Command.Init as Init @@ -40,35 +38,36 @@ spec = Spec.around withTempDir do spago [ "init", "--name", "aaa" ] >>= shouldBeSuccess spago [ "build", "--purs-args", "--json-errors" ] >>= shouldBeFailureErr (fixture "json-errors-err.txt") - Spec.it "can use a different output folder" \{ spago } -> do + Spec.it "can use a different output folder" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "build", "--output", "myOutput" ] >>= shouldBeSuccess - FS.exists "myOutput" `Assert.shouldReturn` true - FS.exists "output" `Assert.shouldReturn` false + FS.exists (testCwd "myOutput") `Assert.shouldReturn` true + FS.exists (testCwd "output") `Assert.shouldReturn` false - Spec.it "can build with a local custom package set" \{ spago, fixture } -> do + Spec.it "can build with a local custom package set" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - FS.unlink "spago.yaml" - FS.copyFileSync { src: fixture "local-package-set-config.yaml", dst: "spago.yaml" } - FS.copyFileSync { src: fixture "local-package-set.json", dst: "local-package-set.json" } + FS.unlink (testCwd "spago.yaml") + FS.copyFile { src: fixture "local-package-set-config.yaml", dst: testCwd "spago.yaml" } + FS.copyFile { src: fixture "local-package-set.json", dst: testCwd "local-package-set.json" } spago [ "build" ] >>= shouldBeSuccess - Spec.it "can build with a local custom package set in a parent directory" \{ spago, fixture } -> do - FS.copyFileSync { src: fixture "local-package-set.json", dst: "local-package-set.json" } - FS.mkdirp "subdir" - liftEffect $ Process.chdir "subdir" + Spec.it "can build with a local custom package set in a parent directory" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "local-package-set.json", dst: testCwd "local-package-set.json" } + let subdir = testCwd "subdir" + FS.mkdirp subdir + Paths.chdir subdir spago [ "init" ] >>= shouldBeSuccess - FS.unlink "spago.yaml" - FS.copyFileSync { src: fixture "local-package-set-config2.yaml", dst: "spago.yaml" } + FS.unlink $ subdir "spago.yaml" + FS.copyFile { src: fixture "local-package-set-config2.yaml", dst: subdir "spago.yaml" } spago [ "build" ] >>= shouldBeSuccess - Spec.it "there's only one output folder in a monorepo" \{ spago } -> do + Spec.it "there's only one output folder in a monorepo" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp (Path.concat [ "subpackage", "src" ]) - FS.mkdirp (Path.concat [ "subpackage", "test" ]) - FS.writeTextFile (Path.concat [ "subpackage", "src", "Main.purs" ]) (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile (Path.concat [ "subpackage", "test", "Main.purs" ]) (Init.testMainTemplate "Subpackage.Test.Main") - FS.writeYamlFile Config.configCodec (Path.concat [ "subpackage", "spago.yaml" ]) + FS.mkdirp $ testCwd "subpackage" "src" + FS.mkdirp $ testCwd "subpackage" "test" + FS.writeTextFile (testCwd "subpackage" "src" "Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (testCwd "subpackage" "test" "Main.purs") (Init.testMainTemplate "Subpackage.Test.Main") + FS.writeYamlFile Config.configCodec (testCwd "subpackage" "spago.yaml") ( Init.defaultConfig { name: mkPackageName "subpackage" , testModuleName: "Subpackage.Test.Main" @@ -77,12 +76,12 @@ spec = Spec.around withTempDir do ) spago [ "build" ] >>= shouldBeSuccess spago [ "build", "-p", "subpackage" ] >>= shouldBeSuccess - FS.exists "output" `Assert.shouldReturn` true - FS.exists (Path.concat [ "subpackage", "output" ]) `Assert.shouldReturn` false + FS.exists (testCwd "output") `Assert.shouldReturn` true + FS.exists (testCwd "subpackage" "output") `Assert.shouldReturn` false - Spec.it "--strict causes build to fail if there are warnings" \{ spago, fixture } -> do + Spec.it "--strict causes build to fail if there are warnings" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - let srcMain = Path.concat [ "src", "Main.purs" ] + let srcMain = testCwd "src" "Main.purs" FS.unlink srcMain FS.copyFile { src: fixture "check-strict.purs" @@ -90,11 +89,11 @@ spec = Spec.around withTempDir do } spago [ "build", "--strict" ] >>= shouldBeFailure - Spec.it "having 'strict: true' in a package config fails the build if there are warnings" \{ spago, fixture } -> do + Spec.it "having 'strict: true' in a package config fails the build if there are warnings" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess let - srcMain = Path.concat [ "src", "Main.purs" ] - spagoYaml = "spago.yaml" + srcMain = testCwd "src" "Main.purs" + spagoYaml = testCwd "spago.yaml" FS.unlink srcMain FS.copyFile { src: fixture "check-strict.purs" @@ -112,8 +111,8 @@ spec = Spec.around withTempDir do spago [ "build" ] >>= shouldBeSuccess spago [ "build", "--censor-stats" ] >>= shouldBeSuccessErr (fixture "censor-stats-output.txt") - Spec.it "should censor warnings with given errorcode and prefix messsage" \{ spago, fixture } -> do - FS.copyTree { src: fixture "build/censor-warnings", dst: "." } + Spec.it "should censor warnings with given errorcode and prefix messsage" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "build/censor-warnings", dst: testCwd "." } let remainingWarningPath = [ escapePathInErrMsg [ "src", "Main.purs:5:1" ] ] @@ -132,23 +131,23 @@ spec = Spec.around withTempDir do } Spec.describe "lockfile" do - Spec.it "building with a lockfile doesn't need the Registry repo" \{ spago, fixture } -> do + Spec.it "building with a lockfile doesn't need the Registry repo" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "33.0.0" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess -- Check that we have written the lockfile - checkFixture "spago.lock" (fixture "spago.lock") + checkFixture (testCwd "spago.lock") (fixture "spago.lock") -- Then remove the registry repo - FSA.rm' Paths.registryPath { force: true, recursive: true, retryDelay: 0, maxRetries: 0 } + rmRf Paths.registryPath -- And check that we can still build spago [ "build" ] >>= shouldBeSuccess -- And that we still don't have the registry FS.exists Paths.registryPath `Assert.shouldReturn` false - Spec.it "using the --pure flag does not refresh the lockfile" \{ spago, fixture } -> do + Spec.it "using the --pure flag does not refresh the lockfile" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "33.0.0" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess -- Check that we have written the lockfile - checkFixture "spago.lock" (fixture "spago.lock") + checkFixture (testCwd "spago.lock") (fixture "spago.lock") -- Update the config let conf = Init.defaultConfig @@ -156,22 +155,22 @@ spec = Spec.around withTempDir do , testModuleName: "Test.Main" , withWorkspace: Just { setVersion: Just $ mkVersion "33.0.0" } } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") (conf { package = conf.package # map (\pkg -> pkg { dependencies = pkg.dependencies <> mkDependencies [ "maybe" ] }) }) -- Check that building with --pure does not refresh the lockfile spago [ "build", "--pure" ] >>= shouldBeSuccess - checkFixture "spago.lock" (fixture "spago.lock") + checkFixture (testCwd "spago.lock") (fixture "spago.lock") - Spec.it "lockfile is refreshed when the local package set changes" \{ spago, fixture } -> do - FS.copyTree { src: fixture "build/local-package-set-lockfile", dst: "." } + Spec.it "lockfile is refreshed when the local package set changes" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "build/local-package-set-lockfile", dst: testCwd "." } spago [ "build" ] >>= shouldBeSuccess - checkFixture "spago.lock" (fixture "build/local-package-set-lockfile/spago.lock.old") - FS.moveSync { src: "local-package-set.json", dst: "old-package-set.json" } - FS.moveSync { src: "new-package-set.json", dst: "local-package-set.json" } + checkFixture (testCwd "spago.lock") (fixture "build/local-package-set-lockfile/spago.lock.old") + FS.moveSync { src: testCwd "local-package-set.json", dst: testCwd "old-package-set.json" } + FS.moveSync { src: testCwd "new-package-set.json", dst: testCwd "local-package-set.json" } spago [ "build" ] >>= shouldBeSuccess - checkFixture "spago.lock" (fixture "build/local-package-set-lockfile/spago.lock.new") + checkFixture (testCwd "spago.lock") (fixture "build/local-package-set-lockfile/spago.lock.new") - Spec.it "compiles with the specified backend" \{ spago, fixture } -> do + Spec.it "compiles with the specified backend" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess let conf = Init.defaultConfig @@ -181,42 +180,42 @@ spec = Spec.around withTempDir do { setVersion: Just $ mkVersion "0.0.1" } } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") (conf { workspace = conf.workspace # map (_ { backend = Just { cmd: "echo", args: Just [ "hello" ] } }) }) spago [ "build" ] >>= shouldBeSuccess spago [ "run" ] >>= shouldBeSuccessErr (fixture "alternate-backend-output.txt") -- We also make sure that no js files are produced, only corefn - FS.exists "output/Main/index.js" `Assert.shouldReturn` false - FS.exists "output/Main/corefn.json" `Assert.shouldReturn` true + FS.exists (testCwd "output" "Main" "index.js") `Assert.shouldReturn` false + FS.exists (testCwd "output" "Main" "corefn.json") `Assert.shouldReturn` true Spec.it "passing the --codegen flag to purs fails" \{ spago, fixture } -> do spago [ "init", "--name", "7368613235362d68766258694c614d517a3667747a58725778" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "build", "--purs-args", "--codegen", "--purs-args", "corefn" ] >>= shouldBeFailureErr (fixture "codegen-opt.txt") - Spec.it "passing the --ensure-ranges flag without package selection adds ranges to root package when it exists" \{ spago } -> do + Spec.it "passing the --ensure-ranges flag without package selection adds ranges to root package when it exists" \{ spago, testCwd } -> do spago [ "init", "--package-set", "0.0.1" ] >>= shouldBeSuccess spago [ "build", "--ensure-ranges" ] >>= shouldBeSuccess - spagoYaml <- FS.readTextFile "spago.yaml" + spagoYaml <- FS.readTextFile (testCwd "spago.yaml") spagoYaml `shouldContain` "- prelude: \">=6.0.1 <7.0.0\"" - Spec.it "failed build with many warnings and --json-errors does not truncate output" \{ spago, fixture } -> do - FS.copyTree { src: fixture "build/json-truncated-many-warnings", dst: "." } + Spec.it "failed build with many warnings and --json-errors does not truncate output" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "build/json-truncated-many-warnings", dst: testCwd "." } spago [ "build", "--json-errors" ] >>= shouldBeFailureOutput case Process.platform of Just Platform.Win32 -> fixture "build/json-truncated-many-warnings/warnings-windows.json" _ -> fixture "build/json-truncated-many-warnings/warnings.json" - Spec.it "building with old-format config files works, as well as migrating them" \{ spago, fixture } -> do - FS.copyTree { src: fixture "build/migrate-config", dst: "." } + Spec.it "building with old-format config files works, as well as migrating them" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "build/migrate-config", dst: testCwd "." } spago [ "build" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccessErr (fixture "build/migrate-config/unmigrated-warning.txt") spago [ "build", "--migrate" ] >>= shouldBeSuccessErr (fixture "build/migrate-config/migrating-output.txt") spago [ "build" ] >>= shouldBeSuccessErr (fixture "build/migrate-config/migrated-output.txt") - checkFixture "spago.yaml" (fixture "build/migrate-config/migrated-spago.yaml") + checkFixture (testCwd "spago.yaml") (fixture "build/migrate-config/migrated-spago.yaml") - Spec.it "#1148: outputs errors and warnings after build" \{ spago, fixture } -> do + Spec.it "#1148: outputs errors and warnings after build" \{ spago, fixture, testCwd } -> do let shouldBeSuccessErr' = checkOutputsWithPathSeparatorPatchErr isRight shouldBeFailureErr' = checkOutputsWithPathSeparatorPatchErr isLeft @@ -232,13 +231,13 @@ spec = Spec.around withTempDir do >>> String.replaceAll (String.Pattern $ "\r\n") (String.Replacement "\n") } - FS.copyTree { src: fixture "build/1148-warnings-diff-errors", dst: "." } + FS.copyTree { src: fixture "build/1148-warnings-diff-errors", dst: testCwd "." } - liftEffect $ Process.chdir "errors" + Paths.chdir $ testCwd "errors" spago [ "install" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeFailureErr' "build/1148-warnings-diff-errors/errors/expected-stderr.txt" - liftEffect $ Process.chdir "../warnings" + Paths.chdir $ testCwd "warnings" spago [ "install" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccessErr' "build/1148-warnings-diff-errors/warnings/expected-stderr.txt" diff --git a/test/Spago/Build/BuildInfo.purs b/test/Spago/Build/BuildInfo.purs index 4fa0eb863..eb8421cb2 100644 --- a/test/Spago/Build/BuildInfo.purs +++ b/test/Spago/Build/BuildInfo.purs @@ -6,13 +6,13 @@ import Control.Monad.Error.Class as MonadError import Data.Array as Array import Data.DateTime.Instant as Instant import Effect.Exception as Exception -import Node.Path as Path import Registry.Version as Version import Spago.Command.Init (DefaultConfigOptions(..)) import Spago.Command.Init as Init import Spago.Core.Config as Config import Spago.FS as FS import Spago.Log (LogVerbosity(..)) +import Spago.Path as Path import Spago.Purs (getPurs) import Test.Spec (SpecT) import Test.Spec as Spec @@ -61,8 +61,8 @@ spec = setupSinglePackage spago = do spago [ "init", "--name", packageName ] >>= shouldBeSuccess - FS.writeTextFile (Path.concat [ "src", "Main.purs" ]) $ writeMain srcAndTestContent - FS.writeTextFile (Path.concat [ "test", "Test", "Main.purs" ]) $ writeTestMain srcAndTestContent + FS.writeTextFile (Path.global "src/Main.purs") $ writeMain srcAndTestContent + FS.writeTextFile (Path.global "test/Test/Main.purs") $ writeTestMain srcAndTestContent Spec.it ("'spago build' works") \{ spago } -> do setupSinglePackage spago @@ -93,26 +93,27 @@ spec = let packages = [ "foo", "bar", "baz" ] setupPolyrepo = do - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (Path.global "spago.yaml") $ Init.defaultConfig' $ WorkspaceOnly { setVersion: Just $ unsafeFromRight $ Version.parse "0.0.1" } for_ packages \packageName -> do - FS.mkdirp packageName - FS.writeYamlFile Config.configCodec (Path.concat [ packageName, "spago.yaml" ]) + let package = Path.global packageName + FS.mkdirp package + FS.writeYamlFile Config.configCodec (package "spago.yaml") $ mkPackageOnlyConfig { packageName, srcDependencies: [ "prelude", "effect", "console" ] } [ configAddTestMain ] let - src = Path.concat [ packageName, "src" ] - test = Path.concat [ packageName, "test", "Test" ] + src = package "src" + test = package "test" "Test" fileContent = pursModuleUsingBuildInfo packages FS.mkdirp src FS.mkdirp test - FS.writeTextFile (Path.concat [ src, "Main.purs" ]) $ writePursFile + FS.writeTextFile (src "Main.purs") $ writePursFile { moduleName: mkSrcModuleName packageName , rest: fileContent } - FS.writeTextFile (Path.concat [ test, "Main.purs" ]) $ writePursFile + FS.writeTextFile (test "Main.purs") $ writePursFile { moduleName: mkTestModuleName packageName , rest: fileContent } diff --git a/test/Spago/Build/Monorepo.purs b/test/Spago/Build/Monorepo.purs index 01828c011..257d45be0 100644 --- a/test/Spago/Build/Monorepo.purs +++ b/test/Spago/Build/Monorepo.purs @@ -8,10 +8,9 @@ import Data.String as String import Data.String.Regex as Regex import Data.String.Regex.Flags as Regex.Flags import Effect.Aff (bracket) -import Node.Path as Path -import Node.Process as Process import Spago.Cmd as Cmd import Spago.FS as FS +import Spago.Path as Path import Spago.Paths as Paths import Test.Spec (SpecT) import Test.Spec as Spec @@ -35,8 +34,8 @@ spec = Spec.describe "monorepo" do end ``` -} - Spec.it "Case 1: 'independent packages' builds" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/case1-independent-packages", dst: "." } + Spec.it "Case 1: 'independent packages' builds" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/case1-independent-packages", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess {- @@ -51,8 +50,8 @@ spec = Spec.describe "monorepo" do end ``` -} - Spec.it "Case 2: 'shared dependencies packages' builds" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/case2-shared-dependencies1", dst: "." } + Spec.it "Case 2: 'shared dependencies packages' builds" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/case2-shared-dependencies1", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess {- @@ -68,10 +67,10 @@ spec = Spec.describe "monorepo" do end ``` -} - Spec.it "Case 3: 'dependencies: A&B -> C; A -> B' builds" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/case3-shared-dependencies2", dst: "." } + Spec.it "Case 3: 'dependencies: A&B -> C; A -> B' builds" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/case3-shared-dependencies2", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess - checkFixture "spago.lock" (fixture "polyrepo.lock") + checkFixture (testCwd "spago.lock") (fixture "polyrepo.lock") {- ```mermaid @@ -83,8 +82,8 @@ spec = Spec.describe "monorepo" do end ``` -} - Spec.it "declaring 2+ modules with the same name across 2+ packages fails to build" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/unique-module-names", dst: "." } + Spec.it "declaring 2+ modules with the same name across 2+ packages fails to build" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/unique-module-names", dst: Path.toGlobal testCwd } let sameModuleName = "SameModuleName" hasExpectedModules stdErr = do @@ -103,33 +102,33 @@ spec = Spec.describe "monorepo" do end ``` -} - Spec.it "#1161 regression: 'subpackage with disjoint git dependency' builds" \{ spago, fixture } -> do + Spec.it "#1161 regression: 'subpackage with disjoint git dependency' builds" \{ spago, fixture, testCwd } -> do -- This corner case happens only under very specific conditions: -- 1. there must be a root package -- 2. one of the dependencies of the subpackage must be fetched from git. -- This is a problem only when the git dependency is not a dependency of the root package. -- 3. the workspace needs to contain a subpackage that is using the git dependency - FS.copyTree { src: fixture "monorepo/1161-regression", dst: "." } + FS.copyTree { src: fixture "monorepo/1161-regression", dst: Path.toGlobal testCwd } -- Lastly, this broke only when building the root package spago [ "build", "-p", "root" ] >>= shouldBeSuccess -- Or getting its graph spago [ "uninstall", "-p", "root", "console", "effect", "prelude" ] >>= shouldBeSuccess spago [ "build", "-p", "root", "--pedantic-packages" ] >>= shouldBeSuccess - Spec.it "ignore nested workspaces" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/ignore-nested-workspaces", dst: "." } + Spec.it "ignore nested workspaces" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/ignore-nested-workspaces", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccessErr (fixture "monorepo/ignore-nested-workspaces/expected-stderr.txt") - Spec.it "it's possible to reference local packages when using the solver" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/local-packages-work-with-solver", dst: "." } + Spec.it "it's possible to reference local packages when using the solver" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/local-packages-work-with-solver", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccessErr (fixture "monorepo/local-packages-work-with-solver/expected-stderr.txt") Spec.describe "warning censoring and error-promotion" do - Spec.it "build succeeds when 'strict: true' because warnings were censored" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/strict-true-censored-warnings", dst: "." } + Spec.it "build succeeds when 'strict: true' because warnings were censored" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/strict-true-censored-warnings", dst: Path.toGlobal testCwd } let paths = [ escapePathInErrMsg [ "package-a", "src", "Main.purs:6:13" ] @@ -139,8 +138,8 @@ spec = Spec.describe "monorepo" do shouldNotHaveWarning = assertWarning paths false spago [ "build" ] >>= check { stdout: mempty, stderr: shouldNotHaveWarning, result: isRight } - Spec.it "build fails when 'strict: true' and warnings were not censored" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/strict-true-uncensored-warnings", dst: "." } + Spec.it "build fails when 'strict: true' and warnings were not censored" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/strict-true-uncensored-warnings", dst: Path.toGlobal testCwd } let errs = [ "[ERROR 1/2 UnusedName] " <> escapePathInErrMsg [ "package-b", "src", "Main.purs:6:13" ] @@ -151,16 +150,16 @@ spec = Spec.describe "monorepo" do Spec.describe "passing --ensure-ranges flag..." do - Spec.it "when root package exists adds ranges to the root package" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/ensure-ranges-root-package", dst: "." } + Spec.it "when root package exists adds ranges to the root package" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/ensure-ranges-root-package", dst: Path.toGlobal testCwd } spago [ "build", "--ensure-ranges" ] >>= shouldBeSuccess - spagoYaml <- FS.readTextFile "spago.yaml" + spagoYaml <- FS.readTextFile $ testCwd "spago.yaml" spagoYaml `AssertString.shouldContain` "- prelude: \">=6.0.1 <7.0.0\"" - Spec.it "when root package does not exist fails to build" \{ spago, fixture } -> do + Spec.it "when root package does not exist fails to build" \{ spago, fixture, testCwd } -> do -- Note: this needs to contain at least two subpackages, otherwise, spago will -- automatically select the only package available even if it's a non-root package. - FS.copyTree { src: fixture "monorepo/ensure-ranges-no-root-package", dst: "." } + FS.copyTree { src: fixture "monorepo/ensure-ranges-no-root-package", dst: Path.toGlobal testCwd } let hasNoRootPackageError stdErr = do @@ -213,8 +212,8 @@ spec = Spec.describe "monorepo" do , " " <> pkgModName ] - Spec.it "when package config has 'pedantic_packages: true', build fails with expected errors" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/pedantic-config", dst: "." } + Spec.it "when package config has 'pedantic_packages: true', build fails with expected errors" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/pedantic-config", dst: Path.toGlobal testCwd } let errs = @@ -233,8 +232,8 @@ spec = Spec.describe "monorepo" do Assert.fail $ "STDERR did not contain expected texts:\n" <> (Array.intercalate "\n\n" unfoundTexts) <> "\n\nStderr was:\n" <> stdErr spago [ "build" ] >>= check { stdout: mempty, stderr: hasExpectedErrors, result: isLeft } - Spec.it "passing --pedantic-packages overrides package and test configs" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/pedantic-flag", dst: "." } + Spec.it "passing --pedantic-packages overrides package and test configs" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/pedantic-flag", dst: Path.toGlobal testCwd } let errs = do @@ -248,17 +247,17 @@ spec = Spec.describe "monorepo" do Assert.fail $ "STDERR did not contain expected texts:\n" <> (Array.intercalate "\n\n" unfoundTexts) <> "\n\nStderr was:\n" <> stdErr spago [ "build", "--pedantic-packages" ] >>= check { stdout: mempty, stderr: hasExpectedErrors, result: isLeft } - Spec.it "prevents cross-package imports between local packages" \{ spago, fixture } -> do - FS.copyTree { src: fixture "monorepo/pedantic-cross-package-imports", dst: "." } + Spec.it "prevents cross-package imports between local packages" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "monorepo/pedantic-cross-package-imports", dst: Path.toGlobal testCwd } spago [ "build" ] >>= shouldBeSuccess spago [ "build", "--pedantic-packages" ] >>= shouldBeFailureErr (fixture "monorepo/pedantic-cross-package-imports/expected-stderr.txt") - Spec.it "#1208: clones a monorepo only once, even if multiple packages from it are needed" \{ spago, fixture, testCwd } -> do + Spec.it "#1208: clones a monorepo only once, even if multiple packages from it are needed" \testArgs@{ spago, fixture } -> do -- A local file system Git repo to use as a remote for Spago to clone from let createLibraryRepo = do - let libRepo = Path.concat [ Paths.paths.temp, "spago-1208" ] + let libRepo = Paths.paths.temp "spago-1208" whenM (FS.exists libRepo) $ rmRf libRepo FS.copyTree { src: fixture "monorepo/1208-no-double-cloning/library", dst: libRepo } git_ libRepo [ "init" ] @@ -273,28 +272,30 @@ spec = Spec.describe "monorepo" do bracket createLibraryRepo rmRf \libRepo -> do let + consumerDir = testArgs.testCwd "consumer" + libRepoStr = Path.toRaw libRepo recreateConsumerWorkspace = do - liftEffect $ Process.chdir testCwd - whenM (FS.exists "consumer") $ rmRf "consumer" - FS.mkdirp "consumer" - liftEffect $ Process.chdir "consumer" + Paths.chdir testArgs.testCwd + whenM (FS.exists consumerDir) $ rmRf consumerDir + FS.mkdirp consumerDir + Paths.chdir consumerDir copySpagoYaml "spago-two-deps.yaml" copySpagoYaml src = do - whenM (FS.exists "spago.yaml") $ FS.unlink "spago.yaml" - whenM (FS.exists "spago.lock") $ FS.unlink "spago.lock" - content <- FS.readTextFile $ fixture "monorepo/1208-no-double-cloning/" <> src - FS.writeTextFile "spago.yaml" $ String.replaceAll (String.Pattern "") (String.Replacement libRepo) content + whenM (FS.exists $ consumerDir "spago.yaml") $ FS.unlink $ consumerDir "spago.yaml" + whenM (FS.exists $ consumerDir "spago.lock") $ FS.unlink $ consumerDir "spago.lock" + content <- FS.readTextFile $ fixture "monorepo/1208-no-double-cloning" src + FS.writeTextFile (consumerDir "spago.yaml") $ String.replaceAll (String.Pattern "") (String.Replacement libRepoStr) content assertRefCheckedOut package ref = do -- The `.spago/p//` should be a git repo checked out at `ref` - let path = Path.concat [ ".spago", "p", package, ref ] + let path = consumerDir ".spago" "p" package ref commitHash <- git path [ "rev-parse", ref ] git path [ "rev-parse", "HEAD" ] >>= flip shouldEqualStr commitHash -- And there should be a copy of that repo at -- `.spago/p//`, checked out at the same commit. - let commitHashPath = Path.concat [ ".spago", "p", package, commitHash ] + let commitHashPath = consumerDir ".spago" "p" package commitHash git commitHashPath [ "rev-parse", "HEAD" ] >>= flip shouldEqualStr commitHash shouldBeSuccessErr' = checkOutputsWithPatchErr isRight @@ -307,7 +308,7 @@ spec = Spec.describe "monorepo" do , result , sanitize: String.replaceAll (String.Pattern "\r\n") (String.Replacement "\n") - >>> String.replaceAll (String.Pattern libRepo) (String.Replacement "") + >>> String.replaceAll (String.Pattern libRepoStr) (String.Replacement "") >>> Regex.replace (unsafeFromRight $ Regex.regex "^purs compile: .*$" (Regex.Flags.global <> Regex.Flags.multiline)) "purs compile..." >>> String.trim } @@ -316,7 +317,7 @@ spec = Spec.describe "monorepo" do -- otherwise it may or may not appear in Spago's output and then we can't -- reliably compare it to golden output. recreateConsumerWorkspace - spago [ "ls", "packages" ] >>= shouldBeSuccess + spago [ "ls", "packages", "-v" ] >>= shouldBeSuccess -- Nuke the cache after that so Spago can re-clone the repositories and we -- can check that it's happening only once. @@ -358,14 +359,15 @@ spec = Spec.describe "monorepo" do -- Lockfile test: when it's up to date but the cache is not populated (i.e. a fresh clone) -- then there are no double clones. This is a regression test for #1206 spago [ "build" ] >>= shouldBeSuccess - rmRf ".spago" + rmRf $ consumerDir ".spago" spago [ "build" ] >>= shouldBeSuccessErr' "monorepo/1208-no-double-cloning/expected-stderr/lockfile-up-to-date.txt" where git_ cwd = void <<< git cwd + git :: ∀ path. Path.IsPath path => path -> _ git cwd args = do - let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just cwd } - res <- Cmd.exec "git" args opts + let opts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, cwd = Just $ Path.toGlobal cwd } + res <- Cmd.exec (Path.global "git") args opts res # shouldBeSuccess pure $ Cmd.getStdout res diff --git a/test/Spago/Build/Pedantic.purs b/test/Spago/Build/Pedantic.purs index 154a5586c..700952015 100644 --- a/test/Spago/Build/Pedantic.purs +++ b/test/Spago/Build/Pedantic.purs @@ -4,9 +4,9 @@ import Test.Prelude import Data.Array as Array import Data.Map as Map -import Node.Path as Path import Spago.Core.Config (Dependencies(..), Config) import Spago.FS as FS +import Spago.Path as Path import Test.Spec (SpecT) import Test.Spec as Spec @@ -18,8 +18,8 @@ spec = -- Here we are importing the `Control.Alt` module, which is in the `control` -- package, which comes through `maybe` but we are not importing directly - Spec.it "appear in the source package" \{ spago, fixture } -> do - setup spago + Spec.it "appear in the source package" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installSourcePackages = [ "maybe" ] , main = Just @@ -41,8 +41,8 @@ spec = -- We are importing `Control.Alt` in the test package, which is in the `control` -- package, which comes through `maybe` but we are not importing directly, so we -- should get a transitivity warning about that - Spec.it "appear in the test package" \{ spago, fixture } -> do - setup spago + Spec.it "appear in the test package" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installTestPackages = [ "maybe" ] , main = Just @@ -76,8 +76,8 @@ spec = -- Here we install `effect` and `console` in the test package, and we don't use them -- in the source, so we should get an "unused" warning about them - Spec.it "in a source package" \{ spago, fixture } -> do - setup spago + Spec.it "in a source package" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installTestPackages = [ "effect", "console" ] , main = Just @@ -93,8 +93,8 @@ spec = -- Here we do not install `effect` and `console` in the test package, and we don't use them -- in the source, so we should get an "unused" warning about them for the source, and a prompt -- to install them in test - Spec.it "in a source package, but they are used in test" \{ spago, fixture } -> do - setup spago + Spec.it "in a source package, but they are used in test" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { main = Just [ "import Prelude" @@ -107,8 +107,8 @@ spec = spago [ "build" ] >>= shouldBeFailureErr (fixture "pedantic/check-unused-dependency.txt") -- Complain about the unused `newtype` dependency in the test package - Spec.it "in a test package" \{ spago, fixture } -> do - setup spago + Spec.it "in a test package" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installTestPackages = [ "newtype" ] , testMain = Just @@ -126,8 +126,8 @@ spec = >>= shouldBeFailureErr (fixture "pedantic/check-unused-test-dependency.txt") -- `console` and `effect` are going to be unused for both source and test packages - Spec.it "in both the source and test packages" \{ spago, fixture } -> do - setup spago + Spec.it "in both the source and test packages" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installSourcePackages = [ "prelude", "effect", "console" ] , installTestPackages = [ "prelude", "effect", "console" ] @@ -157,8 +157,8 @@ spec = -- so consider that fixed -- * `either` is transitively imported, and it's going to be removed from the source -- dependencies, so we get a "transitive" warning to install it in test - Spec.it "fails to build and reports deduplicated src and test unused/transitive dependencies" \{ spago, fixture } -> do - setup spago + Spec.it "fails to build and reports deduplicated src and test unused/transitive dependencies" \{ spago, fixture, testCwd } -> do + setup testCwd spago ( defaultSetupConfig { installSourcePackages = [ "prelude", "control", "either" ] , installTestPackages = [ "tuples" ] @@ -191,8 +191,8 @@ spec = -- So, if we don't have `effect` as a direct dependency, we'll get a pedantic error -- where the fix is to install that missing package. -- Following those instructions shouldn't cause an error. - Spec.it "following installation instructions does not fail with an unrelated pedantic error" \{ spago, fixture } -> do - FS.copyTree { src: fixture "pedantic/follow-instructions", dst: "." } + Spec.it "following installation instructions does not fail with an unrelated pedantic error" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "pedantic/follow-instructions", dst: testCwd } spago [ "uninstall", "effect" ] >>= shouldBeSuccess -- Get rid of "Compiling..." messages spago [ "build" ] >>= shouldBeSuccess @@ -204,10 +204,9 @@ spec = let gitignores = [".spago", "/.spago", ".spago/**"] for_ gitignores \gitignore -> Spec.it - (".gitignore does not affect discovery of transitive deps (" <> gitignore <> ")") - \{ spago, fixture } -> do - FS.copyTree { src: fixture "pedantic/follow-instructions", dst: "." } - FS.writeTextFile ".gitignore" gitignore + (".gitignore does not affect discovery of transitive deps (" <> gitignore <> ")") \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "pedantic/follow-instructions", dst: testCwd } + FS.writeTextFile (Path.global ".gitignore") gitignore spago [ "uninstall", "effect" ] >>= shouldBeSuccess -- Get rid of "Compiling..." messages spago [ "build" ] >>= shouldBeSuccess @@ -255,16 +254,16 @@ defaultSetupConfig = , testMain: Nothing } -setup :: (Array String -> Aff (Either ExecResult ExecResult)) -> SetupConfig -> Aff Unit -setup spago config = do +setup :: RootPath -> (Array String -> Aff (Either ExecResult ExecResult)) -> SetupConfig -> Aff Unit +setup root spago config = do spago [ "init", "--name", "pedantic" ] >>= shouldBeSuccess unless (Array.null config.installSourcePackages) do spago ([ "install" ] <> config.installSourcePackages) >>= shouldBeSuccess unless (Array.null config.installTestPackages) do spago ([ "install", "--test-deps" ] <> config.installTestPackages) >>= shouldBeSuccess for_ config.main \main -> - FS.writeTextFile (Path.concat [ "src", "Main.purs" ]) $ writeMain main + FS.writeTextFile (root "src" "Main.purs") $ writeMain main for_ config.testMain \testMain -> - FS.writeTextFile (Path.concat [ "test", "Test", "Main.purs" ]) $ writeTestMain testMain + FS.writeTextFile (root "test" "Test" "Main.purs") $ writeTestMain testMain -- get rid of "Compiling ..." messages and other compiler warnings spago [ "build" ] >>= shouldBeSuccess diff --git a/test/Spago/Bundle.purs b/test/Spago/Bundle.purs index b3e005927..d11e9051e 100644 --- a/test/Spago/Bundle.purs +++ b/test/Spago/Bundle.purs @@ -15,68 +15,68 @@ spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "bundle" do - Spec.it "bundles into an app (browser)" \{ spago, fixture } -> do + Spec.it "bundles into an app (browser)" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "bundle", "-v", "--bundle-type", "app", "--outfile", "bundle-app-browser.js" ] >>= shouldBeSuccess - checkBundle "bundle-app-browser.js" (fixture "bundle-app-browser.js") + checkBundle (testCwd "bundle-app-browser.js") (fixture "bundle-app-browser.js") - Spec.it "bundles into an app (node)" \{ spago, fixture } -> do + Spec.it "bundles into an app (node)" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "bundle", "-v", "--bundle-type", "app", "--outfile", "bundle-app-node.js", "--platform", "node" ] >>= shouldBeSuccess - checkBundle "bundle-app-node.js" (fixture "bundle-app-node.js") + checkBundle (testCwd "bundle-app-node.js") (fixture "bundle-app-node.js") - Spec.it "bundles into a module" \{ spago, fixture } -> do + Spec.it "bundles into a module" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "bundle", "--bundle-type=module", "--outfile", "bundle-module.js" ] >>= shouldBeSuccess - checkBundle "bundle-module.js" (fixture "bundle-module.js") + checkBundle (testCwd "bundle-module.js") (fixture "bundle-module.js") - Spec.it "bundles an app with source map (browser)" \{ spago, fixture } -> do + Spec.it "bundles an app with source map (browser)" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "bundle", "-v", "--outfile", "bundle-app-browser-map.js", "--source-maps", "--bundle-type", "app" ] >>= shouldBeSuccess - checkBundle "bundle-app-browser-map.js" (fixture "bundle-app-browser-map.js") - checkFixture "bundle-app-browser-map.js.map" (fixture "bundle-app-browser-map.js.map") + checkBundle (testCwd "bundle-app-browser-map.js") (fixture "bundle-app-browser-map.js") + checkFixture (testCwd "bundle-app-browser-map.js.map") (fixture "bundle-app-browser-map.js.map") - Spec.it "bundles an app with source map (node)" \{ spago, fixture } -> do + Spec.it "bundles an app with source map (node)" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "bundle", "-v", "--outfile", "bundle-app-node-map.js", "--source-maps", "--bundle-type", "app", "--platform", "node" ] >>= shouldBeSuccess - checkBundle "bundle-app-node-map.js" (fixture "bundle-app-node-map.js") - checkFixture "bundle-app-node-map.js.map" (fixture "bundle-app-node-map.js.map") + checkBundle (testCwd "bundle-app-node-map.js") (fixture "bundle-app-node-map.js") + checkFixture (testCwd "bundle-app-node-map.js.map") (fixture "bundle-app-node-map.js.map") - Spec.it "bundles a module with source map" \{ spago, fixture } -> do + Spec.it "bundles a module with source map" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "bundle", "--bundle-type", "module", "--outfile", "bundle-module-map.js", "--source-maps" ] >>= shouldBeSuccess - checkBundle "bundle-module-map.js" (fixture "bundle-module-map.js") - checkFixture "bundle-module-map.js.map" (fixture "bundle-module-map.js.map") + checkBundle (testCwd "bundle-module-map.js") (fixture "bundle-module-map.js") + checkFixture (testCwd "bundle-module-map.js.map") (fixture "bundle-module-map.js.map") - Spec.it "bundles a module with extra esbuild arguments" \{ spago, fixture } -> do + Spec.it "bundles a module with extra esbuild arguments" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "bundle", "--bundle-type", "module", "--outfile", "bundle-module-map.js", "--source-maps" ] >>= shouldBeSuccess - checkBundle "bundle-module-map.js" (fixture "bundle-module-map.js") - checkFixture "bundle-module-map.js.map" (fixture "bundle-module-map.js.map") + checkBundle (testCwd "bundle-module-map.js") (fixture "bundle-module-map.js") + checkFixture (testCwd "bundle-module-map.js.map") (fixture "bundle-module-map.js.map") - Spec.it "refuses to overwrite existing bundle that is not Spago-generated" \{ spago, fixture } -> do - FS.writeTextFile checkWatermarkMarkerFileName "." + Spec.it "refuses to overwrite existing bundle that is not Spago-generated" \{ spago, fixture, testCwd } -> do + FS.writeTextFile (testCwd checkWatermarkMarkerFileName) "." spago [ "init", "--name", "project" ] >>= shouldBeSuccess spago [ "bundle" ] >>= shouldBeSuccess spago [ "bundle" ] >>= shouldBeSuccess - FS.readTextFile "index.js" >>= \content -> content `shouldStartWith` "/* Generated by Spago" - FS.writeTextFile "index.js" "Bogus" + FS.readTextFile (testCwd "index.js") >>= \content -> content `shouldStartWith` "/* Generated by Spago" + FS.writeTextFile (testCwd "index.js") "Bogus" spago [ "bundle" ] >>= shouldBeFailureErr (fixture "bundle-refuse-overwrite-output.txt") - FS.readTextFile "index.js" >>= shouldEqual "Bogus" + FS.readTextFile (testCwd "index.js") >>= shouldEqual "Bogus" spago [ "bundle", "--force" ] >>= shouldBeSuccess - FS.readTextFile "index.js" >>= shouldNotEqual "Bogus" + FS.readTextFile (testCwd "index.js") >>= shouldNotEqual "Bogus" - Spec.it "overwrites non-Spago-generated bundle when there is no magic marker file" \{ spago, fixture } -> do + Spec.it "overwrites non-Spago-generated bundle when there is no magic marker file" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "project" ] >>= shouldBeSuccess spago [ "bundle" ] >>= shouldBeSuccess - FS.writeTextFile "index.js" "Bogus" + FS.writeTextFile (testCwd "index.js") "Bogus" spago [ "bundle" ] >>= shouldBeSuccess - checkBundle "index.js" (fixture "bundle-default.js") + checkBundle (testCwd "index.js") (fixture "bundle-default.js") where -- This is a version of `checkFixture`, but it replaces the "v0" placeholder diff --git a/test/Spago/Docs.purs b/test/Spago/Docs.purs index bd0be93a3..faa7d5134 100644 --- a/test/Spago/Docs.purs +++ b/test/Spago/Docs.purs @@ -11,17 +11,17 @@ spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "docs" do - Spec.it "documents successfully with no flags" \{ spago } -> do + Spec.it "documents successfully with no flags" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "docs" ] >>= shouldBeSuccess - FS.exists "generated-docs/html/index.html" `Assert.shouldReturn` true + FS.exists (testCwd "generated-docs" "html" "index.html") `Assert.shouldReturn` true - Spec.it "builds successfully a solver-only package" \{ spago } -> do + Spec.it "builds successfully a solver-only package" \{ spago, testCwd } -> do spago [ "init", "--name", "aaa", "--use-solver" ] >>= shouldBeSuccess spago [ "docs" ] >>= shouldBeSuccess - FS.exists "generated-docs/html/index.html" `Assert.shouldReturn` true + FS.exists (testCwd "generated-docs" "html" "index.html") `Assert.shouldReturn` true - Spec.it "can output ctags instead of html" \{ spago } -> do + Spec.it "can output ctags instead of html" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "docs", "--format", "ctags" ] >>= shouldBeSuccess - FS.exists "tags" `Assert.shouldReturn` true + FS.exists (testCwd "tags") `Assert.shouldReturn` true diff --git a/test/Spago/Errors.purs b/test/Spago/Errors.purs index 2830c95e8..6bf412413 100644 --- a/test/Spago/Errors.purs +++ b/test/Spago/Errors.purs @@ -13,21 +13,21 @@ spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "errors" do - Spec.it "fails with a spago.yml" \{ spago, fixture } -> do + Spec.it "fails with a spago.yml" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa" ] >>= shouldBeSuccess - FS.moveSync { src: "spago.yaml", dst: "spago.yml" } + FS.moveSync { src: testCwd "spago.yaml", dst: testCwd "spago.yml" } spago [ "build" ] >>= shouldBeFailureErr (fixture "spago-yml-check-stderr.txt") Spec.it "fails for package names that are too long" \{ spago, fixture } -> do let name = String.joinWith "" $ Array.replicate 256 "a" spago [ "init", "--name", name ] >>= shouldBeFailureErr (fixture "package-name-too-long-stderr.txt") - Spec.it "prints suggested package names when package is not found" \{ spago, fixture } -> do + Spec.it "prints suggested package names when package is not found" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "root" ] >>= shouldBeSuccess ["finder", "binder", "founder"] # traverse_ \name -> do - FS.mkdirp name - FS.writeTextFile (name <> "/spago.yaml") $ + FS.mkdirp $ testCwd name + FS.writeTextFile (testCwd name "spago.yaml") $ "{ package: { name: \"" <> name <> "\", dependencies: [] } }" spago [ "build", "-p", "inder" ] >>= shouldBeFailureErr (fixture "package-typo-suggestions/1.txt") diff --git a/test/Spago/Glob.purs b/test/Spago/Glob.purs index 45129b970..130623d73 100644 --- a/test/Spago/Glob.purs +++ b/test/Spago/Glob.purs @@ -5,23 +5,23 @@ import Test.Prelude import Data.Array as Array import Data.Foldable (intercalate) import Effect.Aff as Aff -import Node.Path as Path import Spago.FS as FS import Spago.Glob as Glob +import Spago.Path as Path import Test.Spec (Spec) import Test.Spec as Spec import Test.Spec.Assertions as Assert -globTmpDir :: (String -> Aff Unit) -> Aff Unit +globTmpDir :: (RootPath -> Aff Unit) -> Aff Unit globTmpDir m = Aff.bracket make cleanup m where - touch name base = FS.writeTextFile (Path.concat [ base, name ]) "" + touch name base = FS.writeTextFile (base name) "" dir name contents base = do - FS.mkdirp $ Path.concat [ base, name ] - for_ contents \f -> f $ Path.concat [ base, name ] + FS.mkdirp $ base name + for_ contents \f -> f =<< Path.mkRoot (base name) cleanup _ = pure unit make = do - base <- mkTemp' $ Just "spago-test-" + base <- Path.mkRoot =<< mkTemp' (Just "spago-test-") dir ".git" [ dir "fruits" [ touch "apple" ] ] @@ -49,46 +49,48 @@ spec = Spec.around globTmpDir do Spec.describe "glob" do Spec.describe "glob behavior" do Spec.it "'**/..' matches 0 or more directories" \p -> do - a <- Glob.gitignoringGlob (Path.concat [ p, "fruits/left" ]) [ "**/apple" ] - b <- Glob.gitignoringGlob (Path.concat [ p, "fruits" ]) [ "**/apple" ] - Array.sort a `Assert.shouldEqual` [ "apple" ] - Array.sort b `Assert.shouldEqual` [ "left/apple", "right/apple" ] + aRoot <- Path.mkRoot (p "fruits" "left") + bRoot <- Path.mkRoot (p "fruits") + a <- Glob.gitignoringGlob aRoot [ "**/apple" ] + b <- Glob.gitignoringGlob bRoot [ "**/apple" ] + sortedPaths a `Assert.shouldEqual` [ "apple" ] + sortedPaths b `Assert.shouldEqual` [ "left/apple", "right/apple" ] Spec.it "'../**/..' matches 0 or more directories" \p -> do a <- Glob.gitignoringGlob p [ "fruits/**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] Spec.it "'../**' matches 0 or more directories" \p -> do a <- Glob.gitignoringGlob p [ "fruits/left/**" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left", "fruits/left/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left", "fruits/left/apple" ] Spec.describe "gitignoringGlob" do Spec.it "when no .gitignore, yields all matches" \p -> do a <- Glob.gitignoringGlob p [ "**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple", "src/fruits/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple", "src/fruits/apple" ] Spec.it "respects a .gitignore pattern that doesn't conflict with search" \p -> do - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) "fruits/right" + FS.writeTextFile (p ".gitignore") "fruits/right" a <- Glob.gitignoringGlob p [ "fruits/**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple" ] Spec.it "respects some .gitignore patterns" \p -> do - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) "fruits\nfruits/right" + FS.writeTextFile (p ".gitignore") "fruits\nfruits/right" a <- Glob.gitignoringGlob p [ "fruits/**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple" ] Spec.it "respects a negated .gitignore pattern" \p -> do - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) "!/fruits/left/apple\n/fruits/**/apple" + FS.writeTextFile (p ".gitignore") "!/fruits/left/apple\n/fruits/**/apple" a <- Glob.gitignoringGlob p [ "**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "src/fruits/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "src/fruits/apple" ] for_ [ "/fruits", "fruits", "fruits/", "**/fruits", "fruits/**", "**/fruits/**" ] \gitignore -> do Spec.it ("does not respect a .gitignore pattern that conflicts with search: " <> gitignore) \p -> do - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) gitignore + FS.writeTextFile (p ".gitignore") gitignore a <- Glob.gitignoringGlob p [ "fruits/**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] Spec.it "is stacksafe" \p -> do let @@ -97,9 +99,12 @@ spec = Spec.around globTmpDir do words = [ \a b c d -> a <> b <> c <> d ] <*> chars <*> chars <*> chars <*> chars hugeGitignore = intercalate "\n" words -- Write it in a few places - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) hugeGitignore - FS.writeTextFile (Path.concat [ p, "fruits", ".gitignore" ]) hugeGitignore - FS.writeTextFile (Path.concat [ p, "fruits", "left", ".gitignore" ]) hugeGitignore - FS.writeTextFile (Path.concat [ p, "fruits", "right", ".gitignore" ]) hugeGitignore + FS.writeTextFile (p ".gitignore") hugeGitignore + FS.writeTextFile (p "fruits" ".gitignore") hugeGitignore + FS.writeTextFile (p "fruits" "left" ".gitignore") hugeGitignore + FS.writeTextFile (p "fruits" "right" ".gitignore") hugeGitignore a <- Glob.gitignoringGlob p [ "fruits/**/apple" ] - Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] + sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] + + where + sortedPaths = map show >>> Array.sort diff --git a/test/Spago/Init.purs b/test/Spago/Init.purs index 977ff68a1..f9aa267e3 100644 --- a/test/Spago/Init.purs +++ b/test/Spago/Init.purs @@ -2,7 +2,6 @@ module Test.Spago.Init where import Test.Prelude -import Node.Path as Path import Spago.FS as FS import Test.Spago.InitSubpackage as Subpackage import Test.Spec (Spec) @@ -16,15 +15,15 @@ spec = Spec.around withTempDir do Spec.it "sets up a project" \{ spago } -> do spago [ "init" ] >>= shouldBeSuccess - Spec.it "does not overwrite files when initing a project" \{ spago } -> do - FS.mkdirp "src" - FS.writeTextFile (Path.concat [ "src", "Main.purs" ]) "Something" + Spec.it "does not overwrite files when initing a project" \{ spago, testCwd } -> do + FS.mkdirp (testCwd "src") + FS.writeTextFile (testCwd "src" "Main.purs") "Something" spago [ "init" ] >>= shouldBeSuccess - fileContent <- FS.readTextFile (Path.concat [ "src", "Main.purs" ]) + fileContent <- FS.readTextFile (testCwd "src" "Main.purs") fileContent `Assert.shouldEqual` "Something" - Spec.it "should use user-specified tag if it exists instead of latest release" \({ spago, fixture } :: TestDirs) -> do + Spec.it "should use user-specified tag if it exists instead of latest release" \{ spago, fixture, testCwd } -> do spago [ "init", "--package-set", "9.0.0", "--name", "7368613235362d47665357393342584955783641314b70674c" ] >>= shouldBeSuccess - checkFixture "spago.yaml" (fixture "older-package-set-tag.yaml") + checkFixture (testCwd "spago.yaml") (fixture "older-package-set-tag.yaml") Subpackage.spec diff --git a/test/Spago/InitSubpackage.purs b/test/Spago/InitSubpackage.purs index 567b4dae1..1cf7befd3 100644 --- a/test/Spago/InitSubpackage.purs +++ b/test/Spago/InitSubpackage.purs @@ -4,8 +4,8 @@ import Test.Prelude import Data.String.Regex as Regex import Data.String.Regex.Flags as RF -import Node.Path as Path import Spago.FS as FS +import Spago.Path (withForwardSlashes') import Test.Spago.Cli (sanitizeCliHelpOutput) import Test.Spec (SpecT) import Test.Spec as Spec @@ -13,32 +13,32 @@ import Test.Spec as Spec spec :: SpecT Aff TestDirs Identity Unit spec = Spec.describe "subpackage" do - Spec.it "sets up a sub-project in a subdirectory" \{ spago, fixture } -> do + Spec.it "sets up a sub-project in a subdirectory" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "init", "--subpackage", "subdir" ] >>= shouldBeSuccess - checkFixture "subdir/spago.yaml" (fixture "init/subpackage/subdir-spago.yaml") + checkFixture (testCwd "subdir" "spago.yaml") (fixture "init/subpackage/subdir-spago.yaml") spago [ "init", "--subpackage", "subdir2" ] >>= shouldBeSuccess - checkFixture "subdir2/spago.yaml" (fixture "init/subpackage/subdir2-spago.yaml") + checkFixture (testCwd "subdir2" "spago.yaml") (fixture "init/subpackage/subdir2-spago.yaml") - Spec.it "does not overwrite existing files" \{ spago, fixture } -> do + Spec.it "does not overwrite existing files" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subdir/src" - FS.writeTextFile (Path.concat [ "subdir", "src", "Main.purs" ]) "Something" + FS.mkdirp $ testCwd "subdir" "src" + FS.writeTextFile (testCwd "subdir" "src" "Main.purs") "Something" spago [ "init", "--subpackage", "subdir" ] >>= shouldBeSuccessErr' (fixture "init/subpackage/existing-src-file.txt") - fileContent <- FS.readTextFile (Path.concat [ "subdir", "src", "Main.purs" ]) + fileContent <- FS.readTextFile (testCwd "subdir" "src" "Main.purs") fileContent `shouldEqualStr` "Something" - Spec.it "warns when --package-set or --use-solver flags are used" \{ spago, fixture } -> do + Spec.it "warns when --package-set or --use-solver flags are used" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "init", "--package-set", "9.0.0", "--subpackage", "subdir" ] >>= shouldBeSuccessErr' (fixture "init/subpackage/package-set-solver-warning.txt") - checkFixture "subdir/spago.yaml" (fixture "init/subpackage/subdir-spago.yaml") + checkFixture (testCwd "subdir" "spago.yaml") (fixture "init/subpackage/subdir-spago.yaml") spago [ "init", "--use-solver", "--subpackage", "subdir" ] >>= shouldBeSuccessErr' (fixture "init/subpackage/package-set-solver-warning-existing-files.txt") - checkFixture "subdir/spago.yaml" (fixture "init/subpackage/subdir-spago.yaml") + checkFixture (testCwd "subdir" "spago.yaml") (fixture "init/subpackage/subdir-spago.yaml") Spec.it "does not allow both --name and --subpackage flags" \{ spago, fixture } -> do spago [ "init", "--name", "foo", "--subpackage", "bar" ] @@ -55,7 +55,7 @@ spec = , result , sanitize: sanitizeCliHelpOutput - >>> withForwardSlashes + >>> withForwardSlashes' >>> Regex.replace versionsRegex "Found PureScript a.b.c, will use package set x.y.z" } diff --git a/test/Spago/Install.purs b/test/Spago/Install.purs index 4e310c631..c911d44da 100644 --- a/test/Spago/Install.purs +++ b/test/Spago/Install.purs @@ -5,13 +5,12 @@ import Test.Prelude import Data.Array as Array import Data.Map as Map import Effect.Now as Now -import Node.FS.Aff as FSA -import Node.Path as Path import Registry.Version as Version import Spago.Command.Init as Init import Spago.Core.Config as Config import Spago.FS as FS import Spago.Log (LogVerbosity(..)) +import Spago.Path as Path import Spago.Paths as Paths import Spago.Purs as Purs import Test.Spec (Spec) @@ -36,19 +35,19 @@ spec = Spec.around withTempDir do -- -- dep added without "purescript-" prefix -- checkFixture "spago.yaml" (fixture "spago-strips-purescript.yaml") - Spec.it "adds dependencies to the config file" \{ spago, fixture } -> do + Spec.it "adds dependencies to the config file" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "29.3.0" ] >>= shouldBeSuccess spago [ "install", "foreign" ] >>= shouldBeSuccess - checkFixture "spago.yaml" (fixture "spago-install-success.yaml") + checkFixture (testCwd "spago.yaml") (fixture "spago-install-success.yaml") - Spec.it "adds test dependencies to the config file" \{ spago, fixture } -> do + Spec.it "adds test dependencies to the config file" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "29.3.0" ] >>= shouldBeSuccess spago [ "install", "--test-deps", "foreign" ] >>= shouldBeSuccess - checkFixture "spago.yaml" (fixture "spago-install-test-deps-success.yaml") + checkFixture (testCwd "spago.yaml") (fixture "spago-install-test-deps-success.yaml") - Spec.it "adds test dependencies to the config file when the test section does not exist" \{ spago, fixture } -> do + Spec.it "adds test dependencies to the config file when the test section does not exist" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "29.3.0" ] >>= shouldBeSuccess - let spagoYaml = "spago.yaml" + let spagoYaml = testCwd "spago.yaml" FS.unlink spagoYaml FS.copyFile { src: fixture "no-test-section.yaml" @@ -57,12 +56,12 @@ spec = Spec.around withTempDir do spago [ "install", "--test-deps", "foreign" ] >>= shouldBeSuccess checkFixture spagoYaml (fixture "spago-install-test-deps-success.yaml") - Spec.it "can't add dependencies that are not in the package set" \{ spago, fixture } -> do + Spec.it "can't add dependencies that are not in the package set" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaaa", "--package-set", "29.3.0" ] >>= shouldBeSuccess spago [ "install", "foo-foo-foo", "bar-bar-bar", "effcet", "arrys" ] >>= shouldBeFailureErr (fixture "missing-dependencies.txt") - checkFixture "spago.yaml" (fixture "spago-install-failure.yaml") + checkFixture (testCwd "spago.yaml") (fixture "spago-install-failure.yaml") - Spec.it "does not allow circular dependencies" \{ spago, fixture } -> do + Spec.it "does not allow circular dependencies" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess let conf = Init.defaultConfig @@ -72,7 +71,7 @@ spec = Spec.around withTempDir do } , testModuleName: "Test.Main" } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") ( conf { workspace = conf.workspace # map ( _ @@ -96,14 +95,14 @@ spec = Spec.around withTempDir do ) spago [ "install", "a", "b" ] >>= shouldBeFailureErr (fixture "circular-dependencies.txt") - Spec.it "installs a package in the set from a commit hash" \{ spago } -> do + Spec.it "installs a package in the set from a commit hash" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - writeConfigWithEither + writeConfigWithEither testCwd spago [ "install", "either" ] >>= shouldBeSuccess - Spec.it "can't install (uncached) dependencies if offline" \{ spago, fixture } -> do + Spec.it "can't install (uncached) dependencies if offline" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - writeConfigWithEither + writeConfigWithEither testCwd spago [ "install", "--offline", "either" ] >>= shouldBeFailureErr (fixture "offline.txt") Spec.it "installs a package version by branch name with / in it" \{ spago, testCwd } -> do @@ -116,7 +115,7 @@ spec = Spec.around withTempDir do } , testModuleName: "Test.Main" } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") ( conf { workspace = conf.workspace # map ( _ @@ -133,14 +132,14 @@ spec = Spec.around withTempDir do } ) spago [ "install", "nonexistent-package" ] >>= shouldBeSuccess - let slashyPath = Path.concat [ Paths.toLocalCachePackagesPath testCwd, "nonexistent-package", "spago-test%2fbranch-with-slash" ] + let slashyPath = testCwd Paths.localCachePackagesPath "nonexistent-package" "spago-test%2fbranch-with-slash" unlessM (FS.exists slashyPath) do - Assertions.fail $ "Expected path to exist: " <> slashyPath - kids <- FSA.readdir slashyPath + Assertions.fail $ "Expected path to exist: " <> Path.quote slashyPath + kids <- FS.ls slashyPath when (Array.length kids == 0) do - Assertions.fail $ "Expected path exists but contains nothing: " <> slashyPath + Assertions.fail $ "Expected path exists but contains nothing: " <> Path.quote slashyPath - Spec.it "installs a package not in the set from a commit hash" \{ spago } -> do + Spec.it "installs a package not in the set from a commit hash" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess let conf = Init.defaultConfig @@ -150,7 +149,7 @@ spec = Spec.around withTempDir do } , testModuleName: "Test.Main" } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") ( conf { workspace = conf.workspace # map ( _ @@ -168,7 +167,7 @@ spec = Spec.around withTempDir do ) spago [ "install", "spago" ] >>= shouldBeSuccess - Spec.it "can't install a package from a not-existing commit hash" \{ spago } -> do + Spec.it "can't install a package from a not-existing commit hash" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess let conf = Init.defaultConfig @@ -178,7 +177,7 @@ spec = Spec.around withTempDir do } , testModuleName: "Test.Main" } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") ( conf { workspace = conf.workspace # map ( _ @@ -196,13 +195,14 @@ spec = Spec.around withTempDir do ) spago [ "install", "spago" ] >>= shouldBeFailure - Spec.it "can update dependencies in a sub-package" \{ spago, fixture } -> do + Spec.it "can update dependencies in a sub-package" \{ spago, fixture, testCwd } -> do + let subpackage = testCwd "subpackage" spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile "subpackage/test/Main.purs" (Init.testMainTemplate "Subpackage.Test.Main") - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src" "Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (subpackage "test" "Main.purs") (Init.testMainTemplate "Subpackage.Test.Main") + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( Init.defaultConfig { name: mkPackageName "subpackage" , withWorkspace: Nothing @@ -210,7 +210,7 @@ spec = Spec.around withTempDir do } ) spago [ "install", "-p", "subpackage", "either" ] >>= shouldBeSuccess - checkFixture "subpackage/spago.yaml" (fixture "spago-subpackage-install-success.yaml") + checkFixture (subpackage "spago.yaml") (fixture "spago-subpackage-install-success.yaml") Spec.it "can build with a newer (but still compatible) compiler than the one in the package set" \{ spago } -> do spago [ "init", "--package-set", "10.0.0" ] >>= shouldBeSuccess @@ -222,20 +222,20 @@ spec = Spec.around withTempDir do false -> Assert.fail $ "Expected purs version to be newer than 0.15.4, but it was " <> Version.print purs.version spago [ "install" ] >>= shouldBeSuccess - Spec.it "can refresh the lockfile, and uninstall restores it" \{ spago, fixture } -> do + Spec.it "can refresh the lockfile, and uninstall restores it" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "33.0.0" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess -- Check that we have written the lockfile - checkFixture "spago.lock" (fixture "spago.lock") + checkFixture (testCwd "spago.lock") (fixture "spago.lock") spago [ "install", "maybe" ] >>= shouldBeSuccess -- Check that the new lockfile includes maybe - checkFixture "spago.lock" (fixture "spago-with-maybe.lock") + checkFixture (testCwd "spago.lock") (fixture "spago-with-maybe.lock") spago [ "uninstall", "maybe" ] >>= shouldBeSuccess -- Check that the lockfile is back to the original - checkFixture "spago.lock" (fixture "spago.lock") + checkFixture (testCwd "spago.lock") (fixture "spago.lock") -writeConfigWithEither :: Aff Unit -writeConfigWithEither = do +writeConfigWithEither :: RootPath -> Aff Unit +writeConfigWithEither root = do -- The commit for `either` is for the `v6.1.0` release let conf = Init.defaultConfig @@ -245,7 +245,7 @@ writeConfigWithEither = do } , testModuleName: "Test.Main" } - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (root "spago.yaml") ( conf { workspace = conf.workspace # map ( _ diff --git a/test/Spago/Lock.purs b/test/Spago/Lock.purs index de09ed934..98a594ca6 100644 --- a/test/Spago/Lock.purs +++ b/test/Spago/Lock.purs @@ -29,10 +29,10 @@ spec = Spec.around withTempDir do Right _ -> pure unit - Spec.it "#1158: always uses forward slash separator for doubly nested projects' paths" \{ spago, fixture } -> do - FS.copyTree { src: fixture "lock/1158-doubly-nested-projects", dst: "." } + Spec.it "#1158: always uses forward slash separator for doubly nested projects' paths" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "lock/1158-doubly-nested-projects", dst: testCwd "." } spago [ "install" ] >>= shouldBeSuccess - checkFixture "spago.lock" (fixture "lock/1158-doubly-nested-projects/expected-lockfile.txt") + checkFixture (testCwd "spago.lock") (fixture "lock/1158-doubly-nested-projects/expected-lockfile.txt") validLockfile :: Lockfile validLockfile = diff --git a/test/Spago/Ls.purs b/test/Spago/Ls.purs index 4133f83b1..d7e8595f8 100644 --- a/test/Spago/Ls.purs +++ b/test/Spago/Ls.purs @@ -16,9 +16,9 @@ spec = Spec.around withTempDir do spago [ "init" ] >>= shouldBeSuccess spago [ "ls", "deps" ] >>= shouldBeSuccessOutput (fixture "list-dependencies.txt") - Spec.it "direct dependencies in JSON, and requires selecting a package when many are present" \{ spago, fixture } -> do + Spec.it "direct dependencies in JSON, and requires selecting a package when many are present" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa" ] >>= shouldBeSuccess - makeSubpackage + makeSubpackage testCwd spago [ "install", "-p", "aaa", "aaa2" ] >>= shouldBeSuccess spago [ "ls", "deps", "-p", "aaa", "--json" ] >>= shouldBeSuccessOutput (fixture "list-dependencies.json") @@ -26,9 +26,9 @@ spec = Spec.around withTempDir do spago [ "init", "--name", "aaa", "--package-set", "41.2.0" ] >>= shouldBeSuccess spago [ "ls", "packages" ] >>= shouldBeSuccessOutput (fixture "list-packages.txt") - Spec.it "package set in JSON" \{ spago, fixture } -> do + Spec.it "package set in JSON" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "41.2.0" ] >>= shouldBeSuccess - makeSubpackage + makeSubpackage testCwd spago [ "install", "-p", "aaa", "aaa2" ] >>= shouldBeSuccess spago [ "ls", "packages", "--json" ] >>= shouldBeSuccessOutput (fixture "list-packages.json") @@ -36,13 +36,14 @@ spec = Spec.around withTempDir do spago [ "init", "--name", "aaa", "--use-solver" ] >>= shouldBeSuccess spago [ "ls", "packages" ] >>= shouldBeFailureErr (fixture "list-packages-registry.txt") -makeSubpackage :: Aff Unit -makeSubpackage = do - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile "subpackage/test/Main.purs" (Init.testMainTemplate "Subpackage.Test.Main") - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" +makeSubpackage :: RootPath -> Aff Unit +makeSubpackage root = do + let subpackage = root "subpackage" + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src" "Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (subpackage "test" "Main.purs") (Init.testMainTemplate "Subpackage.Test.Main") + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( Init.defaultConfig { name: mkPackageName "aaa2" , withWorkspace: Nothing diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index 36bdd247c..8a6f35891 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -9,6 +9,7 @@ import Node.Platform as Platform import Node.Process as Process import Spago.Cmd as Cmd import Spago.FS as FS +import Spago.Path as Path import Test.Spec (Spec) import Test.Spec as Spec @@ -27,57 +28,57 @@ spec = Spec.around withTempDir do spago [ "fetch", "--ensure-ranges" ] >>= shouldBeSuccess spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish-no-config.txt") - Spec.it "fails if the git tree is not clean" \{ spago, fixture } -> do - FS.copyFile { src: fixture "spago-publish.yaml", dst: "spago.yaml" } - FS.mkdirp "src" - FS.copyFile { src: fixture "publish.purs", dst: "src/Main.purs" } + Spec.it "fails if the git tree is not clean" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "spago-publish.yaml", dst: testCwd "spago.yaml" } + FS.mkdirp $ testCwd "src" + FS.copyFile { src: fixture "publish.purs", dst: testCwd "src/Main.purs" } spago [ "build" ] >>= shouldBeSuccess spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish-no-git.txt") - Spec.it "fails if the module is called Main" \{ spago, fixture } -> do + Spec.it "fails if the module is called Main" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "aaaa" ] >>= shouldBeSuccess - FS.unlink "spago.yaml" - FS.copyFile { src: fixture "spago-publish.yaml", dst: "spago.yaml" } + FS.unlink $ testCwd "spago.yaml" + FS.copyFile { src: fixture "spago-publish.yaml", dst: testCwd "spago.yaml" } spago [ "build" ] >>= shouldBeSuccess doTheGitThing spago [ "publish", "--offline" ] >>= shouldBeFailureErr case Process.platform of Just Platform.Win32 -> fixture "publish-main-win.txt" _ -> fixture "publish-main.txt" - Spec.it "fails if the publish repo location is not among git remotes" \{ spago, fixture } -> do - FS.copyFile { src: fixture "spago-publish.yaml", dst: "spago.yaml" } - FS.mkdirp "src" - FS.copyFile { src: fixture "publish.purs", dst: "src/Main.purs" } + Spec.it "fails if the publish repo location is not among git remotes" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "spago-publish.yaml", dst: testCwd "spago.yaml" } + FS.mkdirp $ testCwd "src" + FS.copyFile { src: fixture "publish.purs", dst: testCwd "src/Main.purs" } spago [ "build" ] >>= shouldBeSuccess doTheGitThing git [ "remote", "set-url", "origin", "git@github.com:purescript/bbb.git" ] spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish-invalid-location.txt") - Spec.it "fails if a core dependency is not in the registry" \{ spago, fixture } -> do - FS.copyTree { src: fixture "publish/extra-package-core", dst: "." } + Spec.it "fails if a core dependency is not in the registry" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "publish/extra-package-core", dst: testCwd } spago [ "build" ] >>= shouldBeSuccess doTheGitThing spago [ "fetch" ] >>= shouldBeSuccess spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish-extra-package-core-dependency.txt") - Spec.it "can get a package ready to publish" \{ spago, fixture } -> do - FS.copyFile { src: fixture "spago-publish.yaml", dst: "spago.yaml" } - FS.mkdirp "src" - FS.copyFile { src: fixture "publish.purs", dst: "src/Main.purs" } + Spec.it "can get a package ready to publish" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "spago-publish.yaml", dst: testCwd "spago.yaml" } + FS.mkdirp $ testCwd "src" + FS.copyFile { src: fixture "publish.purs", dst: testCwd "src/Main.purs" } spago [ "build" ] >>= shouldBeSuccess doTheGitThing -- It will fail because it can't hit the registry, but the fixture will check that everything else is ready spago [ "fetch" ] >>= shouldBeSuccess spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish.txt") - Spec.it "allows to publish with a test dependency not in the registry" \{ spago, fixture } -> do - FS.copyTree { src: fixture "publish/extra-package-test", dst: "." } + Spec.it "allows to publish with a test dependency not in the registry" \{ spago, fixture, testCwd } -> do + FS.copyTree { src: fixture "publish/extra-package-test", dst: testCwd } spago [ "build" ] >>= shouldBeSuccess doTheGitThing spago [ "fetch" ] >>= shouldBeSuccess spago [ "publish", "--offline" ] >>= shouldBeFailureErr (fixture "publish.txt") - Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture } -> do + Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do let shouldBeFailureErr' file = checkOutputs' { stdoutFile: Nothing @@ -85,7 +86,7 @@ spec = Spec.around withTempDir do , result: isLeft , sanitize: String.trim - >>> withForwardSlashes + >>> String.replaceAll (String.Pattern "\\") (String.Replacement "/") >>> String.replaceAll (String.Pattern "\r\n") (String.Replacement "\n") >>> Regex.replace buildOrderRegex "[x of 3] Compiling module-name" } @@ -97,7 +98,7 @@ spec = Spec.around withTempDir do buildOrderRegex = unsafeFromRight $ Regex.regex "\\[\\d of 3\\] Compiling (Effect\\.Console|Effect\\.Class\\.Console|Lib)" RF.global - FS.copyTree { src: fixture "publish/1110-solver-different-version", dst: "." } + FS.copyTree { src: fixture "publish/1110-solver-different-version", dst: testCwd } spago [ "build" ] >>= shouldBeSuccess doTheGitThing spago [ "fetch" ] >>= shouldBeSuccess @@ -105,21 +106,21 @@ spec = Spec.around withTempDir do -- The local `spago.yaml` specifies `console: 6.0.0` in `extraPackages`, -- so that's what should be in local cache after running `fetch`. -- Importantly, `console-6.1.0` should not be there yet. - FS.exists ".spago/p/console-6.0.0" >>= (_ `shouldEqual` true) - FS.exists ".spago/p/console-6.1.0" >>= (_ `shouldEqual` false) + FS.exists (testCwd ".spago/p/console-6.0.0") >>= (_ `shouldEqual` true) + FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` false) spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/expected-stderr.txt") -- When `publish` runs, it uses the registry solver, which returns -- `console-6.1.0` version, so `publish` should fetch that into local -- cache and build with it. - FS.exists ".spago/p/console-6.1.0" >>= (_ `shouldEqual` true) + FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` true) -- Now screw up the `console-6.1.0` package in the local cache, so that it -- doesn't compile anymore, and check that the relevant compile error -- happens on publish. - FS.unlink ".spago/p/console-6.1.0/src/Effect/Console.js" - rmRf ".spago/p/console-6.1.0/output" + FS.unlink $ testCwd ".spago/p/console-6.1.0/src/Effect/Console.js" + rmRf $ testCwd ".spago/p/console-6.1.0/output" spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/failure-stderr.txt") doTheGitThing :: Aff Unit @@ -137,8 +138,8 @@ doTheGitThing = do git :: Array String -> Aff Unit git = git' Nothing -git' :: Maybe FilePath -> Array String -> Aff Unit +git' :: Maybe GlobalPath -> Array String -> Aff Unit git' cwd args = - Cmd.exec "git" args + Cmd.exec (Path.global "git") args (Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false, pipeStdin = StdinNewPipe, cwd = cwd }) >>= shouldBeSuccess diff --git a/test/Spago/Repl.purs b/test/Spago/Repl.purs index e621ceedf..dbe1cfba3 100644 --- a/test/Spago/Repl.purs +++ b/test/Spago/Repl.purs @@ -10,16 +10,16 @@ spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "repl" do - Spec.it "writes .purs-repl if not there" \{ spago, spago' } -> do + Spec.it "writes .purs-repl if not there" \{ spago, spago', testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - FS.readTextFile ".purs-repl" >>= shouldEqual "import Prelude\n" + FS.readTextFile (testCwd ".purs-repl") >>= shouldEqual "import Prelude\n" - FS.unlink ".purs-repl" + FS.unlink $ testCwd ".purs-repl" spago' (StdinWrite ":q") [ "repl" ] >>= shouldBeSuccess - FS.readTextFile ".purs-repl" >>= shouldEqual "import Prelude\n" + FS.readTextFile (testCwd ".purs-repl") >>= shouldEqual "import Prelude\n" - Spec.it "does not write .purs-repl if already there" \{ spago, spago' } -> do + Spec.it "does not write .purs-repl if already there" \{ spago, spago', testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - FS.writeTextFile ".purs-repl" "import Data.Maybe\n" + FS.writeTextFile (testCwd ".purs-repl") "import Data.Maybe\n" spago' (StdinWrite ":q") [ "repl" ] >>= shouldBeSuccess - FS.readTextFile ".purs-repl" >>= shouldEqual "import Data.Maybe\n" + FS.readTextFile (testCwd ".purs-repl") >>= shouldEqual "import Data.Maybe\n" diff --git a/test/Spago/Run.purs b/test/Spago/Run.purs index 663fa2ff0..65c55ad07 100644 --- a/test/Spago/Run.purs +++ b/test/Spago/Run.purs @@ -6,11 +6,6 @@ import Spago.FS as FS import Test.Spec (Spec) import Test.Spec as Spec -cp :: forall m. MonadAff m => String -> String -> m Unit -cp from to = do - str <- FS.readTextFile from - FS.writeTextFile to str - spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "run" do @@ -20,32 +15,32 @@ spec = Spec.around withTempDir do spago [ "build" ] >>= shouldBeSuccess spago [ "run" ] >>= shouldBeSuccessOutput (fixture "run-output.txt") - Spec.it "can pass stdin to the application" \{ spago, spago', fixture } -> do + Spec.it "can pass stdin to the application" \{ spago, spago', fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - cp (fixture "spago-run-stdin.purs") "src/Main.purs" + FS.copyTree { src: fixture "spago-run-stdin.purs", dst: testCwd "src" "Main.purs" } spago [ "install", "node-buffer", "node-streams", "node-process", "node-event-emitter" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago' (StdinWrite "wut") [ "run" ] >>= shouldBeSuccessOutput (fixture "run-passthrough.txt") - Spec.it "can pass arguments to the application" \{ spago, fixture } -> do + Spec.it "can pass arguments to the application" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - cp (fixture "spago-run-args.purs") "src/Main.purs" + FS.copyTree { src: fixture "spago-run-args.purs", dst: testCwd "src" "Main.purs" } spago [ "install", "node-process", "arrays" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "run", "hello" , "world" ] >>= shouldBeSuccessOutput (fixture "run-args-output.txt") - Spec.it "args in spago.yaml should be used as the fallback args" \{ spago, fixture } -> do + Spec.it "args in spago.yaml should be used as the fallback args" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - cp (fixture "spago-run-args.purs") "src/Main.purs" - cp (fixture "spago-args.yaml") "spago.yaml" + FS.copyTree { src: fixture "spago-run-args.purs", dst: testCwd "src" "Main.purs" } + FS.copyTree { src: fixture "spago-args.yaml", dst: testCwd "spago.yaml" } spago [ "install", "node-process", "arrays" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "run" ] >>= shouldBeSuccessOutput (fixture "run-args-output.txt") - Spec.it "explicit args has more priority than args in spago.yaml" \{ spago, fixture } -> do + Spec.it "explicit args has more priority than args in spago.yaml" \{ spago, fixture, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess - cp (fixture "spago-args.yaml") "spago.yaml" - cp (fixture "spago-run-args.purs") "src/Main.purs" + FS.copyTree { src: fixture "spago-args.yaml", dst: testCwd "spago.yaml" } + FS.copyTree { src: fixture "spago-run-args.purs", dst: testCwd "src" "Main.purs" } spago [ "install", "node-process", "arrays" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess spago [ "run", "bye" , "world" ] >>= shouldBeSuccessOutput (fixture "run-args-output2.txt") diff --git a/test/Spago/Sources.purs b/test/Spago/Sources.purs index 9b0b0c9c4..0acb85c6c 100644 --- a/test/Spago/Sources.purs +++ b/test/Spago/Sources.purs @@ -20,13 +20,14 @@ spec = Spec.around withTempDir do Just Platform.Win32 -> fixture "sources-output.win.txt" _ -> fixture "sources-output.txt" - Spec.it "contains subproject sources when selecting a subproject" \{ spago, fixture } -> do + Spec.it "contains subproject sources when selecting a subproject" \{ spago, fixture, testCwd } -> do + let subpackage = testCwd "subpackage" spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile "subpackage/test/Main.purs" (Init.testMainTemplate "Subpackage.Test.Main") - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src/Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (subpackage "test/Main.purs") (Init.testMainTemplate "Subpackage.Test.Main") + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( Init.defaultConfig { name: mkPackageName "subpackage" , withWorkspace: Nothing diff --git a/test/Spago/Test.purs b/test/Spago/Test.purs index 37f4ce3f3..f518cb367 100644 --- a/test/Spago/Test.purs +++ b/test/Spago/Test.purs @@ -4,7 +4,6 @@ import Test.Prelude import Data.Array as Array import Data.String as String -import Node.Path as Path import Node.Platform as Platform import Node.Process as Process import Registry.Version as Version @@ -12,7 +11,8 @@ import Spago.Command.Init (DefaultConfigOptions(..)) import Spago.Command.Init as Init import Spago.Core.Config as Config import Spago.FS as FS -import Spago.Paths (paths) +import Spago.Path as Path +import Spago.Paths as Paths import Test.Spec (Spec) import Test.Spec as Spec import Test.Spec.Assertions as Assert @@ -29,23 +29,24 @@ spec = Spec.around withTempDir do Spec.it "tests successfully when using a different output dir" \{ spago, fixture } -> do spago [ "init", "--name", "7368613235362d6a336156536c675a7033334e7659556c6d38" ] >>= shouldBeSuccess - let tempDir = Path.concat [ paths.temp, "output" ] + let tempDir = Path.toRaw $ Paths.paths.temp "output" spago [ "build", "--output", tempDir ] >>= shouldBeSuccess spago [ "test", "--output", tempDir ] >>= shouldBeSuccessOutputWithErr (fixture "test-output-stdout.txt") (fixture "test-output-stderr.txt") - Spec.it "fails nicely when the test module is not found" \{ spago, fixture } -> do + Spec.it "fails nicely when the test module is not found" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "7368613235362d6a336156536c675a7033334e7659556c6d38" ] >>= shouldBeSuccess spago [ "build" ] >>= shouldBeSuccess - FS.moveSync { dst: "test2", src: "test" } + FS.moveSync { dst: testCwd "test2", src: testCwd "test" } spago [ "test" ] >>= shouldBeFailureErr (fixture "test-missing-module.txt") - Spec.it "runs tests from a sub-package" \{ spago } -> do + Spec.it "runs tests from a sub-package" \{ spago, testCwd } -> do + let subpackage = testCwd "subpackage" spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile "subpackage/test/Main.purs" (Init.testMainTemplate "Subpackage.Test.Main") - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src/Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (subpackage "test/Main.purs") (Init.testMainTemplate "Subpackage.Test.Main") + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( Init.defaultConfig { name: mkPackageName "subpackage" , withWorkspace: Nothing @@ -54,23 +55,24 @@ spec = Spec.around withTempDir do ) spago [ "test", "-p", "subpackage" ] >>= shouldBeSuccess - Spec.it "runs tests from a sub-package in the current working directory, not the sub-package's directory" \{ spago, fixture } -> do + Spec.it "runs tests from a sub-package in the current working directory, not the sub-package's directory" \{ spago, fixture, testCwd } -> do + let subpackage = testCwd "subpackage" spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src" "Main.purs") (Init.srcMainTemplate "Subpackage.Main") -- We write a file into the current working directory. -- The subpackage test will read the given file without changing its directory -- and log its content as its output. - let textFilePath = "foo.txt" + let textFilePath = testCwd "foo.txt" let fileContent = "foo" FS.writeTextFile textFilePath fileContent FS.copyFile { src: fixture "spago-subpackage-test-cwd.purs" - , dst: "subpackage/test/Main.purs" + , dst: subpackage "test" "Main.purs" } - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( ( Init.defaultConfig { name: mkPackageName "subpackage" , withWorkspace: Nothing @@ -80,13 +82,14 @@ spec = Spec.around withTempDir do ) spago [ "test", "-p", "subpackage" ] >>= checkOutputsStr { stdoutStr: Just fileContent, stderrStr: Nothing, result: isRight } - Spec.it "fails when running tests from a sub-package, where the module does not exist" \{ spago } -> do + Spec.it "fails when running tests from a sub-package, where the module does not exist" \{ spago, testCwd } -> do + let subpackage = testCwd "subpackage" spago [ "init" ] >>= shouldBeSuccess - FS.mkdirp "subpackage/src" - FS.mkdirp "subpackage/test" - FS.writeTextFile "subpackage/src/Main.purs" (Init.srcMainTemplate "Subpackage.Main") - FS.writeTextFile "subpackage/test/Main.purs" (Init.testMainTemplate "Subpackage.Test.Main2") - FS.writeYamlFile Config.configCodec "subpackage/spago.yaml" + FS.mkdirp (subpackage "src") + FS.mkdirp (subpackage "test") + FS.writeTextFile (subpackage "src/Main.purs") (Init.srcMainTemplate "Subpackage.Main") + FS.writeTextFile (subpackage "test/Main.purs") (Init.testMainTemplate "Subpackage.Test.Main2") + FS.writeYamlFile Config.configCodec (subpackage "spago.yaml") ( Init.defaultConfig { name: mkPackageName "subpackage" , withWorkspace: Nothing @@ -95,15 +98,15 @@ spec = Spec.around withTempDir do ) spago [ "test", "-p", "subpackage" ] >>= shouldBeFailure - Spec.it "can use a custom output folder" \{ spago } -> do + Spec.it "can use a custom output folder" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess spago [ "test", "--output", "myOutput" ] >>= shouldBeSuccess - FS.exists "myOutput" `Assert.shouldReturn` true + FS.exists (testCwd "myOutput") `Assert.shouldReturn` true - Spec.it "'strict: true' on package src does not cause test code containing warnings to fail to build" \{ spago } -> do + Spec.it "'strict: true' on package src does not cause test code containing warnings to fail to build" \{ spago, testCwd } -> do spago [ "init" ] >>= shouldBeSuccess -- add --strict - FS.writeYamlFile Config.configCodec "spago.yaml" $ Init.defaultConfig' $ PackageAndWorkspace + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") $ Init.defaultConfig' $ PackageAndWorkspace { name: mkPackageName "package-a" , dependencies: [ "prelude", "effect", "console" ] , test: Just { moduleMain: "Test.Main", strict: Nothing, censorTestWarnings: Nothing, pedanticPackages: Nothing, dependencies: Nothing } @@ -112,7 +115,7 @@ spec = Spec.around withTempDir do { setVersion: Just $ unsafeFromRight $ Version.parse "0.0.1" } -- add version where test file has warning - FS.writeTextFile (Path.concat [ "test", "Test", "Main.purs" ]) $ Array.intercalate "\n" + FS.writeTextFile (testCwd "test" "Test" "Main.purs") $ Array.intercalate "\n" [ "module Test.Main where" , "" , "import Prelude" diff --git a/test/Spago/Uninstall.purs b/test/Spago/Uninstall.purs index 8c4170d78..f363e5ac1 100644 --- a/test/Spago/Uninstall.purs +++ b/test/Spago/Uninstall.purs @@ -3,7 +3,6 @@ module Test.Spago.Uninstall where import Test.Prelude import Data.String as String -import Node.Path as Path import Spago.Command.Init (DefaultConfigOptions(..)) import Spago.Command.Init as Init import Spago.Core.Config as Config @@ -16,18 +15,18 @@ spec ∷ Spec Unit spec = Spec.around withTempDir do Spec.describe "uninstall" do - Spec.it "fails when no package was selected" \{ spago, fixture } -> do + Spec.it "fails when no package was selected" \{ spago, fixture, testCwd } -> do let setupSubpackage packageName = do - let subdir = Path.concat [ packageName, "src" ] + let subdir = testCwd packageName "src" FS.mkdirp subdir - FS.writeTextFile (Path.concat [ subdir, "Main.purs" ]) $ "module " <> String.toUpper packageName <> " where" - FS.writeYamlFile Config.configCodec (Path.concat [ packageName, "spago.yaml" ]) $ mkPackageOnlyConfig + FS.writeTextFile (subdir "Main.purs") $ "module " <> String.toUpper packageName <> " where" + FS.writeYamlFile Config.configCodec (testCwd packageName "spago.yaml") $ mkPackageOnlyConfig { packageName: packageName , srcDependencies: [] } [] - FS.writeYamlFile Config.configCodec "spago.yaml" + FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") $ Init.defaultConfig' $ WorkspaceOnly { setVersion: Just $ mkVersion "0.0.1" } setupSubpackage "foo" @@ -35,9 +34,9 @@ spec = Spec.around withTempDir do spago [ "build" ] >>= shouldBeSuccess spago [ "uninstall" ] >>= shouldBeFailureErr (fixture "uninstall-no-package-selection.txt") - Spec.it "warns when test config does not exist and uninstalling test deps" \{ spago, fixture } -> do + Spec.it "warns when test config does not exist and uninstalling test deps" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "uninstall-tests" ] >>= shouldBeSuccess - editSpagoYaml' "spago.yaml" \config -> + editSpagoYaml' (testCwd "spago.yaml") \config -> config { package = config.package <#> \p -> p { test = Nothing } } spago [ "uninstall", "--test-deps", "either" ] >>= shouldBeSuccessErr (fixture "uninstall-no-test-config.txt") @@ -49,26 +48,26 @@ spec = Spec.around withTempDir do spago [ "init", "--name", "uninstall-tests" ] >>= shouldBeSuccess spago [ "uninstall", "--test-deps", "either" ] >>= shouldBeSuccessErr (fixture "uninstall-deps-undeclared-test-deps.txt") - Spec.it "removes declared packages in source config" \{ spago, fixture } -> do + Spec.it "removes declared packages in source config" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "uninstall-tests" ] >>= shouldBeSuccess - originalConfig <- FS.readTextFile "spago.yaml" + originalConfig <- FS.readTextFile (testCwd "spago.yaml") spago [ "install", "either" ] >>= shouldBeSuccess - postInstallConfig <- FS.readTextFile "spago.yaml" + postInstallConfig <- FS.readTextFile (testCwd "spago.yaml") originalConfig `Assert.shouldNotEqual` postInstallConfig spago [ "uninstall", "either" ] >>= shouldBeSuccessErr (fixture "uninstall-remove-src-deps.txt") - postUninstallConfig <- FS.readTextFile "spago.yaml" + postUninstallConfig <- FS.readTextFile (testCwd "spago.yaml") originalConfig `Assert.shouldEqual` postUninstallConfig - Spec.it "removes declared packages in test config" \{ spago, fixture } -> do + Spec.it "removes declared packages in test config" \{ spago, fixture, testCwd } -> do spago [ "init", "--name", "uninstall-tests" ] >>= shouldBeSuccess - originalConfig <- FS.readTextFile "spago.yaml" + originalConfig <- FS.readTextFile (testCwd "spago.yaml") spago [ "install", "--test-deps", "either" ] >>= shouldBeSuccess - postInstallConfig <- FS.readTextFile "spago.yaml" + postInstallConfig <- FS.readTextFile (testCwd "spago.yaml") originalConfig `Assert.shouldNotEqual` postInstallConfig spago [ "uninstall", "--test-deps", "either" ] >>= shouldBeSuccessErr (fixture "uninstall-remove-test-deps.txt") - postUninstallConfig <- FS.readTextFile "spago.yaml" + postUninstallConfig <- FS.readTextFile (testCwd "spago.yaml") originalConfig `Assert.shouldEqual` postUninstallConfig diff --git a/test/Spago/Unit.purs b/test/Spago/Unit.purs index 96648229c..065c5dd23 100644 --- a/test/Spago/Unit.purs +++ b/test/Spago/Unit.purs @@ -5,6 +5,7 @@ import Prelude import Test.Spago.Unit.CheckInjectivity as CheckInjectivity import Test.Spago.Unit.FindFlags as FindFlags import Test.Spago.Unit.Git as Git +import Test.Spago.Unit.Path as Path import Test.Spago.Unit.Printer as Printer import Test.Spec (Spec) import Test.Spec as Spec @@ -15,3 +16,4 @@ spec = Spec.describe "unit" do CheckInjectivity.spec Printer.spec Git.spec + Path.spec diff --git a/test/Spago/Unit/Path.purs b/test/Spago/Unit/Path.purs new file mode 100644 index 000000000..ea5fcfb8d --- /dev/null +++ b/test/Spago/Unit/Path.purs @@ -0,0 +1,36 @@ +module Test.Spago.Unit.Path where + +import Test.Prelude + +import Effect.Unsafe (unsafePerformEffect) +import Spago.Path as Path +import Test.Spec (Spec) +import Test.Spec as Spec + +spec :: Spec Unit +spec = Spec.describe "Paths" do + + Spec.describe "RootPath" do + Spec.it "can append strings" do + (root "/foo" "bar") `shouldPointAt` "/foo/bar" + (root "/foo" "bar" "baz") `shouldPointAt` "/foo/bar/baz" + (root "/foo/x/y" "/bar" "baz") `shouldPointAt` "/bar/baz" + (root "/foo/x/y" "/foo/x/y/z") `shouldPointAt` "/foo/x/y/z" + + Spec.it "can append LocalPath" do + pure unit + + Spec.it "can append GlobalPath" do + pure unit + + Spec.it "has to have absolute root" do + pure unit + + Spec.describe "LocalPath" do + Spec.it "always keeps the original root" do + pure unit + + where + root = unsafePerformEffect <<< Path.mkRoot <<< Path.global + + shouldPointAt path raw = Path.toRaw path `shouldEqual` raw diff --git a/test/Spago/Upgrade.purs b/test/Spago/Upgrade.purs index 6c6722159..3a797e68b 100644 --- a/test/Spago/Upgrade.purs +++ b/test/Spago/Upgrade.purs @@ -14,33 +14,35 @@ spec :: Spec Unit spec = Spec.around withTempDir do Spec.describe "upgrade" do - Spec.it "can upgrade a package set version" \{ spago } -> do + Spec.it "can upgrade a package set version" \{ spago, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "20.0.1" ] >>= shouldBeSuccess spago [ "upgrade" ] >>= shouldBeSuccess -- we can't just check a fixture here, as there are new package set versions all the time. -- so we read the config file, and check that the package set version is more recent than the one we started with - assertExpectedVersion + assertExpectedVersion testCwd { check: (_ > mkVersion "20.0.1") , error: "Could not upgrade the package set." } - Spec.it "allows to specify package set version" \{ spago } -> do + Spec.it "allows to specify package set version" \{ spago, testCwd } -> do spago [ "init", "--name", "aaa", "--package-set", "20.0.0" ] >>= shouldBeSuccess - assertExpectedVersion + assertExpectedVersion testCwd { check: (_ == mkVersion "20.0.0") , error: "Could not init with package set 20.0.0." } spago [ "upgrade", "--package-set", "20.0.1" ] >>= shouldBeSuccess - assertExpectedVersion + assertExpectedVersion testCwd { check: (_ == mkVersion "20.0.1") , error: "Could not upgrade the package set to 20.0.1." } where - assertExpectedVersion { check, error } = do + assertExpectedVersion root { check, error } = do startingTime <- liftEffect $ Now.now - maybeConfig <- runSpago { logOptions: { color: false, verbosity: LogQuiet, startingTime } } (Config.readConfig "spago.yaml") + maybeConfig <- runSpago + { logOptions: { color: false, verbosity: LogQuiet, startingTime }, rootPath: root } + (Config.readConfig $ root "spago.yaml") case maybeConfig of Right { yaml: { workspace: Just { packageSet: Just (SetFromRegistry { registry }) } } } | check registry -> pure unit Right { yaml: c } -> Assert.fail $ error <> " Config: " <> printJson Config.configCodec c From c0d1fdfef35272ddeeb7d2b1b62db01260ad38a2 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Fri, 25 Oct 2024 01:00:27 -0400 Subject: [PATCH 02/30] Satisfy Windows --- src/Spago/Command/Bundle.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spago/Command/Bundle.purs b/src/Spago/Command/Bundle.purs index 4e9d76684..5d3ddbb44 100644 --- a/src/Spago/Command/Bundle.purs +++ b/src/Spago/Command/Bundle.purs @@ -51,7 +51,7 @@ run = do output = workspace.buildOptions.output # fromMaybe (rootPath "output") -- TODO: we might need to use `Path.relative selected.path output` instead of just output there - mainPath = Path.localPart $ output opts.module "index.js" + mainPath = Path.localPart $ Path.withForwardSlashes $ output opts.module "index.js" { input, entrypoint } = case opts.type of BundleApp -> From 05b5da6330235a8727fc3ecc6fbeae8bcdfc4bea Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sun, 27 Oct 2024 00:10:58 -0400 Subject: [PATCH 03/30] Dev docs --- CONTRIBUTING.md | 47 ++++++++++++++++++++++++++++++++++++++++++++++ core/src/Path.purs | 26 ++++++++++++------------- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46151bfcb..056533417 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,3 +111,50 @@ Learn by doing and get your hands dirty! [f-f]: https://github.com/f-f [discord]: https://purescript.org/chat [spago-issues]: https://github.com/purescript/spago/issues + +## Working with file paths + +File paths are very important in Spago. A very big chunk of Spago does is +shuffling files around and manipulating their paths. Representing them as plain +strings is not enough. + +Spago has three different kinds of paths, represented as distinct types: + +- `RootPath` can generally be the root of anything, but in practice it usually + points to the root directory of the current workspace. It is constructed in + `Main.purs` close to the entry point and is available in all `ReaderT` + environments as `rootPath :: RootPath` +- `LocalPath` is path of a particular file or directory within the workspace. It + doesn't have to be literally _within_ the workspace directory - e.g. a custom + dependency that lives somewhere on the local file system, - but it's still + _relative_ to the workspace. A `LocalPath` is explicitly broken into two + parts: a `RootPath` and the "local" part relative to the root. This is useful + for printing out workspace-relative paths in user-facing output, while still + retaining the full path for actual file operations. A `LocalPath` can be + constructed by appending to a `RootPath`. Once so constructed, the `LocalPath` + always retains the same root, no matter what subsequent manipulations are done + to it. Therefore, if you have a `LocalPath` value, its root is probably + pointing to the current workspace directory. +- `GlobalPath` is for things that are not related to the current workspace. + Examples include paths to executables, such as `node` and `purs`, and global + directories, such as `registryPath` and `globalCachePath`. + +Paths can be appended by using the `` operator. It is overloaded for all +three path types and allows to append string segments to them. When appending to +a `RootPath`, the result comes out as `LocalPath`. You cannot produce a new +`RootPath` by appending. + +Most code that deals with the workspace operates in `LocalPath` values. Most +code that deals with external and global things operates in `GlobalPath` values. +Lower-level primitives, such as in the `Spago.FS` module, are polymorphic and +can take all three path types as parameters. + +For example: + +```haskell +rootPath <- Path.mkRootPath =<< Paths.cwd +config <- readConfig (rootPath "spago.yaml") +let srcDir = rootPath "src" +compileResult <- callCompiler [ srcDir "Main.purs", srcDir "Lib.purs" ] +FS.writeFile (rootPath "result.json") (serialize compipleResult) +``` \ No newline at end of file diff --git a/core/src/Path.purs b/core/src/Path.purs index 0ed5f2abb..351ea6b4b 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -69,28 +69,28 @@ class (Show path, Eq path, Ord path) <= IsPath path where withForwardSlashes :: path -> path instance IsPath LocalPath where - toGlobal (LocalPath { root: RootPath root, local }) = + toGlobal (LocalPath { root: RootPath root, local }) = GlobalPath $ Path.concat [ root, local ] relativeTo path root | rootPart path == root = path | otherwise = toGlobal path `relativeTo` root quote (LocalPath path) - | path.local == "" = "\".\"" + | path.local == "" = "\".\"" | otherwise = "\"" <> path.local <> "\"" - replaceExtension p r (LocalPath path) = + replaceExtension p r (LocalPath path) = LocalPath <<< path { local = _ } <$> replaceExtension_ p r path.local - withForwardSlashes (LocalPath path) = + withForwardSlashes (LocalPath path) = LocalPath { root: withForwardSlashes path.root, local: withForwardSlashes' path.local } instance IsPath GlobalPath where toGlobal = identity - relativeTo (GlobalPath path) (RootPath root) = + relativeTo (GlobalPath path) (RootPath root) = LocalPath { root: RootPath root, local: Path.relative root path } - quote (GlobalPath path) = + quote (GlobalPath path) = "\"" <> path <> "\"" - replaceExtension p r (GlobalPath path) = + replaceExtension p r (GlobalPath path) = GlobalPath <$> replaceExtension_ p r path - withForwardSlashes (GlobalPath path) = + withForwardSlashes (GlobalPath path) = GlobalPath $ withForwardSlashes' path instance IsPath RootPath where @@ -100,17 +100,17 @@ instance IsPath RootPath where replaceExtension p r (RootPath path) = RootPath <$> replaceExtension_ p r path withForwardSlashes (RootPath path) = RootPath $ withForwardSlashes' path -class AppendPath base path result | base path -> result where - appendPath :: base -> path -> result -instance AppendPath RootPath AdHocFilePath LocalPath where +class AppendPath base result | base -> result where + appendPath :: base -> AdHocFilePath -> result +instance AppendPath RootPath LocalPath where appendPath root local | Path.isAbsolute local = global local `relativeTo` root | otherwise = LocalPath { root, local } -instance AppendPath LocalPath AdHocFilePath LocalPath where +instance AppendPath LocalPath LocalPath where appendPath (LocalPath { root, local }) path | Path.isAbsolute path = global path `relativeTo` root | otherwise = LocalPath { root, local: Path.concat [ local, path ] } -instance AppendPath GlobalPath AdHocFilePath GlobalPath where +instance AppendPath GlobalPath GlobalPath where appendPath (GlobalPath path) p | Path.isAbsolute p = GlobalPath p | otherwise = GlobalPath $ Path.concat [ path, p ] From 86a24e9691c8d97e780b767b13f00f56f33479bf Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Mon, 28 Oct 2024 10:56:37 -0400 Subject: [PATCH 04/30] Run purs graph with a CWD too --- src/Spago/Command/Build.purs | 2 +- src/Spago/Command/Docs.purs | 2 +- src/Spago/Command/Graph.purs | 4 ++-- src/Spago/Command/Publish.purs | 10 +++++----- src/Spago/Command/Run.purs | 2 +- src/Spago/Purs.purs | 16 ++++++++-------- src/Spago/Purs/Graph.purs | 4 ++-- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Spago/Command/Build.purs b/src/Spago/Command/Build.purs index 307203bf5..36fc8f2fe 100644 --- a/src/Spago/Command/Build.purs +++ b/src/Spago/Command/Build.purs @@ -156,7 +156,7 @@ run opts = do pure true else do logInfo "Looking for unused and undeclared transitive dependencies..." - eitherGraph <- Graph.runGraph globs opts.pursArgs + eitherGraph <- Graph.runGraph rootPath globs opts.pursArgs logDebug "Decoded the output of `purs graph` successfully. Analyzing dependencies..." eitherGraph # either (prepareToDie >>> (_ $> false)) \graph -> do env <- ask diff --git a/src/Spago/Command/Docs.purs b/src/Spago/Command/Docs.purs index dc0007cc8..5268fe87f 100644 --- a/src/Spago/Command/Docs.purs +++ b/src/Spago/Command/Docs.purs @@ -44,7 +44,7 @@ run = do , depsOnly } - result <- Purs.docs globs docsFormat + result <- Purs.docs rootPath globs docsFormat case result of Left r -> die r.message _ -> pure unit diff --git a/src/Spago/Command/Graph.purs b/src/Spago/Command/Graph.purs index 37b695152..a7269051f 100644 --- a/src/Spago/Command/Graph.purs +++ b/src/Spago/Command/Graph.purs @@ -46,7 +46,7 @@ graphModules { dot, json, topo } = do let allDependencies = Fetch.toAllDependencies dependencies let selected = Config.getWorkspacePackages workspace.packageSet let globs = Build.getBuildGlobs { rootPath, selected, withTests: false, dependencies: allDependencies, depsOnly: false } - eitherGraph <- Graph.runGraph globs [] + eitherGraph <- Graph.runGraph rootPath globs [] graph <- either die pure eitherGraph moduleGraph <- runSpago (Record.union { selected } env) (Graph.getModuleGraphWithPackage graph) @@ -72,7 +72,7 @@ graphPackages { dot, json, topo } = do let allDependencies = Fetch.toAllDependencies dependencies let selected = Config.getWorkspacePackages workspace.packageSet let globs = Build.getBuildGlobs { rootPath, selected, withTests: false, dependencies: allDependencies, depsOnly: false } - eitherGraph <- Graph.runGraph globs [] + eitherGraph <- Graph.runGraph rootPath globs [] graph <- either die pure eitherGraph packageGraph <- runSpago (Record.union { selected } env) (Graph.getPackageGraph graph) diff --git a/src/Spago/Command/Publish.purs b/src/Spago/Command/Publish.purs index 67169d767..2be9176e7 100644 --- a/src/Spago/Command/Publish.purs +++ b/src/Spago/Command/Publish.purs @@ -120,7 +120,7 @@ publish _args = do -- We then need to check that the dependency graph is accurate. If not, queue the errors let allCoreDependencies = Fetch.toAllDependencies $ dependencies <#> _ { test = Map.empty } let globs = Build.getBuildGlobs { rootPath, selected: NEA.singleton selected, withTests: false, dependencies: allCoreDependencies, depsOnly: false } - eitherGraph <- Graph.runGraph globs [] + eitherGraph <- Graph.runGraph rootPath globs [] case eitherGraph of Right graph -> do graphCheckErrors <- Graph.toImportErrors selected { reportSrc: true, reportTest: false } @@ -229,10 +229,10 @@ publish _args = do , "sources indicated by the `files` key in your manifest." ] Just files -> do - let rootPathPrefix = - Path.toRaw rootPath - # String.stripSuffix (String.Pattern "/") - # fromMaybe (Path.toRaw rootPath) + let rootPathPrefix = + Path.toRaw rootPath + # String.stripSuffix (String.Pattern "/") + # fromMaybe (Path.toRaw rootPath) # (_ <> "/") Operation.Validation.validatePursModules files >>= case _ of Left formattedError -> addError $ toDoc diff --git a/src/Spago/Command/Run.purs b/src/Spago/Command/Run.purs index e033e36d1..a2a4deded 100644 --- a/src/Spago/Command/Run.purs +++ b/src/Spago/Command/Run.purs @@ -105,7 +105,7 @@ run = do , withTests: true , selected: NEA.singleton selected } - Purs.graph globs [] >>= case _ of + Purs.graph rootPath globs [] >>= case _ of Left err -> logWarn $ "Could not decode the output of `purs graph`, error: " <> CJ.DecodeError.print err Right (ModuleGraph graph) -> do when (isNothing $ Map.lookup opts.moduleName graph) do diff --git a/src/Spago/Purs.purs b/src/Spago/Purs.purs index 2d730812b..b3f525094 100644 --- a/src/Spago/Purs.purs +++ b/src/Spago/Purs.purs @@ -45,7 +45,7 @@ parseVersionOutput { cmd, output: stdout } = case parseLenientVersion (dropStuff compile :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) compile cwd globs pursArgs = do { purs } <- ask - let args = [ "compile" ] <> pursArgs <> (globsToArgs cwd globs) + let args = [ "compile" ] <> pursArgs <> globsToArgs cwd globs logDebug [ "Running command:", "purs " <> String.joinWith " " args ] -- PureScript (as of v0.14.0) outputs the compiler errors/warnings to `stdout` -- and outputs "Compiling..." messages to `stderr` @@ -60,7 +60,7 @@ compile cwd globs pursArgs = do repl :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PursEnv a) (Either ExecaResult ExecaResult) repl cwd globs pursArgs = do { purs } <- ask - let args = [ "repl" ] <> pursArgs <> (globsToArgs cwd globs) + let args = [ "repl" ] <> pursArgs <> globsToArgs cwd globs Cmd.exec purs.cmd args $ Cmd.defaultExecOptions { pipeStdout = true , pipeStderr = true @@ -94,10 +94,10 @@ printDocsFormat = case _ of Ctags -> "ctags" Etags -> "etags" -docs :: ∀ a. Set LocalPath -> DocsFormat -> Spago (PursEnv a) (Either ExecaResult ExecaResult) -docs globs format = do +docs :: ∀ a. RootPath -> Set LocalPath -> DocsFormat -> Spago (PursEnv a) (Either ExecaResult ExecaResult) +docs cwd globs format = do { purs } <- ask - let args = [ "docs", "--format", printDocsFormat format ] <> (Path.toRaw <$> Set.toUnfoldable globs) + let args = [ "docs", "--format", printDocsFormat format ] <> globsToArgs cwd globs Cmd.exec purs.cmd args $ Cmd.defaultExecOptions { pipeStdout = true , pipeStderr = true @@ -127,10 +127,10 @@ moduleGraphNodeCodec = CJ.named "ModuleGraphNode" $ CJ.Record.object , depends: CJ.array CJ.string } -graph :: ∀ a. Set LocalPath -> Array String -> Spago (PursEnv a) (Either CJ.DecodeError ModuleGraph) -graph globs pursArgs = do +graph :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PursEnv a) (Either CJ.DecodeError ModuleGraph) +graph cwd globs pursArgs = do { purs } <- ask - let args = [ "graph" ] <> pursArgs <> (Path.toRaw <$> Set.toUnfoldable globs) + let args = [ "graph" ] <> pursArgs <> globsToArgs cwd globs logDebug [ "Running command:", "purs " <> String.joinWith " " args ] let execOpts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } Cmd.exec purs.cmd args execOpts >>= case _ of diff --git a/src/Spago/Purs/Graph.purs b/src/Spago/Purs/Graph.purs index 51b6c9347..09eb54f6f 100644 --- a/src/Spago/Purs/Graph.purs +++ b/src/Spago/Purs/Graph.purs @@ -48,8 +48,8 @@ type PreGraphEnv a = | a } -runGraph :: forall a. Set LocalPath -> Array String -> Spago (PreGraphEnv a) (Either String Purs.ModuleGraph) -runGraph globs pursArgs = map (lmap toErrorMessage) $ Purs.graph globs pursArgs +runGraph :: ∀ a. RootPath -> Set LocalPath -> Array String -> Spago (PreGraphEnv a) (Either String Purs.ModuleGraph) +runGraph root globs pursArgs = map (lmap toErrorMessage) $ Purs.graph root globs pursArgs where toErrorMessage = append "Could not decode the output of `purs graph`, error: " <<< CJ.DecodeError.print From ad73d88936446ee8becf78c5435f79a4572244fe Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Mon, 28 Oct 2024 11:18:35 -0400 Subject: [PATCH 05/30] What would I even do if I didn't have to run the autoformatter? --- bin/src/Main.purs | 4 ++-- core/src/Path.purs | 27 +++++++++++++++++++++------ src/Spago/Command/Init.purs | 2 +- src/Spago/Command/Publish.purs | 10 ++++++++-- src/Spago/Config.purs | 12 +++++------- src/Spago/Git.purs | 1 - src/Spago/Psa/Output.purs | 5 +++-- src/Spago/Psa/Types.purs | 2 +- src/Spago/Purs/Graph.purs | 6 +++--- 9 files changed, 44 insertions(+), 25 deletions(-) diff --git a/bin/src/Main.purs b/bin/src/Main.purs index c2809ed7d..ef4369b35 100644 --- a/bin/src/Main.purs +++ b/bin/src/Main.purs @@ -943,7 +943,7 @@ mkFetchEnv args@{ migrateConfig, offline } = do let parsePackageName p = PackageName.parse p - # lmap \err -> "- Could not parse package " <> show p <> ": " <> err + # lmap \err -> "- Could not parse package " <> show p <> ": " <> err let { right: packageNames, left: failedPackageNames } = partitionMap parsePackageName (Array.fromFoldable args.packages) unless (Array.null failedPackageNames) do die $ [ toDoc "Failed to parse some package name: " ] <> map (indent <<< toDoc) failedPackageNames @@ -956,7 +956,7 @@ mkFetchEnv args@{ migrateConfig, offline } = do { rootPath } <- ask workspace <- runSpago (Record.union env { rootPath }) - (Config.readWorkspace { maybeSelectedPackage, pureBuild: args.pure, migrateConfig }) + (Config.readWorkspace { maybeSelectedPackage, pureBuild: args.pure, migrateConfig }) let fetchOpts = { packages: packageNames, ensureRanges: args.ensureRanges, isTest: args.testDeps, isRepl: args.isRepl } pure { fetchOpts, env: Record.union { workspace, rootPath } env } diff --git a/core/src/Path.purs b/core/src/Path.purs index 351ea6b4b..5fa40ad8d 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -40,6 +40,7 @@ import Node.Path as Path -- | Normally this represents the root directory of the workspace. All Spago -- | Workspace-scoped paths are relative to a `RootPath`. newtype RootPath = RootPath String + derive newtype instance Show RootPath derive newtype instance Eq RootPath derive newtype instance Ord RootPath @@ -48,14 +49,23 @@ derive newtype instance Ord RootPath -- | part, relative to the root. This lets us both have the full path for -- | actually working with files and the local part for user-facing output. newtype LocalPath = LocalPath { root :: RootPath, local :: AdHocFilePath } -instance Show LocalPath where show (LocalPath p) = p.local -instance Eq LocalPath where eq = eq `on` toGlobal -instance Ord LocalPath where compare = compare `on` toGlobal + +instance Show LocalPath where + show (LocalPath p) = p.local + +instance Eq LocalPath where + eq = eq `on` toGlobal + +instance Ord LocalPath where + compare = compare `on` toGlobal -- | A part that is logically not part of the Spago Workspace, but points to -- | something "global", such as registry cache, temp directory, and so on. newtype GlobalPath = GlobalPath String -instance Show GlobalPath where show (GlobalPath p) = p + +instance Show GlobalPath where + show (GlobalPath p) = p + derive newtype instance Eq GlobalPath derive newtype instance Ord GlobalPath @@ -102,14 +112,17 @@ instance IsPath RootPath where class AppendPath base result | base -> result where appendPath :: base -> AdHocFilePath -> result + instance AppendPath RootPath LocalPath where appendPath root local | Path.isAbsolute local = global local `relativeTo` root | otherwise = LocalPath { root, local } + instance AppendPath LocalPath LocalPath where appendPath (LocalPath { root, local }) path | Path.isAbsolute path = global path `relativeTo` root | otherwise = LocalPath { root, local: Path.concat [ local, path ] } + instance AppendPath GlobalPath GlobalPath where appendPath (GlobalPath path) p | Path.isAbsolute p = GlobalPath p @@ -161,5 +174,7 @@ localPathCodec root = CJ.string # dimap printLocalPath (root _) -- | "./" so as not to get them confused by a seeming absence of output. printLocalPath :: LocalPath -> String printLocalPath p = - let l = localPart p - in if l == "" then "./" else l + let + l = localPart p + in + if l == "" then "./" else l diff --git a/src/Spago/Command/Init.purs b/src/Spago/Command/Init.purs index 3078e309f..72a97a22a 100644 --- a/src/Spago/Command/Init.purs +++ b/src/Spago/Command/Init.purs @@ -26,7 +26,7 @@ import Spago.Path as Path import Spago.Registry (RegistryEnv) import Spago.Registry as Registry -type InitEnv a = RegistryEnv ( rootPath :: RootPath | a ) +type InitEnv a = RegistryEnv (rootPath :: RootPath | a) data InitMode = InitWorkspace { packageName :: Maybe String } diff --git a/src/Spago/Command/Publish.purs b/src/Spago/Command/Publish.purs index 2be9176e7..b89b2f207 100644 --- a/src/Spago/Command/Publish.purs +++ b/src/Spago/Command/Publish.purs @@ -229,8 +229,14 @@ publish _args = do , "sources indicated by the `files` key in your manifest." ] Just files -> do - let rootPathPrefix = - Path.toRaw rootPath + let + -- `validatePursModules` returns full paths in its response, so + -- we need to strip the workspace root path to print it out in + -- user-friendly way, but we can't use the machinery from + -- `Spago.Paths`, because the paths are embedded in other text, + -- so we have to resort to substring matching. + rootPathPrefix = + Path.toRaw rootPath # String.stripSuffix (String.Pattern "/") # fromMaybe (Path.toRaw rootPath) # (_ <> "/") diff --git a/src/Spago/Config.purs b/src/Spago/Config.purs index f2e5117ae..dfcabf7a0 100644 --- a/src/Spago/Config.purs +++ b/src/Spago/Config.purs @@ -22,8 +22,7 @@ module Spago.Config , setPackageSetVersionInConfig , sourceGlob , workspacePackageToLockfilePackage - ) - where + ) where import Spago.Prelude @@ -169,7 +168,7 @@ type ReadWorkspaceOptions = -- | Reads all the configurations in the tree and builds up the Map of local -- | packages to be integrated in the package set -readWorkspace :: ∀ a. ReadWorkspaceOptions -> Spago (Registry.RegistryEnv ( rootPath :: RootPath | a )) Workspace +readWorkspace :: ∀ a. ReadWorkspaceOptions -> Spago (Registry.RegistryEnv (rootPath :: RootPath | a)) Workspace readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do { rootPath } <- ask logInfo "Reading Spago workspace configuration..." @@ -233,7 +232,6 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do { right: otherPackages, left: failedPackages } <- partitionMap identity <$> traverse readWorkspaceConfig otherConfigPaths unless (Array.null failedPackages) do logWarn $ [ toDoc "Failed to read some configs:" ] <> failedPackages - -- We prune any configs that use a different workspace. -- For reasoning, see https://github.com/purescript/spago/issues/951 @@ -260,8 +258,8 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do Array.foldM fn { right: [], left: [] } otherPackages unless (Array.null prunedConfigs) do - logDebug $ - [ "Excluding configs that use a different workspace (directly or implicitly via parent directory's config):" ] + logDebug + $ [ "Excluding configs that use a different workspace (directly or implicitly via parent directory's config):" ] <> Array.sort (Path.quote <$> prunedConfigs) rootPackage <- case maybePackage of @@ -288,7 +286,7 @@ readWorkspace { maybeSelectedPackage, pureBuild, migrateConfig } = do <> case (Array.fromFoldable $ Map.keys workspacePackages) of [] -> [ toDoc "No available packages." ] - pkgs -> + pkgs -> case typoSuggestions PackageName.print name pkgs of [] -> [ toDoc "All available packages:", indent (toDoc pkgs) ] suggestions -> [ toDoc "Did you mean:", indent (toDoc suggestions) ] diff --git a/src/Spago/Git.purs b/src/Spago/Git.purs index 9287ddc47..bb656a7a2 100644 --- a/src/Spago/Git.purs +++ b/src/Spago/Git.purs @@ -175,7 +175,6 @@ pushTag cwd version = do ] Right _ -> pure $ Right unit - getGit :: forall a. Spago (LogEnv a) Git getGit = do Cmd.exec cmd [ "--version" ] Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } >>= case _ of diff --git a/src/Spago/Psa/Output.purs b/src/Spago/Psa/Output.purs index 906b54bc2..ea3f95479 100644 --- a/src/Spago/Psa/Output.purs +++ b/src/Spago/Psa/Output.purs @@ -91,8 +91,9 @@ buildOutput loadLines options result = do where pathDecision = case x.filename of Just filename | filename /= "" -> do - let path = root filename - short = Path.localPart path + let + path = root filename + short = Path.localPart path fromMaybe unknownPathInfo $ Array.findMap (\p -> map (toPathInfo short) $ p path) options.decisions _ -> unknownPathInfo diff --git a/src/Spago/Psa/Types.purs b/src/Spago/Psa/Types.purs index 345c4682d..ddc04dddc 100644 --- a/src/Spago/Psa/Types.purs +++ b/src/Spago/Psa/Types.purs @@ -37,7 +37,7 @@ import Spago.Core.Config as Core import Spago.Path (LocalPath, RootPath) import Spago.Purs (PursEnv) -type PsaEnv a = PursEnv ( rootPath :: RootPath | a ) +type PsaEnv a = PursEnv (rootPath :: RootPath | a) type ErrorCode = String type ModuleName = String diff --git a/src/Spago/Purs/Graph.purs b/src/Spago/Purs/Graph.purs index 09eb54f6f..e420bc641 100644 --- a/src/Spago/Purs/Graph.purs +++ b/src/Spago/Purs/Graph.purs @@ -238,9 +238,9 @@ checkImports graph = do { rootPath } <- ask glob :: Set String <- map Set.fromFoldable - $ map (map Path.localPart) - $ map Array.fold - $ traverse compileGlob (Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected)) + $ map (map Path.localPart) + $ map Array.fold + $ traverse compileGlob (Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected)) let -- Filter this improved graph to only have the project modules From bd2d1518140f4185de6c67adf963a697b0e906f6 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Mon, 28 Oct 2024 15:50:08 -0400 Subject: [PATCH 06/30] Even though I walk through the valley of the shadow of Windows, I will fear no forward slashes --- src/Spago/Command/Fetch.purs | 2 +- src/Spago/Command/Publish.purs | 3 ++- src/Spago/Glob.purs | 2 +- src/Spago/Purs.purs | 11 +++++++--- src/Spago/Purs/Graph.purs | 21 +++++++++--------- test/Spago/Build.purs | 2 +- test/Spago/Glob.purs | 2 +- test/Spago/Unit/Path.purs | 39 +++++++++++++++++++++++++--------- 8 files changed, 54 insertions(+), 28 deletions(-) diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index b76e39b1d..5156c30e5 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -299,7 +299,7 @@ fetchPackagesToLocalCache packages = do (liftEffect $ Tar.extract { filename: archivePath, cwd: tempDir }) >>= case _ of Right _ -> pure unit Left err -> die [ "Failed to decode downloaded package " <> packageVersion <> ", error:", show err ] - logDebug $ "Moving extracted file to local cache:" <> Path.quote localPackageLocation + logDebug $ "Moving extracted file to local cache: " <> Path.quote localPackageLocation FS.moveSync { src: tempDir tarInnerFolder, dst: Path.toGlobal localPackageLocation } -- Local package, no work to be done LocalPackage _ -> pure unit diff --git a/src/Spago/Command/Publish.purs b/src/Spago/Command/Publish.purs index b89b2f207..5bf077860 100644 --- a/src/Spago/Command/Publish.purs +++ b/src/Spago/Command/Publish.purs @@ -20,6 +20,7 @@ import Effect.Aff (Milliseconds(..)) import Effect.Aff as Aff import Effect.Ref as Ref import JSON (JSON) +import Node.Path as Node.Path import Node.Process as Process import Record as Record import Registry.API.V1 as V1 @@ -239,7 +240,7 @@ publish _args = do Path.toRaw rootPath # String.stripSuffix (String.Pattern "/") # fromMaybe (Path.toRaw rootPath) - # (_ <> "/") + # (_ <> Node.Path.sep) Operation.Validation.validatePursModules files >>= case _ of Left formattedError -> addError $ toDoc [ "This package has either malformed or disallowed PureScript module names" diff --git a/src/Spago/Glob.purs b/src/Spago/Glob.purs index 8f2ad34ef..8a83aa882 100644 --- a/src/Spago/Glob.purs +++ b/src/Spago/Glob.purs @@ -175,7 +175,7 @@ fsWalk root ignorePatterns includePatterns = Aff.makeAff \cb -> do String.Pattern patternBase `isPrefix` relDirPath relPath :: Entry -> String - relPath entry = Path.localPart $ withForwardSlashes entry.path `Path.relativeTo` root + relPath entry = Path.localPart $ withForwardSlashes $ entry.path `Path.relativeTo` root -- Should `fsWalk` recurse into this directory? deepFilter :: Entry -> Effect Boolean diff --git a/src/Spago/Purs.purs b/src/Spago/Purs.purs index b3f525094..29215af30 100644 --- a/src/Spago/Purs.purs +++ b/src/Spago/Purs.purs @@ -99,7 +99,8 @@ docs cwd globs format = do { purs } <- ask let args = [ "docs", "--format", printDocsFormat format ] <> globsToArgs cwd globs Cmd.exec purs.cmd args $ Cmd.defaultExecOptions - { pipeStdout = true + { cwd = Just $ Path.toGlobal cwd + , pipeStdout = true , pipeStderr = true , pipeStdin = Cmd.StdinPipeParent } @@ -132,8 +133,12 @@ graph cwd globs pursArgs = do { purs } <- ask let args = [ "graph" ] <> pursArgs <> globsToArgs cwd globs logDebug [ "Running command:", "purs " <> String.joinWith " " args ] - let execOpts = Cmd.defaultExecOptions { pipeStdout = false, pipeStderr = false } - Cmd.exec purs.cmd args execOpts >>= case _ of + result <- Cmd.exec purs.cmd args $ Cmd.defaultExecOptions + { cwd = Just $ Path.toGlobal cwd + , pipeStdout = false + , pipeStderr = false + } + case result of Right r -> do logDebug "Called `purs graph`, decoding.." pure $ parseJson moduleGraphCodec r.stdout diff --git a/src/Spago/Purs/Graph.purs b/src/Spago/Purs/Graph.purs index e420bc641..7ffe9f6b7 100644 --- a/src/Spago/Purs/Graph.purs +++ b/src/Spago/Purs/Graph.purs @@ -236,15 +236,16 @@ checkImports graph = do -- Compile the globs for the project, we get the set of source files in the project { rootPath } <- ask - glob :: Set String <- - map Set.fromFoldable - $ map (map Path.localPart) - $ map Array.fold - $ traverse compileGlob (Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected)) + projectFiles :: Set String <- + Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected) + # traverse compileGlob + <#> Array.fold + <#> map (Path.localPart <<< withForwardSlashes) + <#> Set.fromFoldable let -- Filter this improved graph to only have the project modules - projectGraph = Map.filterWithKey (\_ { path } -> Set.member path glob) packageGraph + projectGraph = packageGraph # Map.filter \{ path } -> Set.member path projectFiles -- Go through all the modules in the project graph, figure out which packages each module depends on, -- accumulate all of that in a single place @@ -297,10 +298,10 @@ toImportErrors -> Array { errorMessage :: Docc, correction :: Docc } toImportErrors selected opts { unused, unusedTest, transitive, transitiveTest } = do Array.catMaybes - [ if opts.reportSrc && (not $ Set.isEmpty unused) then Just $ unusedError false selected unused else Nothing - , if opts.reportSrc && (not $ Map.isEmpty transitive) then Just $ transitiveError false selected transitive else Nothing - , if opts.reportTest && (not $ Set.isEmpty unusedTest) then Just $ unusedError true selected unusedTest else Nothing - , if opts.reportTest && (not $ Map.isEmpty transitiveTest) then Just $ transitiveError true selected transitiveTest else Nothing + [ if opts.reportSrc && (not Set.isEmpty unused) then Just $ unusedError false selected unused else Nothing + , if opts.reportSrc && (not Map.isEmpty transitive) then Just $ transitiveError false selected transitive else Nothing + , if opts.reportTest && (not Set.isEmpty unusedTest) then Just $ unusedError true selected unusedTest else Nothing + , if opts.reportTest && (not Map.isEmpty transitiveTest) then Just $ transitiveError true selected transitiveTest else Nothing ] formatImportErrors :: Array { errorMessage :: Docc, correction :: Docc } -> Docc diff --git a/test/Spago/Build.purs b/test/Spago/Build.purs index e37be5035..08169239f 100644 --- a/test/Spago/Build.purs +++ b/test/Spago/Build.purs @@ -183,7 +183,7 @@ spec = Spec.around withTempDir do FS.writeYamlFile Config.configCodec (testCwd "spago.yaml") (conf { workspace = conf.workspace # map (_ { backend = Just { cmd: "echo", args: Just [ "hello" ] } }) }) - spago [ "build" ] >>= shouldBeSuccess + spago [ "build", "-v" ] >>= shouldBeSuccess spago [ "run" ] >>= shouldBeSuccessErr (fixture "alternate-backend-output.txt") -- We also make sure that no js files are produced, only corefn diff --git a/test/Spago/Glob.purs b/test/Spago/Glob.purs index 130623d73..33b670092 100644 --- a/test/Spago/Glob.purs +++ b/test/Spago/Glob.purs @@ -107,4 +107,4 @@ spec = Spec.around globTmpDir do sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] where - sortedPaths = map show >>> Array.sort + sortedPaths = map (Path.localPart <<< Path.withForwardSlashes) >>> Array.sort diff --git a/test/Spago/Unit/Path.purs b/test/Spago/Unit/Path.purs index ea5fcfb8d..110ac5d96 100644 --- a/test/Spago/Unit/Path.purs +++ b/test/Spago/Unit/Path.purs @@ -17,20 +17,39 @@ spec = Spec.describe "Paths" do (root "/foo/x/y" "/bar" "baz") `shouldPointAt` "/bar/baz" (root "/foo/x/y" "/foo/x/y/z") `shouldPointAt` "/foo/x/y/z" - Spec.it "can append LocalPath" do - pure unit + Spec.describe "LocalPath" do + Spec.it "can append strings" do + let p = root "/foo" "bar" "baz" + Path.localPart p `shouldEqual` "bar/baz" + (p "x") `shouldPointAt` "/foo/bar/baz/x" + (p "../x") `shouldPointAt` "/foo/bar/x" + (p "../.." "x") `shouldPointAt` "/foo/x" + (p "x" "y" "z") `shouldPointAt` "/foo/bar/baz/x/y/z" - Spec.it "can append GlobalPath" do - pure unit + Spec.it "always keeps the original root" do + let p1 = root "/foo/x/y" "/bar" "baz" + Path.localPart p1 `shouldEqual` "../../../bar/baz" + Path.rootPart p1 `shouldPointAt` "/foo/x/y" - Spec.it "has to have absolute root" do - pure unit + let p2 = root "/foo/x/y" "bar" "baz" + Path.localPart p2 `shouldEqual` "bar/baz" + Path.rootPart p2 `shouldPointAt` "/foo/x/y" - Spec.describe "LocalPath" do - Spec.it "always keeps the original root" do - pure unit + let p3 = root "/foo/x/y" "../../bar" "baz" + p3 `shouldPointAt` "/foo/bar/baz" + Path.localPart p3 `shouldEqual` "../../bar/baz" + Path.rootPart p3 `shouldPointAt` "/foo/x/y" + + Spec.describe "GlobalPath" do + Spec.it "can append strings" do + (Path.global "/foo" "bar") `shouldPointAt` "/foo/bar" + (Path.global "/foo" "bar" "baz") `shouldPointAt` "/foo/bar/baz" + (Path.global "/foo/x/y" "/bar" "baz") `shouldPointAt` "/bar/baz" + (Path.global "/foo/x/y" "/foo/x/y/z") `shouldPointAt` "/foo/x/y/z" + (Path.global "/foo/x/y" ".." ".." "bar") `shouldPointAt` "/foo/bar" where root = unsafePerformEffect <<< Path.mkRoot <<< Path.global - shouldPointAt path raw = Path.toRaw path `shouldEqual` raw + shouldPointAt :: ∀ path. Path.IsPath path => path -> String -> _ + shouldPointAt path raw = Path.toRaw (Path.withForwardSlashes $ Path.toGlobal path) `shouldEqual` raw From d69abfb6d767c696ad06753467d593f5445f5cdf Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Tue, 29 Oct 2024 00:37:31 -0400 Subject: [PATCH 07/30] And format again --- src/Spago/Purs/Graph.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Spago/Purs/Graph.purs b/src/Spago/Purs/Graph.purs index 7ffe9f6b7..75cfd341e 100644 --- a/src/Spago/Purs/Graph.purs +++ b/src/Spago/Purs/Graph.purs @@ -237,7 +237,7 @@ checkImports graph = do -- Compile the globs for the project, we get the set of source files in the project { rootPath } <- ask projectFiles :: Set String <- - Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected) + Config.sourceGlob rootPath testGlobOption packageName (WorkspacePackage selected) # traverse compileGlob <#> Array.fold <#> map (Path.localPart <<< withForwardSlashes) From f20c3f97fd5bfdf19cef2bd6109bb58ee2f64f47 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Tue, 29 Oct 2024 10:16:30 -0400 Subject: [PATCH 08/30] And we are still not done with the forward slashes --- test/Spago/Unit/Path.purs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Spago/Unit/Path.purs b/test/Spago/Unit/Path.purs index 110ac5d96..710c6c3ad 100644 --- a/test/Spago/Unit/Path.purs +++ b/test/Spago/Unit/Path.purs @@ -27,15 +27,15 @@ spec = Spec.describe "Paths" do (p "x" "y" "z") `shouldPointAt` "/foo/bar/baz/x/y/z" Spec.it "always keeps the original root" do - let p1 = root "/foo/x/y" "/bar" "baz" + let p1 = Path.withForwardSlashes $ root "/foo/x/y" "/bar" "baz" Path.localPart p1 `shouldEqual` "../../../bar/baz" Path.rootPart p1 `shouldPointAt` "/foo/x/y" - let p2 = root "/foo/x/y" "bar" "baz" + let p2 = Path.withForwardSlashes $ root "/foo/x/y" "bar" "baz" Path.localPart p2 `shouldEqual` "bar/baz" Path.rootPart p2 `shouldPointAt` "/foo/x/y" - let p3 = root "/foo/x/y" "../../bar" "baz" + let p3 = Path.withForwardSlashes $ root "/foo/x/y" "../../bar" "baz" p3 `shouldPointAt` "/foo/bar/baz" Path.localPart p3 `shouldEqual` "../../bar/baz" Path.rootPart p3 `shouldPointAt` "/foo/x/y" From 2377dafd4cfc07f1f8cb743c1bf0c999f11944c5 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Tue, 29 Oct 2024 10:43:36 -0400 Subject: [PATCH 09/30] And we are still not done with the forward slashes --- test/Spago/Unit/Path.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spago/Unit/Path.purs b/test/Spago/Unit/Path.purs index 710c6c3ad..51d664aa2 100644 --- a/test/Spago/Unit/Path.purs +++ b/test/Spago/Unit/Path.purs @@ -19,7 +19,7 @@ spec = Spec.describe "Paths" do Spec.describe "LocalPath" do Spec.it "can append strings" do - let p = root "/foo" "bar" "baz" + let p = Path.withForwardSlashes $ root "/foo" "bar" "baz" Path.localPart p `shouldEqual` "bar/baz" (p "x") `shouldPointAt` "/foo/bar/baz/x" (p "../x") `shouldPointAt` "/foo/bar/x" From de8173ccfaf699586544e6e1e35eb6e064b064d5 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Wed, 30 Oct 2024 15:53:31 -0400 Subject: [PATCH 10/30] Fixup after merge --- test/Spago/Glob.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spago/Glob.purs b/test/Spago/Glob.purs index b282b8804..c59cfb121 100644 --- a/test/Spago/Glob.purs +++ b/test/Spago/Glob.purs @@ -107,7 +107,7 @@ spec = Spec.around globTmpDir do sortedPaths a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ] Spec.it "does respect .gitignore even though it might conflict with a search path without base" $ \p -> do - FS.writeTextFile (Path.concat [ p, ".gitignore" ]) "fruits" + FS.writeTextFile (p ".gitignore") "fruits" a <- Glob.gitignoringGlob p [ "**/apple" ] sortedPaths a `Assert.shouldEqual` [] From 1d7c4f3fb37cee40aa98843e63ae81471220d4cc Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Fri, 17 Jan 2025 17:26:54 -0500 Subject: [PATCH 11/30] Fix after rebase --- bin/src/Flags.purs | 4 +-- src/Spago/Command/Auth.purs | 28 +++++++++++------ src/Spago/Command/Publish.purs | 2 +- src/Spago/Command/Registry.purs | 23 +++++++------- src/Spago/Config.purs | 2 +- test/Spago/Publish.purs | 54 ++++++++++++++++----------------- 6 files changed, 61 insertions(+), 52 deletions(-) diff --git a/bin/src/Flags.purs b/bin/src/Flags.purs index 28e3d0e27..ddc40deda 100644 --- a/bin/src/Flags.purs +++ b/bin/src/Flags.purs @@ -336,7 +336,7 @@ depsOnly = <> O.help "Build depedencies only" ) -publicKeyPath :: Parser FilePath +publicKeyPath :: Parser AdHocFilePath publicKeyPath = O.strOption ( O.short 'i' @@ -344,7 +344,7 @@ publicKeyPath = <> O.help "Select the path of the public key to use for authenticating operations of the package" ) -privateKeyPath :: Parser FilePath +privateKeyPath :: Parser AdHocFilePath privateKeyPath = O.strOption ( O.short 'i' diff --git a/src/Spago/Command/Auth.purs b/src/Spago/Command/Auth.purs index 75f0f8b3e..933b60c1f 100644 --- a/src/Spago/Command/Auth.purs +++ b/src/Spago/Command/Auth.purs @@ -6,26 +6,29 @@ import Spago.Prelude import Data.Array as Array import Data.String (Pattern(..)) import Data.String as String -import Node.Path as Path import Registry.SSH as SSH import Spago.Command.Fetch (FetchEnv) import Spago.Config as Config import Spago.FS as FS +import Spago.Path as Path +import Spago.Paths as Paths -type AuthArgs = { keyPath :: FilePath } +type AuthArgs = { keyPath :: AdHocFilePath } -run :: AuthArgs -> Spago (FetchEnv _) Unit +run :: ∀ r. AuthArgs -> Spago (FetchEnv r) Unit run { keyPath } = do logDebug $ "Authenticating with key at path " <> keyPath + + here <- Paths.cwd let -- we don't want to accidentally read the private key, so we always point to the public - path = case String.stripSuffix (Pattern ".pub") keyPath of + path = here case String.stripSuffix (Pattern ".pub") keyPath of Just _rest -> keyPath Nothing -> keyPath <> ".pub" newOwner <- FS.exists path >>= case _ of false -> do - die $ "Cannot read public key at path " <> show path <> ": file does not exist." + die $ "Cannot read public key at path " <> Path.quote path <> ": file does not exist." true -> do content <- FS.readTextFile path let result = SSH.parsePublicKey content @@ -34,11 +37,16 @@ run { keyPath } = do Right public -> pure $ SSH.publicKeyToOwner public logDebug $ "Parsed owner: " <> show (unwrap newOwner) - { workspace } <- ask + { workspace, rootPath } <- ask { doc, package, configPath } <- case workspace.selected, workspace.rootPackage of - Just { doc, package, path: packagePath }, _ -> pure { doc, package, configPath: Path.concat [ packagePath, "spago.yaml" ] } - Nothing, Just rootPackage -> pure { doc: workspace.doc, package: rootPackage, configPath: "spago.yaml" } - Nothing, Nothing -> die "No package was selected. Please select a package with the -p flag" + Just { doc: maybeDoc, package, path: packagePath }, _ -> do + doc <- justOrDieWith maybeDoc Config.configDocMissingErrorMessage + pure { doc, package, configPath: packagePath "spago.yaml" } + Nothing, Just rootPackage -> do + doc <- justOrDieWith workspace.doc Config.configDocMissingErrorMessage + pure { doc, package: rootPackage, configPath: rootPath "spago.yaml" } + Nothing, Nothing -> + die "No package was selected. Please select a package with the -p flag" case package.publish of Nothing -> die @@ -51,7 +59,7 @@ run { keyPath } = do case Array.elem newOwner currentOwners of true -> logWarn "Selected key is already present in the config file." false -> do - logInfo $ "Adding selected key to the list of the owners: " <> path + logInfo $ "Adding selected key to the list of the owners: " <> Path.quote path Config.addOwner configPath doc newOwner logSuccess "The selected key has been added to the list of the owners." logInfo "Once you publish a new version with this configuration you'll be able to transfer and unpublish packages using this key." diff --git a/src/Spago/Command/Publish.purs b/src/Spago/Command/Publish.purs index 6264fabad..91ef2a596 100644 --- a/src/Spago/Command/Publish.purs +++ b/src/Spago/Command/Publish.purs @@ -188,7 +188,7 @@ publish _args = do , "submit a transfer operation." ] - locationResult <- locationIsInGitRemotes location + locationResult <- locationIsInGitRemotes rootPath location unless locationResult.result $ addError $ toDoc [ toDoc "The location specified in the manifest file is not one of the remotes in the git repository." , toDoc "Location:" diff --git a/src/Spago/Command/Registry.purs b/src/Spago/Command/Registry.purs index 1427a536a..9167b9d3a 100644 --- a/src/Spago/Command/Registry.purs +++ b/src/Spago/Command/Registry.purs @@ -26,6 +26,7 @@ import Spago.Db as Db import Spago.FS as FS import Spago.Git as Git import Spago.Json as Json +import Spago.Path as Path import Spago.Registry (RegistryEnv) import Spago.Registry as Registry @@ -35,7 +36,7 @@ type RegistrySearchArgs = } -- TODO: I guess we could also search in (1) the tags and (2) the description -search :: RegistrySearchArgs -> Spago (RegistryEnv _) Unit +search :: ∀ r. RegistrySearchArgs -> Spago (RegistryEnv r) Unit search { package: searchString, json } = do logInfo $ "Searching for " <> show searchString <> " in the Registry package names..." metadataFiles <- Registry.listMetadataFiles @@ -83,7 +84,7 @@ type RegistryInfoArgs = , json :: Boolean } -info :: RegistryInfoArgs -> Spago (RegistryEnv _) Unit +info :: ∀ r. RegistryInfoArgs -> Spago (RegistryEnv r) Unit info { package, json } = do packageName <- case PackageName.parse package of Left err -> die [ toDoc "Could not parse package name, error:", indent (toDoc $ show err) ] @@ -104,7 +105,7 @@ type RegistryPackageSetsArgs = , json :: Boolean } -packageSets :: RegistryPackageSetsArgs -> Spago (RegistryEnv _) Unit +packageSets :: ∀ r. RegistryPackageSetsArgs -> Spago (RegistryEnv r) Unit packageSets { latest, json } = do availableSets <- Registry.listPackageSets @@ -137,12 +138,12 @@ packageSets { latest, json } = do ] } -type RegistryTransferArgs = { privateKeyPath :: String } +type RegistryTransferArgs = { privateKeyPath :: AdHocFilePath } -transfer :: RegistryTransferArgs -> Spago (FetchEnv _) Unit +transfer :: ∀ r. RegistryTransferArgs -> Spago (FetchEnv r) Unit transfer { privateKeyPath } = do logDebug $ "Running package transfer" - { workspace, offline } <- ask + { workspace, offline, rootPath } <- ask selected <- case workspace.selected of Just s -> pure s @@ -177,7 +178,7 @@ transfer { privateKeyPath } = do -- Check that the git tree is clean - since the transfer will obey the new content -- of the config file, it makes sense to have it commited before transferring - Git.getStatus Nothing >>= case _ of + Git.getStatus rootPath >>= case _ of Left _err -> do die $ toDoc [ toDoc "Could not verify whether the git tree is clean. Error was:" @@ -215,7 +216,7 @@ transfer { privateKeyPath } = do let dataToSign = { name: selected.package.name, newLocation } let rawPayload = Json.stringifyJson Operation.transferCodec dataToSign - key <- getPrivateKeyForSigning privateKeyPath + key <- getPrivateKeyForSigning $ rootPath privateKeyPath -- We have a key! We can sign the payload with it, and submit the whole package to the Registry let signature = SSH.sign key rawPayload @@ -229,7 +230,7 @@ transfer { privateKeyPath } = do , payload: Operation.Transfer dataToSign } -getPrivateKeyForSigning :: forall e. FilePath -> Spago (LogEnv e) SSH.PrivateKey +getPrivateKeyForSigning :: ∀ e. LocalPath -> Spago (LogEnv e) SSH.PrivateKey getPrivateKeyForSigning privateKeyPath = do -- If all is well we read in the private key privateKey <- try (FS.readTextFile privateKeyPath) >>= case _ of @@ -250,7 +251,7 @@ getPrivateKeyForSigning privateKeyPath = do Left _ -> do decodeKeyInteractive { requiresPassword: true, attemptsLeft } true -> do - let prompt = "Enter passphrase for " <> privateKeyPath <> ": " + let prompt = "Enter passphrase for " <> Path.quote privateKeyPath <> ": " passphrase <- liftEffect $ runEffectFn1 questionPassword prompt case SSH.parsePrivateKey { key: privateKey, passphrase: Just passphrase } of @@ -266,7 +267,7 @@ getPrivateKeyForSigning privateKeyPath = do type RegistryUnpublishArgs = { version :: Version, reason :: Maybe String } -unpublish :: RegistryUnpublishArgs -> Spago (RegistryEnv _) Unit +unpublish :: ∀ r. RegistryUnpublishArgs -> Spago (RegistryEnv r) Unit unpublish _a = do -- { version, reason } = do logError "Unpublishing packages is not supported yet." die [ "Please contact the maintainers if you need to unpublish a package." ] diff --git a/src/Spago/Config.purs b/src/Spago/Config.purs index 74a3bb88d..5c2885f7b 100644 --- a/src/Spago/Config.purs +++ b/src/Spago/Config.purs @@ -686,7 +686,7 @@ addPublishLocationToConfig doc loc = type OwnerJS = { public :: String, keytype :: String, id :: Nullable String } -addOwner :: forall m. MonadAff m => FilePath -> YamlDoc Core.Config -> Owner -> m Unit +addOwner :: forall m. MonadAff m => LocalPath -> YamlDoc Core.Config -> Owner -> m Unit addOwner configPath doc (Owner { id, keytype, public }) = do liftEffect $ runEffectFn2 addOwnerImpl doc { keytype, public, id: Nullable.toNullable id } liftAff $ FS.writeYamlDocFile configPath doc diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index e4a996811..5971c7fd3 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -82,41 +82,41 @@ spec = Spec.around withTempDir do Spec.it "fails if the publish config is not specified" \{ spago, fixture } -> do spago [ "init", "--name", "aaaa" ] >>= shouldBeSuccess - spago [ "registry", "transfer", "--offline", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-publish-config.txt") + spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-publish-config.txt") - Spec.it "fails if the config does not specify an owner" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/basic.yaml", dst: "spago.yaml" } + Spec.it "fails if the config does not specify an owner" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/basic.yaml", dst: testCwd "spago.yaml" } spago [ "build" ] >>= shouldBeSuccess - spago [ "registry", "transfer", "--offline", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-owner.txt") + spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-owner.txt") - Spec.it "fails if the git tree is not clean" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/basic.yaml", dst: "spago.yaml" } - spago [ "auth", "-i", (fixture "publish/key") ] >>= shouldBeSuccess - spago [ "registry", "transfer", "--offline", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-git.txt") + Spec.it "fails if the git tree is not clean" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/basic.yaml", dst: testCwd "spago.yaml" } + spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess + spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-git.txt") - Spec.it "fails if the package has never been published before" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/basic.yaml", dst: "spago.yaml" } - spago [ "auth", "-i", (fixture "publish/key") ] >>= shouldBeSuccess + Spec.it "fails if the package has never been published before" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/basic.yaml", dst: testCwd "spago.yaml" } + spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/never-published.txt") + spago [ "registry", "transfer", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/never-published.txt") - Spec.it "fails if the new repo location is the same as the current one in the registry" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/transfer/aff.yaml", dst: "spago.yaml" } - spago [ "auth", "-i", (fixture "publish/key") ] >>= shouldBeSuccess + Spec.it "fails if the new repo location is the same as the current one in the registry" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/transfer/aff.yaml", dst: testCwd "spago.yaml" } + spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/same-location.txt") + spago [ "registry", "transfer", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/same-location.txt") - Spec.it "fails if can't find the private key" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: "spago.yaml" } - spago [ "auth", "-i", (fixture "publish/key") ] >>= shouldBeSuccess + Spec.it "fails if can't find the private key" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: testCwd "spago.yaml" } + spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "-i", (fixture "publish/no-key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-key.txt") + spago [ "registry", "transfer", "-i", (Path.toRaw $ fixture "publish/no-key") ] >>= shouldBeFailureErr (fixture "publish/transfer/no-key.txt") - Spec.it "fails if running with --offline" \{ spago, fixture } -> do - FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: "spago.yaml" } - spago [ "auth", "-i", (fixture "publish/key") ] >>= shouldBeSuccess + Spec.it "fails if running with --offline" \{ spago, fixture, testCwd } -> do + FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: testCwd "spago.yaml" } + spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "--offline", "-i", (fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") + spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do let @@ -126,7 +126,7 @@ spec = Spec.around withTempDir do , result: isLeft , sanitize: String.trim - >>> withForwardSlashes + >>> String.replaceAll (String.Pattern "\\") (String.Replacement "/") >>> String.replaceAll (String.Pattern "\r\n") (String.Replacement "\n") >>> Regex.replace buildOrderRegex "[x of 3] Compiling module-name" } @@ -164,9 +164,9 @@ spec = Spec.around withTempDir do rmRf $ testCwd ".spago/p/console-6.1.0/output" spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/failure-stderr.txt") - Spec.describe "#1060 auto-filling the `publish.location` field" \{ testCwd } -> do + Spec.describe "#1060 auto-filling the `publish.location` field" do let - prepareProject spago fixture = do + prepareProject spago fixture testCwd = do FS.copyTree { src: fixture "publish/1060-autofill-location/project", dst: testCwd } spago [ "build" ] >>= shouldBeSuccess doTheGitThing From e11d2b5273c411d591078658d36da08aa5cfe7cf Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Fri, 17 Jan 2025 17:44:12 -0500 Subject: [PATCH 12/30] Remove debug --- spago.lock | 12 ------------ spago.yaml | 1 - 2 files changed, 13 deletions(-) diff --git a/spago.lock b/spago.lock index b729a9359..a139fa007 100644 --- a/spago.lock +++ b/spago.lock @@ -473,7 +473,6 @@ "console", "control", "datetime", - "debug", "docs-search-common", "docs-search-index", "dodo-printer", @@ -542,7 +541,6 @@ "contravariant", "control", "datetime", - "debug", "distributive", "docs-search-common", "docs-search-index", @@ -776,7 +774,6 @@ "contravariant", "control", "datetime", - "debug", "distributive", "docs-search-common", "docs-search-index", @@ -2087,15 +2084,6 @@ "tuples" ] }, - "debug": { - "type": "registry", - "version": "6.0.2", - "integrity": "sha256-vmkYFuXYuELBzeauvgHG6E6Kf/Hp1dAnxwE9ByHfwSg=", - "dependencies": [ - "functions", - "prelude" - ] - }, "distributive": { "type": "registry", "version": "6.0.0", diff --git a/spago.yaml b/spago.yaml index 26188f91c..62e9d6cee 100644 --- a/spago.yaml +++ b/spago.yaml @@ -12,7 +12,6 @@ package: - WildcardInferredType - ImplicitQualifiedImportReExport dependencies: - - debug - aff - aff-promise - affjax From 212ef661fd4161f37744c6cb0f1a25dd59afc8ab Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Fri, 17 Jan 2025 17:55:48 -0500 Subject: [PATCH 13/30] Remove debug type signature, add back script, unnest tests --- src/Spago/Command/Bundle.purs | 2 +- src/Spago/Command/Script.purs | 1 + test/Spago/Publish.purs | 174 +++++++++++++++++----------------- 3 files changed, 89 insertions(+), 88 deletions(-) create mode 100644 src/Spago/Command/Script.purs diff --git a/src/Spago/Command/Bundle.purs b/src/Spago/Command/Bundle.purs index 5d3ddbb44..b59dec977 100644 --- a/src/Spago/Command/Bundle.purs +++ b/src/Spago/Command/Bundle.purs @@ -35,7 +35,7 @@ type BundleOptions = run :: ∀ a. Spago (BundleEnv a) Unit run = do - ({ rootPath, esbuild, selected, workspace, bundleOptions: opts } :: BundleEnv a) <- ask + { rootPath, esbuild, selected, workspace, bundleOptions: opts } <- ask logDebug $ "Bundle options: " <> show opts let minify = if opts.minify then [ "--minify" ] else [] diff --git a/src/Spago/Command/Script.purs b/src/Spago/Command/Script.purs new file mode 100644 index 000000000..9d579a2b4 --- /dev/null +++ b/src/Spago/Command/Script.purs @@ -0,0 +1 @@ +module Spago.Command.Script where diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index 5971c7fd3..b6f15cf00 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -118,93 +118,93 @@ spec = Spec.around withTempDir do doTheGitThing spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") - Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do - let - shouldBeFailureErr' file = checkOutputs' - { stdoutFile: Nothing - , stderrFile: Just file - , result: isLeft - , sanitize: - String.trim - >>> String.replaceAll (String.Pattern "\\") (String.Replacement "/") - >>> String.replaceAll (String.Pattern "\r\n") (String.Replacement "\n") - >>> Regex.replace buildOrderRegex "[x of 3] Compiling module-name" - } - - -- We have to ignore lines like "[1 of 3] Compiling Effect.Console" when - -- comparing output, because the compiler will always compile in - -- different order, depending on how the system resources happened to - -- align at the moment of the test run. - buildOrderRegex = unsafeFromRight $ Regex.regex - "\\[\\d of 3\\] Compiling (Effect\\.Console|Effect\\.Class\\.Console|Lib)" - RF.global - - FS.copyTree { src: fixture "publish/1110-solver-different-version", dst: testCwd } - spago [ "build" ] >>= shouldBeSuccess - doTheGitThing - spago [ "fetch" ] >>= shouldBeSuccess - - -- The local `spago.yaml` specifies `console: 6.0.0` in `extraPackages`, - -- so that's what should be in local cache after running `fetch`. - -- Importantly, `console-6.1.0` should not be there yet. - FS.exists (testCwd ".spago/p/console-6.0.0") >>= (_ `shouldEqual` true) - FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` false) - - spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/expected-stderr.txt") - - -- When `publish` runs, it uses the registry solver, which returns - -- `console-6.1.0` version, so `publish` should fetch that into local - -- cache and build with it. - FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` true) - - -- Now screw up the `console-6.1.0` package in the local cache, so that it - -- doesn't compile anymore, and check that the relevant compile error - -- happens on publish. - FS.unlink $ testCwd ".spago/p/console-6.1.0/src/Effect/Console.js" - rmRf $ testCwd ".spago/p/console-6.1.0/output" - spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/failure-stderr.txt") - - Spec.describe "#1060 auto-filling the `publish.location` field" do - let - prepareProject spago fixture testCwd = do - FS.copyTree { src: fixture "publish/1060-autofill-location/project", dst: testCwd } - spago [ "build" ] >>= shouldBeSuccess - doTheGitThing - spago [ "fetch" ] >>= shouldBeSuccess - - Spec.it "happens for root package" \{ fixture, spago, testCwd } -> do - prepareProject spago fixture testCwd - spago [ "publish", "-p", "aaa", "--offline" ] >>= - shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-root/expected-stderr.txt") - checkFixture (testCwd "spago.yaml") - (fixture "publish/1060-autofill-location/scenario-root/expected-spago.yaml") - - Spec.it "errors out for non-root package" \{ fixture, spago, testCwd } -> do - prepareProject spago fixture testCwd - spago [ "publish", "-p", "bbb", "--offline" ] >>= - shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-subdir/expected-stderr.txt") - - Spec.it "errors out for nested non-root package" \{ fixture, spago, testCwd } -> do - prepareProject spago fixture testCwd - spago [ "publish", "-p", "ccc", "--offline" ] >>= - shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-nested-subdir/expected-stderr.txt") - - Spec.it "errors out when not a GitHub remote" \{ fixture, spago, testCwd } -> do - prepareProject spago fixture testCwd - git [ "remote", "set-url", "origin", "https://not.git-hub.net/foo/bar.git" ] - spago [ "publish", "-p", "aaa", "--offline" ] >>= - shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-non-github/expected-stderr.txt") - checkFixture (testCwd "spago.yaml") - (fixture "publish/1060-autofill-location/scenario-non-github/expected-spago.yaml") - - Spec.it "prints error when no origin remote" \{ fixture, spago, testCwd } -> do - prepareProject spago fixture testCwd - git [ "remote", "remove", "origin" ] - git [ "remote", "add", "upstream", "git@github.com:foo/bar.git" ] - spago [ "publish", "-p", "aaa", "--offline" ] >>= - shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-no-origin/expected-stderr.txt") - checkFixture (testCwd "spago.yaml") - (fixture "publish/1060-autofill-location/project/spago.yaml") + Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do + let + shouldBeFailureErr' file = checkOutputs' + { stdoutFile: Nothing + , stderrFile: Just file + , result: isLeft + , sanitize: + String.trim + >>> String.replaceAll (String.Pattern "\\") (String.Replacement "/") + >>> String.replaceAll (String.Pattern "\r\n") (String.Replacement "\n") + >>> Regex.replace buildOrderRegex "[x of 3] Compiling module-name" + } + + -- We have to ignore lines like "[1 of 3] Compiling Effect.Console" when + -- comparing output, because the compiler will always compile in + -- different order, depending on how the system resources happened to + -- align at the moment of the test run. + buildOrderRegex = unsafeFromRight $ Regex.regex + "\\[\\d of 3\\] Compiling (Effect\\.Console|Effect\\.Class\\.Console|Lib)" + RF.global + + FS.copyTree { src: fixture "publish/1110-solver-different-version", dst: testCwd } + spago [ "build" ] >>= shouldBeSuccess + doTheGitThing + spago [ "fetch" ] >>= shouldBeSuccess + + -- The local `spago.yaml` specifies `console: 6.0.0` in `extraPackages`, + -- so that's what should be in local cache after running `fetch`. + -- Importantly, `console-6.1.0` should not be there yet. + FS.exists (testCwd ".spago/p/console-6.0.0") >>= (_ `shouldEqual` true) + FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` false) + + spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/expected-stderr.txt") + + -- When `publish` runs, it uses the registry solver, which returns + -- `console-6.1.0` version, so `publish` should fetch that into local + -- cache and build with it. + FS.exists (testCwd ".spago/p/console-6.1.0") >>= (_ `shouldEqual` true) + + -- Now screw up the `console-6.1.0` package in the local cache, so that it + -- doesn't compile anymore, and check that the relevant compile error + -- happens on publish. + FS.unlink $ testCwd ".spago/p/console-6.1.0/src/Effect/Console.js" + rmRf $ testCwd ".spago/p/console-6.1.0/output" + spago [ "publish", "--offline" ] >>= shouldBeFailureErr' (fixture "publish/1110-solver-different-version/failure-stderr.txt") + + Spec.describe "#1060 auto-filling the `publish.location` field" do + let + prepareProject spago fixture testCwd = do + FS.copyTree { src: fixture "publish/1060-autofill-location/project", dst: testCwd } + spago [ "build" ] >>= shouldBeSuccess + doTheGitThing + spago [ "fetch" ] >>= shouldBeSuccess + + Spec.it "happens for root package" \{ fixture, spago, testCwd } -> do + prepareProject spago fixture testCwd + spago [ "publish", "-p", "aaa", "--offline" ] >>= + shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-root/expected-stderr.txt") + checkFixture (testCwd "spago.yaml") + (fixture "publish/1060-autofill-location/scenario-root/expected-spago.yaml") + + Spec.it "errors out for non-root package" \{ fixture, spago, testCwd } -> do + prepareProject spago fixture testCwd + spago [ "publish", "-p", "bbb", "--offline" ] >>= + shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-subdir/expected-stderr.txt") + + Spec.it "errors out for nested non-root package" \{ fixture, spago, testCwd } -> do + prepareProject spago fixture testCwd + spago [ "publish", "-p", "ccc", "--offline" ] >>= + shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-nested-subdir/expected-stderr.txt") + + Spec.it "errors out when not a GitHub remote" \{ fixture, spago, testCwd } -> do + prepareProject spago fixture testCwd + git [ "remote", "set-url", "origin", "https://not.git-hub.net/foo/bar.git" ] + spago [ "publish", "-p", "aaa", "--offline" ] >>= + shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-non-github/expected-stderr.txt") + checkFixture (testCwd "spago.yaml") + (fixture "publish/1060-autofill-location/scenario-non-github/expected-spago.yaml") + + Spec.it "prints error when no origin remote" \{ fixture, spago, testCwd } -> do + prepareProject spago fixture testCwd + git [ "remote", "remove", "origin" ] + git [ "remote", "add", "upstream", "git@github.com:foo/bar.git" ] + spago [ "publish", "-p", "aaa", "--offline" ] >>= + shouldBeFailureErr (fixture "publish/1060-autofill-location/scenario-no-origin/expected-stderr.txt") + checkFixture (testCwd "spago.yaml") + (fixture "publish/1060-autofill-location/project/spago.yaml") doTheGitThing :: Aff Unit doTheGitThing = do From af3e06d93972f56f93b7609f223c60f142ff6263 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 11:12:35 -0500 Subject: [PATCH 14/30] Fix a typo, move no-doc crashes closer to where the doc is used --- CONTRIBUTING.md | 4 ++-- src/Spago/Command/Auth.purs | 11 +++++------ src/Spago/Command/Fetch.purs | 11 ++++++----- src/Spago/Command/Uninstall.purs | 11 +++++------ 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 056533417..a926abb5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -156,5 +156,5 @@ rootPath <- Path.mkRootPath =<< Paths.cwd config <- readConfig (rootPath "spago.yaml") let srcDir = rootPath "src" compileResult <- callCompiler [ srcDir "Main.purs", srcDir "Lib.purs" ] -FS.writeFile (rootPath "result.json") (serialize compipleResult) -``` \ No newline at end of file +FS.writeFile (rootPath "result.json") (serialize compileResult) +``` diff --git a/src/Spago/Command/Auth.purs b/src/Spago/Command/Auth.purs index 933b60c1f..ffe74b5c1 100644 --- a/src/Spago/Command/Auth.purs +++ b/src/Spago/Command/Auth.purs @@ -39,12 +39,10 @@ run { keyPath } = do { workspace, rootPath } <- ask { doc, package, configPath } <- case workspace.selected, workspace.rootPackage of - Just { doc: maybeDoc, package, path: packagePath }, _ -> do - doc <- justOrDieWith maybeDoc Config.configDocMissingErrorMessage + Just { doc, package, path: packagePath }, _ -> pure { doc, package, configPath: packagePath "spago.yaml" } - Nothing, Just rootPackage -> do - doc <- justOrDieWith workspace.doc Config.configDocMissingErrorMessage - pure { doc, package: rootPackage, configPath: rootPath "spago.yaml" } + Nothing, Just rootPackage -> + pure { doc: workspace.doc, package: rootPackage, configPath: rootPath "spago.yaml" } Nothing, Nothing -> die "No package was selected. Please select a package with the -p flag" @@ -60,6 +58,7 @@ run { keyPath } = do true -> logWarn "Selected key is already present in the config file." false -> do logInfo $ "Adding selected key to the list of the owners: " <> Path.quote path - Config.addOwner configPath doc newOwner + doc' <- justOrDieWith doc Config.configDocMissingErrorMessage + Config.addOwner configPath doc' newOwner logSuccess "The selected key has been added to the list of the owners." logInfo "Once you publish a new version with this configuration you'll be able to transfer and unpublish packages using this key." diff --git a/src/Spago/Command/Fetch.purs b/src/Spago/Command/Fetch.purs index 59b3af9c6..bea158293 100644 --- a/src/Spago/Command/Fetch.purs +++ b/src/Spago/Command/Fetch.purs @@ -101,8 +101,7 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do , "Please use the `-p` flag to select a package " <> errorMessageEnd ] - doc <- justOrDieWith res.yamlDoc Config.configDocMissingErrorMessage - pure res { yamlDoc = doc } + pure res { yamlDoc = res.yamlDoc } installingPackagesData <- do case packagesRequestedToInstall of @@ -167,7 +166,8 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do 1 -> "1 package" n -> show n <> " packages" logInfo $ "Adding " <> countString <> " to the config in " <> Path.quote configPath - liftAff $ Config.addPackagesToConfig configPath yamlDoc isTest actualPackagesToInstall + 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 @@ -176,8 +176,9 @@ run { packages: packagesRequestedToInstall, ensureRanges, isTest, isRepl } = do packageDeps <- (Map.lookup package.name dependencies) `justOrDieWith` "Impossible: package dependencies must be in dependencies map" let rangeMap = map getRangeFromPackage packageDeps.core - liftEffect $ Config.addRangesToConfig yamlDoc rangeMap - liftAff $ FS.writeYamlDocFile configPath yamlDoc + doc <- justOrDieWith yamlDoc Config.configDocMissingErrorMessage + liftEffect $ Config.addRangesToConfig doc rangeMap + liftAff $ FS.writeYamlDocFile configPath doc -- the repl needs a support package, so we add it here as a sidecar supportPackage <- Repl.supportPackage workspace.packageSet diff --git a/src/Spago/Command/Uninstall.purs b/src/Spago/Command/Uninstall.purs index 4a8626917..44dc2cb42 100644 --- a/src/Spago/Command/Uninstall.purs +++ b/src/Spago/Command/Uninstall.purs @@ -32,13 +32,11 @@ run args = do logDebug "Running `spago uninstall`" { workspace, rootPath } <- ask - { sourceOrTestString, deps, configPath, yamlDoc: doc', name } <- case workspace.selected, workspace.rootPackage of + { sourceOrTestString, deps, configPath, yamlDoc, name } <- case workspace.selected, workspace.rootPackage of Just p, _ -> toContext (p.path "spago.yaml") p.doc p.package Nothing, Just rootPackage -> toContext (rootPath "spago.yaml") workspace.doc rootPackage Nothing, Nothing -> die "No package was selected. Please select a package." - yamlDoc <- justOrDieWith doc' Config.configDocMissingErrorMessage - let { warn, removed: removedSet } = separate deps warnAbout = NEA.fromFoldable warn @@ -121,10 +119,11 @@ run args = do true -> acc { removed = Set.insert next acc.removed } false -> acc { warn = Set.insert next acc.warn } - modifyConfig :: LocalPath -> YamlDoc Core.Config -> String -> NonEmptyArray PackageName -> Spago (FetchEnv _) Unit + modifyConfig :: LocalPath -> Maybe (YamlDoc Core.Config) -> String -> NonEmptyArray PackageName -> Spago (FetchEnv _) Unit modifyConfig configPath yamlDoc sourceOrTestString = \removedPackages -> do logInfo $ "Removing the following " <> sourceOrTestString <> " dependencies: " <> (String.joinWith ", " $ map PackageName.print $ Array.fromFoldable removedPackages) logDebug $ "Editing config file at path: " <> Path.quote configPath - liftEffect $ Config.removePackagesFromConfig yamlDoc args.testDeps $ NonEmptySet.fromFoldable1 removedPackages - liftAff $ FS.writeYamlDocFile configPath yamlDoc + doc <- justOrDieWith yamlDoc Config.configDocMissingErrorMessage + liftEffect $ Config.removePackagesFromConfig doc args.testDeps $ NonEmptySet.fromFoldable1 removedPackages + liftAff $ FS.writeYamlDocFile configPath doc From 09db00e2c3c5a022d42e6efc8f9b96ebe907e2ea Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 21:58:38 -0500 Subject: [PATCH 15/30] Debug Windows failure --- .github/workflows/build.yml | 2 +- spago.yaml | 1 + test/Spago/Publish.purs | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6bb0e2c39..64874f76a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -79,7 +79,7 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - run: node ./bin/bundle.js test + run: node ./bin/bundle.js test -- -e "fails if running with --offline" - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' diff --git a/spago.yaml b/spago.yaml index 62e9d6cee..26188f91c 100644 --- a/spago.yaml +++ b/spago.yaml @@ -12,6 +12,7 @@ package: - WildcardInferredType - ImplicitQualifiedImportReExport dependencies: + - debug - aff - aff-promise - affjax diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index b6f15cf00..577e0d3f5 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -5,6 +5,7 @@ import Test.Prelude import Data.String as String import Data.String.Regex as Regex import Data.String.Regex.Flags as RF +import Debug (traceM) import Node.Platform as Platform import Node.Process as Process import Spago.Cmd as Cmd @@ -114,6 +115,8 @@ spec = Spec.around withTempDir do Spec.it "fails if running with --offline" \{ spago, fixture, testCwd } -> do FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: testCwd "spago.yaml" } + traceM $ Path.quote $ fixture "publish/key" + traceM =<< FS.exists (fixture "publish/key") spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") From ca2be2de196f35fe61410cc9ee3c79113ad15b76 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:07:02 -0500 Subject: [PATCH 16/30] Debug Windows failure --- src/Spago/Command/Auth.purs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Spago/Command/Auth.purs b/src/Spago/Command/Auth.purs index ffe74b5c1..b2d61c36b 100644 --- a/src/Spago/Command/Auth.purs +++ b/src/Spago/Command/Auth.purs @@ -6,6 +6,7 @@ import Spago.Prelude import Data.Array as Array import Data.String (Pattern(..)) import Data.String as String +import Debug (traceM) import Registry.SSH as SSH import Spago.Command.Fetch (FetchEnv) import Spago.Config as Config @@ -28,6 +29,7 @@ run { keyPath } = do newOwner <- FS.exists path >>= case _ of false -> do + traceM { path, keyPath } die $ "Cannot read public key at path " <> Path.quote path <> ": file does not exist." true -> do content <- FS.readTextFile path From a5fd09db498ee8bdc36e3e49992dbfe0c532568b Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:17:57 -0500 Subject: [PATCH 17/30] Debug Windows failure --- spago.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spago.yaml b/spago.yaml index 26188f91c..454774029 100644 --- a/spago.yaml +++ b/spago.yaml @@ -7,7 +7,7 @@ package: githubOwner: purescript githubRepo: spago build: - strict: true + # strict: true censorProjectWarnings: - WildcardInferredType - ImplicitQualifiedImportReExport From 35f448cbd8d64339c603567f09545e02c42d9389 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:26:33 -0500 Subject: [PATCH 18/30] Debug Windows failure --- .github/workflows/build.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 64874f76a..9a1e1e1bf 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,9 @@ jobs: env: cache-name: cache-node-modules with: - path: ~/.npm + path: | + ~/.npm + node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} restore-keys: | ${{ runner.os }}-build-${{ env.cache-name }}- @@ -79,7 +81,7 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - run: node ./bin/bundle.js test -- -e "fails if running with --offline" + run: SPAGO_TEST_DEBUG=1 node ./bin/bundle.js test -- -e "fails if running with --offline" - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' From 73324bb046a1df6fb4542248020459e53ee34701 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:33:09 -0500 Subject: [PATCH 19/30] Debug Windows failure --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a1e1e1bf..28e56aaa5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,7 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - run: SPAGO_TEST_DEBUG=1 node ./bin/bundle.js test -- -e "fails if running with --offline" + run: bash -c SPAGO_TEST_DEBUG=1 node ./bin/bundle.js test -- -e "fails if running with --offline" - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' From 8c9212e2f61beac4c213206b010f7c33facc91d7 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:42:17 -0500 Subject: [PATCH 20/30] Debug Windows failure --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 28e56aaa5..f3a99ce69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,9 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - run: bash -c SPAGO_TEST_DEBUG=1 node ./bin/bundle.js test -- -e "fails if running with --offline" + env: + SPAGO_TEST_DEBUG: 1 + run: node ./bin/bundle.js test -- -e "fails if running with --offline" - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' From d22ab4a65d0edbd3609fc82d216bf58e035585c2 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:50:24 -0500 Subject: [PATCH 21/30] Debug Windows failure --- spago.lock | 12 ++++++++++++ src/Spago/Command/Auth.purs | 2 -- test/Spago/Publish.purs | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/spago.lock b/spago.lock index a139fa007..b729a9359 100644 --- a/spago.lock +++ b/spago.lock @@ -473,6 +473,7 @@ "console", "control", "datetime", + "debug", "docs-search-common", "docs-search-index", "dodo-printer", @@ -541,6 +542,7 @@ "contravariant", "control", "datetime", + "debug", "distributive", "docs-search-common", "docs-search-index", @@ -774,6 +776,7 @@ "contravariant", "control", "datetime", + "debug", "distributive", "docs-search-common", "docs-search-index", @@ -2084,6 +2087,15 @@ "tuples" ] }, + "debug": { + "type": "registry", + "version": "6.0.2", + "integrity": "sha256-vmkYFuXYuELBzeauvgHG6E6Kf/Hp1dAnxwE9ByHfwSg=", + "dependencies": [ + "functions", + "prelude" + ] + }, "distributive": { "type": "registry", "version": "6.0.0", diff --git a/src/Spago/Command/Auth.purs b/src/Spago/Command/Auth.purs index b2d61c36b..ffe74b5c1 100644 --- a/src/Spago/Command/Auth.purs +++ b/src/Spago/Command/Auth.purs @@ -6,7 +6,6 @@ import Spago.Prelude import Data.Array as Array import Data.String (Pattern(..)) import Data.String as String -import Debug (traceM) import Registry.SSH as SSH import Spago.Command.Fetch (FetchEnv) import Spago.Config as Config @@ -29,7 +28,6 @@ run { keyPath } = do newOwner <- FS.exists path >>= case _ of false -> do - traceM { path, keyPath } die $ "Cannot read public key at path " <> Path.quote path <> ": file does not exist." true -> do content <- FS.readTextFile path diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index 577e0d3f5..9a7493b38 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -119,7 +119,7 @@ spec = Spec.around withTempDir do traceM =<< FS.exists (fixture "publish/key") spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") + spago [ "registry", "transfer", "-v", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do let From 8bafbd1e19fb8d0d0fb6cc6425148c853421f3dd Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 22:53:52 -0500 Subject: [PATCH 22/30] Debug Windows failure --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3a99ce69..f0a869187 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,8 +13,8 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest - - os: macOS-latest + # - os: ubuntu-latest + # - os: macOS-latest - os: windows-latest steps: # We set LF endings so that the Windows environment is consistent with the rest @@ -47,6 +47,7 @@ jobs: with: path: | ~/.npm + ~/AppData/Roaming/npm node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} restore-keys: | From 4b401748ba1ced839701101d6a493d39237b6780 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 23:00:11 -0500 Subject: [PATCH 23/30] Debug Windows failure --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0a869187..4fe4362f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,10 +81,10 @@ jobs: - name: Bundle docs-search client run: node ./bin/bundle.js bundle -p docs-search-client-halogen - - name: Run tests - env: - SPAGO_TEST_DEBUG: 1 - run: node ./bin/bundle.js test -- -e "fails if running with --offline" + # - name: Run tests + # env: + # SPAGO_TEST_DEBUG: 1 + # run: node ./bin/bundle.js test -- -e "fails if running with --offline" --no-timeout - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' From 7e69b37c767cf3e9ae82bd870368596c3f638f1e Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 23:05:21 -0500 Subject: [PATCH 24/30] Debug Windows failure --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4fe4362f1..283889265 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,10 +81,10 @@ jobs: - name: Bundle docs-search client run: node ./bin/bundle.js bundle -p docs-search-client-halogen - # - name: Run tests - # env: - # SPAGO_TEST_DEBUG: 1 - # run: node ./bin/bundle.js test -- -e "fails if running with --offline" --no-timeout + - name: Run tests + env: + SPAGO_TEST_DEBUG: 1 + run: node ./bin/bundle.js test -- -e "fails if running with --offline" --no-timeout - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' From 15d1b7714dd1748eef950d112d0a16b1f3fb6e5b Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 23:36:21 -0500 Subject: [PATCH 25/30] Debug Windows failure --- .github/workflows/build.yml | 6 ++---- core/src/Path.purs | 13 +++++++++++-- src/Spago/Command/Registry.purs | 10 +++++++--- test/Spago/Publish.purs | 3 --- test/Spago/Unit/Path.purs | 11 +++++++++++ 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 283889265..ede02bbfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,8 +13,8 @@ jobs: strategy: matrix: include: - # - os: ubuntu-latest - # - os: macOS-latest + - os: ubuntu-latest + - os: macOS-latest - os: windows-latest steps: # We set LF endings so that the Windows environment is consistent with the rest @@ -82,8 +82,6 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - env: - SPAGO_TEST_DEBUG: 1 run: node ./bin/bundle.js test -- -e "fails if running with --offline" --no-timeout - name: Check formatting (Linux only) diff --git a/core/src/Path.purs b/core/src/Path.purs index 5fa40ad8d..76ae1a474 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -94,8 +94,11 @@ instance IsPath LocalPath where instance IsPath GlobalPath where toGlobal = identity - relativeTo (GlobalPath path) (RootPath root) = - LocalPath { root: RootPath root, local: Path.relative root path } + relativeTo (GlobalPath path) (RootPath root) + | bothAbsolutePathsOnDifferentDrives path root = + LocalPath { root: RootPath path, local: "" } + | otherwise = + LocalPath { root: RootPath root, local: Path.relative root path } quote (GlobalPath path) = "\"" <> path <> "\"" replaceExtension p r (GlobalPath path) = @@ -178,3 +181,9 @@ printLocalPath p = l = localPart p in if l == "" then "./" else l + +bothAbsolutePathsOnDifferentDrives :: String -> String -> Boolean +bothAbsolutePathsOnDifferentDrives a b = + Path.isAbsolute a && Path.isAbsolute b && driveLetter a /= driveLetter b + where + driveLetter s = String.toLower $ String.take 1 s diff --git a/src/Spago/Command/Registry.purs b/src/Spago/Command/Registry.purs index 9167b9d3a..766a33050 100644 --- a/src/Spago/Command/Registry.purs +++ b/src/Spago/Command/Registry.purs @@ -27,6 +27,7 @@ import Spago.FS as FS import Spago.Git as Git import Spago.Json as Json import Spago.Path as Path +import Spago.Paths as Paths import Spago.Registry (RegistryEnv) import Spago.Registry as Registry @@ -216,7 +217,7 @@ transfer { privateKeyPath } = do let dataToSign = { name: selected.package.name, newLocation } let rawPayload = Json.stringifyJson Operation.transferCodec dataToSign - key <- getPrivateKeyForSigning $ rootPath privateKeyPath + key <- getPrivateKeyForSigning privateKeyPath -- We have a key! We can sign the payload with it, and submit the whole package to the Registry let signature = SSH.sign key rawPayload @@ -230,8 +231,11 @@ transfer { privateKeyPath } = do , payload: Operation.Transfer dataToSign } -getPrivateKeyForSigning :: ∀ e. LocalPath -> Spago (LogEnv e) SSH.PrivateKey -getPrivateKeyForSigning privateKeyPath = do +getPrivateKeyForSigning :: ∀ e. AdHocFilePath -> Spago (LogEnv e) SSH.PrivateKey +getPrivateKeyForSigning privateKeyPath' = do + here <- Paths.cwd + let privateKeyPath = here privateKeyPath' + -- If all is well we read in the private key privateKey <- try (FS.readTextFile privateKeyPath) >>= case _ of Right key -> pure key diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index 9a7493b38..0f30f6c25 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -5,7 +5,6 @@ import Test.Prelude import Data.String as String import Data.String.Regex as Regex import Data.String.Regex.Flags as RF -import Debug (traceM) import Node.Platform as Platform import Node.Process as Process import Spago.Cmd as Cmd @@ -115,8 +114,6 @@ spec = Spec.around withTempDir do Spec.it "fails if running with --offline" \{ spago, fixture, testCwd } -> do FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: testCwd "spago.yaml" } - traceM $ Path.quote $ fixture "publish/key" - traceM =<< FS.exists (fixture "publish/key") spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing spago [ "registry", "transfer", "-v", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") diff --git a/test/Spago/Unit/Path.purs b/test/Spago/Unit/Path.purs index 51d664aa2..6e3deb3c3 100644 --- a/test/Spago/Unit/Path.purs +++ b/test/Spago/Unit/Path.purs @@ -3,6 +3,8 @@ module Test.Spago.Unit.Path where import Test.Prelude import Effect.Unsafe (unsafePerformEffect) +import Node.Platform (Platform(..)) as Node +import Node.Process (platform) as Node import Spago.Path as Path import Test.Spec (Spec) import Test.Spec as Spec @@ -48,6 +50,15 @@ spec = Spec.describe "Paths" do (Path.global "/foo/x/y" "/foo/x/y/z") `shouldPointAt` "/foo/x/y/z" (Path.global "/foo/x/y" ".." ".." "bar") `shouldPointAt` "/foo/bar" + when (Node.platform == Just Node.Win32) do + Spec.describe "On different drives under Windows" do + Spec.it "LocalPath appends correctly" do + (root "C:\\foo" "bar") `shouldPointAt` "C:/foo/bar" + (root "C:\\foo" "bar" "baz") `shouldPointAt` "C:/foo/bar/baz" + (root "C:\\foo\\x\\y" "D:\\bar" "baz") `shouldPointAt` "D:/bar/baz" + (root "C:\\foo\\x\\y" "D:\\foo\\x\\y\\z") `shouldPointAt` "D:/foo/x/y/z" + (root "C:\\foo\\x\\y" ".." ".." "bar") `shouldPointAt` "C:/foo/bar" + where root = unsafePerformEffect <<< Path.mkRoot <<< Path.global From 47b3dd2cae457ed230aa4c8cfb17a0fe44f78ad4 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 23:38:18 -0500 Subject: [PATCH 26/30] Debug Windows failure --- test/Spago/Publish.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spago/Publish.purs b/test/Spago/Publish.purs index 0f30f6c25..b6f15cf00 100644 --- a/test/Spago/Publish.purs +++ b/test/Spago/Publish.purs @@ -116,7 +116,7 @@ spec = Spec.around withTempDir do FS.copyFile { src: fixture "publish/transfer/aff-new-location.yaml", dst: testCwd "spago.yaml" } spago [ "auth", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeSuccess doTheGitThing - spago [ "registry", "transfer", "-v", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") + spago [ "registry", "transfer", "--offline", "-i", (Path.toRaw $ fixture "publish/key") ] >>= shouldBeFailureErr (fixture "publish/transfer/offline.txt") Spec.it "#1110 installs versions of packages that are returned by the registry solver, but not present in cache" \{ spago, fixture, testCwd } -> do let From baa2a411b4b0c6517dac084fdb6ba4d32a992484 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sat, 18 Jan 2025 23:38:50 -0500 Subject: [PATCH 27/30] Debug Windows failure --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ede02bbfb..800a8f75f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: with: path: | ~/.npm - ~/AppData/Roaming/npm + $APPDATA/npm node_modules key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} restore-keys: | From c0154b78019b5912f842528d311cb5e5f1e709c9 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sun, 19 Jan 2025 00:01:45 -0500 Subject: [PATCH 28/30] Add a comment, remove debugging scaffolding --- .github/workflows/build.yml | 2 +- core/src/Path.purs | 42 ++++++++++++++++++++++++++++++------- spago.lock | 12 ----------- spago.yaml | 3 +-- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 800a8f75f..f4f545a46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: run: node ./bin/bundle.js bundle -p docs-search-client-halogen - name: Run tests - run: node ./bin/bundle.js test -- -e "fails if running with --offline" --no-timeout + run: node ./bin/bundle.js test - name: Check formatting (Linux only) if: matrix.os == 'ubuntu-latest' diff --git a/core/src/Path.purs b/core/src/Path.purs index 76ae1a474..7a7c866b8 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -30,12 +30,14 @@ import Prelude import Data.Codec.JSON as CJ import Data.Function (on) -import Data.Maybe (Maybe, isJust) +import Data.Maybe (Maybe(..), isJust) import Data.Profunctor (dimap) import Data.String as String import Effect.Class (class MonadEffect, liftEffect) import Node.Path as Node.Path import Node.Path as Path +import Node.Platform (Platform(..)) as Node +import Node.Process (platform) as Node -- | Normally this represents the root directory of the workspace. All Spago -- | Workspace-scoped paths are relative to a `RootPath`. @@ -95,8 +97,8 @@ instance IsPath LocalPath where instance IsPath GlobalPath where toGlobal = identity relativeTo (GlobalPath path) (RootPath root) - | bothAbsolutePathsOnDifferentDrives path root = - LocalPath { root: RootPath path, local: "" } + | areBothAbsolutePathsOnDifferentDrives path root = + LocalPath { root: RootPath path, local: "" } -- see comments on `areBothAbsolutePathsOnDifferentDrives` | otherwise = LocalPath { root: RootPath root, local: Path.relative root path } quote (GlobalPath path) = @@ -182,8 +184,32 @@ printLocalPath p = in if l == "" then "./" else l -bothAbsolutePathsOnDifferentDrives :: String -> String -> Boolean -bothAbsolutePathsOnDifferentDrives a b = - Path.isAbsolute a && Path.isAbsolute b && driveLetter a /= driveLetter b - where - driveLetter s = String.toLower $ String.take 1 s +-- This function is special handling for Windows, which may have multiple +-- different file systems with separate roots and no way to build a relative +-- path from one root to another, for example C:\foo\bar and D:\qux\baz. When +-- this happens as we try to build a `LocalPath` as a relative from a given +-- `RootPath`, and the two paths are from different file systems, we cannot use +-- the same `RootPath` as root of the result. +-- +-- For example: +-- +-- -- POSIX case: +-- (GlobalPath "/a/b") `relativeTo` (RootPath "/a/x") == LocalPath { root: RootPath "/a/x", local: "../b" } +-- +-- -- Windows case on the same drive: +-- (GlobalPath "C:\\a\\b") `relativeTo` (RootPath "C:\\a\\x") == LocalPath { root: RootPath "C:\\a\\x", local: "..\\b" } +-- +-- -- But Windows case on different drives: +-- (GlobalPath "C:\\a\\b") `relativeTo` (RootPath "D:\\a\\x") == LocalPath { root: RootPath "C:\\a\\b", local: "" } +-- +-- In the last case the root path `D:\\a\\x` could not be used as the root of +-- the resulting `LocalPath`, because there is no way to build a relative path +-- from it to `C:\\a\\b`. +areBothAbsolutePathsOnDifferentDrives :: String -> String -> Boolean +areBothAbsolutePathsOnDifferentDrives a b + | Node.platform == Just Node.Win32 = + Path.isAbsolute a && Path.isAbsolute b && driveLetter a /= driveLetter b + where + driveLetter s = String.toLower $ String.take 1 s + | otherwise = + false diff --git a/spago.lock b/spago.lock index b729a9359..a139fa007 100644 --- a/spago.lock +++ b/spago.lock @@ -473,7 +473,6 @@ "console", "control", "datetime", - "debug", "docs-search-common", "docs-search-index", "dodo-printer", @@ -542,7 +541,6 @@ "contravariant", "control", "datetime", - "debug", "distributive", "docs-search-common", "docs-search-index", @@ -776,7 +774,6 @@ "contravariant", "control", "datetime", - "debug", "distributive", "docs-search-common", "docs-search-index", @@ -2087,15 +2084,6 @@ "tuples" ] }, - "debug": { - "type": "registry", - "version": "6.0.2", - "integrity": "sha256-vmkYFuXYuELBzeauvgHG6E6Kf/Hp1dAnxwE9ByHfwSg=", - "dependencies": [ - "functions", - "prelude" - ] - }, "distributive": { "type": "registry", "version": "6.0.0", diff --git a/spago.yaml b/spago.yaml index 454774029..62e9d6cee 100644 --- a/spago.yaml +++ b/spago.yaml @@ -7,12 +7,11 @@ package: githubOwner: purescript githubRepo: spago build: - # strict: true + strict: true censorProjectWarnings: - WildcardInferredType - ImplicitQualifiedImportReExport dependencies: - - debug - aff - aff-promise - affjax From b0e2ae9d4fb458dfb05440e4b70e1cb1260f44ec Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sun, 19 Jan 2025 08:18:10 -0500 Subject: [PATCH 29/30] Rename AdHocFilePath to RawFilePath --- bin/src/Flags.purs | 4 ++-- core/src/Config.purs | 14 +++++++------- core/src/FS.purs | 2 +- core/src/Path.purs | 10 +++++----- core/src/Prelude.purs | 2 +- src/Spago/Command/Auth.purs | 2 +- src/Spago/Command/Bundle.purs | 2 +- src/Spago/Command/Registry.purs | 4 ++-- src/Spago/Glob.purs | 4 ++-- src/Spago/Lock.purs | 6 +++--- src/Spago/Paths.purs | 8 ++++---- src/Spago/Prelude.purs | 2 +- test/Prelude.purs | 2 +- 13 files changed, 31 insertions(+), 31 deletions(-) diff --git a/bin/src/Flags.purs b/bin/src/Flags.purs index ddc40deda..cae73a8c6 100644 --- a/bin/src/Flags.purs +++ b/bin/src/Flags.purs @@ -336,7 +336,7 @@ depsOnly = <> O.help "Build depedencies only" ) -publicKeyPath :: Parser AdHocFilePath +publicKeyPath :: Parser RawFilePath publicKeyPath = O.strOption ( O.short 'i' @@ -344,7 +344,7 @@ publicKeyPath = <> O.help "Select the path of the public key to use for authenticating operations of the package" ) -privateKeyPath :: Parser AdHocFilePath +privateKeyPath :: Parser RawFilePath privateKeyPath = O.strOption ( O.short 'i' diff --git a/core/src/Config.purs b/core/src/Config.purs index 84d6add91..5e9339243 100644 --- a/core/src/Config.purs +++ b/core/src/Config.purs @@ -100,8 +100,8 @@ type PublishConfig = { version :: Version , license :: License , location :: Maybe Location - , include :: Maybe (Array AdHocFilePath) - , exclude :: Maybe (Array AdHocFilePath) + , include :: Maybe (Array RawFilePath) + , exclude :: Maybe (Array RawFilePath) , owners :: Maybe (Array Owner) } @@ -210,7 +210,7 @@ packageBuildOptionsCodec = CJ.named "PackageBuildOptionsInput" $ CJS.objectStric type BundleConfig = { minify :: Maybe Boolean , module :: Maybe String - , outfile :: Maybe AdHocFilePath + , outfile :: Maybe RawFilePath , platform :: Maybe BundlePlatform , type :: Maybe BundleType , extraArgs :: Maybe (Array String) @@ -347,7 +347,7 @@ workspaceConfigCodec = CJ.named "WorkspaceConfig" $ CJS.objectStrict $ CJS.record type WorkspaceBuildOptionsInput = - { output :: Maybe AdHocFilePath + { output :: Maybe RawFilePath , censorLibraryWarnings :: Maybe CensorBuildWarnings , statVerbosity :: Maybe StatVerbosity } @@ -440,7 +440,7 @@ statVerbosityCodec = CJ.Sum.enumSum print parse data SetAddress = SetFromRegistry { registry :: Version } | SetFromUrl { url :: String, hash :: Maybe Sha256 } - | SetFromPath { path :: AdHocFilePath } + | SetFromPath { path :: RawFilePath } derive instance Eq SetAddress @@ -474,7 +474,7 @@ extraPackageCodec = Codec.codec' decode encode decode json = map ExtraLocalPackage (Codec.decode localPackageCodec json) <|> map ExtraRemotePackage (Codec.decode remotePackageCodec json) -type LocalPackage = { path :: AdHocFilePath } +type LocalPackage = { path :: RawFilePath } localPackageCodec :: CJ.Codec LocalPackage localPackageCodec = CJ.named "LocalPackage" $ CJ.Record.objectStrict { path: CJ.string } @@ -500,7 +500,7 @@ remotePackageCodec = Codec.codec' decode encode type GitPackage = { git :: String , ref :: String - , subdir :: Maybe AdHocFilePath + , subdir :: Maybe RawFilePath , dependencies :: Maybe Dependencies } diff --git a/core/src/FS.purs b/core/src/FS.purs index 65b6b019f..57c434088 100644 --- a/core/src/FS.purs +++ b/core/src/FS.purs @@ -85,7 +85,7 @@ readTextFileSync path = liftEffect $ FS.Sync.readTextFile UTF8 (toRaw path) writeFile :: forall m path. Path.IsPath path => MonadAff m => path -> Buffer -> m Unit writeFile path buf = liftAff $ FS.Aff.writeFile (toRaw path) buf -ls :: forall m path. Path.IsPath path => MonadAff m => path -> m (Array AdHocFilePath) +ls :: forall m path. Path.IsPath path => MonadAff m => path -> m (Array RawFilePath) ls = liftAff <<< FS.Aff.readdir <<< toRaw chmod :: forall m path. Path.IsPath path => MonadAff m => path -> Perms -> m Unit diff --git a/core/src/Path.purs b/core/src/Path.purs index 7a7c866b8..627e2a2f6 100644 --- a/core/src/Path.purs +++ b/core/src/Path.purs @@ -1,6 +1,6 @@ module Spago.Path ( () - , AdHocFilePath + , RawFilePath , GlobalPath , LocalPath , RootPath @@ -50,7 +50,7 @@ derive newtype instance Ord RootPath -- | A Spago Workspace-scoped path, consists of two parts: `RootPath` and local -- | part, relative to the root. This lets us both have the full path for -- | actually working with files and the local part for user-facing output. -newtype LocalPath = LocalPath { root :: RootPath, local :: AdHocFilePath } +newtype LocalPath = LocalPath { root :: RootPath, local :: RawFilePath } instance Show LocalPath where show (LocalPath p) = p.local @@ -71,7 +71,7 @@ instance Show GlobalPath where derive newtype instance Eq GlobalPath derive newtype instance Ord GlobalPath -type AdHocFilePath = String +type RawFilePath = String class (Show path, Eq path, Ord path) <= IsPath path where toGlobal :: path -> GlobalPath @@ -116,7 +116,7 @@ instance IsPath RootPath where withForwardSlashes (RootPath path) = RootPath $ withForwardSlashes' path class AppendPath base result | base -> result where - appendPath :: base -> AdHocFilePath -> result + appendPath :: base -> RawFilePath -> result instance AppendPath RootPath LocalPath where appendPath root local @@ -147,7 +147,7 @@ global = GlobalPath rootPart :: LocalPath -> RootPath rootPart (LocalPath { root }) = root -localPart :: LocalPath -> AdHocFilePath +localPart :: LocalPath -> RawFilePath localPart (LocalPath { local }) = local dirname :: ∀ path. IsPath path => path -> GlobalPath diff --git a/core/src/Prelude.purs b/core/src/Prelude.purs index fb3c35cab..585724c71 100644 --- a/core/src/Prelude.purs +++ b/core/src/Prelude.purs @@ -48,7 +48,7 @@ import Registry.ManifestIndex (ManifestIndex) as Extra import Registry.Types (PackageName, Version, Range, Location, License, Manifest(..), Metadata(..), Sha256) as Extra import Spago.Json (printJson, parseJson) as Extra import Spago.Log (logDebug, logError, logInfo, Docc, logSuccess, logWarn, die, die', justOrDieWith, justOrDieWith', rightOrDie, rightOrDie_, rightOrDieWith, rightOrDieWith', toDoc, indent, indent2, output, LogEnv, LogOptions, OutputFormat(..)) as Extra -import Spago.Path (AdHocFilePath, GlobalPath, LocalPath, RootPath, class AppendPath, appendPath, ()) as Extra +import Spago.Path (RawFilePath, GlobalPath, LocalPath, RootPath, class AppendPath, appendPath, ()) as Extra import Spago.Yaml (YamlDoc, printYaml, parseYaml) as Extra newtype Spago env a = Spago (ReaderT env Extra.Aff a) diff --git a/src/Spago/Command/Auth.purs b/src/Spago/Command/Auth.purs index ffe74b5c1..d01e374a4 100644 --- a/src/Spago/Command/Auth.purs +++ b/src/Spago/Command/Auth.purs @@ -13,7 +13,7 @@ import Spago.FS as FS import Spago.Path as Path import Spago.Paths as Paths -type AuthArgs = { keyPath :: AdHocFilePath } +type AuthArgs = { keyPath :: RawFilePath } run :: ∀ r. AuthArgs -> Spago (FetchEnv r) Unit run { keyPath } = do diff --git a/src/Spago/Command/Bundle.purs b/src/Spago/Command/Bundle.purs index b59dec977..b12e9ceda 100644 --- a/src/Spago/Command/Bundle.purs +++ b/src/Spago/Command/Bundle.purs @@ -26,7 +26,7 @@ type BundleOptions = { minify :: Boolean , sourceMaps :: Boolean , module :: String - , outfile :: AdHocFilePath + , outfile :: RawFilePath , force :: Boolean , platform :: BundlePlatform , type :: BundleType diff --git a/src/Spago/Command/Registry.purs b/src/Spago/Command/Registry.purs index 766a33050..1c5bb8b91 100644 --- a/src/Spago/Command/Registry.purs +++ b/src/Spago/Command/Registry.purs @@ -139,7 +139,7 @@ packageSets { latest, json } = do ] } -type RegistryTransferArgs = { privateKeyPath :: AdHocFilePath } +type RegistryTransferArgs = { privateKeyPath :: RawFilePath } transfer :: ∀ r. RegistryTransferArgs -> Spago (FetchEnv r) Unit transfer { privateKeyPath } = do @@ -231,7 +231,7 @@ transfer { privateKeyPath } = do , payload: Operation.Transfer dataToSign } -getPrivateKeyForSigning :: ∀ e. AdHocFilePath -> Spago (LogEnv e) SSH.PrivateKey +getPrivateKeyForSigning :: ∀ e. RawFilePath -> Spago (LogEnv e) SSH.PrivateKey getPrivateKeyForSigning privateKeyPath' = do here <- Paths.cwd let privateKeyPath = here privateKeyPath' diff --git a/src/Spago/Glob.purs b/src/Spago/Glob.purs index b941cd6da..eb56c09b0 100644 --- a/src/Spago/Glob.purs +++ b/src/Spago/Glob.purs @@ -25,7 +25,7 @@ type Glob = , include :: Array String } -foreign import testGlob :: Glob -> AdHocFilePath -> Boolean +foreign import testGlob :: Glob -> RawFilePath -> Boolean splitGlob :: Glob -> Array Glob splitGlob { ignore, include } = (\a -> { ignore, include: [ a ] }) <$> include @@ -95,7 +95,7 @@ fsWalk { root, ignorePatterns, includePatterns } = Aff.makeAff \cb -> do -- Pattern for directories which can be outright ignored. -- This will be updated whenver a .gitignore is found. - ignoreMatcherRef :: Ref (AdHocFilePath -> Boolean) <- Ref.new (testGlob { ignore: [], include: ignorePatterns }) + ignoreMatcherRef :: Ref (RawFilePath -> Boolean) <- Ref.new (testGlob { ignore: [], include: ignorePatterns }) -- If this Ref contains `true` because this Aff has been canceled, then deepFilter will always return false. canceled <- Ref.new false diff --git a/src/Spago/Lock.purs b/src/Spago/Lock.purs index 1c9d9b4e6..6d61ddee7 100644 --- a/src/Spago/Lock.purs +++ b/src/Spago/Lock.purs @@ -57,7 +57,7 @@ type PackageSetInfo = type WorkspaceLockPackage = { core :: WorkspaceLockPackageEnv , test :: WorkspaceLockPackageEnv - , path :: AdHocFilePath + , path :: RawFilePath } type WorkspaceLockPackageEnv = @@ -119,7 +119,7 @@ registryLockType :: String registryLockType = "registry" type PathLock = - { path :: AdHocFilePath + { path :: RawFilePath , dependencies :: Array PackageName } @@ -136,7 +136,7 @@ pathLockCodec = Profunctor.dimap toRep fromRep $ CJ.named "PathLock" $ CJS.objec type GitLock = { url :: String , rev :: String - , subdir :: Maybe AdHocFilePath + , subdir :: Maybe RawFilePath , dependencies :: Array PackageName } diff --git a/src/Spago/Paths.purs b/src/Spago/Paths.purs index 016786938..2a3853bd9 100644 --- a/src/Spago/Paths.purs +++ b/src/Spago/Paths.purs @@ -18,7 +18,7 @@ import Prelude import Effect.Class (class MonadEffect, liftEffect) import Node.Path as Node.Path import Node.Process as Process -import Spago.Path (class IsPath, AdHocFilePath, GlobalPath, global, toRaw, ()) +import Spago.Path (class IsPath, RawFilePath, GlobalPath, global, toRaw, ()) type NodePaths p = { config :: p @@ -48,13 +48,13 @@ chdir path = liftEffect $ Process.chdir (toRaw path) globalCachePath :: GlobalPath globalCachePath = paths.cache -localCachePath :: AdHocFilePath +localCachePath :: RawFilePath localCachePath = ".spago" -localCachePackagesPath :: AdHocFilePath +localCachePackagesPath :: RawFilePath localCachePackagesPath = Node.Path.concat [ localCachePath, "p" ] -localCacheGitPath :: AdHocFilePath +localCacheGitPath :: RawFilePath localCacheGitPath = Node.Path.concat [ localCachePath, "g" ] registryPath ∷ GlobalPath diff --git a/src/Spago/Prelude.purs b/src/Spago/Prelude.purs index 1aceb09cf..04df4e7ed 100644 --- a/src/Spago/Prelude.purs +++ b/src/Spago/Prelude.purs @@ -41,7 +41,7 @@ import Partial.Unsafe (unsafeCrashWith) import Registry.Sha256 as Registry.Sha256 import Registry.Sha256 as Sha256 import Registry.Version as Version -import Spago.Path (class IsPath, AdHocFilePath, GlobalPath, LocalPath, RootPath, (), withForwardSlashes) +import Spago.Path (class IsPath, RawFilePath, GlobalPath, LocalPath, RootPath, (), withForwardSlashes) import Spago.Path as Path import Spago.Paths as Paths import Unsafe.Coerce (unsafeCoerce) diff --git a/test/Prelude.purs b/test/Prelude.purs index 9de88231d..9df8d5d59 100644 --- a/test/Prelude.purs +++ b/test/Prelude.purs @@ -38,7 +38,7 @@ type FixturePath = GlobalPath type TestDirs = { spago :: Array String -> Aff (Either ExecResult ExecResult) , spago' :: StdinConfig -> Array String -> Aff (Either ExecResult ExecResult) - , fixture :: AdHocFilePath -> FixturePath + , fixture :: RawFilePath -> FixturePath , oldCwd :: GlobalPath , testCwd :: RootPath } From 22af5915fde85cc392e32785e25539e339a50bc0 Mon Sep 17 00:00:00 2001 From: Fyodor Soikin Date: Sun, 19 Jan 2025 08:47:05 -0500 Subject: [PATCH 30/30] Increase timeout --- test/Spago.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spago.purs b/test/Spago.purs index a452d7bf1..74b64c393 100644 --- a/test/Spago.purs +++ b/test/Spago.purs @@ -33,7 +33,7 @@ import Test.Spec.Runner.Node.Config as Config testConfig :: Config.TestRunConfig testConfig = Config.defaultConfig - { timeout = Just (Milliseconds 90_000.0) + { timeout = Just (Milliseconds 120_000.0) } main :: Effect Unit