-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Better Win support: correct resolving of node/npm/npx & prisma, no ANSI codes on Win #3258
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
ea72e06
ea42f99
064a357
dd2d413
198a387
4ea9679
d75da8b
0b1cf61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| module Wasp.Node.Executables | ||
| ( nodeExec, | ||
| npmExec, | ||
| npxExec, | ||
| ) | ||
| where | ||
|
|
||
| import GHC.IO (unsafePerformIO) | ||
| import StrongPath (fromAbsFile) | ||
| import Wasp.Util.System (ExecName, resolveExecNameIO) | ||
|
|
||
| -- | Node executable name to be passed to Haskell's "System.Process" functions. | ||
| -- | ||
| -- This function being top level form in combo with NOINLINE guarantees that IO action will get | ||
| -- executed only once per lifetime of the Haskell program. | ||
| {-# NOINLINE nodeExec #-} | ||
| nodeExec :: ExecName | ||
| nodeExec = | ||
| -- NOTE: We are taking whole resolved absolute path here because just using the resolved exec name | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This I am not super happy with. Interested in discussion here. Maybe I should try I again and write down the errors, that might help us make a decision. |
||
| -- was still flaky on Windows in some situations. | ||
| fromAbsFile $ snd $ unsafePerformIO $ resolveExecNameIO "node" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of doing unsaferPerformIO, we could have actually obtained these in real IO, somweher at the start of our app, and then propagated them through the code, likely via our monad stack. |
||
|
|
||
| -- | Npm executable name to be passed to Haskell's "System.Process" functions. | ||
| -- | ||
| -- This function being top level form in combo with NOINLINE guarantees that IO action will get | ||
| -- executed only once per lifetime of the Haskell program. | ||
| {-# NOINLINE npmExec #-} | ||
| npmExec :: ExecName | ||
| npmExec = | ||
| -- NOTE: We are taking whole resolved absolute path here because just using the resolved exec name | ||
| -- was still flaky on Windows in some situations. | ||
| fromAbsFile $ snd $ unsafePerformIO $ resolveExecNameIO "npm" | ||
|
|
||
| -- | Node executable name to be passed to Haskell's "System.Process" functions. | ||
| -- | ||
| -- This function being top level form in combo with NOINLINE guarantees that IO action will get | ||
| -- executed only once per lifetime of the Haskell program. | ||
| {-# NOINLINE npxExec #-} | ||
| npxExec :: ExecName | ||
| npxExec = | ||
| -- NOTE: We are taking whole resolved absolute path here because just using the resolved exec name | ||
| -- was still flaky on Windows in some situations. | ||
| fromAbsFile $ snd $ unsafePerformIO $ resolveExecNameIO "npx" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| module Wasp.Node.NodeModules | ||
| ( getPathToExecutableInNodeModules, | ||
| ) | ||
| where | ||
|
|
||
| import Data.Maybe (fromJust) | ||
| import StrongPath (File, Path', Rel, parseRelFile, reldir, (</>)) | ||
| import Wasp.Util.System (isSystemWindows) | ||
|
|
||
| -- | Represents some node_modules dir. | ||
| data NodeModulesDir | ||
|
|
||
| -- | Node modules (node_modules) have a place where they put all the executables/binaries | ||
| -- produced by the packages/modules. | ||
| -- This function returns a path to such an executable with a given name, taking into account | ||
| -- details like current operating system. | ||
| -- | ||
| -- Example: @getPathToExecutableInNodeModules "npm"@ -> @".bin/npm.cmd"@ | ||
| getPathToExecutableInNodeModules :: String -> Path' (Rel NodeModulesDir) (File f) | ||
| getPathToExecutableInNodeModules execName = | ||
| [reldir|.bin|] </> fromJust (parseRelFile systemSpecificExecFilename) | ||
| where | ||
| systemSpecificExecFilename | ||
| | isSystemWindows = execName <> ".cmd" | ||
| | otherwise = execName |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| {-# LANGUAGE TupleSections #-} | ||
|
|
||
| module Wasp.Util.System | ||
| ( resolveExecNameIO, | ||
| isSystemWindows, | ||
| isSystemMacOS, | ||
| ExecName, | ||
| isEnvVarValueTruthy, | ||
| ) | ||
| where | ||
|
|
||
| import Control.Exception (throwIO) | ||
| import Control.Monad.Extra (firstJustM) | ||
| import StrongPath (Abs, File', Path', parseAbsFile) | ||
| import System.Directory (findExecutable) | ||
| import qualified System.Info | ||
|
|
||
| -- | Executable name as expected by Haskell's "System.Process" and its 'System.Process.RawCommand', | ||
| -- therefore suited for passing to their functions for creating/executing processes. | ||
| -- It can be just "node", or "node.exe", or relative or full path, ... . | ||
| type ExecName = FilePath | ||
|
|
||
| -- | Resolve given executable name (e.g. "node") to the version of the name that resolves | ||
| -- successfully and the corresponding full path to which it resolves. | ||
| -- | ||
| -- "Version of the name" because we might try a couple of different versions of the name till we | ||
| -- find one that resolves (e.g. for "node" we might also try "node.cmd" and "node.exe" on Windows). | ||
| -- | ||
| -- The resolved path corresponds to the program that would be executed by | ||
| -- 'System.Process.createProcess' if exec name was provided as 'System.Process.RawCommand'. Check | ||
| -- 'System.Process.findExecutable' for more details since that is what we use internally. | ||
| -- | ||
| -- Motivation for this function was mainly driven by how exec names are resolved when executing a | ||
| -- process on Windows. | ||
| -- On Linux/MacOS situation is simple, the system will normally do the name resolution for us, so | ||
| -- e.g. if we pass "npm" to 'System.Process.proc', that will work out of the box. | ||
| -- But on Windows, that will normally fail, since there is no "npm" really but instead "npm.cmd" or | ||
| -- "npm.exe". In that case, we want to figure out what exactly is the right exec name to use. | ||
| -- Note that we don't have to bother with this when using 'System.Process.shell' instead of | ||
| -- 'System.Process.proc', but at the price of abandoning any argument escaping. | ||
| -- | ||
| -- Throws IOError if it failed to resolve the name. | ||
| -- | ||
| -- Example: resolveExecNameIO "npm" -> ("npm.cmd", "C:\...\npm.cmd") | ||
| resolveExecNameIO :: ExecName -> IO (ExecName, Path' Abs File') | ||
| resolveExecNameIO execName = do | ||
| firstJustM (\name -> ((name,) <$>) <$> findExecutable name) execNamesToLookForByPriorityDesc >>= \case | ||
| Just (execName', execPath) -> (execName',) <$> parseAbsFile execPath | ||
| Nothing -> | ||
| (throwIO . userError . unlines) | ||
| [ "Could not find '" <> execName <> "' executable on your system.", | ||
| "Please ensure " <> execName <> " is installed and available in your PATH." | ||
| ] | ||
| where | ||
| execNamesToLookForByPriorityDesc | ||
| | isSystemWindows = (execName <>) <$> ["", ".cmd", ".exe", ".ps1"] | ||
| | otherwise = [execName] | ||
|
|
||
| isSystemWindows :: Bool | ||
| isSystemWindows = System.Info.os == "mingw32" | ||
|
|
||
| isSystemMacOS :: Bool | ||
| isSystemMacOS = System.Info.os == "darwin" | ||
|
|
||
| isEnvVarValueTruthy :: String -> Bool | ||
| isEnvVarValueTruthy envVarValue = envVarValue `notElem` ["0", "false", "no", "off"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are now being system-agnostic here, when figuring out abs path to prisma binary.