diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 049cf40c..733b81c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - purs_test_version: [v0.12.0, v0.12.4, v0.12.5, v0.13.0, v0.14.0, v0.14.5] + purs_test_version: [v0.12.0, v0.12.4, v0.12.5, v0.13.0, v0.14.0, v0.14.5, v0.15.0-alpha-01] fail-fast: false runs-on: ${{ matrix.os }} env: diff --git a/CHANGELOG.md b/CHANGELOG.md index d57daf18..f68436d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,11 @@ Breaking: compiler versions earlier than that. (#399 by @JordanMartinez) * Increased minimum `psa` version to `v0.7.0` and dropped support for all versions earlier than that. (#399 by @JordanMartinez) +* Increased minimum `node` version to `v12.0.0` and dropped support for all + versions earlier than that. (#401 by @JordanMartinez) Internal: +* Added support for the `v0.15.0` compiler version (#401 by @JordanMartinez) * Update project and its dependencies to use PureScript `v0.14.5` and `v0.14.0`-compatible libraries. (#399 by @JordanMartinez) * Migrated from Travis CI to GitHub Actions. (#399 by @JordanMartinez) diff --git a/src/Main.purs b/src/Main.purs index bea8eea6..a85e942c 100644 --- a/src/Main.purs +++ b/src/Main.purs @@ -8,9 +8,8 @@ import Data.Either (Either(..), either) import Data.Foldable (elem) import Data.List (List(Nil)) import Data.Map (insert) -import Data.Maybe (Maybe(..), fromMaybe) -import Data.String (stripPrefix, Pattern(..)) -import Data.Version (Version, version, showVersion, parseVersion) +import Data.Maybe (Maybe(..)) +import Data.Version (Version, showVersion, version) import Effect (Effect) import Effect.Aff (Aff, attempt, runAff, throwError) import Effect.Class (liftEffect) @@ -44,7 +43,7 @@ import Pulp.Server as Server import Pulp.Shell as Shell import Pulp.System.FFI (unsafeInspect) import Pulp.Test as Test -import Pulp.Validate (validate) +import Pulp.Validate (getNodeVersion, validate) import Pulp.Version (printVersion) import Pulp.Watch as Watch import Text.Parsing.Parser (parseErrorMessage) @@ -241,7 +240,7 @@ succeeded = const (pure unit) main :: Effect Unit main = void $ runAff (either failed succeeded) do - requireNodeAtLeast (version 4 0 0 Nil Nil) + requireNodeAtLeast (version 12 0 0 Nil Nil) argv <- drop 2 <$> liftEffect Process.argv args <- parse globals commands argv case args of @@ -304,20 +303,12 @@ runWithArgs args = do requireNodeAtLeast :: Version -> Aff Unit requireNodeAtLeast minimum = do - case parseVersion (stripV Process.version) of - Left err -> - let message = parseErrorMessage err - in throwError (error ("Failed to parse node.js version: " <> message)) - Right actual -> - when (actual < minimum) - (throwError (error - ("Your node.js version is too old " <> - "(required: " <> showVersion minimum <> - ", actual: " <> showVersion actual <> ")"))) - - where - stripV str = - fromMaybe str (stripPrefix (Pattern "v") str) + actual <- getNodeVersion + when (actual < minimum) do + throwError $ error $ + "Your node.js version is too old " <> + "(required: " <> showVersion minimum <> + ", actual: " <> showVersion actual <> ")" argsParserDiagnostics :: Args.Args -> Aff Unit argsParserDiagnostics opts = do diff --git a/src/Pulp/Browserify.purs b/src/Pulp/Browserify.purs index 0f7f0882..925ef93e 100644 --- a/src/Pulp/Browserify.purs +++ b/src/Pulp/Browserify.purs @@ -27,16 +27,20 @@ import Pulp.Exec (pursBundle) import Pulp.Files (outputModules) import Pulp.Outputter (getOutputter) import Pulp.Project (Project(..)) -import Pulp.Run (makeEntry, jsEscape) +import Pulp.Run (jsEscape, makeCjsEntry) import Pulp.Sorcery (sorcery) import Pulp.System.FFI (Callback, runNode) import Pulp.System.Files (openTemp) import Pulp.System.Stream (WritableStream) +import Pulp.Validate (failIfUsingEsModulesPsVersion) action :: Action action = Action \args -> do out <- getOutputter args + failIfUsingEsModulesPsVersion out $ Just + "Code path reason: browserify only works on CommonJS modules" + cwd <- liftEffect Process.cwd out.log $ "Browserifying project in " <> cwd @@ -52,7 +56,7 @@ makeExport :: String -> Boolean -> String makeExport main export = if export then "module.exports = require(\"" <> jsEscape main <> "\");\n" - else makeEntry main + else makeCjsEntry main makeOptExport :: String -> String makeOptExport main = "module.exports = PS[\"" <> jsEscape main <> "\"];\n" diff --git a/src/Pulp/Build.purs b/src/Pulp/Build.purs index f3a6f619..5bf71be7 100644 --- a/src/Pulp/Build.purs +++ b/src/Pulp/Build.purs @@ -4,6 +4,7 @@ module Pulp.Build , testBuild , runBuild , withOutputStream + , shouldBundle ) where import Prelude @@ -31,7 +32,8 @@ import Pulp.Outputter (getOutputter) import Pulp.Sorcery (sorcery) import Pulp.System.Files as Files import Pulp.System.Stream (write, end, WritableStream, stdout) -import Pulp.Validate (getPsaVersion, getPursVersion) +import Pulp.Validate (dropPreRelBuildMeta, failIfUsingEsModulesPsVersion, getPsaVersion, getPursVersion) +import Pulp.Versions.PureScript (psVersions) data BuildType = NormalBuild | TestBuild | RunBuild @@ -68,7 +70,7 @@ go buildType = Action \args -> do jobs :: Maybe Int <- getOption "jobs" args.commandOpts let jobsArgs = maybe [] (\j -> ["+RTS", "-N" <> show j, "-RTS"]) jobs sourceMapArg = case sourceMaps of - true | ver >= Version (NEL.cons' 0 (Cons 12 (Cons 0 Nil))) Nil -> [ "--codegen", "sourcemaps" ] + true | (dropPreRelBuildMeta ver) >= psVersions.v0_12_0 -> [ "--codegen", "sourcemaps" ] true -> ["--source-maps"] _ -> [] sourceGlobs = sources globs @@ -90,8 +92,16 @@ go buildType = Action \args -> do out.log "Build successful." - shouldBundle <- (||) <$> getFlag "optimise" opts <*> hasOption "to" opts - when shouldBundle (bundle args) + shouldBundle' <- shouldBundle args + when shouldBundle' do + failIfUsingEsModulesPsVersion out $ Just + "Code path reason: you used the --optimize and/or --to flag(s)" + bundle args + +shouldBundle :: Args -> Aff Boolean +shouldBundle args = do + let opts = union args.globalOpts args.commandOpts + (||) <$> getFlag "optimise" opts <*> hasOption "to" opts shouldUsePsa :: Args -> Aff Boolean shouldUsePsa args = do diff --git a/src/Pulp/Docs.purs b/src/Pulp/Docs.purs index 9b527ded..dc59b7c2 100644 --- a/src/Pulp/Docs.purs +++ b/src/Pulp/Docs.purs @@ -3,12 +3,9 @@ module Pulp.Docs where import Prelude -import Data.List (List(..)) -import Data.List.NonEmpty as NEL import Data.Map as Map import Data.Maybe (Maybe(..)) import Data.Set as Set -import Data.Version.Haskell (Version(..)) import Effect.Class (liftEffect) import Node.Process as Process import Pulp.Args (Action(..)) @@ -16,7 +13,8 @@ import Pulp.Args.Get (getFlag, getOption') import Pulp.Exec (exec) import Pulp.Files (defaultGlobs, sources, testGlobs) import Pulp.Outputter (getOutputter) -import Pulp.Validate (getPursVersion) +import Pulp.Validate (dropPreRelBuildMeta, getPursVersion) +import Pulp.Versions.PureScript (psVersions) action :: Action action = Action \args -> do @@ -35,7 +33,7 @@ action = Action \args -> do buildPath <- getOption' "buildPath" opts - when (pursVersion < Version (NEL.cons' 0 (Cons 13 (Cons 0 Nil))) Nil) + when ((dropPreRelBuildMeta pursVersion) < psVersions.v0_13_0) (out.log "Warning: 'pulp docs' now only supports 'purs' v0.13.0 and above. Please either update 'purs' or downgrade 'pulp'.") exec "purs" (["docs", "--compile-output", buildPath] <> args.remainder <> sources globInputFiles) Nothing diff --git a/src/Pulp/Init.purs b/src/Pulp/Init.purs index e0b487e9..3119d052 100644 --- a/src/Pulp/Init.purs +++ b/src/Pulp/Init.purs @@ -7,11 +7,8 @@ import Prelude import Control.Monad.Error.Class (throwError) import Data.Array (cons) import Data.Foldable (for_) -import Data.List (List(..), (:)) -import Data.List.NonEmpty as NEL +import Data.Maybe (Maybe(..)) import Data.String (joinWith) -import Data.Version.Haskell (Version(..)) -import Data.Version.Haskell as HVer import Effect.Aff (Aff) import Effect.Class (liftEffect) import Effect.Exception (error) @@ -25,7 +22,8 @@ import Pulp.Outputter (Outputter, getOutputter) import Pulp.PackageManager (launchBower, launchPscPackage) import Pulp.System.Files (mkdirIfNotExist) import Pulp.Utils (throw) -import Pulp.Validate (getPursVersion) +import Pulp.Validate (dropPreRelBuildMeta, failIfUsingEsModulesPsVersion, getPursVersion) +import Pulp.Versions.PureScript (psVersions) foreign import bowerFile :: String -> String @@ -141,10 +139,12 @@ init initStyle effOrEffect force out = do psVer <- getPursVersion out - install initStyle effOrEffect (getDepsVersions psVer) + install initStyle effOrEffect (getDepsVersions $ dropPreRelBuildMeta psVer) where install Bower UseEff p = do + failIfUsingEsModulesPsVersion out $ Just + "'purescript-eff' has been archived, so the FFI's CJS modules cannot be migrated to ES modules." launchBower ["install", "--save", p.prelude, p.console] launchBower ["install", "--save-dev", p.psciSupport] @@ -153,42 +153,50 @@ init initStyle effOrEffect force out = do launchBower ["install", "--save-dev", p.psciSupport ] install PscPackage UseEff _ = do + failIfUsingPscPackageAndEsModules + launchPscPackage ["init"] launchPscPackage ["install", "eff"] launchPscPackage ["install", "console"] launchPscPackage ["install", "psci-support"] install PscPackage UseEffect _ = do + failIfUsingPscPackageAndEsModules + launchPscPackage ["init"] launchPscPackage ["install", "effect"] launchPscPackage ["install", "console"] launchPscPackage ["install", "psci-support"] + failIfUsingPscPackageAndEsModules = do + failIfUsingEsModulesPsVersion out $ Just + "'psc-package' not yet supported on a `purs` version that compiles to ES modules." + getDepsVersions v - | v >= Version (NEL.cons' 0 (14 : 0 : Nil)) Nil = + | v >= psVersions.v0_15_0 = + { prelude: "purescript-prelude=working-group-purescript-es/purescript-prelude#es-modules-libraries" + , console: "purescript-console=working-group-purescript-es/purescript-console#es-modules-libraries" + , effect: "purescript-effect=working-group-purescript-es/purescript-effect#es-modules-libraries" + , psciSupport: "purescript-psci-support=purescript/purescript-psci-support#update-to-0.15" + } + | v >= psVersions.v0_14_0 = { prelude: "purescript-prelude@v5.0.1" , console: "purescript-console@v5.0.0" , effect: "purescript-effect@v3.0.0" , psciSupport: "purescript-psci-support@v5.0.0" } - | v >= Version (NEL.cons' 0 (13 : 0 : Nil)) Nil = + | v >= psVersions.v0_13_0 = { prelude: "purescript-prelude@v4.1.1" , console: "purescript-console@v4.4.0" , effect: "purescript-effect@v2.0.1" , psciSupport: "purescript-psci-support@v4.0.0" } - | v >= Version (NEL.cons' 0 (12 : 0 : Nil)) Nil = + | otherwise = { prelude: "purescript-prelude@v4.1.1" , console: "purescript-console@v4.4.0" , effect: "purescript-effect@v2.0.1" , psciSupport: "purescript-psci-support@v4.0.0" } - | otherwise = - { prelude: "purescript-prelude@v3.3.0" - , console: "purescript-console@v3.0.0" - , effect: "purescript-effect@v1.1.0" - , psciSupport: "purescript-psci-support@v3.0.0" - } action :: Action action = Action \args -> do @@ -205,13 +213,13 @@ action = Action \args -> do where - minEffectVersion = HVer.Version (NEL.cons' 0 (Cons 12 (Cons 0 Nil))) Nil + minEffectVersion = psVersions.v0_12_0 getEffOrEffect out withEff withEffect | withEff = pure UseEff | withEffect = pure UseEffect | otherwise = do ver <- getPursVersion out - if ver < minEffectVersion + if (dropPreRelBuildMeta ver) < minEffectVersion then pure UseEff else pure UseEffect diff --git a/src/Pulp/Publish.purs b/src/Pulp/Publish.purs index 6ac7efc9..2407dc08 100644 --- a/src/Pulp/Publish.purs +++ b/src/Pulp/Publish.purs @@ -7,8 +7,6 @@ import Control.Parallel (parTraverse) import Data.Array as Array import Data.Either (Either(..)) import Data.Foldable (fold, or) -import Data.List (List(..)) -import Data.List.NonEmpty as NEL import Data.Maybe (Maybe(..), maybe) import Data.Options ((:=)) import Data.String as String @@ -16,7 +14,6 @@ import Data.Tuple (Tuple(..)) import Data.Tuple.Nested ((/\)) import Data.Version (Version) import Data.Version as Version -import Data.Version.Haskell (Version(..)) as Haskell import Effect.Aff (Aff, attempt, throwError) import Effect.Class (liftEffect) import Foreign (renderForeignError) @@ -39,7 +36,8 @@ import Pulp.System.HTTP (httpRequest) import Pulp.System.Read as Read import Pulp.System.Stream (concatStream, concatStreamToBuffer, createGzip, end, write) import Pulp.Utils (orErr, throw) -import Pulp.Validate (getPursVersion) +import Pulp.Validate (dropPreRelBuildMeta, getPursVersion) +import Pulp.Versions.PureScript (psVersions) import Simple.JSON as SimpleJSON -- TODO: @@ -195,7 +193,7 @@ resolutionsFile manifest args = do out <- getOutputter args ver <- getPursVersion out resolutionsData <- - if ver >= Haskell.Version (NEL.cons' 0 (Cons 12 (Cons 4 Nil))) Nil + if (dropPreRelBuildMeta ver) >= psVersions.v0_12_4 then do let hasDependencies = maybe false (not <<< Object.isEmpty) manifest.dependencies diff --git a/src/Pulp/Run.purs b/src/Pulp/Run.purs index c4f8d4dc..a27755e8 100644 --- a/src/Pulp/Run.purs +++ b/src/Pulp/Run.purs @@ -2,41 +2,45 @@ module Pulp.Run where import Prelude +import Data.Array (fold) +import Data.List (List(..)) import Data.Map as Map import Data.Maybe (Maybe(..)) -import Data.String (replace, Pattern(..), Replacement(..)) +import Data.String (Pattern(..), Replacement(..), replace, replaceAll) +import Data.Version (version) import Effect.Aff (Aff) import Effect.Class (liftEffect) import Foreign.Object (Object) import Foreign.Object as Object import Node.Buffer as Buffer import Node.Encoding (Encoding(UTF8)) -import Node.FS.Aff as FS +import Node.FS.Aff as FSA import Node.Path as Path import Node.Process as Process import Pulp.Args (Action(..)) import Pulp.Args.Get (getOption') import Pulp.Build as Build import Pulp.Exec (exec) -import Pulp.System.Files (openTemp) +import Pulp.Outputter (Outputter, getOutputter) +import Pulp.System.Files (tempDir) +import Pulp.Validate (dropPreRelBuildMeta, getNodeVersion, getPursVersion) +import Pulp.Versions.PureScript (psVersions) action :: Action action = Action \args -> do let opts = Map.union args.globalOpts args.commandOpts + out <- getOutputter args Build.runBuild args main <- getOption' "main" opts - src <- liftEffect $ Buffer.fromString (makeEntry main) UTF8 - - info <- openTemp { prefix: "pulp-run", suffix: ".js" } - _ <- FS.fdAppend info.fd src - _ <- FS.fdClose info.fd buildPath <- getOption' "buildPath" opts runtime <- getOption' "runtime" opts + scriptFilePath <- makeRunnableScript { out, buildPath, prefix: "pulp-run", moduleName: main } env <- setupEnv buildPath - exec runtime ([info.path] <> args.remainder) (Just env) + nodeFlags <- getNodeFlags out runtime + exec runtime (nodeFlags <> [scriptFilePath] <> args.remainder) (Just env) -- | Given a build path, create an environment that is just like this process' -- | environment, except with NODE_PATH set up for commands like `pulp run`. @@ -60,6 +64,51 @@ jsEscape = replace (Pattern "'") (Replacement "\\'") <<< replace (Pattern "\\") (Replacement "\\\\") +-- | Returns an empty array or `[ "--experimental-modules" ]` +-- | if using a version of PureScript that outputs ES modules +-- | on a Node runtime with a version < `13.0.0`. +getNodeFlags :: Outputter -> String -> Aff (Array String) +getNodeFlags out runtime + | runtime == "node" = do + nodeVer <- getNodeVersion + psVer <- getPursVersion out + let + usingEsModules = (dropPreRelBuildMeta psVer) >= psVersions.v0_15_0 + nodeNeedsFlag = nodeVer < (version 13 0 0 Nil Nil) + pure if usingEsModules && nodeNeedsFlag then [ "--experimental-modules" ] else [] + | otherwise = pure [] + +makeRunnableScript :: { out :: Outputter, buildPath :: String, prefix :: String, moduleName :: String } -> Aff String +makeRunnableScript { out, buildPath, prefix, moduleName } = do + psVer <- getPursVersion out + fullPath' <- liftEffect $ Path.resolve [] buildPath + let + fullPath = replaceAll (Pattern "\\") (Replacement "/") fullPath' + { makeEntry, writePackageJsonFile } = + if (dropPreRelBuildMeta psVer) < psVersions.v0_15_0 then + { makeEntry: makeCjsEntry, writePackageJsonFile: false } + else + { makeEntry: makeEsEntry fullPath, writePackageJsonFile: true } + src <- liftEffect $ Buffer.fromString (makeEntry moduleName) UTF8 + + parentDir <- tempDir { prefix, suffix: ".js" } + let + scriptFile = Path.concat [ parentDir, "index.js" ] + packageJson = Path.concat [ parentDir, "package.json" ] + FSA.writeFile scriptFile src + when writePackageJsonFile do + FSA.writeTextFile UTF8 packageJson """{"type": "module"}""" + pure scriptFile + -- | Construct a JS string to be used as an entry point from a module name. -makeEntry :: String -> String -makeEntry main = "require('" <> jsEscape main <> "').main();\n" +makeCjsEntry :: String -> String +makeCjsEntry main = "require('" <> jsEscape main <> "').main();\n" + +makeEsEntry :: String -> String -> String +makeEsEntry buildPath main = fold + [ "import { main } from 'file://" + , buildPath + , "/" + , jsEscape main + , "/index.js'; main();" + ] diff --git a/src/Pulp/Server.purs b/src/Pulp/Server.purs index c71acc8d..a167aab2 100644 --- a/src/Pulp/Server.purs +++ b/src/Pulp/Server.purs @@ -23,6 +23,7 @@ import Pulp.Build as Build import Pulp.Outputter (getOutputter) import Pulp.System.StaticServer as StaticServer import Pulp.Utils (orErr) +import Pulp.Validate (failIfUsingEsModulesPsVersion) import Pulp.Watch (watchAff, watchDirectories) data BuildResult @@ -38,6 +39,10 @@ action = Action \args -> do let opts = Map.union args.globalOpts args.commandOpts out <- getOutputter args + whenM (Build.shouldBundle args) do + failIfUsingEsModulesPsVersion out $ Just + "Code path reason: `pulp server` uses `purs bundle` implicitly." + bundleFileName <- getBundleFileName opts hostname <- getOption' "host" opts port <- getOption' "port" opts diff --git a/src/Pulp/Test.purs b/src/Pulp/Test.purs index 9eb7504b..3d393e74 100644 --- a/src/Pulp/Test.purs +++ b/src/Pulp/Test.purs @@ -7,18 +7,13 @@ import Prelude import Data.Map as Map import Data.Maybe (Maybe(..)) -import Effect.Class (liftEffect) import Foreign (unsafeToForeign) -import Node.Buffer as Buffer -import Node.Encoding (Encoding(UTF8)) -import Node.FS.Aff as FS import Pulp.Args (Action(..), Options) import Pulp.Args.Get (getOption') import Pulp.Build as Build import Pulp.Exec (exec) import Pulp.Outputter (getOutputter) -import Pulp.Run (setupEnv, makeEntry) -import Pulp.System.Files (openTemp) +import Pulp.Run (getNodeFlags, makeRunnableScript, setupEnv) action :: Action action = Action \args -> do @@ -42,12 +37,10 @@ action = Action \args -> do main <- getOption' "main" opts buildPath <- getOption' "buildPath" opts env <- setupEnv buildPath - info <- openTemp { prefix: "pulp-test", suffix: ".js" } - src <- liftEffect $ Buffer.fromString (makeEntry main) UTF8 - _ <- FS.fdAppend info.fd src - _ <- FS.fdClose info.fd + scriptFilePath <- makeRunnableScript { out, buildPath, prefix: "pulp-test", moduleName: main } + nodeFlags <- getNodeFlags out runtime exec runtime - ([info.path] <> args.remainder) + (nodeFlags <> [scriptFilePath] <> args.remainder) (Just env) else do to <- getOption' "to" buildArgs.commandOpts diff --git a/src/Pulp/Validate.purs b/src/Pulp/Validate.purs index 8283d6e3..c795ee50 100644 --- a/src/Pulp/Validate.purs +++ b/src/Pulp/Validate.purs @@ -2,21 +2,31 @@ module Pulp.Validate ( validate , getPursVersion , getPsaVersion + , getNodeVersion + , dropPreRelBuildMeta + , failIfUsingEsModulesPsVersion ) where import Prelude import Control.Monad.Error.Class (throwError) +import Data.Array (fold) import Data.Either (Either(..)) +import Data.Foldable (for_) import Data.List (List(..)) -import Data.List.NonEmpty as NEL -import Data.Maybe (Maybe(..)) -import Data.String (codePointFromChar, takeWhile, trim) +import Data.Maybe (Maybe(..), fromMaybe) +import Data.String (Pattern(..), codePointFromChar, stripPrefix, takeWhile, trim) import Data.Version.Haskell (Version(..), parseVersion, showVersion) +import Data.Version.Haskell as HVersion +import Data.Version as SemVer import Effect.Aff (Aff) -import Effect.Exception (error) +import Effect.Class (liftEffect) +import Effect.Exception (error, throw) +import Node.Process as Process import Pulp.Exec (execQuiet) import Pulp.Outputter (Outputter) +import Pulp.Versions.PureScript (psVersions) +import Text.Parsing.Parser (parseErrorMessage) validate :: Outputter -> Aff Version validate out = do @@ -34,11 +44,23 @@ getPursVersion :: Outputter -> Aff Version getPursVersion = getVersionFrom "purs" minimumPursVersion :: Version -minimumPursVersion = Version (NEL.cons' 0 (Cons 12 (Cons 0 Nil))) Nil +minimumPursVersion = psVersions.v0_12_0 getPsaVersion :: Outputter -> Aff Version getPsaVersion = getVersionFrom "psa" +getNodeVersion :: Aff SemVer.Version +getNodeVersion = do + case SemVer.parseVersion (stripV Process.version) of + Left err -> + let message = parseErrorMessage err + in throwError (error ("Failed to parse node.js version: " <> message)) + Right actual -> + pure actual + where + stripV str = + fromMaybe str (stripPrefix (Pattern "v") str) + getVersionFrom :: String -> Outputter -> Aff Version getVersionFrom bin out = do verStr <- takeWhile (_ /= codePointFromChar ' ') <$> trim <$> execQuiet bin ["--version"] Nothing @@ -49,3 +71,18 @@ getVersionFrom bin out = do out.err $ "Unable to parse the version from " <> bin <> ". (It was: " <> verStr <> ")" out.err $ "Please check that the right executable is on your PATH." throwError $ error ("Couldn't parse version from " <> bin) + +failIfUsingEsModulesPsVersion :: Outputter -> Maybe String -> Aff Unit +failIfUsingEsModulesPsVersion out mbMsg = do + psVer <- getPursVersion out + unless ((dropPreRelBuildMeta psVer) < psVersions.v0_15_0) do + out.err $ fold + [ "This code path implicitly uses `purs bundle` or CommonsJS modules, both of which are no longer supported in PureScript v0.15.0. " + , "You are using PureScript " <> HVersion.showVersion psVer <> ". " + , "See https://github.com/purescript/documentation/blob/master/migration-guides/0.15-Migration-Guide.md" + ] + for_ mbMsg out.err + liftEffect $ throw $ "Your version of PureScript cannot use `purs bundle` or CommonJS modules. Please use another bundler (e.g. esbuild) instead." + +dropPreRelBuildMeta :: Version -> Version +dropPreRelBuildMeta (Version mmp _) = Version mmp Nil diff --git a/src/Pulp/Versions/PureScript.purs b/src/Pulp/Versions/PureScript.purs new file mode 100644 index 00000000..77d0401e --- /dev/null +++ b/src/Pulp/Versions/PureScript.purs @@ -0,0 +1,22 @@ +module Pulp.Versions.PureScript where + +import Data.List (List(..), (:)) +import Data.List.NonEmpty as NEL +import Data.Version.Haskell (Version(..)) + +type PureScriptVersions = + { v0_12_0 :: Version + , v0_12_4 :: Version + , v0_13_0 :: Version + , v0_14_0 :: Version + , v0_15_0 :: Version + } + +psVersions :: PureScriptVersions +psVersions = + { v0_12_0: Version (NEL.cons' 0 (12 : 0 : Nil)) Nil + , v0_12_4: Version (NEL.cons' 0 (12 : 4 : Nil)) Nil + , v0_13_0: Version (NEL.cons' 0 (13 : 0 : Nil)) Nil + , v0_14_0: Version (NEL.cons' 0 (14 : 0 : Nil)) Nil + , v0_15_0: Version (NEL.cons' 0 (15 : 0 : Nil)) Nil + } diff --git a/test-js/resources/Main.js b/test-js/cjs/Main.js similarity index 100% rename from test-js/resources/Main.js rename to test-js/cjs/Main.js diff --git a/test-js/resources/Main.purs b/test-js/cjs/Main.purs similarity index 100% rename from test-js/resources/Main.purs rename to test-js/cjs/Main.purs diff --git a/test-js/es/Main.js b/test-js/es/Main.js new file mode 100644 index 00000000..161b3749 --- /dev/null +++ b/test-js/es/Main.js @@ -0,0 +1,15 @@ +// module Test.Main + +export const argv = function argv() { + return process.argv; +}; + +export const slice = function slice(n) { + return function (arr) { + return arr.slice(n); + }; +}; + +export const length = function length(arr) { + return arr.length; +}; diff --git a/test-js/es/Main.purs b/test-js/es/Main.purs new file mode 100644 index 00000000..5e371a5d --- /dev/null +++ b/test-js/es/Main.purs @@ -0,0 +1,20 @@ +module Test.Main where + +import Prelude +import Effect (Effect, foreachE) +import Effect.Console (log) + +foreign import argv :: Effect (Array String) +foreign import slice :: forall a. Int -> Array a -> Array a +foreign import length :: forall a. Array a -> Int + +drop :: forall a. Int -> Array a -> Array a +drop n arr + | n < 1 = arr + | n >= length arr = [] + | otherwise = slice n arr + +main :: Effect Unit +main = do + args <- drop 2 <$> argv + foreachE args log diff --git a/test-js/integration.js b/test-js/integration.js index ebe9608c..f10e6305 100644 --- a/test-js/integration.js +++ b/test-js/integration.js @@ -5,6 +5,7 @@ import run from "./sh"; import semver from "semver"; import touch from "touch"; import which from "which"; +import CP from "child_process"; const hello = "Hello sailor!"; const test = "You should add some tests."; @@ -35,6 +36,14 @@ const testBowerJson = { } }; +const testBowerJsonV0_15_0 = Object.assign({}, testBowerJson, { + "dependencies": { + "purescript-prelude": "^5.0.1", + "purescript-console": "^5.0.0", + "purescript-effect": "^3.0.0" + } +}); + function resolvePath(cmd) { return new Promise((resolve, reject) => { which(cmd, (err, res) => err ? reject(err) : resolve(res)); @@ -122,13 +131,28 @@ function* createModule(sh, temp, name) { const getPursVersion = co.wrap(function*(sh, assert) { const [out] = yield sh("purs --version"); - const pursVer = semver.parse(out.split(/\s/)[0]); + // Drops pre-release identifiers + // 0.15.0-alpha-01 --> 0.15.0 + const pursVerWithExtras = semver.coerce(out.split(/\s/)[0]); + const pursVer = pursVerWithExtras === null ? pursVerWithExtras : semver.parse(pursVerWithExtras); if (pursVer == null) { assert.fail("Unable to parse output of purs --version"); } return pursVer; }); +const psVersionUsed = CP.execSync("purs --version").toString("utf-8"); + +const notEsModulesPsVersionIt = function(testName, cb) { + const pursVerWithExtras = semver.coerce(psVersionUsed.split(/\s/)[0]); + const pursVer = semver.parse(pursVerWithExtras); + if (semver.lt(pursVer, semver.parse('0.15.0'))) { + it(testName, cb); + } else { + it.skip(testName, cb); + } +} + describe("integration tests", function() { // This is, unfortunately, required, as CI is horrendously slow. this.timeout(90000); @@ -148,7 +172,7 @@ describe("integration tests", function() { assert.equal(err.trim(), bowerMissing); })); - it("Bower has precedence over psc-package", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("Bower has precedence over psc-package", run(function*(sh, pulp, assert) { yield pulp("init"); // Create psc-package.json without installing any dependencies @@ -213,7 +237,7 @@ describe("integration tests", function() { assert.exists(path.join("output", "Main", "index.js")); })); - it("pulp build with psc-package", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp build with psc-package", run(function*(sh, pulp, assert, temp) { yield pulp("--psc-package init"); yield pulp("build"); @@ -282,44 +306,49 @@ describe("integration tests", function() { })); it("pulp run -- program args", run(function*(sh, pulp, assert, temp) { - const srcDir = path.join(temp, "run_with_args"); yield pulp("init"); + + const srcDir = path.join(temp, "run_with_args"); yield fs.mkdir(srcDir); - yield fs.copy(path.resolve(__dirname, "resources/Main.purs"), path.join(srcDir, "Main.purs")); - yield fs.copy(path.resolve(__dirname, "resources/Main.js"), path.join(srcDir, "Main.js")); + + const pursVer = yield getPursVersion(sh, assert); + const resourceDir = semver.gte(pursVer, semver.parse('0.15.0')) ? "es": "cjs"; + yield fs.copy(path.resolve(__dirname, `${resourceDir}/Main.purs`), path.join(srcDir, "Main.purs")); + yield fs.copy(path.resolve(__dirname, `${resourceDir}/Main.js`), path.join(srcDir, "Main.js")); + const [out] = yield pulp("run --src-path run_with_args -m Test.Main -- program args"); assert.equal(out.trim(), "program\nargs"); })); - it("pulp build --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp build --to", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("build --to out.js"); const [out] = yield sh("node out.js"); assert.equal(out.trim(), hello); })); - it("pulp build --to creates parent directories", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp build --to creates parent directories", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("build --to nonexistent/out.js"); const [out] = yield sh("node nonexistent/out.js"); assert.equal(out.trim(), hello); })); - it("pulp build --skip-entry-point --to", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp build --skip-entry-point --to", run(function*(sh, pulp, assert, temp) { yield pulp("init"); yield pulp(`--then "echo module.exports = PS >> out.js" build --skip-entry-point --to out.js`); const [out] = yield sh(`node -e "var o = require('./out'); o.Main.main();"`); assert.equal(out.trim(), hello); })); - it("pulp build -O", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp build -O", run(function*(sh, pulp, assert) { yield pulp("init"); const [src] = yield pulp("build -O"); const [out] = yield sh("node", src); assert.equal(out.trim(), hello); })); - it("pulp build -O --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp build -O --to", run(function*(sh, pulp, assert) { // Should be identical to `pulp build --to`. yield pulp("init"); yield pulp("build -O --to out.js"); @@ -327,7 +356,7 @@ describe("integration tests", function() { assert.equal(out.trim(), hello); })); - it("pulp build --source-maps -O --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp build --source-maps -O --to", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("build --source-maps -O --to out.js"); const [out] = yield sh("node out.js"); @@ -335,7 +364,7 @@ describe("integration tests", function() { assert.exists("out.js.map"); })); - it("pulp build -O --src-path alt", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp build -O --src-path alt", run(function*(sh, pulp, assert, temp) { yield pulp("init"); yield fs.rename(path.join(temp, "src"), path.join(temp, "alt")); const [src] = yield pulp("build -O --src-path alt"); @@ -357,30 +386,35 @@ describe("integration tests", function() { })); it("pulp test --test-path test2 -- --something-node-wouldnt-like", run(function*(sh, pulp, assert, temp) { - const newTest = path.join(temp, "test2"); yield pulp("init"); + + const newTest = path.join(temp, "test2"); yield fs.mkdir(newTest); - yield fs.copy(path.resolve(__dirname, "resources/Main.purs"), path.join(newTest, "Main.purs")); - yield fs.copy(path.resolve(__dirname, "resources/Main.js"), path.join(newTest, "Main.js")); + + const pursVer = yield getPursVersion(sh, assert); + const resourceDir = semver.gte(pursVer, semver.parse('0.15.0')) ? "es": "cjs"; + yield fs.copy(path.resolve(__dirname, `${resourceDir}/Main.purs`), path.join(newTest, "Main.purs")); + yield fs.copy(path.resolve(__dirname, `${resourceDir}/Main.js`), path.join(newTest, "Main.js")); + const [out] = yield pulp("test --test-path test2 -- --something-node-wouldnt-like"); assert.equal(out.trim(), "--something-node-wouldnt-like"); })); - it("pulp browserify", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp browserify", run(function*(sh, pulp, assert) { yield pulp("init"); const [src] = yield pulp("browserify"); const [out] = yield sh("node", src); assert.equal(out.trim(), hello); })); - it("pulp browserify --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp browserify --to", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("browserify --to out.js"); const [out] = yield sh("node out.js"); assert.equal(out.trim(), hello); })); - it("pulp browserify --source-maps --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp browserify --source-maps --to", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("browserify --source-maps --to out.js"); const [out] = yield sh("node out.js"); @@ -388,28 +422,28 @@ describe("integration tests", function() { assert.exists("out.js.map"); })); - it("pulp browserify --source-maps --to subdir", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp browserify --source-maps --to subdir", run(function*(sh, pulp, assert, temp) { yield pulp("init"); yield fs.mkdir(path.join(temp, "subdir")); yield pulp("browserify --source-maps --to subdir/out.js"); assert.exists("subdir/out.js.map"); })); - it("pulp browserify -O", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp browserify -O", run(function*(sh, pulp, assert) { yield pulp("init"); const [src] = yield pulp("browserify -O"); const [out] = yield sh("node", src); assert.equal(out.trim(), hello); })); - it("pulp browserify -O --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp browserify -O --to", run(function*(sh, pulp, assert) { yield pulp("init"); yield pulp("browserify -O --to out.js"); const [out] = yield sh("node out.js"); assert.equal(out.trim(), hello); })); - it("output of pulp browserify --standalone may be `require()`d", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("output of pulp browserify --standalone may be `require()`d", run(function*(sh, pulp, assert, temp) { yield pulp("init"); yield pulp("browserify --main Data.Function --standalone data-function --to data-function.js"); const dataFunction = require(path.join(temp, "data-function.js")); @@ -434,7 +468,7 @@ describe("integration tests", function() { assert.exists("subdir/out.js.map"); })); - it("pulp browserify --skip-compile", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp browserify --skip-compile", run(function*(sh, pulp, assert, temp) { yield pulp("init"); yield fs.rename(path.join(temp, "src"), path.join(temp, "alt")); yield pulp("build --src-path alt"); @@ -469,7 +503,7 @@ describe("integration tests", function() { assert.match(out, /hello, world/); })); - it("pulp psci includes dependencies with psc-package", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp psci includes dependencies with psc-package", run(function*(sh, pulp, assert) { yield pulp("--psc-package init"); yield pulp("psci"); @@ -485,7 +519,7 @@ describe("integration tests", function() { assert.match(out, new RegExp(hello)); })); - it("pulp --before something build", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp --before something build", run(function*(sh, pulp, assert, temp) { yield pulp("init"); // In reality, this is likely to be a "--before clear" or something, but @@ -500,7 +534,7 @@ describe("integration tests", function() { "test file before.txt was not found as after.txt"); })); - it("pulp --then something build", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp --then something build", run(function*(sh, pulp, assert) { yield pulp("init"); const mv = process.platform === "win32" ? "rename" : "mv"; yield pulp(`--then "${mv} out.js out2.js" build --to out.js`); @@ -529,14 +563,14 @@ describe("integration tests", function() { assert.exists(path.join(temp, "afterFailed.txt")); // --else has run })); - it("pulp --then something browserify", run(function*(sh, pulp, assert, temp) { + notEsModulesPsVersionIt("pulp --then something browserify", run(function*(sh, pulp, assert, temp) { yield pulp("init"); const mv = process.platform === "win32" ? "rename" : "mv"; yield pulp(`--then "echo lol > out.txt" browserify`); assert.equal((yield fs.readFile(path.join(temp, "out.txt"), "utf-8")).trim(), "lol"); })); - it("pulp --then something browserify --to", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp --then something browserify --to", run(function*(sh, pulp, assert) { yield pulp("init"); const mv = process.platform === "win32" ? "rename" : "mv"; yield pulp(`--then "${mv} out.js out2.js" browserify --to out.js`); @@ -545,7 +579,7 @@ describe("integration tests", function() { assert.equal(out.trim(), hello); })); - it("pulp test --runtime", run(function*(sh, pulp, assert) { + notEsModulesPsVersionIt("pulp test --runtime", run(function*(sh, pulp, assert) { yield pulp("init"); const [out] = yield pulp("test --runtime cat"); const [out2] = yield sh("node", out); @@ -628,6 +662,25 @@ describe("integration tests", function() { yield setupPackage(temp, sh); yield fs.writeFile(path.join(temp, "bower.json"), JSON.stringify(testBowerJson)); + + const psVer = yield getPursVersion(sh, assert); + if (semver.gte(psVer, semver.parse('0.15.0'))) { + // For tests to pass on the `v0.15.0-alpha-01` purs version, + // we need to use a custom fork of a non-core repo + // (i.e. working-group-purescript-es/purescript-prelude#es-modules-libraries). + // Since those repos' `bower.json` files point to dependencies + // whose "versions" are branch names as well, the `bower.json` JSON parsing fails. + // So, we need to remove the `bower_components` folder that gets set up + // when `pulp init` is called, and re-install the bower deps + // using the `bower.json` file. + // + // Furthermore, we have to install all 3 deps used in a typical `pulp init` + // setup. Otherwise, `pulp version` fails due to other dependency-related reasons. + yield fs.writeFile(path.join(temp, "bower.json"), JSON.stringify(testBowerJsonV0_15_0)); + yield sh("rm -rf bower_components"); + yield sh("bower install"); + } + yield sh("git commit -am \"updating bower.json\""); yield sh("git tag v2.0.0"); yield sh("git commit --allow-empty -m \"an empty commit for the new tag\"");