-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Better Win support: correct resolving of node/npm/npx & prisma #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 2 commits
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 |
|---|---|---|
|
|
@@ -15,14 +15,14 @@ where | |
|
|
||
| import StrongPath (Abs, Dir, File', Path', (</>)) | ||
| import qualified StrongPath as SP | ||
| import StrongPath.TH (relfile) | ||
| import Wasp.Generator.Common (ProjectRootDir) | ||
| import Wasp.Generator.DbGenerator.Common (MigrateArgs (..), dbSchemaFileInProjectRootDir) | ||
| import Wasp.Generator.ServerGenerator.Common (serverRootDirInProjectRootDir) | ||
| import Wasp.Generator.ServerGenerator.Db.Seed (dbSeedNameEnvVarName) | ||
| import qualified Wasp.Job as J | ||
| import Wasp.Job.Process (runNodeCommandAsJobWithExtraEnv) | ||
| import Wasp.Project.Common (WaspProjectDir, waspProjectDirFromProjectRootDir) | ||
| import Wasp.Node.NodeModules (getPathToExecutableInNodeModules) | ||
| import Wasp.Project.Common (WaspProjectDir, nodeModulesDirInWaspProjectDir, waspProjectDirFromProjectRootDir) | ||
|
|
||
| migrateDev :: Path' Abs (Dir ProjectRootDir) -> MigrateArgs -> J.Job | ||
| migrateDev projectRootDir migrateArgs = | ||
|
|
@@ -178,4 +178,7 @@ absPrismaExecutableFp :: Path' Abs (Dir WaspProjectDir) -> FilePath | |
| absPrismaExecutableFp waspProjectDir = SP.fromAbsFile prismaExecutableAbs | ||
| where | ||
| prismaExecutableAbs :: Path' Abs File' | ||
| prismaExecutableAbs = waspProjectDir </> [relfile|./node_modules/.bin/prisma|] | ||
| prismaExecutableAbs = | ||
| waspProjectDir | ||
| </> nodeModulesDirInWaspProjectDir | ||
| </> SP.castRel (getPathToExecutableInNodeModules "prisma") | ||
|
Member
Author
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. We are now being system-agnostic here, when figuring out abs path to prisma binary.
Member
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. See my comment on the |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| module Wasp.Node.Executables | ||
| ( nodeExec, | ||
| npmExec, | ||
| npxExec, | ||
| ) | ||
| where | ||
|
|
||
| import GHC.IO (unsafePerformIO) | ||
| import StrongPath (fromAbsFile) | ||
| import Wasp.Util.System (ExecName, resolveExecNameIO) | ||
|
|
||
| {-# NOINLINE nodeExec #-} -- To ensure single execution, for unsafePerformIO. | ||
| nodeExec :: ExecName | ||
| nodeExec = fromAbsFile $ unsafePerformIO $ resolveExecNameIO "node" | ||
|
|
||
| {-# NOINLINE npmExec #-} -- To ensure single execution, for unsafePerformIO. | ||
| npmExec :: ExecName | ||
| npmExec = fromAbsFile $ unsafePerformIO $ resolveExecNameIO "npm" | ||
|
|
||
| {-# NOINLINE npxExec #-} -- To ensure single execution, for unsafePerformIO. | ||
| npxExec :: ExecName | ||
| npxExec = fromAbsFile $ unsafePerformIO $ resolveExecNameIO "npx" |
|
Member
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. I see this logic is only used for calling idea: Could it be possible to call |
| 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" | ||
|
Member
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. Can we have a comment with a link to somewhere that says that npm adds |
||
| | otherwise = execName | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| module Wasp.Util.System | ||
| ( resolveExecNameIO, | ||
| isSystemWindows, | ||
| isSystemMacOS, | ||
| ExecName, | ||
| ) | ||
| 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 full executable path. | ||
| -- | ||
| -- Note that filename of the resolved path might not be exactly equal to the provided executable | ||
| -- name, but may have additional extension (e.g. "node" might resolve to "/some/path/node.cmd"). | ||
| -- | ||
| -- The reason why we return resolved absolute executable path and not just the resolved filename | ||
| -- (e.g. "node.cmd" for "node") is that, as per Haskell docs, when passing just the filename to | ||
| -- 'System.Process.createProcess', we can get unexpected resolution if 'cwd' option is set. | ||
| -- For example it can resolve to "npm.cmd" in the local ".node_modules" instead of a global one. | ||
| -- So it is better to stick with absolute paths. | ||
| -- | ||
| -- 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', becuase then the shell will do system resolution for us, | ||
| -- but at the price of abandoning any argument escaping. | ||
| -- | ||
| -- Throws IOError if it failed to resolve the name. | ||
| -- | ||
| -- Example: resolveExecNameIO "npm" -> "C:\...\npm.cmd" | ||
| resolveExecNameIO :: ExecName -> IO (Path' Abs File') | ||
| resolveExecNameIO execName = do | ||
| firstJustM findExecutable execNamesToLookForByPriorityDesc >>= \case | ||
| Just execPath -> 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"] | ||
|
Member
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. We should get this list from the |
||
| | otherwise = [execName] | ||
|
|
||
| isSystemWindows :: Bool | ||
| isSystemWindows = System.Info.os == "mingw32" | ||
|
|
||
| isSystemMacOS :: Bool | ||
| isSystemMacOS = System.Info.os == "darwin" | ||
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.
This should go on "unreleased". Also I'd remove or at least rephrase the last part. Now it is not clear if this change means Wasp on Windows finally works, or if it doesn't yet.