Work around versioned package NPM bug (#661)

This commit is contained in:
Shayne Czyzewski 2022-07-07 10:37:48 -04:00 committed by GitHub
parent d1bea1c513
commit d5a9039722
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 50 additions and 152 deletions

View File

@ -90,7 +90,7 @@ jobs:
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16.15.0'
node-version: '16'
- name: Run tests
run: cabal test

View File

@ -46,7 +46,7 @@
"file",
"server/package.json"
],
"c893d7974db760e624d16bec1ada58262e732911789fb6410c8288ae5b9bc84f"
"9c3186abd79e11706fd56699054b653303dbae26b3b7037a2104377d697b7b22"
],
[
[
@ -207,7 +207,7 @@
"file",
"web-app/package.json"
],
"3b3724d8f44d2a7652b6426f7e1e494031df868d23a29850afa260e67ff8da72"
"bf1d166d30b2b04b50180276c8c34d7cae29e31ce11635e0199e2fde537e46ce"
],
[
[

View File

@ -18,8 +18,8 @@
},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"name": "server",
"nodemonConfig": {

View File

@ -25,8 +25,8 @@
"devDependencies": {},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"eslintConfig": {
"extends": "react-app"

View File

@ -46,7 +46,7 @@
"file",
"server/package.json"
],
"c893d7974db760e624d16bec1ada58262e732911789fb6410c8288ae5b9bc84f"
"9c3186abd79e11706fd56699054b653303dbae26b3b7037a2104377d697b7b22"
],
[
[
@ -207,7 +207,7 @@
"file",
"web-app/package.json"
],
"8351f0d9a0eb46ffc9b222567e1bcf89b68f59cec062caaaac0a71ff6f001b26"
"8d5e3c23f6fdfe422e307d0dee0e5503d1584fa47a4cff5ca4299e653a6b0fb6"
],
[
[

View File

@ -18,8 +18,8 @@
},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"name": "server",
"nodemonConfig": {

View File

@ -25,8 +25,8 @@
"devDependencies": {},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"eslintConfig": {
"extends": "react-app"

View File

@ -46,7 +46,7 @@
"file",
"server/package.json"
],
"c3c013dfedbca440240c464e4dd55be13cb0bdf70e8334bc17deb6a625ba9698"
"aed180a5eb3804680a90c96e24aa9ae4f78a3c64f46976fccab5b25f2e9e2992"
],
[
[
@ -221,7 +221,7 @@
"file",
"web-app/package.json"
],
"b6e6aef85445808b54fa8cd9b5ac7b85105eeb2d55dc002fe20f374f06212c66"
"5b5fd1238f4ab4e031b9dde5967358a8d704d9028938071393293b07e906da10"
],
[
[

View File

@ -19,8 +19,8 @@
},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"name": "server",
"nodemonConfig": {

View File

@ -25,8 +25,8 @@
"devDependencies": {},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"eslintConfig": {
"extends": "react-app"

View File

@ -46,7 +46,7 @@
"file",
"server/package.json"
],
"2d4b9b5f8e4f1c9e2057d1709d122a40ff04134d329c59a1e2334ef2b159ff4a"
"c1483b8ee9fb6b7b19575ee3327d88a92a3be99427a4714b28dfa2c2207434fa"
],
[
[
@ -207,7 +207,7 @@
"file",
"web-app/package.json"
],
"581f025e9f75fdca5ec2b11b1f2b7981142ea8ebb9662e61b535276eba6ae149"
"b3843b68d443332680fe36af1262ed79a76b1fb600f91153adf1abbe60902ea7"
],
[
[

View File

@ -18,8 +18,8 @@
},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"name": "server",
"nodemonConfig": {

View File

@ -25,8 +25,8 @@
"devDependencies": {},
"engineStrict": true,
"engines": {
"node": "^16.0.0 <=16.15.0",
"npm": "^8.0.0 <=8.5.5"
"node": "^16.0.0",
"npm": "^8.0.0"
},
"eslintConfig": {
"extends": "react-app"

View File

@ -13,28 +13,15 @@ data ProjectRootDir
-- | Range of node versions that node packages generated by this generator work correctly with.
nodeVersionRange :: SV.Range
nodeVersionRange =
SV.rangeFromVersionsIntersection
[ (SV.BackwardsCompatibleWith, latestLTSVersion),
(SV.LessThanOrEqual, latestLTSExactVersionThatWeKnowWorks)
]
nodeVersionRange = SV.rangeFromVersion (SV.BackwardsCompatibleWith, latestLTSVersion)
where
latestLTSVersion = SV.Version 16 0 0
-- There is a bug in node 16.15.1 (more correctly, in npm 8.11.0 that comes with it)
-- that messes up how Wasp uses Prisma. That is why we limited ourselves to <=16.15.0 for now.
-- Bug issue on NPM CLI repo: https://github.com/npm/cli/issues/5018 .
latestLTSExactVersionThatWeKnowWorks = SV.Version 16 15 0
-- | Range of npm versions that Wasp and generated projects work correctly with.
npmVersionRange :: SV.Range
npmVersionRange =
SV.rangeFromVersionsIntersection
[ (SV.BackwardsCompatibleWith, latestLTSVersion),
(SV.LessThanOrEqual, latestLTSExactVersionThatWeKnowWorks)
]
npmVersionRange = SV.rangeFromVersion (SV.BackwardsCompatibleWith, latestLTSVersion)
where
latestLTSVersion = SV.Version 8 0 0 -- Goes with node 16
latestLTSExactVersionThatWeKnowWorks = SV.Version 8 5 5 -- Goes with node 16.15.0
latestLTSVersion = SV.Version 8 0 0 -- Goes with node 16 (but also higher versions too)
prismaVersion :: SV.Version
prismaVersion = SV.Version 3 15 2

View File

@ -5,26 +5,23 @@ module Wasp.Generator.DbGenerator.Jobs
)
where
import Control.Concurrent (Chan, newChan, readChan, writeChan, writeList2Chan)
import Control.Concurrent.Async (concurrently)
import StrongPath (Abs, Dir, Path', (</>))
import StrongPath (Abs, Dir, File', Path', Rel, (</>))
import qualified StrongPath as SP
import System.Exit (ExitCode (..))
import qualified System.Info
import Wasp.Generator.Common (ProjectRootDir, prismaVersion)
import Wasp.Generator.Common (ProjectRootDir)
import Wasp.Generator.DbGenerator.Common (dbSchemaFileInProjectRootDir)
import Wasp.Generator.Job (JobMessage, JobMessageData (JobExit, JobOutput))
import qualified Wasp.Generator.Job as J
import Wasp.Generator.Job.Process (runNodeCommandAsJob)
import Wasp.Generator.ServerGenerator.Common (serverRootDirInProjectRootDir)
-- `--no-install` is the magic that causes this command to fail if npx cannot find it locally
-- (either in node_modules, or globally in npx). We do not want to allow npx to ask
-- a user without prisma to install the latest version.
-- We also pin the version to what we need so it won't accidentally find a different version globally
-- somewhere on the PATH.
npxPrismaCmd :: [String]
npxPrismaCmd = ["npx", "--no-install", "prisma@" ++ show prismaVersion]
-- | NOTE: The expectation is that `npm install` was already executed
-- such that we can use the locally installed package.
-- This assumption is ok since it happens during compilation now.
prismaInServerNodeModules :: Path' (Rel ProjectRootDir) File'
prismaInServerNodeModules = serverRootDirInProjectRootDir </> [SP.relfile|./node_modules/.bin/prisma|]
absPrismaExecutableFp :: Path' Abs (Dir ProjectRootDir) -> FilePath
absPrismaExecutableFp projectDir = SP.toFilePath $ projectDir </> prismaInServerNodeModules
migrateDev :: Path' Abs (Dir ProjectRootDir) -> Maybe String -> J.Job
migrateDev projectDir maybeMigrationName = do
@ -42,115 +39,29 @@ migrateDev projectDir maybeMigrationName = do
-- we are using `script` to trick Prisma into thinking it is running in TTY (interactively).
-- NOTE(martin): For this to work on Mac, filepath in the list below must be as it is now - not wrapped in any quotes.
let npxPrismaMigrateCmd = npxPrismaCmd ++ ["migrate", "dev", "--schema", SP.toFilePath schemaFile] ++ optionalMigrationArgs
let prismaMigrateCmd = absPrismaExecutableFp projectDir : ["migrate", "dev", "--schema", SP.toFilePath schemaFile] ++ optionalMigrationArgs
let scriptArgs =
if System.Info.os == "darwin"
then -- NOTE(martin): On MacOS, command that `script` should execute is treated as multiple arguments.
["-Fq", "/dev/null"] ++ npxPrismaMigrateCmd
["-Fq", "/dev/null"] ++ prismaMigrateCmd
else -- NOTE(martin): On Linux, command that `script` should execute is treated as one argument.
["-feqc", unwords npxPrismaMigrateCmd, "/dev/null"]
["-feqc", unwords prismaMigrateCmd, "/dev/null"]
let job = runNodeCommandAsJob serverDir "script" scriptArgs J.Db
retryJobOnErrorWith job (npmInstall projectDir) ForwardEverything
runNodeCommandAsJob serverDir "script" scriptArgs J.Db
-- | Runs `prisma studio` - Prisma's db inspector.
runStudio :: Path' Abs (Dir ProjectRootDir) -> J.Job
runStudio projectDir = do
let serverDir = projectDir </> serverRootDirInProjectRootDir
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
let prismaStudioCmdArgs = ["studio", "--schema", SP.toFilePath schemaFile]
let npxPrismaStudioCmd = npxPrismaCmd ++ ["studio", "--schema", SP.toFilePath schemaFile]
let job = runNodeCommandAsJob serverDir (head npxPrismaStudioCmd) (tail npxPrismaStudioCmd) J.Db
retryJobOnErrorWith job (npmInstall projectDir) ForwardEverything
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) prismaStudioCmdArgs J.Db
generatePrismaClient :: Path' Abs (Dir ProjectRootDir) -> J.Job
generatePrismaClient projectDir = do
let serverDir = projectDir </> serverRootDirInProjectRootDir
let schemaFile = projectDir </> dbSchemaFileInProjectRootDir
let prismaGenerateCmdArgs = ["generate", "--schema", SP.toFilePath schemaFile]
let npxPrismaGenerateCmd = npxPrismaCmd ++ ["generate", "--schema", SP.toFilePath schemaFile]
let job = runNodeCommandAsJob serverDir (head npxPrismaGenerateCmd) (tail npxPrismaGenerateCmd) J.Db
retryJobOnErrorWith job (npmInstall projectDir) ForwardOnlyRetryErrors
-- | Runs `npm install` to install dependencies.
-- May be needed before npx commands if dependencies (e.g. Prisma) are not installed.
npmInstall :: Path' Abs (Dir ProjectRootDir) -> J.Job
npmInstall projectDir = do
let serverDir = projectDir </> serverRootDirInProjectRootDir
runNodeCommandAsJob serverDir "npm" ["install"] J.Db
data JobMessageForwardingStrategy = ForwardEverything | ForwardOnlyRetryErrors
data Attempt = InitialAttempt | Retry
-- | Runs the original job, and if there is an error, it attempts recovery by running the second job
-- before retrying the original one last time.
-- TODO(shayne): Consider moving this function and data structures into a separate module to highlight it
-- is unique. We could also decouple this from DB-only jobs by passing in _jobType or using one of the
-- messages as a clue.
retryJobOnErrorWith :: J.Job -> J.Job -> JobMessageForwardingStrategy -> J.Job
retryJobOnErrorWith originalJob afterErrorJob forwardingStrategy externalChannel = do
let channelForwarder = case forwardingStrategy of
ForwardEverything -> allJobOutput
ForwardOnlyRetryErrors -> silentUntilRetryFails
intermediateChannel <- newChan
(initialExitCode, _) <- concurrently (originalJob intermediateChannel) (channelForwarder InitialAttempt intermediateChannel externalChannel)
exitCode <-
case initialExitCode of
ExitSuccess -> return ExitSuccess
ExitFailure _ -> do
-- NOTE(shayne): We don't care what the exit code of the recovery job is. If it succeeds, great, and hopefully retry works.
-- If it fails, retry will likely fail again in the same manner. There may be other quirks but this is
-- sufficient for the npx and npm install use case we are targeting now.
_ <- concurrently (afterErrorJob intermediateChannel) (channelForwarder InitialAttempt intermediateChannel externalChannel)
(retryExitCode, _) <- concurrently (originalJob intermediateChannel) (channelForwarder Retry intermediateChannel externalChannel)
return retryExitCode
-- NOTE(shayne): We only want to forward one JobExit message to the external channel from this function, as callers will stop
-- listening once they get it. This is why our job message forwarders do not send them and we do it ourselves here at the end.
-- The reason is this function is creating a job that can run one or three sub-jobs, depending on success of the first. In the three sub-job
-- case we have a first try (fails), a recovery job, and a final retry. Since JobExits indicate completion when sent over a channel, we cannot
-- blindly forward all messages from the sub-jobs, as they would get 3 JobExits in this case (but stop listening on the first).
-- Instead, we must ensure we only send one at the very end, when all sub-jobs are complete, and that it represents the ultimate success of the entire process.
writeJobExitChanMessage exitCode
return exitCode
where
writeJobExitChanMessage exitCode =
writeChan
externalChannel
J.JobMessage
{ J._data = J.JobExit exitCode,
J._jobType = J.Db
}
-- Forwards all JobOutput messages to the output channel, but does not send JobExit
-- as this job might be only part of a bigger job, therefore we might not want to signal
-- end of the job yet. The caller is responsible for sending a final JobExit.
allJobOutput :: Attempt -> Chan JobMessage -> Chan JobMessage -> IO ()
allJobOutput attempt fromChan toChan = do
message <- readChan fromChan
case J._data message of
JobOutput _ _ -> writeChan toChan message >> allJobOutput attempt fromChan toChan
JobExit _ -> return ()
-- Only forwards JobOutput messages for the retry attempt if it fails.
-- All first attempt output is ignored, and so is success on retry.
-- We do not send JobExit as this job might be only part of a bigger job,
-- therefore we might not want to signal end of the job yet.
-- The caller is responsible for sending a final JobExit.
silentUntilRetryFails :: Attempt -> Chan JobMessage -> Chan JobMessage -> IO ()
silentUntilRetryFails attempt fromChan toChan = go []
where
go messagesSoFar = do
newMessage <- readChan fromChan
case J._data newMessage of
JobOutput _ _ -> case attempt of
InitialAttempt -> go []
Retry -> go (newMessage : messagesSoFar)
JobExit exitCode -> case exitCode of
ExitSuccess -> return ()
ExitFailure _ -> writeList2Chan toChan messagesSoFar
runNodeCommandAsJob serverDir (absPrismaExecutableFp projectDir) prismaGenerateCmdArgs J.Db

View File

@ -12,8 +12,8 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
## 1. Requirements
You need to have `node` (and `npm`) installed on your machine and available in `PATH`.
- `node`: >=16.11.0 and <=16.15.0
- `npm`: >= 8.0.0 and <= 8.5.5
- `node`: 16.x.x
- `npm`: 8.x.x
You can check `node` and `npm` versions by running:
```shell-session
@ -33,12 +33,12 @@ We recommend using [nvm](https://github.com/nvm-sh/nvm) for managing your Node.j
Then, install a version of node that you need, e.g.:
```shell-session
nvm install 16.15.0
nvm install 16
```
Finally, whenever you need to ensure specific version of node is used, run e.g.
```shell-session
nvm use 16.15.0
nvm use 16
```
to set the node version for current shell session.