diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a30651d..500ae38dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +Features: +* Reinstate `spago script` for running standalone PureScript files with package-set and dependency options + Bugfixes: * Fix flaky `SQLITE_IOERR_TRUNCATE` on Windows when multiple spago processes connect concurrently to the cache DB, by skipping `PRAGMA journal_mode = WAL` when it's already enabled (WAL mode is persistent in the DB file header) and tolerating the race on the initial set * Retry transient network failures (connection errors and 5xx responses) when fetching package tarballs and calling the registry API, instead of failing immediately diff --git a/bin/src/Flags.purs b/bin/src/Flags.purs index c0cde249d..d8a52752a 100644 --- a/bin/src/Flags.purs +++ b/bin/src/Flags.purs @@ -278,6 +278,23 @@ packages = <> O.help "Package name to add as dependency" ) +scriptSource :: Parser String +scriptSource = + O.strArgument + ( O.metavar "SOURCE" + <> O.help "PureScript source file to run" + ) + +scriptDependencies :: Parser (List String) +scriptDependencies = + O.many $ + O.strOption + ( O.long "dependency" + <> O.short 'd' + <> O.metavar "PACKAGE" + <> O.help "Package dependency to make available to the script" + ) + packagesToRemove :: Parser (List String) packagesToRemove = O.many $ diff --git a/bin/src/Main.purs b/bin/src/Main.purs index fb2c54d06..81e6307f9 100644 --- a/bin/src/Main.purs +++ b/bin/src/Main.purs @@ -39,6 +39,7 @@ import Spago.Command.Registry (RegistryInfoArgs, RegistryPackageSetsArgs, Regist import Spago.Command.Registry as RegistryCmd import Spago.Command.Repl as Repl import Spago.Command.Run as Run +import Spago.Command.Script as Script import Spago.Command.Sources as Sources import Spago.Command.Test as Test import Spago.Command.Uninstall as Uninstall @@ -145,6 +146,12 @@ type RunArgs = , pure :: Boolean } +type ScriptArgs = + { source :: String + , dependencies :: List String + , packageSet :: Maybe String + } + type TestArgs = { selectedPackage :: Maybe String , output :: Maybe String @@ -212,6 +219,7 @@ data Command a | RegistryTransfer RegistryTransferArgs | Repl ReplArgs | Run RunArgs + | Script ScriptArgs | Sources SourcesArgs | Test TestArgs | Upgrade UpgradeArgs @@ -270,6 +278,7 @@ argParser = ) , commandParser "repl" (Repl <$> replArgsParser) "Start a REPL" , commandParser "run" (Run <$> runArgsParser) "Run the project" + , commandParser "script" (Script <$> scriptArgsParser) "Run a standalone PureScript source file" , commandParser "sources" (Sources <$> sourcesArgsParser) "List all the source paths (globs) for the dependencies of the project" , commandParser "test" (Test <$> testArgsParser) "Test the project" , commandParser "uninstall" (Uninstall <$> uninstallArgsParser) "Remove dependencies from a package" @@ -414,6 +423,14 @@ bundleArgsParser = , pure: Flags.pureLockfile } +scriptArgsParser :: Parser ScriptArgs +scriptArgsParser = + Optparse.fromRecord + { source: Flags.scriptSource + , dependencies: Flags.scriptDependencies + , packageSet: Flags.maybeSetVersion + } + publishArgsParser :: Parser PublishArgs publishArgsParser = Optparse.fromRecord @@ -657,8 +674,66 @@ main = do let options = { depsOnly: false, pursArgs: List.toUnfoldable args.pursArgs, jsonErrors: false } built <- runSpago buildEnv (Build.run options) when built do - runEnv <- runSpago env (mkRunEnv args buildEnv) + runEnv <- runSpago env (mkRunEnv args buildEnv Nothing) + runSpago runEnv Run.run + Script args -> do + originalCwd <- Paths.cwd + sourcePath <- Path.toAbsolute (Path.global args.source) + tmpDir <- mkTemp + FS.mkdirp tmpDir + Paths.chdir tmpDir + tmpRootPath <- Path.mkRoot tmpDir + registryEnv <- mkRegistryEnv offline <#> Record.union { rootPath: tmpRootPath } + setVersion <- parseSetVersion args.packageSet + void $ runSpago registryEnv $ Init.run + { setVersion + , mode: Init.InitWorkspace { packageName: Just "script" } + , useSolver: false + } + FS.copyTree + { src: sourcePath + , dst: tmpRootPath "src" (Script.moduleName <> ".purs") + } + { env, fetchOpts } <- mkFetchEnv + { packages: args.dependencies + , selectedPackage: Nothing + , ensureRanges: false + , testDeps: false + , isRepl: false + , pure: false + , migrateConfig: false + , offline + } + dependencies <- runSpago env (Fetch.run fetchOpts) + buildEnv <- runSpago env + ( mkBuildEnv + { backendArgs: mempty + , output: Nothing + , pedanticPackages: false + , statVerbosity: Nothing + , strict: Nothing + } + dependencies + ) + built <- runSpago buildEnv (Build.run { depsOnly: false, pursArgs: mempty, jsonErrors: false }) + when built do + let + runArgs = + { selectedPackage: Nothing + , output: Nothing + , pedanticPackages: false + , pursArgs: mempty + , backendArgs: mempty + , execArgs: Nothing + , main: Just Script.moduleName + , ensureRanges: false + , strict: Nothing + , statVerbosity: Nothing + , pure: false + } + runEnv <- runSpago env (mkRunEnv runArgs buildEnv (Just $ Path.toGlobal originalCwd)) runSpago runEnv Run.run + Paths.chdir originalCwd Test args@{ selectedPackage, pure } -> do { env, fetchOpts } <- mkFetchEnv { packages: mempty, selectedPackage, pure, ensureRanges: false, testDeps: false, isRepl: false, migrateConfig, offline } dependencies <- runSpago env (Fetch.run fetchOpts) @@ -787,8 +862,8 @@ mkBundleEnv bundleArgs { dependencies, purs } = do let bundleEnv = { esbuild, logOptions, rootPath, workspace: newWorkspace, selected, bundleOptions, purs, dependencies } pure bundleEnv -mkRunEnv :: forall a b. RunArgs -> Build.BuildEnv b -> Spago (Fetch.FetchEnv a) (Run.RunEnv ()) -mkRunEnv runArgs { dependencies, purs } = do +mkRunEnv :: forall a b. RunArgs -> Build.BuildEnv b -> Maybe GlobalPath -> Spago (Fetch.FetchEnv a) (Run.RunEnv ()) +mkRunEnv runArgs { dependencies, purs } executeDir = do { workspace, logOptions, rootPath } <- ask logDebug $ "Run args: " <> show runArgs @@ -824,7 +899,7 @@ mkRunEnv runArgs { dependencies, purs } = do runOptions = { moduleName , execArgs - , executeDir: Path.toGlobal rootPath + , executeDir: fromMaybe (Path.toGlobal rootPath) executeDir , successMessage: Nothing , failureMessage: "Running failed." } diff --git a/src/Spago/Command/Script.purs b/src/Spago/Command/Script.purs index 9d579a2b4..9d32bfe6d 100644 --- a/src/Spago/Command/Script.purs +++ b/src/Spago/Command/Script.purs @@ -1 +1,6 @@ -module Spago.Command.Script where +module Spago.Command.Script + ( moduleName + ) where + +moduleName :: String +moduleName = "Main" diff --git a/test-fixtures/spago-script-make-file.purs b/test-fixtures/spago-script-make-file.purs new file mode 100644 index 000000000..96cfc117e --- /dev/null +++ b/test-fixtures/spago-script-make-file.purs @@ -0,0 +1,10 @@ +module Main where + +import Prelude +import Effect (Effect) +import Node.FS.Sync (writeTextFile) +import Node.Encoding as Encoding + +main :: Effect Unit +main = do + writeTextFile Encoding.UTF8 "spago-script-result.txt" "HELLO\n" diff --git a/test-fixtures/spago-script-result.txt b/test-fixtures/spago-script-result.txt new file mode 100644 index 000000000..e427984d4 --- /dev/null +++ b/test-fixtures/spago-script-result.txt @@ -0,0 +1 @@ +HELLO diff --git a/test/Spago.purs b/test/Spago.purs index ea46b3d7e..ec0bc65c3 100644 --- a/test/Spago.purs +++ b/test/Spago.purs @@ -25,6 +25,7 @@ import Test.Spago.Publish as Publish import Test.Spago.Registry as Registry import Test.Spago.Repl as Repl import Test.Spago.Run as Run +import Test.Spago.Script as Script import Test.Spago.Sources as Sources import Test.Spago.Test as Test import Test.Spago.Transfer as Transfer @@ -69,6 +70,7 @@ main = do Ls.spec cmdLocks Repl.spec Run.spec cmdLocks + Script.spec cmdLocks Test.spec cmdLocks Bundle.spec cmdLocks Registry.spec diff --git a/test/Spago/Script.purs b/test/Spago/Script.purs new file mode 100644 index 000000000..6cc0bf96f --- /dev/null +++ b/test/Spago/Script.purs @@ -0,0 +1,15 @@ +module Test.Spago.Script where + +import Test.Prelude + +import Spago.Path as Path +import Test.Spec (Spec) +import Test.Spec as Spec + +spec :: CommandLocks -> Spec Unit +spec locks = Spec.parallel $ Spec.around (withBuildLock locks) do + Spec.describe "script" do + Spec.it "runs a standalone source file from the caller's directory" \{ spago, fixture, testCwd } -> do + let source = fixture "spago-script-make-file.purs" + spago [ "script", "-d", "node-fs", Path.toRaw source ] >>= shouldBeSuccess + checkFixture (testCwd "spago-script-result.txt") (fixture "spago-script-result.txt")