4import Data.Array (fromFoldable, head, (..))
5import Data.Either (Either(..))
6import Data.Foldable (for_)
7import Data.Int (fromString)
8import Data.Maybe (Maybe(..), fromMaybe)
9import Data.Newtype (un)
10import Data.Posix.Signal (Signal(..))
11import Data.String.Utils (lines)
12import Data.Time.Duration (Seconds(..), fromDuration)
14import Effect.Aff (Aff, 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 Playground.Playground (Folder(..), copy)
40import Shared.Json (readAff)
41import Shared.Models.Body (PursResult, RunResult, CompileRequest)
42import Shared.Models.Body as Body
43import Simple.JSON (read_, write)
44import Simple.JSON as JSON
46toBody ∷ ∀ r m. MonadEffect m => { stdout ∷ Buffer, stderr ∷ Buffer | r } -> m RunResult
50 stdout <- Buffer.toString UTF8 result.stdout
51 stderr <- Buffer.toString UTF8 result.stderr
53 (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
54 in { code, stdout, stderr } ∷ RunResult
57 = { code ∷ Maybe Int }
59asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
60asErrorWithCode = read_ <<< unsafeToForeign
62compileCode ∷ Folder -> String -> Aff PursResult
63compileCode folder code = do
64 saveMainFile folder code
65 { stderr } <- execCommand folder "spago build --purs-args \"--json-errors\""
66 strResult <- liftEffect $ Buffer.toString UTF8 stderr
67 case checkOutput strResult of
69 Nothing -> liftEffect $ throw $ "No result in " <> strResult
75 (parseable :: Maybe PursResult) = JSON.readJSON_ line
76 fromFoldable parseable
78runCode ∷ Folder -> Aff ExecResult
79runCode folder = execCommand folder "node run.js"
81saveMainFile ∷ Folder -> String -> Aff Unit
82saveMainFile folder code = writeTextFile UTF8 (un Folder folder <> "/src/Main.purs") code
84execCommand ∷ Folder -> String -> Aff ExecResult
85execCommand folder command =
86 makeAff \callback -> do
88 options = defaultExecOptions { cwd = Just (un Folder folder) }
89 childProcess <- exec command options (callback <<< Right)
90 pure $ effectCanceler ((log $ "Killing " <> show (pid childProcess)) *> kill SIGKILL childProcess)
92compileAndRunJob ∷ CompileRequest -> (Handler -> Aff Unit) -> NewJob Folder
93compileAndRunJob json handle =
95 compileResult <- compileCode folder (json ∷ Body.CompileRequest).code
96 if compileResult.errors /= [] then do
97 handle $ setStatus 422
98 handle $ Response.send $ write ({ result: compileResult } ∷ Body.CompileResult)
100 runResult <- runCode folder
101 resultBody <- toBody runResult
102 handle $ Response.send $ write (resultBody ∷ Body.RunResult)
104compileAndRunHandler ∷ Queue Folder -> Handler
105compileAndRunHandler queue = do
107 json <- readAff body # liftAff
108 HandlerM \req res next -> do
110 handle = unHandler req res next
112 runJob = compileAndRunJob json handle
113 queueRes <- Q.enqueue runJob queue # liftEffect
115 Enqueued _ -> pure unit
119 Response.send $ write { error: "Queue full" }
121unHandler ∷ ∀ a. Request -> Response -> Effect Unit -> HandlerM a -> Aff a
122unHandler req res next (HandlerM h) = h req res next
125srcFolder = "../playground"
128destFolder = "../playgrounds/"
133 cpus <- numCpus # liftEffect
135 foldersToUse = max 2 (cpus - 1) -- use at least 2
137 mkFolder = Folder <<< (destFolder <> _) <<< show
139 folders = mkFolder <$> (0 .. foldersToUse)
141 folderPool = ResourcePool folders
142 for_ folders \(Folder f) -> copy srcFolder f
146 , timeout: 60.0 # Seconds # fromDuration
149 serverSetup (makeApp q)
151makeApp ∷ Queue Folder -> App
153 use $ static "assets"
154 useExternal jsonBodyParser
155 E.post "/compileAndRun" (compileAndRunHandler q)
157data CertificatesToUse
158 = UseLocalCertificates
159 | DoNotUseLocalCertificates
161parseCertificatesToUse ∷ String -> CertificatesToUse
162parseCertificatesToUse = case _ of
163 "LOCAL" -> UseLocalCertificates
164 _ -> DoNotUseLocalCertificates
166serverSetup ∷ App -> Aff Server
168 maybePortString <- lookupEnv "PORT" # liftEffect
170 port = maybePortString >>= fromString # fromMaybe 14188
171 useLocalCertificates <- fromMaybe DoNotUseLocalCertificates <<< map parseCertificatesToUse <$> lookupEnv "CERTS" # liftEffect
172 listen <- case useLocalCertificates of
173 UseLocalCertificates -> do
174 httpsOptions <- makeHttpsOptions
175 pure $ listenHttps app (port ∷ Int) httpsOptions
176 DoNotUseLocalCertificates -> pure $ listenHttp app port
177 liftEffect $ listen \_ -> info $ "psfp server started on port " <> show port
179 makeHttpsOptions = do
180 key <- readTextFile UTF8 "server.key"
181 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