5import Data.Array (fromFoldable, head, (..))
6import Data.Either (Either(..))
7import Data.Foldable (for_)
8import Data.Int (fromString)
9import Data.Maybe (Maybe(..), fromMaybe)
10import Data.Newtype (un)
11import Data.Posix.Signal (Signal(..))
12import Data.String.Utils (lines)
13import Data.Time.Duration (Seconds(..), fromDuration)
15import Effect.AVar as AVar
16import Effect.Aff (Aff, Milliseconds(..), bracket, delay, effectCanceler, launchAff_, makeAff)
17import Effect.Aff.Class (liftAff)
18import Effect.Class (class MonadEffect, liftEffect)
19import Effect.Class.Console (info, log)
20import Effect.Exception (throw)
21import Foreign (unsafeToForeign)
22import JobQueue (EnqueueResult(..), NewJob(..), Queue, ResourcePool(..))
24import Middleware.JsonBodyParser (jsonBodyParser)
25import Node.Buffer (Buffer)
26import Node.Buffer as Buffer
27import Node.ChildProcess (ExecResult, defaultExecOptions, exec, kill, pid)
28import Node.Encoding (Encoding(..))
29import Node.Express.App (App, listenHttp, listenHttps, use, useExternal)
30import Node.Express.App as E
31import Node.Express.Handler (HandlerM(..), Handler)
32import Node.Express.Middleware.Static (static)
33import Node.Express.Request (getBody')
34import Node.Express.Response (setStatus)
35import Node.Express.Response as Response
36import Node.Express.Types (Request, Response)
37import Node.FS.Aff (readTextFile, writeTextFile)
38import Node.HTTP (Server)
39import Node.OS (numCpus)
40import Node.Process (lookupEnv)
41import Playground.Playground (Folder(..), copy)
42import Shared.Json (readAff)
43import Shared.Models.Body (RunResult, PursResult)
44import Shared.Models.Body as Body
45import Simple.JSON (read_, write)
46import Simple.JSON as JSON
48toBody ∷ ∀ r m. MonadEffect m => { stdout ∷ Buffer, stderr ∷ Buffer | r } -> m RunResult
52 stdout <- Buffer.toString UTF8 result.stdout
53 stderr <- Buffer.toString UTF8 result.stderr
55 (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
56 in { code, stdout, stderr } ∷ RunResult
59 = { code ∷ Maybe Int }
61asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
62asErrorWithCode = read_ <<< unsafeToForeign
64compileCode ∷ Folder -> String -> Aff PursResult
65compileCode folder code = do
66 saveMainFile folder code
67 { stderr } <- execCommand folder "spago build --purs-args \"--json-errors\""
68 strResult <- liftEffect $ Buffer.toString UTF8 stderr
69 case checkOutput strResult of
71 Nothing -> liftEffect $ throw $ "No result in " <> strResult
77 (parseable :: Maybe PursResult) = JSON.readJSON_ line
78 fromFoldable parseable
80runCode ∷ Folder -> Aff ExecResult
81runCode folder = execCommand folder "node run.js"
83saveMainFile ∷ Folder -> String -> Aff Unit
84saveMainFile folder code = writeTextFile UTF8 (un Folder folder <> "/src/Main.purs") code
86execCommand ∷ Folder -> String -> Aff ExecResult
87execCommand folder command =
88 makeAff \callback -> do
90 options = defaultExecOptions { cwd = Just (un Folder folder) }
91 childProcess <- exec command options (callback <<< Right)
92 pure $ effectCanceler ((log $ "Killing " <> show (pid childProcess)) *> kill SIGKILL childProcess)
94compileHandler ∷ Queue Folder -> HandlerM Unit
95compileHandler queue = do
97 json <- readAff body # liftAff
98 resultVar <- AVar.empty # liftEffect
101 cleanup = AVar.tryPut Nothing resultVar # liftEffect # void
105 bracket (pure unit) (const cleanup) \_ -> do
106 result <- compileCode folder (json ∷ Body.CompileRequest).code
107 AVar.tryPut (Just result) resultVar # liftEffect # void
108 queueRes <- Q.enqueue compileJob queue # liftEffect
112 maybeResult <- AVar.tryRead resultVar # liftEffect
114 Just (Just result) -> Response.send $ write ({ result } ∷ Body.CompileResult)
117 Response.send $ write { error: "Timeout" }
119 delay (100.0 # Milliseconds) # liftAff
122 Enqueued _ -> getResult
125 Response.send $ write { error: "Queue full" }
127unHandler ∷ ∀ t6. Request -> Response -> Effect Unit -> HandlerM t6 -> Aff t6
128unHandler req res next (HandlerM h) = h req res next
130runHandler ∷ Queue Folder -> HandlerM Unit
132 HandlerM \req res next -> do
134 handle = unHandler req res next
138 bracket (pure unit) (const (pure unit)) \_ -> do
139 result <- runCode folder
140 resultBody <- toBody result
141 handle $ Response.send $ write (resultBody ∷ Body.RunResult)
142 queueRes <- Q.enqueue runJob queue # liftEffect
144 Enqueued _ -> pure unit
148 Response.send $ write { error: "Queue full" }
151srcFolder = "../playground"
154destFolder = "../playgrounds/"
159 cpus <- numCpus # liftEffect
161 foldersToUse = max 2 (cpus - 1) -- use at least 2
163 mkFolder = Folder <<< (destFolder <> _) <<< show
165 folders = mkFolder <$> (0 .. foldersToUse)
166 folderPool = ResourcePool folders
167 for_ folders \(Folder f) -> copy srcFolder f
171 , timeout: 60.0 # Seconds # fromDuration
174 serverSetup (makeApp q)
176makeApp ∷ Queue Folder -> App
178 use $ static "assets"
179 useExternal jsonBodyParser
180 E.post "/compile" (compileHandler q)
181 E.post "/run" (runHandler q)
183data CertificatesToUse
184 = UseLocalCertificates
185 | DoNotUseLocalCertificates
187parseCertificatesToUse ∷ String -> CertificatesToUse
188parseCertificatesToUse = case _ of
189 "LOCAL" -> UseLocalCertificates
190 _ -> DoNotUseLocalCertificates
192serverSetup ∷ App -> Aff Server
194 maybePortString <- lookupEnv "PORT" # liftEffect
196 port = maybePortString >>= fromString # fromMaybe 14188
197 useLocalCertificates <- fromMaybe DoNotUseLocalCertificates <<< map parseCertificatesToUse <$> lookupEnv "CERTS" # liftEffect
198 listen <- case useLocalCertificates of
199 UseLocalCertificates -> do
200 httpsOptions <- makeHttpsOptions
201 pure $ listenHttps app (port ∷ Int) httpsOptions
202 DoNotUseLocalCertificates -> pure $ listenHttp app port
203 liftEffect $ listen \_ -> info $ "psfp server started on port " <> show port
205 makeHttpsOptions = do
206 key <- readTextFile UTF8 "server.key"
207 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