4import Data.Array (fromFoldable, head, (..))
5import Data.Either (Either(..))
6import Data.Int (fromString)
7import Data.Maybe (Maybe(..), fromMaybe)
8import Data.Newtype (class Newtype, un)
9import Data.Posix.Signal (Signal(..))
10import Data.String.Utils (lines)
11import Data.Time.Duration (Seconds(..), fromDuration)
13import Effect.AVar as AVar
14import Effect.Aff (Aff, Milliseconds(..), bracket, delay, effectCanceler, launchAff_, makeAff)
15import Effect.Aff.Class (liftAff)
16import Effect.Class (class MonadEffect, liftEffect)
17import Effect.Class.Console (info, log)
18import Effect.Exception (throw)
19import Foreign (unsafeToForeign)
20import JobQueue (EnqueueResult(..), NewJob(..), Queue, ResourcePool(..))
22import Middleware.JsonBodyParser (jsonBodyParser)
23import Node.Buffer (Buffer)
24import Node.Buffer as Buffer
25import Node.ChildProcess (ExecResult, defaultExecOptions, exec, kill, pid)
26import Node.Encoding (Encoding(..))
27import Node.Express.App (App, listenHttp, listenHttps, use, useExternal)
28import Node.Express.App as E
29import Node.Express.Handler (HandlerM(..), Handler)
30import Node.Express.Middleware.Static (static)
31import Node.Express.Request (getBody')
32import Node.Express.Response (setStatus)
33import Node.Express.Response as Response
34import Node.Express.Types (Request, Response)
35import Node.FS.Aff (readTextFile, writeTextFile)
36import Node.HTTP (Server)
37import Node.OS (numCpus)
38import Node.Process (lookupEnv)
39import Shared.Json (readAff)
40import Shared.Models.Body (RunResult, PursResult)
41import Shared.Models.Body as Body
42import Simple.JSON (read_, write)
43import Simple.JSON as JSON
45toBody ∷ ∀ r m. MonadEffect m => { stdout ∷ Buffer, stderr ∷ Buffer | r } -> m RunResult
49 stdout <- Buffer.toString UTF8 result.stdout
50 stderr <- Buffer.toString UTF8 result.stderr
52 (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
53 in { code, stdout, stderr } ∷ RunResult
56 = { code ∷ Maybe Int }
58asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
59asErrorWithCode = read_ <<< unsafeToForeign
61compileCode ∷ Folder -> String -> Aff PursResult
62compileCode folder code = do
63 saveMainFile folder code
64 { stderr } <- execCommand folder "spago build --purs-args \"--json-errors\""
65 strResult <- liftEffect $ Buffer.toString UTF8 stderr
66 case checkOutput strResult of
68 Nothing -> liftEffect $ throw $ "No result in " <> strResult
74 (parseable :: Maybe PursResult) = JSON.readJSON_ line
75 fromFoldable parseable
77runCode ∷ Folder -> Aff ExecResult
78runCode folder = execCommand folder "node run.js"
80saveMainFile ∷ Folder -> String -> Aff Unit
81saveMainFile folder code = writeTextFile UTF8 (un Folder folder <> "/src/Main.purs") code
83execCommand ∷ Folder -> String -> Aff ExecResult
84execCommand folder command =
85 makeAff \callback -> do
87 options = defaultExecOptions { cwd = Just (un Folder folder) }
88 childProcess <- exec command options (callback <<< Right)
89 pure $ effectCanceler ((log $ "Killing " <> show (pid childProcess)) *> kill SIGKILL childProcess)
94derive instance ntFolder :: Newtype Folder _
95derive newtype instance eqFolder :: Eq Folder
96compileHandler ∷ Queue Folder -> HandlerM Unit
97compileHandler queue = do
99 json <- readAff body # liftAff
100 resultVar <- AVar.empty # liftEffect
103 cleanup = AVar.tryPut Nothing resultVar # liftEffect # void
107 bracket (pure unit) (const cleanup) \_ -> do
108 result <- compileCode folder (json ∷ Body.CompileRequest).code
109 AVar.tryPut (Just result) resultVar # liftEffect # void
110 queueRes <- Q.enqueue compileJob queue # liftEffect
114 maybeResult <- AVar.tryRead resultVar # liftEffect
116 Just (Just result) -> Response.send $ write ({ result } ∷ Body.CompileResult)
119 Response.send $ write { error: "Timeout" }
121 delay (100.0 # Milliseconds) # liftAff
124 Enqueued _ -> getResult
127 Response.send $ write { error: "Queue full" }
129unHandler ∷ ∀ t6. Request -> Response -> Effect Unit -> HandlerM t6 -> Aff t6
130unHandler req res next (HandlerM h) = h req res next
132runHandler ∷ Queue Folder -> HandlerM Unit
134 HandlerM \req res next -> do
136 handle = unHandler req res next
140 bracket (pure unit) (const (pure unit)) \_ -> do
141 result <- runCode folder
142 resultBody <- toBody result
143 handle $ Response.send $ write (resultBody ∷ Body.RunResult)
144 queueRes <- Q.enqueue runJob queue # liftEffect
146 Enqueued _ -> pure unit
150 Response.send $ write { error: "Queue full" }
155 cpus <- numCpus # liftEffect
157 foldersToUse = min 2 (max 15 (cpus - 1))
159 mkFolder = Folder <<< ("../playground" <> _) <<< show
161 folderPool = ResourcePool $ mkFolder <$> (0 .. foldersToUse)
165 , timeout: 60.0 # Seconds # fromDuration
168 serverSetup (makeApp q)
170makeApp ∷ Queue Folder -> App
172 use $ static "assets"
173 useExternal jsonBodyParser
174 E.post "/compile" (compileHandler q)
175 E.post "/run" (runHandler q)
177data CertificatesToUse
178 = UseLocalCertificates
179 | DoNotUseLocalCertificates
181parseCertificatesToUse ∷ String -> CertificatesToUse
182parseCertificatesToUse = case _ of
183 "LOCAL" -> UseLocalCertificates
184 _ -> DoNotUseLocalCertificates
186serverSetup ∷ App -> Aff Server
188 maybePortString <- lookupEnv "PORT" # liftEffect
190 port = maybePortString >>= fromString # fromMaybe 14188
191 useLocalCertificates <- fromMaybe DoNotUseLocalCertificates <<< map parseCertificatesToUse <$> lookupEnv "CERTS" # liftEffect
192 listen <- case useLocalCertificates of
193 UseLocalCertificates -> do
194 httpsOptions <- makeHttpsOptions
195 pure $ listenHttps app (port ∷ Int) httpsOptions
196 DoNotUseLocalCertificates -> pure $ listenHttp app port
197 liftEffect $ listen \_ -> info $ "psfp server started on port " <> show port
199 makeHttpsOptions = do
200 key <- readTextFile UTF8 "server.key"
201 cert <- readTextFile UTF8 "server.cert"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list