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.
@@ -216,7 +216,7 @@ startProxy(
216
216
  targetPort: Int
217
217
  ) {
218
218
  let net = Js.import("node:net")
219
- let targetServer = "localhost" // 127.0.0.1
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_ = "localhost";
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_ = "localhost";
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(\"&\", \"&amp;\")\r\n .replace(\"'\", \"&#039;\")\r\n .replace(\"\\\"\", \"&quot;\")\r\n .replace(\"<\", \"&lt;\")\r\n .replace(\">\", \"&gt;\")\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(\"&\", \"&amp;\")\r\n .replace(\"'\", \"&#039;\")\r\n .replace(\"\\\"\", \"&quot;\")\r\n .replace(\"<\", \"&lt;\")\r\n .replace(\">\", \"&gt;\")\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
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly compiler",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.5.92",
7
+ "version": "0.5.93",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"
@@ -4,7 +4,7 @@
4
4
  "description": "Firefly language support",
5
5
  "author": "Firefly team",
6
6
  "license": "MIT",
7
- "version": "0.5.92",
7
+ "version": "0.5.93",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "https://github.com/Ahnfelt/firefly-boot"