fix: workover of the monaco update
This commit is contained in:
@@ -158,8 +158,9 @@ mix assets.build
|
|||||||
|
|
||||||
## Monaco Editor
|
## Monaco Editor
|
||||||
|
|
||||||
Monaco is bundled via esbuild using its ESM entry point (`monaco-editor/esm/vs/editor/editor.api.js`). The
|
Monaco is bundled via esbuild using its ESM entry point (`monaco-editor/esm/vs/editor/editor.main.js`). The
|
||||||
bundle is built as part of `mix assets.build` and lives at `priv/static/assets/monaco.js` and `monaco.css`.
|
main bundle is built as part of `mix assets.build` and lives at `priv/static/assets/monaco.js` and
|
||||||
|
`monaco.css`; ESM worker bundles live under `priv/static/assets/monaco/`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mix monaco.version # show current monaco-editor version
|
mix monaco.version # show current monaco-editor version
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// App-wide error reporter — catches JS errors and displays them as an overlay.
|
|
||||||
// Enables debugging in WKWebView where devtools are unavailable.
|
|
||||||
(function () {
|
|
||||||
const overlay = document.createElement("div");
|
|
||||||
overlay.id = "error-reporter";
|
|
||||||
overlay.style.cssText =
|
|
||||||
"position:fixed;bottom:0;left:0;right:0;max-height:200px;overflow:auto;background:#1a1a2e;color:#f0f0f0;font:12px monospace;padding:8px 12px;border-top:2px solid #e74c3c;z-index:99999;display:none;";
|
|
||||||
overlay.innerHTML = '<strong style="color:#e74c3c">⚠ JS Errors</strong><pre id="error-reporter-log"></pre>';
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
|
|
||||||
const log = document.getElementById("error-reporter-log");
|
|
||||||
|
|
||||||
const append = (msg) => {
|
|
||||||
overlay.style.display = "block";
|
|
||||||
log.textContent += msg + "\n";
|
|
||||||
overlay.scrollTop = overlay.scrollHeight;
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("error", (e) => {
|
|
||||||
const src = e.filename || "";
|
|
||||||
const loc = e.lineno !== undefined ? `:${e.lineno}` : "";
|
|
||||||
append(`[${new Date().toLocaleTimeString()}] ${e.message || e.error}\n at ${src}${loc}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener("unhandledrejection", (e) => {
|
|
||||||
append(`[${new Date().toLocaleTimeString()}] Unhandled rejection: ${e.reason}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also wrap console.error to catch Monaco errors
|
|
||||||
const origError = console.error;
|
|
||||||
console.error = (...args) => {
|
|
||||||
origError.apply(console, args);
|
|
||||||
append(`[${new Date().toLocaleTimeString()}] ${args.join(" ")}`);
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
@@ -4,7 +4,51 @@ import { registerLiquidLanguage, registerMarkdownWithMacrosLanguage } from "./la
|
|||||||
let monacoLoaderPromise;
|
let monacoLoaderPromise;
|
||||||
const monacoEditors = new Map();
|
const monacoEditors = new Map();
|
||||||
|
|
||||||
|
const monacoWorkerUrls = {
|
||||||
|
editor: "/assets/monaco/editor.worker.js",
|
||||||
|
css: "/assets/monaco/css.worker.js",
|
||||||
|
html: "/assets/monaco/html.worker.js",
|
||||||
|
json: "/assets/monaco/json.worker.js",
|
||||||
|
ts: "/assets/monaco/ts.worker.js"
|
||||||
|
};
|
||||||
|
|
||||||
|
const workerNameForLanguage = (label) => {
|
||||||
|
switch (label) {
|
||||||
|
case "css":
|
||||||
|
case "scss":
|
||||||
|
case "less":
|
||||||
|
return "css";
|
||||||
|
case "html":
|
||||||
|
case "handlebars":
|
||||||
|
case "razor":
|
||||||
|
return "html";
|
||||||
|
case "json":
|
||||||
|
return "json";
|
||||||
|
case "typescript":
|
||||||
|
case "javascript":
|
||||||
|
return "ts";
|
||||||
|
default:
|
||||||
|
return "editor";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureMonacoEnvironment = () => {
|
||||||
|
if (globalThis.MonacoEnvironment?.getWorker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.MonacoEnvironment = {
|
||||||
|
...globalThis.MonacoEnvironment,
|
||||||
|
getWorker(_workerId, label) {
|
||||||
|
const workerName = workerNameForLanguage(label);
|
||||||
|
return new Worker(monacoWorkerUrls[workerName], { name: label, type: "module" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const loadMonaco = () => {
|
export const loadMonaco = () => {
|
||||||
|
ensureMonacoEnvironment();
|
||||||
|
|
||||||
if (window.monaco?.editor) {
|
if (window.monaco?.editor) {
|
||||||
ensureMonacoTheme(window.monaco);
|
ensureMonacoTheme(window.monaco);
|
||||||
registerLiquidLanguage(window.monaco);
|
registerLiquidLanguage(window.monaco);
|
||||||
|
|||||||
1
assets/js/monaco_workers/css.worker.js
Normal file
1
assets/js/monaco_workers/css.worker.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "monaco-editor/esm/vs/language/css/css.worker.js";
|
||||||
1
assets/js/monaco_workers/editor.worker.js
Normal file
1
assets/js/monaco_workers/editor.worker.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "monaco-editor/esm/vs/editor/editor.worker.js";
|
||||||
1
assets/js/monaco_workers/html.worker.js
Normal file
1
assets/js/monaco_workers/html.worker.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "monaco-editor/esm/vs/language/html/html.worker.js";
|
||||||
1
assets/js/monaco_workers/json.worker.js
Normal file
1
assets/js/monaco_workers/json.worker.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "monaco-editor/esm/vs/language/json/json.worker.js";
|
||||||
1
assets/js/monaco_workers/ts.worker.js
Normal file
1
assets/js/monaco_workers/ts.worker.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "monaco-editor/esm/vs/language/typescript/ts.worker.js";
|
||||||
@@ -67,6 +67,22 @@ config :esbuild,
|
|||||||
--loader:.ttf=dataurl
|
--loader:.ttf=dataurl
|
||||||
),
|
),
|
||||||
env: %{"NODE_PATH" => Path.expand("../node_modules", __DIR__)}
|
env: %{"NODE_PATH" => Path.expand("../node_modules", __DIR__)}
|
||||||
|
],
|
||||||
|
monaco_workers: [
|
||||||
|
cd: Path.expand("../assets", __DIR__),
|
||||||
|
args: ~w(
|
||||||
|
js/monaco_workers/editor.worker.js
|
||||||
|
js/monaco_workers/css.worker.js
|
||||||
|
js/monaco_workers/html.worker.js
|
||||||
|
js/monaco_workers/json.worker.js
|
||||||
|
js/monaco_workers/ts.worker.js
|
||||||
|
--bundle
|
||||||
|
--target=es2022
|
||||||
|
--format=esm
|
||||||
|
--outdir=../priv/static/assets/monaco
|
||||||
|
--loader:.ttf=dataurl
|
||||||
|
),
|
||||||
|
env: %{"NODE_PATH" => Path.expand("../node_modules", __DIR__)}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :bds, :scripting,
|
config :bds, :scripting,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ defmodule BDS.Desktop.Endpoint do
|
|||||||
plug(Plug.Static,
|
plug(Plug.Static,
|
||||||
at: "/assets",
|
at: "/assets",
|
||||||
from: {:bds, "priv/static/assets"},
|
from: {:bds, "priv/static/assets"},
|
||||||
only: ["app.css", "app.js", "monaco.css", "monaco.js"]
|
only: ["app.css", "app.js", "monaco.css", "monaco.js", "monaco"]
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(BDS.Desktop.Router)
|
plug(BDS.Desktop.Router)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ defmodule Mix.Tasks.Monaco.Update do
|
|||||||
This task:
|
This task:
|
||||||
|
|
||||||
1. Runs `npm install monaco-editor@<version> --save-dev`
|
1. Runs `npm install monaco-editor@<version> --save-dev`
|
||||||
2. Removes the old `priv/static/assets/monaco.js` and `monaco.css`
|
2. Removes the old Monaco bundle assets
|
||||||
3. Rebuilds the Monaco bundle via esbuild (both dev and minified)
|
3. Rebuilds the Monaco main and worker bundles via esbuild (both dev and minified)
|
||||||
|
|
||||||
Use `mix monaco.version` to check the result.
|
Use `mix monaco.version` to check the result.
|
||||||
|
|
||||||
@@ -27,14 +27,14 @@ defmodule Mix.Tasks.Monaco.Update do
|
|||||||
Mix.shell().error("Missing version argument.")
|
Mix.shell().error("Missing version argument.")
|
||||||
Mix.shell().info("Usage: mix monaco.update <version>")
|
Mix.shell().info("Usage: mix monaco.update <version>")
|
||||||
Mix.shell().info("Example: mix monaco.update 0.55.1")
|
Mix.shell().info("Example: mix monaco.update 0.55.1")
|
||||||
System.at_exit(fn _ -> System.halt(1) end)
|
Mix.raise("missing Monaco version argument")
|
||||||
|
|
||||||
[version] ->
|
[version] ->
|
||||||
do_update(version)
|
do_update(version)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Mix.shell().error("Too many arguments. Usage: mix monaco.update <version>")
|
Mix.shell().error("Too many arguments. Usage: mix monaco.update <version>")
|
||||||
System.at_exit(fn _ -> System.halt(1) end)
|
Mix.raise("too many arguments for mix monaco.update")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -49,24 +49,31 @@ defmodule Mix.Tasks.Monaco.Update do
|
|||||||
# 2. Clean old bundle
|
# 2. Clean old bundle
|
||||||
monaco_js = Path.join(File.cwd!(), "priv/static/assets/monaco.js")
|
monaco_js = Path.join(File.cwd!(), "priv/static/assets/monaco.js")
|
||||||
monaco_css = Path.join(File.cwd!(), "priv/static/assets/monaco.css")
|
monaco_css = Path.join(File.cwd!(), "priv/static/assets/monaco.css")
|
||||||
|
monaco_workers = Path.join(File.cwd!(), "priv/static/assets/monaco")
|
||||||
|
|
||||||
for path <- [monaco_js, monaco_css] do
|
for path <- [monaco_js, monaco_css, monaco_workers] do
|
||||||
if File.exists?(path) do
|
if File.exists?(path) do
|
||||||
File.rm!(path)
|
File.rm_rf!(path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Mix.shell().info("Removed old monaco.js and monaco.css")
|
Mix.shell().info("Removed old Monaco bundle assets")
|
||||||
|
|
||||||
# 3. Rebuild via esbuild (dev + minified)
|
# 3. Rebuild via esbuild (dev + minified)
|
||||||
Mix.Task.run("esbuild", ["monaco"])
|
run_esbuild(["monaco"])
|
||||||
Mix.Task.run("esbuild", ["monaco", "--minify"])
|
run_esbuild(["monaco_workers"])
|
||||||
|
run_esbuild(["monaco", "--minify"])
|
||||||
|
run_esbuild(["monaco_workers", "--minify"])
|
||||||
|
|
||||||
# 4. Verify
|
# 4. Verify
|
||||||
new = current_version()
|
new = current_version()
|
||||||
Mix.shell().info("monaco-editor is now at #{new}")
|
Mix.shell().info("monaco-editor is now at #{new}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp run_esbuild(args) do
|
||||||
|
Mix.Task.run("esbuild", args)
|
||||||
|
end
|
||||||
|
|
||||||
defp current_version do
|
defp current_version do
|
||||||
lock = Path.join(File.cwd!(), "package-lock.json")
|
lock = Path.join(File.cwd!(), "package-lock.json")
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ defmodule Mix.Tasks.Monaco.Version do
|
|||||||
|
|
||||||
mix monaco.version
|
mix monaco.version
|
||||||
|
|
||||||
Reads the version from package-lock.json. The Monaco bundle lives at
|
Reads the version from package-lock.json. The Monaco main bundle lives at
|
||||||
`priv/static/assets/monaco.js` and is built via `mix esbuild monaco`.
|
`priv/static/assets/monaco.js`; worker bundles live under
|
||||||
|
`priv/static/assets/monaco/`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
use Mix.Task
|
use Mix.Task
|
||||||
@@ -30,12 +31,12 @@ defmodule Mix.Tasks.Monaco.Version do
|
|||||||
v when is_binary(v) -> Mix.shell().info("monaco-editor: #{v}")
|
v when is_binary(v) -> Mix.shell().info("monaco-editor: #{v}")
|
||||||
nil ->
|
nil ->
|
||||||
Mix.shell().error("Could not find monaco-editor in package-lock.json")
|
Mix.shell().error("Could not find monaco-editor in package-lock.json")
|
||||||
System.at_exit(fn _ -> System.halt(1) end)
|
Mix.raise("could not find monaco-editor in package-lock.json")
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
Mix.shell().error("package-lock.json not found: #{reason}")
|
Mix.shell().error("package-lock.json not found: #{reason}")
|
||||||
System.at_exit(fn _ -> System.halt(1) end)
|
Mix.raise("package-lock.json not found: #{reason}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
9
mix.exs
9
mix.exs
@@ -66,8 +66,13 @@ defmodule BDS.MixProject do
|
|||||||
"ecto.setup": ["ecto.create", "ecto.migrate"],
|
"ecto.setup": ["ecto.create", "ecto.migrate"],
|
||||||
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
"assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
|
||||||
"assets.build": ["tailwind default", "esbuild default", "esbuild monaco"],
|
"assets.build": ["tailwind default", "esbuild default", "esbuild monaco", "esbuild monaco_workers"],
|
||||||
"assets.deploy": ["tailwind default --minify", "esbuild default --minify", "esbuild monaco"],
|
"assets.deploy": [
|
||||||
|
"tailwind default --minify",
|
||||||
|
"esbuild default --minify",
|
||||||
|
"esbuild monaco --minify",
|
||||||
|
"esbuild monaco_workers --minify"
|
||||||
|
],
|
||||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
|
||||||
validate: ["test", "credo --strict", "deps.audit --ignore-file .mix_audit.ignore", "dialyzer"]
|
validate: ["test", "credo --strict", "deps.audit --ignore-file .mix_audit.ignore", "dialyzer"]
|
||||||
]
|
]
|
||||||
|
|||||||
39
priv/static/assets/app.js
vendored
39
priv/static/assets/app.js
vendored
@@ -8821,7 +8821,46 @@ removing illegal node: "${(childNode.outerHTML || childNode.nodeValue).trim()}"
|
|||||||
// js/monaco/services.js
|
// js/monaco/services.js
|
||||||
var monacoLoaderPromise;
|
var monacoLoaderPromise;
|
||||||
var monacoEditors = /* @__PURE__ */ new Map();
|
var monacoEditors = /* @__PURE__ */ new Map();
|
||||||
|
var monacoWorkerUrls = {
|
||||||
|
editor: "/assets/monaco/editor.worker.js",
|
||||||
|
css: "/assets/monaco/css.worker.js",
|
||||||
|
html: "/assets/monaco/html.worker.js",
|
||||||
|
json: "/assets/monaco/json.worker.js",
|
||||||
|
ts: "/assets/monaco/ts.worker.js"
|
||||||
|
};
|
||||||
|
var workerNameForLanguage = (label) => {
|
||||||
|
switch (label) {
|
||||||
|
case "css":
|
||||||
|
case "scss":
|
||||||
|
case "less":
|
||||||
|
return "css";
|
||||||
|
case "html":
|
||||||
|
case "handlebars":
|
||||||
|
case "razor":
|
||||||
|
return "html";
|
||||||
|
case "json":
|
||||||
|
return "json";
|
||||||
|
case "typescript":
|
||||||
|
case "javascript":
|
||||||
|
return "ts";
|
||||||
|
default:
|
||||||
|
return "editor";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var ensureMonacoEnvironment = () => {
|
||||||
|
if (globalThis.MonacoEnvironment?.getWorker) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
globalThis.MonacoEnvironment = {
|
||||||
|
...globalThis.MonacoEnvironment,
|
||||||
|
getWorker(_workerId, label) {
|
||||||
|
const workerName = workerNameForLanguage(label);
|
||||||
|
return new Worker(monacoWorkerUrls[workerName], { name: label, type: "module" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
var loadMonaco = () => {
|
var loadMonaco = () => {
|
||||||
|
ensureMonacoEnvironment();
|
||||||
if (window.monaco?.editor) {
|
if (window.monaco?.editor) {
|
||||||
ensureMonacoTheme(window.monaco);
|
ensureMonacoTheme(window.monaco);
|
||||||
registerLiquidLanguage(window.monaco);
|
registerLiquidLanguage(window.monaco);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
239905
priv/static/assets/monaco.js
vendored
239905
priv/static/assets/monaco.js
vendored
File diff suppressed because one or more lines are too long
93
priv/static/assets/monaco/css.worker.js
vendored
Normal file
93
priv/static/assets/monaco/css.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
26
priv/static/assets/monaco/editor.worker.js
vendored
Normal file
26
priv/static/assets/monaco/editor.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
470
priv/static/assets/monaco/html.worker.js
vendored
Normal file
470
priv/static/assets/monaco/html.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
58
priv/static/assets/monaco/json.worker.js
vendored
Normal file
58
priv/static/assets/monaco/json.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
67736
priv/static/assets/monaco/ts.worker.js
vendored
Normal file
67736
priv/static/assets/monaco/ts.worker.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
scripts/desktop_automation_app.exs
Normal file
7
scripts/desktop_automation_app.exs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Enum.reduce_while(IO.stream(:stdio, :line), :ok, fn line, _acc ->
|
||||||
|
if String.trim(line) == "stop" do
|
||||||
|
{:halt, :ok}
|
||||||
|
else
|
||||||
|
{:cont, :ok}
|
||||||
|
end
|
||||||
|
end)
|
||||||
138
scripts/desktop_automation_runner.mjs
Normal file
138
scripts/desktop_automation_runner.mjs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { chromium } from "playwright";
|
||||||
|
import readline from "node:readline";
|
||||||
|
|
||||||
|
const [url, screenshotDir] = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
console.log(JSON.stringify({ status: "error", message: "missing automation url" }));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const browser = await chromium.launch({ headless: true });
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await page.goto(url, { waitUntil: "networkidle" });
|
||||||
|
await page.locator("#bds-shell-app").waitFor({ state: "visible" });
|
||||||
|
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||||
|
console.log(JSON.stringify({ status: "ready", screenshotDir }));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(JSON.stringify({ status: "error", message: error.message }));
|
||||||
|
await browser.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
||||||
|
|
||||||
|
for await (const line of rl) {
|
||||||
|
if (!line.trim()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = JSON.parse(line);
|
||||||
|
const ref = message.ref;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (message.command === "snapshot") {
|
||||||
|
const result = await page.evaluate(() => {
|
||||||
|
const text = (selector) => document.querySelector(selector)?.textContent?.trim() ?? null;
|
||||||
|
const texts = (selector, mapper) => Array.from(document.querySelectorAll(selector)).map(mapper);
|
||||||
|
const hasClass = (selector, className) => document.querySelector(selector)?.classList.contains(className) ?? false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
window_title: text("[data-testid='window-title']"),
|
||||||
|
active_view: document.querySelector("[data-testid='activity-button'][data-active='true']")?.dataset.view ?? null,
|
||||||
|
sidebar_visible: !hasClass("[data-testid='sidebar-shell']", "is-hidden"),
|
||||||
|
sidebar_width: document.querySelector("[data-testid='sidebar-shell']")?.getBoundingClientRect().width ?? 0,
|
||||||
|
assistant_visible: !hasClass("[data-testid='assistant-shell']", "is-hidden"),
|
||||||
|
assistant_width: document.querySelector("[data-testid='assistant-shell']")?.getBoundingClientRect().width ?? 0,
|
||||||
|
panel_visible: !hasClass(".panel-shell", "is-hidden"),
|
||||||
|
editor_title: text("[data-testid='editor-title']"),
|
||||||
|
activity_labels: texts("[data-testid='activity-button']", (node) => node.getAttribute("aria-label")),
|
||||||
|
sidebar_sections: texts("[data-testid='sidebar-section-title']", (node) => node.textContent.trim()),
|
||||||
|
editor_meta_labels: texts("[data-testid='editor-meta-label']", (node) => node.textContent.trim())
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "click") {
|
||||||
|
await page.locator(message.selector).click();
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "press") {
|
||||||
|
await page.keyboard.press(message.shortcut);
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "native_menu_action") {
|
||||||
|
await page.evaluate((action) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const ackId = `ack-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||||
|
const handler = (event) => {
|
||||||
|
if (event.detail?.ackId === ackId) {
|
||||||
|
window.removeEventListener("bds:native-menu-action-ack", handler);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener("bds:native-menu-action-ack", handler);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent("bds:native-menu-action", { detail: { action, ackId } })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, message.action);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "drag") {
|
||||||
|
const locator = page.locator(message.selector);
|
||||||
|
const box = await locator.boundingBox();
|
||||||
|
|
||||||
|
if (!box) {
|
||||||
|
throw new Error(`unable to drag missing element: ${message.selector}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startX = box.x + box.width / 2;
|
||||||
|
const startY = box.y + box.height / 2;
|
||||||
|
await page.mouse.move(startX, startY);
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.mouse.move(startX + message.deltaX, startY, { steps: 10 });
|
||||||
|
await page.mouse.up();
|
||||||
|
await page.waitForTimeout(50);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "reload") {
|
||||||
|
await page.reload({ waitUntil: "networkidle" });
|
||||||
|
await page.locator("#bds-shell-app").waitFor({ state: "visible" });
|
||||||
|
await page.waitForTimeout(100);
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "ok" }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "screenshot") {
|
||||||
|
await page.screenshot({ path: message.path, fullPage: false });
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: message.path }));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.command === "close") {
|
||||||
|
await browser.close();
|
||||||
|
console.log(JSON.stringify({ ref, status: "ok", result: "closed" }));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(JSON.stringify({ ref, status: "error", message: `unknown command: ${message.command}` }));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(JSON.stringify({ ref, status: "error", message: error.message }));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,7 @@ defmodule BDS.AI.ChatStreamingTest do
|
|||||||
|
|
||||||
setup do
|
setup do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
|
||||||
|
|
||||||
Application.put_env(:bds, :chat_stream_test_pid, self())
|
Application.put_env(:bds, :chat_stream_test_pid, self())
|
||||||
Application.put_env(:bds, :chat_stream_scenario, :short)
|
Application.put_env(:bds, :chat_stream_scenario, :short)
|
||||||
@@ -101,6 +102,7 @@ defmodule BDS.AI.ChatStreamingTest do
|
|||||||
)
|
)
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, :manual)
|
||||||
Application.delete_env(:bds, :chat_stream_scenario)
|
Application.delete_env(:bds, :chat_stream_scenario)
|
||||||
|
|
||||||
case original_chat do
|
case original_chat do
|
||||||
|
|||||||
@@ -325,6 +325,8 @@ defmodule BDS.AITest do
|
|||||||
|
|
||||||
setup do
|
setup do
|
||||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
|
||||||
|
on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, :manual) end)
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ defmodule BDS.DesktopTest do
|
|||||||
|
|
||||||
assert conn.resp_body =~ ~s(data-phx-main)
|
assert conn.resp_body =~ ~s(data-phx-main)
|
||||||
assert conn.resp_body =~ ~s(href="/assets/app.css")
|
assert conn.resp_body =~ ~s(href="/assets/app.css")
|
||||||
|
assert conn.resp_body =~ ~s(href="/assets/monaco.css")
|
||||||
assert conn.resp_body =~ ~s(src="/assets/app.js")
|
assert conn.resp_body =~ ~s(src="/assets/app.js")
|
||||||
refute conn.resp_body =~ ~s(src="/assets/live.js")
|
refute conn.resp_body =~ ~s(src="/assets/live.js")
|
||||||
refute conn.resp_body =~ ~s(src="/vendor/phoenix/phoenix.min.js")
|
refute conn.resp_body =~ ~s(src="/vendor/phoenix/phoenix.min.js")
|
||||||
@@ -371,6 +372,31 @@ defmodule BDS.DesktopTest do
|
|||||||
assert byte_size(js_conn.resp_body) > 0
|
assert byte_size(js_conn.resp_body) > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "desktop endpoint serves Monaco ESM bundle and worker assets" do
|
||||||
|
paths = [
|
||||||
|
"/assets/monaco.css",
|
||||||
|
"/assets/monaco.js",
|
||||||
|
"/assets/monaco/editor.worker.js",
|
||||||
|
"/assets/monaco/css.worker.js",
|
||||||
|
"/assets/monaco/html.worker.js",
|
||||||
|
"/assets/monaco/json.worker.js",
|
||||||
|
"/assets/monaco/ts.worker.js"
|
||||||
|
]
|
||||||
|
|
||||||
|
for path <- paths do
|
||||||
|
asset_conn = conn(:get, "#{path}?k=#{Desktop.Auth.login_key()}")
|
||||||
|
asset_conn = BDS.Desktop.Endpoint.call(asset_conn, BDS.Desktop.Endpoint.init([]))
|
||||||
|
|
||||||
|
assert asset_conn.status == 200
|
||||||
|
assert byte_size(asset_conn.resp_body) > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "desktop automation launch scripts exist at the paths used by automation" do
|
||||||
|
assert File.regular?("/Users/gb/Projects/bDS2/scripts/desktop_automation_app.exs")
|
||||||
|
assert File.regular?("/Users/gb/Projects/bDS2/scripts/desktop_automation_runner.mjs")
|
||||||
|
end
|
||||||
|
|
||||||
test "desktop endpoint serves the live shell without extra router-side secret injection" do
|
test "desktop endpoint serves the live shell without extra router-side secret injection" do
|
||||||
conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}")
|
conn = conn(:get, "/?k=#{Desktop.Auth.login_key()}")
|
||||||
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
conn = BDS.Desktop.Endpoint.call(conn, BDS.Desktop.Endpoint.init([]))
|
||||||
|
|||||||
@@ -358,6 +358,22 @@ defmodule BDS.UI.ShellTest do
|
|||||||
assert File.exists?("/Users/gb/Projects/bDS2/assets/js/monaco/languages.js")
|
assert File.exists?("/Users/gb/Projects/bDS2/assets/js/monaco/languages.js")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "monaco ESM build config emits served worker bundles" do
|
||||||
|
mix_exs = File.read!("/Users/gb/Projects/bDS2/mix.exs")
|
||||||
|
config = File.read!("/Users/gb/Projects/bDS2/config/config.exs")
|
||||||
|
services = File.read!("/Users/gb/Projects/bDS2/assets/js/monaco/services.js")
|
||||||
|
update_task = File.read!("/Users/gb/Projects/bDS2/lib/mix/tasks/monaco.update.ex")
|
||||||
|
|
||||||
|
assert mix_exs =~ ~s("esbuild monaco --minify")
|
||||||
|
assert mix_exs =~ ~s("esbuild monaco_workers --minify")
|
||||||
|
assert config =~ "monaco_workers:"
|
||||||
|
assert config =~ "js/monaco_workers/editor.worker.js"
|
||||||
|
assert services =~ "globalThis.MonacoEnvironment"
|
||||||
|
assert services =~ "/assets/monaco/editor.worker.js"
|
||||||
|
assert update_task =~ ~S|run_esbuild(["monaco_workers", "--minify"])|
|
||||||
|
refute File.exists?("/Users/gb/Projects/bDS2/assets/js/error_reporter.js")
|
||||||
|
end
|
||||||
|
|
||||||
test "top level shell render uses utility classes for common layout" do
|
test "top level shell render uses utility classes for common layout" do
|
||||||
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
|
template = File.read!("/Users/gb/Projects/bDS2/lib/bds/desktop/shell_live/index.html.heex")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user