firefly-compiler 0.5.92 → 0.5.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/compiler/DevelopMode.ff
CHANGED
|
@@ -216,7 +216,7 @@ startProxy(
|
|
|
216
216
|
targetPort: Int
|
|
217
217
|
) {
|
|
218
218
|
let net = Js.import("node:net")
|
|
219
|
-
let targetServer = "
|
|
219
|
+
let targetServer = "127.0.0.1" // localhost
|
|
220
220
|
let proxyServer = net->createServer(Js->(pauseOnConnect = True), Js->{clientSocket =>
|
|
221
221
|
mutable targetSocket = Js.undefined()
|
|
222
222
|
mutable connected = False
|
|
@@ -334,7 +334,7 @@ runner_.changedSinceCompilationStarted_ = ff_core_Set.Set_add(runner_.changedSin
|
|
|
334
334
|
|
|
335
335
|
export function startProxy_(system_, runner_, proxyPort_, targetPort_) {
|
|
336
336
|
const net_ = import$1;
|
|
337
|
-
const targetServer_ = "
|
|
337
|
+
const targetServer_ = "127.0.0.1";
|
|
338
338
|
const proxyServer_ = net_.createServer({pauseOnConnect: true}, ((clientSocket_) => {
|
|
339
339
|
let targetSocket_ = (void 0);
|
|
340
340
|
let connected_ = false;
|
|
@@ -619,7 +619,7 @@ runner_.changedSinceCompilationStarted_ = ff_core_Set.Set_add(runner_.changedSin
|
|
|
619
619
|
|
|
620
620
|
export async function startProxy_$(system_, runner_, proxyPort_, targetPort_, $task) {
|
|
621
621
|
const net_ = import$1;
|
|
622
|
-
const targetServer_ = "
|
|
622
|
+
const targetServer_ = "127.0.0.1";
|
|
623
623
|
const proxyServer_ = net_.createServer({pauseOnConnect: true}, (async (a_1) => await (async (clientSocket_, $task) => {
|
|
624
624
|
let targetSocket_ = (void 0);
|
|
625
625
|
let connected_ = false;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"../../../../compiler/DevelopMode.ff"
|
|
5
5
|
],
|
|
6
6
|
"sourcesContent": [
|
|
7
|
-
"import Main\r\nimport ModuleCache\r\nimport Dependencies\r\nimport DependencyLock\r\nimport Syntax\r\nimport JsEmitter\r\nimport Builder from ff:compiler\r\n\r\ncapability Runner(\r\n lock: Lock\r\n lockCondition: LockCondition\r\n mutable iteration: Int\r\n mutable state: RunnerState\r\n mutable changedSinceCompilationStarted: Set[String]\r\n mutable recompile: Bool\r\n mutable appRunning: Bool\r\n)\r\n\r\ndata RunnerState {\r\n CompilingState\r\n CompileErrorState(at: Option[Location], output: String)\r\n ApplicationRunningState\r\n ApplicationCrashedState(output: String)\r\n}\r\n\r\nclass EsbuildContext(mutable jsValue: Option[JsValue])\r\n\r\nrun(\r\n system: NodeSystem\r\n fireflyPath: Path\r\n proxyPort: Int\r\n targetPort: Int\r\n mainFile: String\r\n arguments: List[String]\r\n) {\r\n let esbuildContext = EsbuildContext(None)\r\n let lock = system.mainTask().lock()\r\n let lockCondition = lock.condition()\r\n let runner = Runner(lock, lockCondition, 1, CompilingState, Set.new(), False, False)\r\n startProxy(system, runner, proxyPort, targetPort)\r\n startChangeListener(system, runner, system.path(\".\"))\r\n let moduleCache = ModuleCache.new(0)\r\n while {True} {\r\n let moduleKey = build(system, runner, mainFile, moduleCache)\r\n let task = moduleKey.{\r\n | None => \r\n system.mainTask().spawn {_ => }\r\n | Some(key) => \r\n runner.lock.do {\r\n runner.state = ApplicationRunningState\r\n }\r\n startApp(system, runner, fireflyPath, esbuildContext, moduleCache, key, mainFile, arguments)\r\n }\r\n runner.lock.do {\r\n if(runner.appRunning) {\r\n while {runner.appRunning} {\r\n runner.lockCondition.sleep()\r\n }\r\n }\r\n while {!runner.recompile} {\r\n runner.lockCondition.sleep()\r\n }\r\n task.abort()\r\n runner.changedSinceCompilationStarted.each {key =>\r\n moduleCache.invalidate(key)\r\n }\r\n runner.state = CompilingState\r\n runner.recompile = False\r\n runner.changedSinceCompilationStarted = Set.new()\r\n runner.iteration += 1\r\n }\r\n }\r\n}\r\n\r\nbuild(system: NodeSystem, runner: Runner, mainFile: String, moduleCache: ModuleCache): Option[ModuleKey] {\r\n try {\r\n Main.prepareFireflyDirectory(system.path(\".\"))\r\n let resolvedDependencies = Dependencies.process(\r\n system.httpClient()\r\n DependencyLock.new(system.mainTask())\r\n system.path(mainFile)\r\n )\r\n let mainPath = system.path(mainFile)\r\n Some(Main.buildScript(\r\n system\r\n mainPath\r\n resolvedDependencies.mainPackagePair\r\n EmitNode\r\n resolvedDependencies\r\n moduleCache\r\n printMeasurements = False\r\n ))\r\n } tryCatch {| CompileError(at, message), error =>\r\n Log.debug(message)\r\n Log.debug(\" at \" + at.file.replace(\"./\", \"\") + \":\" + at.line + \":\" + at.column)\r\n runner.lock.do {\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n None\r\n } tryCatch {| CompileErrors(compileErrors), error =>\r\n compileErrors.each {| CompileError(at, message) =>\r\n Log.debug(message)\r\n Log.debug(\" at \" + at.file.replace(\"./\", \"\") + \":\" + at.line + \":\" + at.column)\r\n runner.lock.do {\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n }\r\n None\r\n } catchAny {error =>\r\n Log.debug(error.message())\r\n runner.lock.do {\r\n runner.state = CompileErrorState(None, error.message())\r\n }\r\n None\r\n }\r\n}\r\n\r\nstartApp(\r\n system: NodeSystem\r\n runner: Runner\r\n fireflyPath: Path\r\n esbuildContext: EsbuildContext\r\n moduleCache: ModuleCache\r\n moduleKey: ModuleKey\r\n mainFile: String\r\n arguments: List[String]\r\n): Task {\r\n let taskIteration = runner.iteration\r\n system.mainTask().spawn {task =>\r\n try {\r\n let runFile = Main.locateRunFile(system, \"node\", moduleKey)\r\n let runFilePath = if(runFile.contains(\"://\")) {system.pathFromUrl(runFile)} else {system.path(runFile)}\r\n let startPath = runFilePath.parent().grab().slash(runFilePath.base() + \".start.mjs\")\r\n startPath.writeText(\r\n \"import * as run from \" + Json.string(\"./\" + runFilePath.base()).write() + \"\\n\" + \r\n \"globalThis.ffDevelopMode = true\\n\" + \r\n // The following should be awaited, but esbuild doesn't support top level await - still seems to work though\r\n \"run.$run$(\" + Json.string(fireflyPath.absolute()).write() + \", \" + Json.toJson(arguments).write() + \")\"\r\n )\r\n let esBuildPath = runFilePath.parent().grab().slash(runFilePath.base() + \".minified.js\")\r\n let context = esbuildContext.jsValue.else {\r\n let jsValue = BuildSystem.internalNodeCallEsBuildContext(system, startPath.absolute(), esBuildPath.absolute(), minify = True)\r\n esbuildContext.jsValue = Some(jsValue)\r\n jsValue\r\n }\r\n Js.await(context->rebuild())\r\n let relativeStartFile = esBuildPath.relativeTo(system.path(\".\"))\r\n let result = system.execute(relativeStartFile, arguments, node = Some({message, forkedProcess =>\r\n if(message->ffDevelopMode === \"internalCompile\") {\r\n let mainFiles: List[String] = message->mainFiles?\r\n let mainPaths = mainFiles.map {system.path(_)}\r\n let target: String = message->target?\r\n runner.lock.do {\r\n if(taskIteration == runner.iteration):\r\n try {\r\n Builder.buildViaBuildSystem(system, fireflyPath, mainPaths, target, moduleCache, printMeasurements = False)\r\n forkedProcess->send(Js->(ffDevelopMode = \"internalCompile\"))\r\n Unit\r\n } tryCatch {| CompileError(at, message), error =>\r\n runner.state = CompileErrorState(Some(at), message)\r\n } tryCatch {| CompileErrors(compileErrors), error =>\r\n compileErrors.each {| CompileError(at, message) =>\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n } catchAny {error =>\r\n runner.state = CompileErrorState(None, error.message())\r\n }\r\n }\r\n }\r\n }))\r\n let standardOut = result.standardOut.toString()\r\n let standardError = result.standardError.toString()\r\n runner.lock.do {\r\n runner.appRunning = False\r\n runner.state.{\r\n | ApplicationRunningState {taskIteration == runner.iteration} => \r\n runner.state = ApplicationCrashedState(\r\n \"Exited with code: \" + result.exitCode + \"\\n\\n\" + standardOut + \"\\n\\n\" + standardError\r\n )\r\n | _ => \r\n }\r\n runner.lockCondition.wakeAll()\r\n }\r\n } catchAny {error =>\r\n runner.lock.do {\r\n runner.appRunning = False\r\n runner.lockCondition.wakeAll()\r\n }\r\n }\r\n }\r\n}\r\n\r\nstartChangeListener(\r\n system: NodeSystem\r\n runner: Runner\r\n path: Path\r\n) {\r\n let fs = Js.import(\"node:fs\")\r\n fs->watch(path.absolute(), Js->(recursive = True), Js->{eventType, fileName =>\r\n if(!fileName.isNullOrUndefined()):\r\n let file: String = fileName?\r\n if(file.endsWith(\".ff\") || file.endsWith(\".firefly-workspace\")) {\r\n let key = system.path(file).absolute()\r\n runner.lock.do {\r\n // Probably we should also listen for other files, e.g. resources\r\n runner.changedSinceCompilationStarted = runner.changedSinceCompilationStarted.add(key)\r\n }\r\n }\r\n })\r\n}\r\n\r\nstartProxy(\r\n system: NodeSystem\r\n runner: Runner\r\n proxyPort: Int\r\n targetPort: Int\r\n) {\r\n let net = Js.import(\"node:net\")\r\n let targetServer = \"localhost\" // 127.0.0.1\r\n let proxyServer = net->createServer(Js->(pauseOnConnect = True), Js->{clientSocket =>\r\n mutable targetSocket = Js.undefined()\r\n mutable connected = False\r\n clientSocket->on(\"error\", Js->{err =>\r\n if(!targetSocket.isUndefined()) {targetSocket->end()}\r\n // Mute \"Error: write EPIPE\" and other errors that happen while writing a custom response to the client\r\n //Log.debugDynamic(err)\r\n })\r\n function serveWaiterHtml() {\r\n if(runner.changedSinceCompilationStarted.size() != 0) {\r\n runner.recompile = True\r\n system.mainTask().spawn {task =>\r\n runner.lock.do {\r\n runner.lockCondition.wakeAll()\r\n }\r\n }\r\n }\r\n function escapeHtml(html: String): String {\r\n html.replace(\"&\", \"&\")\r\n .replace(\"'\", \"'\")\r\n .replace(\"\\\"\", \""\")\r\n .replace(\"<\", \"<\")\r\n .replace(\">\", \">\")\r\n }\r\n let status = runner.state.{\r\n | _ {runner.recompile} => \"Restarting...\"\r\n | ApplicationCrashedState(output) => \"Application crashed!<br>\" + escapeHtml(output)\r\n | ApplicationRunningState => \"Starting application...\"\r\n | CompileErrorState(Some(at), output) => \r\n let location = escapeHtml(at.file + \":\" + at.line + \":\" + at.column)\r\n let relativeFile = system.path(at.file).relativeTo(system.path(\".\"))\r\n let relativeLocation = escapeHtml(relativeFile + \":\" + at.line + \":\" + at.column)\r\n let link = \"<a href='vscode://file/\" + escapeHtml(location) + \"'>\" + relativeLocation + \"</a>\"\r\n escapeHtml(output) + \"<br><br>at \" + link\r\n | CompileErrorState(None, output) => \"Compiler crashed!<br>\" + escapeHtml(output)\r\n | CompilingState => \"Compiling...\"\r\n }\r\n \r\n let waiterBuffer = waiterHtml.replace(\"[STATUS]\", status).toBuffer()\r\n clientSocket->write(\"HTTP/1.1 200 OK\\r\\n\")\r\n clientSocket->write(\"Content-Type: text/html\\r\\n\")\r\n clientSocket->write(\"Content-Length: \" + waiterBuffer.size() + \"\\r\\n\")\r\n clientSocket->write(\"x-firefly-develop-mode: true\\r\\n\")\r\n clientSocket->write(\"Connection: close\\r\\n\")\r\n clientSocket->write(\"\\r\\n\")\r\n clientSocket->write(\r\n Js->Buffer->from(waiterBuffer!->buffer, waiterBuffer!->byteOffset, waiterBuffer!->byteLength)\r\n )\r\n clientSocket->end()\r\n }\r\n targetSocket = net->createConnection(targetPort, targetServer, Js->{\r\n connected = True\r\n let direct = runner.state.{\r\n | ApplicationRunningState => !runner.recompile && runner.changedSinceCompilationStarted.size() == 0\r\n | _ => False\r\n }\r\n if(direct) {\r\n clientSocket->pipe(targetSocket)->pipe(clientSocket)\r\n clientSocket->resume()?\r\n } else {\r\n serveWaiterHtml()\r\n }\r\n })\r\n targetSocket->on(\"error\", Js->{err =>\r\n if(connected) {\r\n clientSocket->end()?\r\n } else {\r\n serveWaiterHtml()\r\n }\r\n })?\r\n })\r\n \r\n proxyServer->listen(proxyPort, Js->{\r\n //print(system, \"Proxy server running on port \" + proxyPort)\r\n })\r\n}\r\n\r\nwaiterHtml = \"\"\"<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Firefly develop mode</title>\r\n <style>\r\n body {\r\n background-color: #121212;\r\n color: #e0e0e0;\r\n font-family: 'Courier New', monospace;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n height: 100vh;\r\n margin: 0;\r\n background-image: radial-gradient(circle at center, #1a1a2e, #121212);\r\n }\r\n\r\n h1 {\r\n color: #00f2ff;\r\n text-shadow: 0 0 10px #00f2ff;\r\n font-size: 3rem;\r\n margin-bottom: 20px;\r\n animation: glow 2s ease-in-out infinite alternate;\r\n }\r\n\r\n p {\r\n background-color: #1a1a2e;\r\n color: #00f2ff;\r\n padding: 15px 30px;\r\n border-radius: 25px;\r\n font-size: 1.2rem;\r\n border: 2px solid #00f2ff;\r\n box-shadow: 0 0 15px #00f2ff;\r\n animation: pulse 1.5s infinite;\r\n }\r\n \r\n a {\r\n color: #00f2ff;\r\n }\r\n\r\n @media only screen and (max-width: 600px) {\r\n h1 {\r\n font-size: 1.7rem;\r\n }\r\n p {\r\n padding: 10px 20px;\r\n border-radius: 50px;\r\n font-size: 1.1rem;\r\n max-width: 100%;\r\n box-sizing: border-box;\r\n }\r\n }\r\n \r\n @keyframes glow {\r\n from {\r\n text-shadow: 0 0 10px #00f2ff;\r\n }\r\n to {\r\n text-shadow: 0 0 20px #00f2ff, 0 0 30px #00f2ff;\r\n }\r\n }\r\n\r\n @keyframes pulse {\r\n 0% {\r\n box-shadow: 0 0 0 0 rgba(0, 242, 255, 0.7);\r\n }\r\n 70% {\r\n box-shadow: 0 0 0 10px rgba(0, 242, 255, 0);\r\n }\r\n 100% {\r\n box-shadow: 0 0 0 0 rgba(0, 242, 255, 0);\r\n }\r\n }\r\n </style>\r\n <script>\r\n let start = Date.now()\r\n let appStarted = null\r\n let delay = 10\r\n let poll = async () => {\r\n //delay *= 1.1\r\n try {\r\n let response = await fetch(\".\", {cache: 'no-store'})\r\n if(!response.headers.has('x-firefly-develop-mode')) {\r\n let now = Date.now()\r\n let compiling = appStarted - start\r\n let appStarting = now - appStarted\r\n //window.alert(\"Reloading after: \" + (now - start) + \" ms. Compiling: \" + compiling + \" ms. Starting application: \" + appStarting + \" ms.\")\r\n window.location.reload(true)\r\n return\r\n } else {\r\n let html = await response.text()\r\n if(appStarted == null && html.includes(\"Starting application...\")) appStarted = Date.now()\r\n let parser = new DOMParser()\r\n let d = parser.parseFromString(html, 'text/html')\r\n let bodyHtml = d.body.innerHTML\r\n if(document.body.innerHTML !== bodyHtml) {\r\n document.body.innerHTML = bodyHtml\r\n }\r\n setTimeout(poll, delay)\r\n }\r\n } catch (error) {\r\n console.error(\"Polling error:\", error)\r\n setTimeout(poll, delay)\r\n }\r\n }\r\n setTimeout(poll, delay)\r\n </script>\r\n</head>\r\n<body>\r\n <h1>Firefly develop mode</h1>\r\n <p>[STATUS]</p>\r\n</body>\r\n</html>\r\n\"\"\"\r\n"
|
|
7
|
+
"import Main\r\nimport ModuleCache\r\nimport Dependencies\r\nimport DependencyLock\r\nimport Syntax\r\nimport JsEmitter\r\nimport Builder from ff:compiler\r\n\r\ncapability Runner(\r\n lock: Lock\r\n lockCondition: LockCondition\r\n mutable iteration: Int\r\n mutable state: RunnerState\r\n mutable changedSinceCompilationStarted: Set[String]\r\n mutable recompile: Bool\r\n mutable appRunning: Bool\r\n)\r\n\r\ndata RunnerState {\r\n CompilingState\r\n CompileErrorState(at: Option[Location], output: String)\r\n ApplicationRunningState\r\n ApplicationCrashedState(output: String)\r\n}\r\n\r\nclass EsbuildContext(mutable jsValue: Option[JsValue])\r\n\r\nrun(\r\n system: NodeSystem\r\n fireflyPath: Path\r\n proxyPort: Int\r\n targetPort: Int\r\n mainFile: String\r\n arguments: List[String]\r\n) {\r\n let esbuildContext = EsbuildContext(None)\r\n let lock = system.mainTask().lock()\r\n let lockCondition = lock.condition()\r\n let runner = Runner(lock, lockCondition, 1, CompilingState, Set.new(), False, False)\r\n startProxy(system, runner, proxyPort, targetPort)\r\n startChangeListener(system, runner, system.path(\".\"))\r\n let moduleCache = ModuleCache.new(0)\r\n while {True} {\r\n let moduleKey = build(system, runner, mainFile, moduleCache)\r\n let task = moduleKey.{\r\n | None => \r\n system.mainTask().spawn {_ => }\r\n | Some(key) => \r\n runner.lock.do {\r\n runner.state = ApplicationRunningState\r\n }\r\n startApp(system, runner, fireflyPath, esbuildContext, moduleCache, key, mainFile, arguments)\r\n }\r\n runner.lock.do {\r\n if(runner.appRunning) {\r\n while {runner.appRunning} {\r\n runner.lockCondition.sleep()\r\n }\r\n }\r\n while {!runner.recompile} {\r\n runner.lockCondition.sleep()\r\n }\r\n task.abort()\r\n runner.changedSinceCompilationStarted.each {key =>\r\n moduleCache.invalidate(key)\r\n }\r\n runner.state = CompilingState\r\n runner.recompile = False\r\n runner.changedSinceCompilationStarted = Set.new()\r\n runner.iteration += 1\r\n }\r\n }\r\n}\r\n\r\nbuild(system: NodeSystem, runner: Runner, mainFile: String, moduleCache: ModuleCache): Option[ModuleKey] {\r\n try {\r\n Main.prepareFireflyDirectory(system.path(\".\"))\r\n let resolvedDependencies = Dependencies.process(\r\n system.httpClient()\r\n DependencyLock.new(system.mainTask())\r\n system.path(mainFile)\r\n )\r\n let mainPath = system.path(mainFile)\r\n Some(Main.buildScript(\r\n system\r\n mainPath\r\n resolvedDependencies.mainPackagePair\r\n EmitNode\r\n resolvedDependencies\r\n moduleCache\r\n printMeasurements = False\r\n ))\r\n } tryCatch {| CompileError(at, message), error =>\r\n Log.debug(message)\r\n Log.debug(\" at \" + at.file.replace(\"./\", \"\") + \":\" + at.line + \":\" + at.column)\r\n runner.lock.do {\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n None\r\n } tryCatch {| CompileErrors(compileErrors), error =>\r\n compileErrors.each {| CompileError(at, message) =>\r\n Log.debug(message)\r\n Log.debug(\" at \" + at.file.replace(\"./\", \"\") + \":\" + at.line + \":\" + at.column)\r\n runner.lock.do {\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n }\r\n None\r\n } catchAny {error =>\r\n Log.debug(error.message())\r\n runner.lock.do {\r\n runner.state = CompileErrorState(None, error.message())\r\n }\r\n None\r\n }\r\n}\r\n\r\nstartApp(\r\n system: NodeSystem\r\n runner: Runner\r\n fireflyPath: Path\r\n esbuildContext: EsbuildContext\r\n moduleCache: ModuleCache\r\n moduleKey: ModuleKey\r\n mainFile: String\r\n arguments: List[String]\r\n): Task {\r\n let taskIteration = runner.iteration\r\n system.mainTask().spawn {task =>\r\n try {\r\n let runFile = Main.locateRunFile(system, \"node\", moduleKey)\r\n let runFilePath = if(runFile.contains(\"://\")) {system.pathFromUrl(runFile)} else {system.path(runFile)}\r\n let startPath = runFilePath.parent().grab().slash(runFilePath.base() + \".start.mjs\")\r\n startPath.writeText(\r\n \"import * as run from \" + Json.string(\"./\" + runFilePath.base()).write() + \"\\n\" + \r\n \"globalThis.ffDevelopMode = true\\n\" + \r\n // The following should be awaited, but esbuild doesn't support top level await - still seems to work though\r\n \"run.$run$(\" + Json.string(fireflyPath.absolute()).write() + \", \" + Json.toJson(arguments).write() + \")\"\r\n )\r\n let esBuildPath = runFilePath.parent().grab().slash(runFilePath.base() + \".minified.js\")\r\n let context = esbuildContext.jsValue.else {\r\n let jsValue = BuildSystem.internalNodeCallEsBuildContext(system, startPath.absolute(), esBuildPath.absolute(), minify = True)\r\n esbuildContext.jsValue = Some(jsValue)\r\n jsValue\r\n }\r\n Js.await(context->rebuild())\r\n let relativeStartFile = esBuildPath.relativeTo(system.path(\".\"))\r\n let result = system.execute(relativeStartFile, arguments, node = Some({message, forkedProcess =>\r\n if(message->ffDevelopMode === \"internalCompile\") {\r\n let mainFiles: List[String] = message->mainFiles?\r\n let mainPaths = mainFiles.map {system.path(_)}\r\n let target: String = message->target?\r\n runner.lock.do {\r\n if(taskIteration == runner.iteration):\r\n try {\r\n Builder.buildViaBuildSystem(system, fireflyPath, mainPaths, target, moduleCache, printMeasurements = False)\r\n forkedProcess->send(Js->(ffDevelopMode = \"internalCompile\"))\r\n Unit\r\n } tryCatch {| CompileError(at, message), error =>\r\n runner.state = CompileErrorState(Some(at), message)\r\n } tryCatch {| CompileErrors(compileErrors), error =>\r\n compileErrors.each {| CompileError(at, message) =>\r\n runner.state = CompileErrorState(Some(at), message)\r\n }\r\n } catchAny {error =>\r\n runner.state = CompileErrorState(None, error.message())\r\n }\r\n }\r\n }\r\n }))\r\n let standardOut = result.standardOut.toString()\r\n let standardError = result.standardError.toString()\r\n runner.lock.do {\r\n runner.appRunning = False\r\n runner.state.{\r\n | ApplicationRunningState {taskIteration == runner.iteration} => \r\n runner.state = ApplicationCrashedState(\r\n \"Exited with code: \" + result.exitCode + \"\\n\\n\" + standardOut + \"\\n\\n\" + standardError\r\n )\r\n | _ => \r\n }\r\n runner.lockCondition.wakeAll()\r\n }\r\n } catchAny {error =>\r\n runner.lock.do {\r\n runner.appRunning = False\r\n runner.lockCondition.wakeAll()\r\n }\r\n }\r\n }\r\n}\r\n\r\nstartChangeListener(\r\n system: NodeSystem\r\n runner: Runner\r\n path: Path\r\n) {\r\n let fs = Js.import(\"node:fs\")\r\n fs->watch(path.absolute(), Js->(recursive = True), Js->{eventType, fileName =>\r\n if(!fileName.isNullOrUndefined()):\r\n let file: String = fileName?\r\n if(file.endsWith(\".ff\") || file.endsWith(\".firefly-workspace\")) {\r\n let key = system.path(file).absolute()\r\n runner.lock.do {\r\n // Probably we should also listen for other files, e.g. resources\r\n runner.changedSinceCompilationStarted = runner.changedSinceCompilationStarted.add(key)\r\n }\r\n }\r\n })\r\n}\r\n\r\nstartProxy(\r\n system: NodeSystem\r\n runner: Runner\r\n proxyPort: Int\r\n targetPort: Int\r\n) {\r\n let net = Js.import(\"node:net\")\r\n let targetServer = \"127.0.0.1\" // localhost\r\n let proxyServer = net->createServer(Js->(pauseOnConnect = True), Js->{clientSocket =>\r\n mutable targetSocket = Js.undefined()\r\n mutable connected = False\r\n clientSocket->on(\"error\", Js->{err =>\r\n if(!targetSocket.isUndefined()) {targetSocket->end()}\r\n // Mute \"Error: write EPIPE\" and other errors that happen while writing a custom response to the client\r\n //Log.debugDynamic(err)\r\n })\r\n function serveWaiterHtml() {\r\n if(runner.changedSinceCompilationStarted.size() != 0) {\r\n runner.recompile = True\r\n system.mainTask().spawn {task =>\r\n runner.lock.do {\r\n runner.lockCondition.wakeAll()\r\n }\r\n }\r\n }\r\n function escapeHtml(html: String): String {\r\n html.replace(\"&\", \"&\")\r\n .replace(\"'\", \"'\")\r\n .replace(\"\\\"\", \""\")\r\n .replace(\"<\", \"<\")\r\n .replace(\">\", \">\")\r\n }\r\n let status = runner.state.{\r\n | _ {runner.recompile} => \"Restarting...\"\r\n | ApplicationCrashedState(output) => \"Application crashed!<br>\" + escapeHtml(output)\r\n | ApplicationRunningState => \"Starting application...\"\r\n | CompileErrorState(Some(at), output) => \r\n let location = escapeHtml(at.file + \":\" + at.line + \":\" + at.column)\r\n let relativeFile = system.path(at.file).relativeTo(system.path(\".\"))\r\n let relativeLocation = escapeHtml(relativeFile + \":\" + at.line + \":\" + at.column)\r\n let link = \"<a href='vscode://file/\" + escapeHtml(location) + \"'>\" + relativeLocation + \"</a>\"\r\n escapeHtml(output) + \"<br><br>at \" + link\r\n | CompileErrorState(None, output) => \"Compiler crashed!<br>\" + escapeHtml(output)\r\n | CompilingState => \"Compiling...\"\r\n }\r\n \r\n let waiterBuffer = waiterHtml.replace(\"[STATUS]\", status).toBuffer()\r\n clientSocket->write(\"HTTP/1.1 200 OK\\r\\n\")\r\n clientSocket->write(\"Content-Type: text/html\\r\\n\")\r\n clientSocket->write(\"Content-Length: \" + waiterBuffer.size() + \"\\r\\n\")\r\n clientSocket->write(\"x-firefly-develop-mode: true\\r\\n\")\r\n clientSocket->write(\"Connection: close\\r\\n\")\r\n clientSocket->write(\"\\r\\n\")\r\n clientSocket->write(\r\n Js->Buffer->from(waiterBuffer!->buffer, waiterBuffer!->byteOffset, waiterBuffer!->byteLength)\r\n )\r\n clientSocket->end()\r\n }\r\n targetSocket = net->createConnection(targetPort, targetServer, Js->{\r\n connected = True\r\n let direct = runner.state.{\r\n | ApplicationRunningState => !runner.recompile && runner.changedSinceCompilationStarted.size() == 0\r\n | _ => False\r\n }\r\n if(direct) {\r\n clientSocket->pipe(targetSocket)->pipe(clientSocket)\r\n clientSocket->resume()?\r\n } else {\r\n serveWaiterHtml()\r\n }\r\n })\r\n targetSocket->on(\"error\", Js->{err =>\r\n if(connected) {\r\n clientSocket->end()?\r\n } else {\r\n serveWaiterHtml()\r\n }\r\n })?\r\n })\r\n \r\n proxyServer->listen(proxyPort, Js->{\r\n //print(system, \"Proxy server running on port \" + proxyPort)\r\n })\r\n}\r\n\r\nwaiterHtml = \"\"\"<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\">\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n <title>Firefly develop mode</title>\r\n <style>\r\n body {\r\n background-color: #121212;\r\n color: #e0e0e0;\r\n font-family: 'Courier New', monospace;\r\n display: flex;\r\n flex-direction: column;\r\n justify-content: center;\r\n align-items: center;\r\n height: 100vh;\r\n margin: 0;\r\n background-image: radial-gradient(circle at center, #1a1a2e, #121212);\r\n }\r\n\r\n h1 {\r\n color: #00f2ff;\r\n text-shadow: 0 0 10px #00f2ff;\r\n font-size: 3rem;\r\n margin-bottom: 20px;\r\n animation: glow 2s ease-in-out infinite alternate;\r\n }\r\n\r\n p {\r\n background-color: #1a1a2e;\r\n color: #00f2ff;\r\n padding: 15px 30px;\r\n border-radius: 25px;\r\n font-size: 1.2rem;\r\n border: 2px solid #00f2ff;\r\n box-shadow: 0 0 15px #00f2ff;\r\n animation: pulse 1.5s infinite;\r\n }\r\n \r\n a {\r\n color: #00f2ff;\r\n }\r\n\r\n @media only screen and (max-width: 600px) {\r\n h1 {\r\n font-size: 1.7rem;\r\n }\r\n p {\r\n padding: 10px 20px;\r\n border-radius: 50px;\r\n font-size: 1.1rem;\r\n max-width: 100%;\r\n box-sizing: border-box;\r\n }\r\n }\r\n \r\n @keyframes glow {\r\n from {\r\n text-shadow: 0 0 10px #00f2ff;\r\n }\r\n to {\r\n text-shadow: 0 0 20px #00f2ff, 0 0 30px #00f2ff;\r\n }\r\n }\r\n\r\n @keyframes pulse {\r\n 0% {\r\n box-shadow: 0 0 0 0 rgba(0, 242, 255, 0.7);\r\n }\r\n 70% {\r\n box-shadow: 0 0 0 10px rgba(0, 242, 255, 0);\r\n }\r\n 100% {\r\n box-shadow: 0 0 0 0 rgba(0, 242, 255, 0);\r\n }\r\n }\r\n </style>\r\n <script>\r\n let start = Date.now()\r\n let appStarted = null\r\n let delay = 10\r\n let poll = async () => {\r\n //delay *= 1.1\r\n try {\r\n let response = await fetch(\".\", {cache: 'no-store'})\r\n if(!response.headers.has('x-firefly-develop-mode')) {\r\n let now = Date.now()\r\n let compiling = appStarted - start\r\n let appStarting = now - appStarted\r\n //window.alert(\"Reloading after: \" + (now - start) + \" ms. Compiling: \" + compiling + \" ms. Starting application: \" + appStarting + \" ms.\")\r\n window.location.reload(true)\r\n return\r\n } else {\r\n let html = await response.text()\r\n if(appStarted == null && html.includes(\"Starting application...\")) appStarted = Date.now()\r\n let parser = new DOMParser()\r\n let d = parser.parseFromString(html, 'text/html')\r\n let bodyHtml = d.body.innerHTML\r\n if(document.body.innerHTML !== bodyHtml) {\r\n document.body.innerHTML = bodyHtml\r\n }\r\n setTimeout(poll, delay)\r\n }\r\n } catch (error) {\r\n console.error(\"Polling error:\", error)\r\n setTimeout(poll, delay)\r\n }\r\n }\r\n setTimeout(poll, delay)\r\n </script>\r\n</head>\r\n<body>\r\n <h1>Firefly develop mode</h1>\r\n <p>[STATUS]</p>\r\n</body>\r\n</html>\r\n\"\"\"\r\n"
|
|
8
8
|
],
|
|
9
9
|
"names": [
|
|
10
10
|
"Runner",
|
package/package.json
CHANGED