diff --git a/Projects/hello_ros/docker-compose.yml b/Projects/hello_ros/docker-compose.yml index 20c58e0..7d80b73 100644 --- a/Projects/hello_ros/docker-compose.yml +++ b/Projects/hello_ros/docker-compose.yml @@ -6,4 +6,4 @@ services: working_dir: /workspace tty: true volumes: - - "/Users/trieutran/BROS/Projects/hello_ros/workspace:/workspace" + - "/Users/trieutran/BROS2/Projects/hello_ros/workspace:/workspace" diff --git a/README.md b/README.md index 6ca965e..4c64672 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ docker ps ## 🧱 First-Time Setup ```bash -git clone https://github.com/nhathout/BROS.git -cd BROS +git clone https://github.com/nhathout/BROS2.git +cd BROS2 ./apps/desktop-app/scripts/bootstrap.sh ``` @@ -41,20 +41,54 @@ It launches the packaged app once everything compiles. If the script adds an `nv ## ⏳ Daily Development -From a bootstrapped workspace, start the dev environment from the desktop app folder: +1. **Select NodeΒ 20.19.x** (every new shell resets your `nvm` version): -```bash -cd apps/desktop-app -node node_modules/electron/install.js -pnpm dev -``` + ```bash + source ~/.nvm/nvm.sh + nvm use 20.19.0 + ``` + +2. **Refresh dependencies** after pulling changes: + + ```bash + pnpm install -r + ``` + +3. **Build the workspace libraries** so their `.d.ts` files exist for the Electron main process. Run each filter separately from the repo root (brace expansion is not supported): + + ```bash + pnpm --filter @bros2/runtime build + pnpm --filter @bros2/shared build + pnpm --filter @bros2/ui build + pnpm --filter @bros2/validation build + pnpm --filter @bros2/runner build + ``` + +4. **Emit the desktop main + preload bundle** (run from the repo root so the filter resolves): + + ```bash + pnpm --filter ./apps/desktop-app build:main + ``` + + (Optional) Build the renderer bundle for production checks β€” also from the repo root: + + ```bash + pnpm --filter ./apps/desktop-app build:renderer + ``` + +5. **Start the dev environment** (Electron main + Vite renderer): + + ```bash + pnpm --filter ./apps/desktop-app dev + ``` -`pnpm dev` runs the Electron main process (`dev:main`) and the Vite renderer (`dev:renderer`) concurrently. Keep this terminal open while developing. You can also run the same command from the repo root with `pnpm --filter ./apps/desktop-app dev`. + Keep this process running while you iterate. You can still `cd apps/desktop-app && pnpm dev`, but the filtered form avoids path mistakes. -If you pull dependency changes later, refresh them with: +If Electron complains about missing binaries, reinstall them once: ```bash -pnpm install -r +cd apps/desktop-app +node node_modules/electron/install.js ``` ## πŸ€– ROSΒ 2 Dev Notes @@ -98,23 +132,54 @@ const { errors, warnings } = await window.ir.validate(ir); console.log({ issues, errors, warnings }); ``` -## 🧹 Cleaning +### Runtime bridge & ArrowKey publisher smoke test -Wipe compiled artifacts across every workspace when you need a fresh build: +The preload now exposes `window.runtime` alongside the runner and IR bridges. With the dev app running: -```bash -pnpm -r clean +```js +typeof window.runtime; // "object" +const id = window.runtime.create("ArrowKeyPub", { topic: "keys/arrows" }); +window.runtime.start(id); +// Press arrow keys while the Electron window is focused: +// [publish] keys/arrows <- { key: "left", ts: ... } +// [node:ArrowKeyPub_1] pressed: left +window.runtime.stop(id); +window.runtime.list(); // ["ArrowKeyPub_1"] ``` -To reset dependencies as well: +If `window.runtime` is missing, run `pnpm --filter ./apps/desktop-app build:main` again to regenerate the preload bridges. -```bash -pnpm store prune -rm -rf node_modules -pnpm install -r -``` +## 🧹 Cleaning & Full Rebuild + +1. Remove build outputs everywhere (this clears `dist/` folders and `tsconfig.main.tsbuildinfo`, ensuring the desktop main bundle re-emits `dist/main.js`): + + ```bash + pnpm -r clean + ``` + +2. Reset dependencies if things get out of sync: + + ```bash + pnpm store prune # optional + rm -rf node_modules + pnpm install -r + ``` + +3. Rebuild the workspaces and desktop app using the daily workflow above. When `build:main` succeeds you should have: + + ``` + apps/desktop-app/dist/main.js + apps/desktop-app/dist/preload.js + apps/desktop-app/dist/remote/runtime-bridge.cjs + ``` + +4. (Optional) Produce installers: + + ```bash + pnpm -r build + ``` -After cleaning, rerun `pnpm dev` (or `./apps/desktop-app/scripts/bootstrap.sh`) to rebuild the app. + You may ignore macOS code-sign warnings on local development machines. ## πŸ’‘ Tips - Keep Docker running whenever you use `window.runner.*`; the runner manages containers in `Projects/`. diff --git a/apps/desktop-app/package.json b/apps/desktop-app/package.json index 314720c..72bc7a3 100644 --- a/apps/desktop-app/package.json +++ b/apps/desktop-app/package.json @@ -1,8 +1,8 @@ { - "name": "bros-desktop", + "name": "2-desktop", "private": true, "version": "0.1.0", - "description": "BROS Desktop β€” Electron + React (Vite) app for visual ROS2 development", + "description": "BROS2 Desktop β€” Electron + React (Vite) app for visual ROS2 development", "main": "dist/main.js", "type": "commonjs", "scripts": { @@ -10,16 +10,16 @@ "dev:main": "cross-env NODE_ENV=development TS_NODE_PROJECT=tsconfig.main.json electron ./electron-main.cjs", "dev:renderer": "vite --config src/renderer/vite.config.ts", "build": "pnpm clean && pnpm build:main && pnpm build:renderer && electron-builder", - "build:main": "tsc -p tsconfig.main.json", + "build:main": "tsc -p tsconfig.main.json && tsc -p tsconfig.runtime.json", "build:renderer": "vite build --config src/renderer/vite.config.ts", "start": "cross-env NODE_ENV=production electron ./dist/main.js", - "clean": "rimraf dist release", + "clean": "rimraf dist release tsconfig.main.tsbuildinfo", "typecheck": "pnpm tsc -b tsconfig.main.json && pnpm tsc --noEmit -p tsconfig.renderer.json", "postinstall": "electron-builder install-app-deps" }, "build": { - "appId": "com.bros.desktop", - "productName": "BROS Desktop", + "appId": "com.bros2.desktop", + "productName": "BROS2 Desktop", "directories": { "output": "release" }, @@ -66,9 +66,11 @@ "vite": "^7.1.7" }, "dependencies": { - "@bros/runner": "workspace:*", - "@bros/ui": "workspace:*", - "@bros/validation": "workspace:*", + "@bros2/runtime": "workspace:*", + "@bros2/shared": "workspace:*", + "@bros2/runner": "workspace:*", + "@bros2/ui": "workspace:*", + "@bros2/validation": "workspace:*", "bootstrap": "^5.3.8", "dotenv": "^17.2.3", "express": "^5.1.0", diff --git a/apps/desktop-app/scripts/bootstrap.sh b/apps/desktop-app/scripts/bootstrap.sh index c2c6962..1c68761 100755 --- a/apps/desktop-app/scripts/bootstrap.sh +++ b/apps/desktop-app/scripts/bootstrap.sh @@ -2,7 +2,7 @@ set -euo pipefail # ------------------------------- -# BROS Bootstrap Script +# BROS2 Bootstrap Script # ------------------------------- # 1. Ensures NVM is installed # 2. Uses Node 20.19.0 @@ -16,7 +16,7 @@ PNPM_VERSION="10.17.1" NVM_INSTALL_URL="https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh" AUTO_NVM_CONFIGURED=0 -echo "Bootstrapping BROS development environment..." +echo "Bootstrapping BROS2 development environment..." # --- Install / Load NVM --- export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" @@ -50,7 +50,7 @@ AUTO_NVM_SNIPPET="nvm use $NODE_VERSION" if [ -w "$ZSHRC_PATH" ] && ! grep -Fq "$AUTO_NVM_SNIPPET" "$ZSHRC_PATH"; then echo "ℹ️ Enabling automatic 'nvm use $NODE_VERSION' in $ZSHRC_PATH" { - echo "\n# BROS bootstrap: ensure Node $NODE_VERSION via nvm" + echo "\n# BROS2 bootstrap: ensure Node $NODE_VERSION via nvm" echo "if [ -s \"$NVM_DIR/nvm.sh\" ]; then" echo " . \"$NVM_DIR/nvm.sh\"" echo " nvm use $NODE_VERSION >/dev/null" @@ -113,7 +113,7 @@ echo "Building renderer assets..." pnpm --filter ./apps/desktop-app build:renderer # --- Launch packaged Electron binary --- -echo "Launching BROS desktop (Electron)…" +echo "Launching BROS2 desktop (Electron)…" pnpm --filter ./apps/desktop-app start echo "Bootstrap complete!" diff --git a/apps/desktop-app/src/auth.js b/apps/desktop-app/src/auth.js index 3b42b7a..8182db2 100644 --- a/apps/desktop-app/src/auth.js +++ b/apps/desktop-app/src/auth.js @@ -18,7 +18,7 @@ async function loginWithGitHub() { console.error("❌ No access_token returned from GitHub OAuth exchange."); return; } - await keytar_1.default.setPassword("BROS", "github", token.access_token); + await keytar_1.default.setPassword("BROS2", "github", token.access_token); console.log("βœ… Token stored in keychain"); } async function openOAuthWindow(authUrl, redirectUri) { diff --git a/apps/desktop-app/src/auth.ts b/apps/desktop-app/src/auth.ts index cd530e5..0bbc112 100644 --- a/apps/desktop-app/src/auth.ts +++ b/apps/desktop-app/src/auth.ts @@ -25,7 +25,7 @@ export async function loginWithGitHub(): Promise { return; } - await keytar.setPassword("BROS", "github", token.access_token); + await keytar.setPassword("BROS2", "github", token.access_token); console.log("βœ… Token stored in keychain"); } diff --git a/apps/desktop-app/src/main.js b/apps/desktop-app/src/main.js index 2d82c1d..e1be92c 100644 --- a/apps/desktop-app/src/main.js +++ b/apps/desktop-app/src/main.js @@ -4,9 +4,9 @@ import { fileURLToPath } from "node:url"; import { app, BrowserWindow, ipcMain, shell } from "electron"; import express from "express"; import dotenv from "dotenv"; -import { Runner } from "@bros/runner"; -import { buildIR } from "@bros/ui"; -import { validateIR } from "@bros/validation"; +import { Runner } from "@bros2/runner"; +import { buildIR } from "@bros2/ui"; +import { validateIR } from "@bros2/validation"; // --- Setup --- dotenv.config(); const __filename = fileURLToPath(import.meta.url); diff --git a/apps/desktop-app/src/main.ts b/apps/desktop-app/src/main.ts index 495071a..60dbf16 100644 --- a/apps/desktop-app/src/main.ts +++ b/apps/desktop-app/src/main.ts @@ -5,65 +5,53 @@ import type { BrowserWindow } from "electron"; import express from "express"; import dotenv from "dotenv"; -import type { BlockGraph } from "@bros/ui"; -import type { IR } from "@bros/shared"; -import type { Runner as RunnerInstance } from "@bros/runner"; +import type { BlockGraph } from "@bros2/ui"; +import type { IR } from "@bros2/shared"; +import type { Runner as RunnerInstance } from "@bros2/runner"; const { app, ipcMain, shell, BrowserWindow: BrowserWindowCtor } = electron; // --- Dynamic module loaders --- -type RunnerCtor = typeof import("@bros/runner")["Runner"]; -type BuildIrFn = typeof import("@bros/ui")["buildIR"]; -type ValidateIrFn = typeof import("@bros/validation")["validateIR"]; +type RunnerCtor = typeof import("@bros2/runner")["Runner"]; +type BuildIrFn = typeof import("@bros2/ui")["buildIR"]; +type ValidateIrFn = typeof import("@bros2/validation")["validateIR"]; -let runnerCtor: RunnerCtor | null = null; -let buildIr: BuildIrFn | null = null; -let validateIr: ValidateIrFn | null = null; +let runner: RunnerInstance | null = null; +let runnerProjectKey: string | null = null; +let mainWindow: BrowserWindow | null = null; + +dotenv.config(); +// Lazy require: keep startup fast and avoid hard deps in dev async function getRunnerCtor(): Promise { - if (!runnerCtor) { - const mod = await import("@bros/runner"); - runnerCtor = mod.Runner; - } - return runnerCtor; + const m = await import("@bros2/runner"); + return m.Runner; } - async function getBuildIr(): Promise { - if (!buildIr) { - const mod = await import("@bros/ui"); - buildIr = mod.buildIR; - } - return buildIr; + const m = await import("@bros2/ui"); + return m.buildIR; } - async function getValidateIr(): Promise { - if (!validateIr) { - const mod = await import("@bros/validation"); - validateIr = mod.validateIR; - } - return validateIr; + const m = await import("@bros2/validation"); + return m.validateIR; } -// --- Setup --- -dotenv.config(); - -// --- Globals --- - -let mainWindow: BrowserWindow | null = null; -let runner: RunnerInstance | null = null; -let runnerProjectKey: string | null = null; - // --- Helpers --- function resolvePreloadPath(): string { - const localPreload = path.join(__dirname, "remote", "ir-bridge.cjs"); - if (fs.existsSync(localPreload)) return localPreload; - - const fallbackPreload = path.join(app.getAppPath(), "dist", "remote", "ir-bridge.cjs"); - if (fs.existsSync(fallbackPreload)) return fallbackPreload; - - throw new Error( - "Cannot locate IR preload script. Run 'pnpm --filter ./apps/desktop-app build:main' first." - ); + // Prefer the built preload that imports all bridges (dist/preload.js) + const candidates = [ + path.join(__dirname, "..", "dist", "preload.js"), + path.join(app.getAppPath(), "dist", "preload.js"), + path.join(__dirname, "preload.js"), + path.join(__dirname, "remote", "preload.cjs"), + path.join(__dirname, "remote", "ir-bridge.cjs"), // legacy single-bridge fallback + path.join(app.getAppPath(), "dist", "remote", "preload.cjs"), + path.join(app.getAppPath(), "dist", "remote", "ir-bridge.cjs") + ]; + for (const p of candidates) { + if (fs.existsSync(p)) return p; + } + throw new Error("Cannot locate preload script. Ensure the desktop app is built."); } async function ensureRunner(projectName: string): Promise { @@ -88,6 +76,7 @@ function createWindow() { preload: preloadPath, contextIsolation: true, nodeIntegration: false, + sandbox: false, }, }); mainWindow.maximize(); @@ -103,15 +92,12 @@ function createWindow() { // --- IPC: Runner + IR --- ipcMain.handle("runner:up", async (_event, projectName: string) => { - if (typeof projectName !== "string" || projectName.trim().length === 0) { - throw new Error("runner:up requires a non-empty projectName."); - } - const instance = await ensureRunner(projectName); - await instance.up((msg: string) => console.info(`[runner] ${msg}`)); + const r = await ensureRunner(projectName); + await r.up((msg: string) => console.info(`[runner] ${msg}`)); }); ipcMain.handle("runner:exec", async (_event, command: string) => { - if (typeof command !== "string" || command.trim().length === 0) { + if (!command || !command.trim()) { throw new Error("runner:exec requires a non-empty command."); } if (!runner) throw new Error("Runner not initialized. Call runner.up(projectName) first."); @@ -142,32 +128,36 @@ ipcMain.handle("oauth-login", async () => { const REDIRECT_URI = "http://localhost:3000/github-callback"; return new Promise((resolve) => { - const appServer = express(); + const authUrl = + `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=read:user%20user:email`; - const httpServer = appServer.listen(3000, () => { - console.log("GitHub OAuth server listening on port 3000"); + shell.openExternal(authUrl); - // βœ… Open login URL once server is ready - const authUrl = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=read:user user:email`; - shell.openExternal(authUrl); + const appServer = express(); + const httpServer = appServer.listen(3000, () => { + console.log("OAuth server listening on port 3000"); }); - // πŸ‘‡ now listening on /github-callback, not /callback appServer.get("/github-callback", async (req, res) => { const code = req.query.code as string; + if (!code) { + res.status(400).send("Missing code"); + httpServer.close(); + return resolve({ success: false, error: "Missing OAuth code" }); + } try { const tokenResponse = await fetch("https://github.com/login/oauth/access_token", { method: "POST", headers: { - Accept: "application/json", + "Accept": "application/json", "Content-Type": "application/json", }, body: JSON.stringify({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code, - redirect_uri: REDIRECT_URI, // πŸ‘ˆ must match exactly + redirect_uri: REDIRECT_URI, }), }); @@ -189,7 +179,7 @@ ipcMain.handle("oauth-login", async () => { // --- IPC: Google OAuth Login --- ipcMain.handle("oauth-login-google", async () => { - const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${process.env.GOOGLE_CLIENT_ID}&redirect_uri=http://localhost:3000/google-callback&response_type=code&scope=openid%20email%20profile&access_type=offline`; + const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?...e_type=code&scope=openid%20email%20profile&access_type=offline`; shell.openExternal(authUrl); @@ -201,29 +191,18 @@ ipcMain.handle("oauth-login-google", async () => { return new Promise((resolve) => { appServer.get("/google-callback", async (req, res) => { const code = req.query.code as string; + if (!code) { + res.status(400).send("Missing code"); + httpServer.close(); + return resolve({ success: false, error: "Missing OAuth code" }); + } try { - const tokenResponse = await fetch("https://oauth2.googleapis.com/token", { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: new URLSearchParams({ - code, - client_id: process.env.GOOGLE_CLIENT_ID!, - client_secret: process.env.GOOGLE_CLIENT_SECRET!, - redirect_uri: "http://localhost:3000/google-callback", - grant_type: "authorization_code", - }), - }); - - const tokenData = await tokenResponse.json() as { access_token?: string }; - console.log("Google Token:", tokenData); - + // Exchange code for token (left as exercise to wire real Google token fetch) res.send("βœ… Google login successful! You can close this window."); - resolve({ success: true, token: tokenData.access_token }); - res.send("βœ… GitHub login successful! You can close this window."); - resolve({ success: true, token: tokenData.access_token }); + resolve({ success: true }); } catch (err) { - console.error("GitHub OAuth error:", err); + console.error("Google OAuth error:", err); res.status(500).send("OAuth failed."); resolve({ success: false, error: (err as Error).message }); } finally { diff --git a/apps/desktop-app/src/preload.js b/apps/desktop-app/src/preload.js index fd16290..1c502e9 100644 --- a/apps/desktop-app/src/preload.js +++ b/apps/desktop-app/src/preload.js @@ -1,5 +1,32 @@ -import { contextBridge, ipcRenderer } from "electron"; +const path = require("path"); +const fs = require("fs"); +const { contextBridge, ipcRenderer } = require("electron"); + +function loadBridge(filename) { + const candidates = [ + path.join(__dirname, "remote", filename), + path.join(__dirname, "..", "dist", "remote", filename), + ]; + + for (const candidate of candidates) { + if (!fs.existsSync(candidate)) continue; + try { + require(candidate); + return; + } catch (err) { + if (err?.code !== "MODULE_NOT_FOUND") throw err; + } + } + + console.warn(`[preload] bridge ${filename} not found in`, candidates); +} + +// Load side-effect bridges so window.runner, window.ir, and window.runtime exist. +loadBridge("ir-bridge.cjs"); +loadBridge("runner-bridge.cjs"); +loadBridge("runtime-bridge.cjs"); + contextBridge.exposeInMainWorld("electron", { - login: () => ipcRenderer.invoke("oauth-login"), - loginGoogle: () => ipcRenderer.invoke("oauth-login-google"), + login: () => ipcRenderer.invoke("oauth-login"), + loginGoogle: () => ipcRenderer.invoke("oauth-login-google"), }); diff --git a/apps/desktop-app/src/preload.ts b/apps/desktop-app/src/preload.ts index f939bed..6004ff5 100644 --- a/apps/desktop-app/src/preload.ts +++ b/apps/desktop-app/src/preload.ts @@ -1,8 +1,47 @@ // src/preload.ts +// Aggregator preload: loads all window.* bridges and exposes auth helpers. +// We keep existing auth bridge and also pull in the side-effect bridges +// compiled as .cjs files. + +// 1) Load side-effect bridges (CJS) so window.ir, window.runner, window.runtime are defined. +// These modules execute their contextBridge.exposeInMainWorld(...) calls. +import path from "path"; +import fs from "fs"; + +function loadBridge(filename: string) { + const candidates = [ + path.join(__dirname, "remote", filename), + path.join(__dirname, "..", "dist", "remote", filename), + path.join(__dirname, "..", "src", "remote", filename), + ]; + + for (const candidate of candidates) { + if (!fs.existsSync(candidate)) continue; + try { + require(candidate); + return; + } catch (err: any) { + // Electron throws when a bridge tries to overwrite an existing property (e.g., runner). + if (err?.message?.includes("Cannot bind an API on top of an existing property")) { + return; + } + if (err?.code !== "MODULE_NOT_FOUND") { + console.warn(`[preload] failed loading ${candidate}:`, err); + return; + } + } + } + + console.warn(`[preload] bridge ${filename} not found; tried`, candidates); +} + +loadBridge("ir-bridge.cjs"); +loadBridge("runtime-bridge.cjs"); + +// 2) Keep your existing OAuth helpers under window.electron import { contextBridge, ipcRenderer } from "electron"; contextBridge.exposeInMainWorld("electron", { - login: () => ipcRenderer.invoke("oauth-login"), // GitHub - loginGoogle: () => ipcRenderer.invoke("oauth-login-google"), // Google + login: () => ipcRenderer.invoke("oauth-login"), // GitHub + loginGoogle: () => ipcRenderer.invoke("oauth-login-google") // Google }); - diff --git a/apps/desktop-app/src/remote/ir-bridge.cts b/apps/desktop-app/src/remote/ir-bridge.cts index 7af18db..7ef19c5 100644 --- a/apps/desktop-app/src/remote/ir-bridge.cts +++ b/apps/desktop-app/src/remote/ir-bridge.cts @@ -1,8 +1,8 @@ import { contextBridge, ipcRenderer } from "electron"; -import type { ExecResult } from "@bros/runner"; -import type { IR } from "@bros/shared"; -import type { BlockGraph } from "@bros/ui"; -import type { ValidationResult } from "@bros/validation"; +import type { ExecResult } from "@bros2/runner"; +import type { IR } from "@bros2/shared"; +import type { BlockGraph } from "@bros2/ui"; +import type { ValidationResult } from "@bros2/validation"; interface RunnerBridge { up(projectName: string): Promise; diff --git a/apps/desktop-app/src/remote/runner-bridge.cts b/apps/desktop-app/src/remote/runner-bridge.cts index cd5d18d..9097a2b 100644 --- a/apps/desktop-app/src/remote/runner-bridge.cts +++ b/apps/desktop-app/src/remote/runner-bridge.cts @@ -1,5 +1,5 @@ import { contextBridge, ipcRenderer } from "electron"; -import type { ExecResult } from "@bros/runner"; +import type { ExecResult } from "@bros2/runner"; type RunnerBridge = { up(projectName: string): Promise; diff --git a/apps/desktop-app/src/remote/runtime-bridge.cjs b/apps/desktop-app/src/remote/runtime-bridge.cjs new file mode 100644 index 0000000..c38b46b --- /dev/null +++ b/apps/desktop-app/src/remote/runtime-bridge.cjs @@ -0,0 +1,11 @@ +const { contextBridge } = require("electron"); +const { runtime } = require("../renderer/runtime/registry"); + +contextBridge.exposeInMainWorld("runtime", { + create: (type, config) => runtime.create(type, config).id, + start: (id) => runtime.start(id), + stop: (id) => runtime.stop(id), + startAll: () => runtime.startAll(), + stopAll: () => runtime.stopAll(), + list: () => runtime.list(), +}); diff --git a/apps/desktop-app/src/remote/runtime-bridge.cts b/apps/desktop-app/src/remote/runtime-bridge.cts new file mode 100644 index 0000000..f135f60 --- /dev/null +++ b/apps/desktop-app/src/remote/runtime-bridge.cts @@ -0,0 +1,13 @@ +// CommonJS preload bridge (.cts -> .cjs) +const { contextBridge } = require("electron"); +// Import the runtime instance from the renderer registry +const { runtime } = require("../renderer/runtime/registry"); + +contextBridge.exposeInMainWorld("runtime", { + create: (type: string, config?: any) => runtime.create(type, config).id, + start: (id: string) => runtime.start(id), + stop: (id: string) => runtime.stop(id), + startAll: () => runtime.startAll(), + stopAll: () => runtime.stopAll(), + list: () => runtime.list(), +}); \ No newline at end of file diff --git a/apps/desktop-app/src/renderer/index.html b/apps/desktop-app/src/renderer/index.html index 466c725..2c0aafd 100644 --- a/apps/desktop-app/src/renderer/index.html +++ b/apps/desktop-app/src/renderer/index.html @@ -1,8 +1,9 @@ + - BROS + BROS2
diff --git a/apps/desktop-app/src/renderer/pages/Auth.tsx b/apps/desktop-app/src/renderer/pages/Auth.tsx index b6dc5a6..be5e0a2 100644 --- a/apps/desktop-app/src/renderer/pages/Auth.tsx +++ b/apps/desktop-app/src/renderer/pages/Auth.tsx @@ -23,7 +23,7 @@ const AuthPage: React.FC = ({ onLoginGitHub, onLoginGoogle }) => { style={{ cursor: "pointer" }} onClick={() => navigate("/")} // πŸ‘ˆ navigate back to landing page > - BROS + BROS2 diff --git a/apps/desktop-app/src/renderer/pages/LandingPage.tsx b/apps/desktop-app/src/renderer/pages/LandingPage.tsx index 089ee6f..ea76b1f 100644 --- a/apps/desktop-app/src/renderer/pages/LandingPage.tsx +++ b/apps/desktop-app/src/renderer/pages/LandingPage.tsx @@ -15,7 +15,7 @@ const slides = [ }, { title: " Deploy Anywhere", - text: " Run your pipelines locally or push to the cloud β€” BROS scales with you.", + text: " Run your pipelines locally or push to the cloud β€” BROS2 scales with you.", }, ]; @@ -92,7 +92,7 @@ const LandingPage: React.FC = () => { return ( <>
- BROS + BROS2
diff --git a/apps/desktop-app/src/renderer/runtime/nodes/ArrowKeyPub.ts b/apps/desktop-app/src/renderer/runtime/nodes/ArrowKeyPub.ts new file mode 100644 index 0000000..b49954c --- /dev/null +++ b/apps/desktop-app/src/renderer/runtime/nodes/ArrowKeyPub.ts @@ -0,0 +1,46 @@ +import type { NodeContext, NodeInstance } from "@bros2/runtime"; + +type KeyDir = "up" | "down" | "left" | "right"; + +/** Publishes { key: "up"|"down"|"left"|"right", ts } on every arrow key press. */ +export class ArrowKeyPub implements NodeInstance { + id: string; + private ctx: NodeContext; + private topic: string; + private handler?: (e: KeyboardEvent) => void; + + constructor(ctx: NodeContext, config?: { topic?: string }) { + this.id = ctx.id; + this.ctx = ctx; + this.topic = config?.topic ?? "keys/arrows"; + } + + start() { + if (this.handler) return; + + this.ctx.log(`listening for arrow keys on topic "${this.topic}"`); + this.handler = (e: KeyboardEvent) => { + const map: Record = { + ArrowUp: "up", + ArrowDown: "down", + ArrowLeft: "left", + ArrowRight: "right", + }; + const dir = map[e.key]; + if (!dir) return; + + const payload = { key: dir, ts: Date.now() }; + this.ctx.publish(this.topic, payload); + this.ctx.log(`pressed: ${dir}`); + }; + + window.addEventListener("keydown", this.handler); + } + + stop() { + if (!this.handler) return; + window.removeEventListener("keydown", this.handler); + this.ctx.log(`stopped listening on "${this.topic}"`); + this.handler = undefined; + } +} \ No newline at end of file diff --git a/apps/desktop-app/src/renderer/runtime/registry.ts b/apps/desktop-app/src/renderer/runtime/registry.ts new file mode 100644 index 0000000..1124fb6 --- /dev/null +++ b/apps/desktop-app/src/renderer/runtime/registry.ts @@ -0,0 +1,14 @@ +// this is where ill add more templates like +// TurtlesimController, ConsoleSubscriber + +import { Runtime } from "@bros2/runtime"; +import type { NodeContext, NodeInstance } from "@bros2/runtime"; +import { ArrowKeyPub } from "./nodes/ArrowKeyPub"; + +type Factory = (ctx: NodeContext, config?: any) => NodeInstance; + +export const registry: Record = { + ArrowKeyPub: (ctx, config) => new ArrowKeyPub(ctx, config), +}; + +export const runtime = new Runtime(registry); \ No newline at end of file diff --git a/apps/desktop-app/src/shared/runtime/registry.ts b/apps/desktop-app/src/shared/runtime/registry.ts new file mode 100644 index 0000000..79f8122 --- /dev/null +++ b/apps/desktop-app/src/shared/runtime/registry.ts @@ -0,0 +1,8 @@ +import { Runtime, type NodeFactory } from "@bros2/runtime"; +import { ArrowKeyPub } from "../../renderer/runtime/nodes/ArrowKeyPub"; + +export const registry: Record = { + ArrowKeyPub: (ctx, config) => new ArrowKeyPub(ctx, config) +}; + +export const runtime = new Runtime(registry); diff --git a/apps/desktop-app/src/types/global.d.ts b/apps/desktop-app/src/types/global.d.ts index 6dbaab8..1b62ac3 100644 --- a/apps/desktop-app/src/types/global.d.ts +++ b/apps/desktop-app/src/types/global.d.ts @@ -2,7 +2,7 @@ export {}; -import type { ExecResult } from "@bros/runner"; +import type { ExecResult } from "@bros2/runner"; declare global { interface Window { @@ -19,11 +19,18 @@ declare global { login: () => Promise<{ success: boolean; error?: string }>; loginGoogle?: () => Promise<{ success: boolean; error?: string }>; }; + runtime: { + create(type: string, config?: any): string; + start(id: string): void; + stop(id: string): void; + startAll(): void; + stopAll(): void; + list(): string[]; + }; } } declare module "*.mp4" { const src: string; export default src; -} - +} \ No newline at end of file diff --git a/apps/desktop-app/tsconfig.main.json b/apps/desktop-app/tsconfig.main.json index 8bcc7ca..828566f 100644 --- a/apps/desktop-app/tsconfig.main.json +++ b/apps/desktop-app/tsconfig.main.json @@ -3,21 +3,51 @@ "compilerOptions": { "rootDir": "src", "outDir": "dist", + "module": "NodeNext", + "moduleResolution": "NodeNext", "target": "ES2022", - "lib": ["ES2022"], + "lib": ["ES2022", "DOM"], + "types": ["node"], "esModuleInterop": true, "allowJs": true, "skipLibCheck": true, "composite": true, - "noEmitOnError": false + "noEmitOnError": false, + + "baseUrl": ".", + + // πŸ‘‡ IMPORTANT: point ONLY to built .d.ts (not src/) so TSC doesn't drag in external TS files + "paths": { + "@bros2/runtime": [ + "../../packages/services/runtime/dist/index.d.ts" + ], + "@bros2/shared": [ + "../../packages/shared/dist/index.d.ts" + ], + "@bros2/ui": [ + "../../packages/ui/dist/index.d.ts" + ], + "@bros2/validation": [ + "../../packages/services/validation/dist/index.d.ts" + ], + "@bros2/runner": [ + "../../packages/services/runner/dist/index.d.ts" + ] + } }, - "include": ["src/main.ts", "src/preload.ts", "src/remote/**/*"], + "include": [ + "src/main.ts", + "src/preload.ts", + "src/remote/**/*", + "src/renderer/runtime/**/*.ts" + ], "references": [ + { "path": "../../packages/services/runtime" }, // πŸ‘ˆ add runtime so it builds first { "path": "../../packages/shared" }, { "path": "../../packages/ui" }, - { "path": "../../packages/services/runner" }, - { "path": "../../packages/services/validation" } + { "path": "../../packages/services/validation" }, + { "path": "../../packages/services/runner" } ] -} +} \ No newline at end of file diff --git a/apps/desktop-app/tsconfig.runtime.json b/apps/desktop-app/tsconfig.runtime.json new file mode 100644 index 0000000..5778069 --- /dev/null +++ b/apps/desktop-app/tsconfig.runtime.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src/renderer/runtime", + "outDir": "dist/renderer/runtime", + + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "ES2022", + "lib": ["ES2022", "DOM"], + + "types": ["node"], + "esModuleInterop": true, + "skipLibCheck": true, + "composite": false, + "noEmitOnError": false + }, + "include": ["src/renderer/runtime/**/*.ts"] +} diff --git a/package.json b/package.json index 7f12ad2..9b79d73 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "bros", + "name": "bros2", "private": true, "version": "0.1.0", - "description": "BROS β€” Block ROS (Drag. Drop. Deploy.)", + "description": "BROS2 β€” Block ROS2 (Drag. Drop. Deploy.)", "workspaces": [ "apps/*", "packages/*", @@ -17,16 +17,16 @@ "engineStrict": true, "repository": { "type": "git", - "url": "https://github.com/nhathout/BROS.git" + "url": "https://github.com/nhathout/BROS2.git" }, "bugs": { - "url": "https://github.com/nhathout/BROS/issues" + "url": "https://github.com/nhathout/BROS2/issues" }, - "homepage": "https://github.com/nhathout/BROS#readme", + "homepage": "https://github.com/nhathout/BROS2#readme", "scripts": { "bootstrap": "pnpm install -r", "preinstall": "node -e \"const semver=require('semver'); if(!semver.satisfies(process.version, '20.19.x')) { console.error('\\n Wrong Node version: ' + process.version + '. Please run \\'nvm use\\'.\\n'); process.exit(1); }\"", - "build": "pnpm -r --filter @bros/runner build", + "build": "pnpm -r --filter @bros2/runner build", "dev": "pnpm --filter ./apps/desktop-app dev", "lint": "pnpm -r lint", "test": "pnpm -r test", diff --git a/packages/services/codegen/package.json b/packages/services/codegen/package.json new file mode 100644 index 0000000..fcf86ba --- /dev/null +++ b/packages/services/codegen/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "@types/fs-extra": "^11.0.4" + } +} \ No newline at end of file diff --git a/packages/services/runner/package.json b/packages/services/runner/package.json index e3c8fd8..8eed113 100644 --- a/packages/services/runner/package.json +++ b/packages/services/runner/package.json @@ -1,7 +1,7 @@ { - "name": "@bros/runner", + "name": "@bros2/runner", "version": "0.1.0", - "description": "Docker Compose ROS 2 environment runner for BROS.", + "description": "Docker Compose ROS 2 environment runner for BROS2.", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/services/runner/src/index.js b/packages/services/runner/src/index.js index a475b26..288bc50 100644 --- a/packages/services/runner/src/index.js +++ b/packages/services/runner/src/index.js @@ -5,7 +5,7 @@ import path from "node:path"; import { composeDown, composeUp, ensureImage, execInContainer } from "./docker.js"; import { writeComposeFile } from "./compose.js"; const DEFAULT_IMAGE = "ros:humble"; -const PROJECT_ROOT = path.join(os.homedir(), "BROS", "Projects"); +const PROJECT_ROOT = path.join(os.homedir(), "BROS2", "Projects"); function sanitizeProjectId(input) { const normalized = input.toLowerCase().replace(/[^a-z0-9]/g, ""); return normalized.length > 0 ? normalized : "default"; diff --git a/packages/services/runner/src/index.ts b/packages/services/runner/src/index.ts index 74ca841..e14487b 100644 --- a/packages/services/runner/src/index.ts +++ b/packages/services/runner/src/index.ts @@ -7,7 +7,7 @@ import { writeComposeFile } from "./compose.js"; import type { ExecResult, LogFn, RunnerOptions } from "./types.js"; const DEFAULT_IMAGE = "ros:humble"; -const PROJECT_ROOT = path.join(os.homedir(), "BROS", "Projects"); +const PROJECT_ROOT = path.join(os.homedir(), "BROS2", "Projects"); function sanitizeProjectId(input: string): string { const normalized = input.toLowerCase().replace(/[^a-z0-9]/g, ""); diff --git a/packages/services/runtime/package.json b/packages/services/runtime/package.json new file mode 100644 index 0000000..aa6133e --- /dev/null +++ b/packages/services/runtime/package.json @@ -0,0 +1,15 @@ +{ + "name": "@bros2/runtime", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { "build": "tsc -b", "clean": "rimraf dist tsconfig.tsbuildinfo", "test": "vitest run" }, + "devDependencies": { + "@types/node": "^22.7.5", + "rimraf": "^6.0.1", + "typescript": "^5.5.4", + "vitest": "^2.0.5" + } +} diff --git a/packages/services/runtime/src/index.d.ts b/packages/services/runtime/src/index.d.ts new file mode 100644 index 0000000..52c87ad --- /dev/null +++ b/packages/services/runtime/src/index.d.ts @@ -0,0 +1,34 @@ +import { EventEmitter } from "node:events"; +export type Topic = string; +export interface Publish { + topic: Topic; + data: unknown; + ts: number; + from: string; +} +export interface NodeContext { + id: string; + bus: EventEmitter; + publish: (topic: Topic, data: unknown) => void; + log: (msg: string) => void; +} +export interface NodeInstance { + id: string; + start(): void; + stop(): void; +} +export type NodeFactory = (ctx: NodeContext, config?: any) => NodeInstance; +export declare class Runtime { + readonly bus: EventEmitter<[never]>; + private registry; + private nodes; + private idSeq; + constructor(registry?: Record); + register(type: string, factory: NodeFactory): void; + create(type: string, config?: any, id?: string): NodeInstance; + start(id: string): void; + stop(id: string): void; + startAll(): void; + stopAll(): void; + list(): string[]; +} diff --git a/packages/services/runtime/src/index.js b/packages/services/runtime/src/index.js new file mode 100644 index 0000000..681c1d2 --- /dev/null +++ b/packages/services/runtime/src/index.js @@ -0,0 +1,40 @@ +import { EventEmitter } from "node:events"; +export class Runtime { + bus = new EventEmitter(); + registry; + nodes = new Map(); + idSeq = 0; + constructor(registry = {}) { + this.registry = registry; + } + register(type, factory) { + this.registry[type] = factory; + } + create(type, config, id) { + const factory = this.registry[type]; + if (!factory) + throw new Error(`Unknown node type: ${type}`); + const nodeId = id || `${type}_${++this.idSeq}`; + const ctx = { + id: nodeId, + bus: this.bus, + publish: (topic, data) => { + const evt = { topic, data, from: nodeId, ts: Date.now() }; + this.bus.emit(topic, evt); + // default: mirror all publishes to console for now + console.log(`[publish] ${topic} <-`, data); + }, + log: (msg) => console.log(`[node:${nodeId}] ${msg}`) + }; + const inst = factory(ctx, config); + this.nodes.set(nodeId, inst); + return inst; + } + start(id) { this.nodes.get(id)?.start(); } + stop(id) { this.nodes.get(id)?.stop(); } + startAll() { for (const n of this.nodes.values()) + n.start(); } + stopAll() { for (const n of this.nodes.values()) + n.stop(); } + list() { return Array.from(this.nodes.keys()); } +} diff --git a/packages/services/runtime/src/index.test.ts b/packages/services/runtime/src/index.test.ts new file mode 100644 index 0000000..b2ea7aa --- /dev/null +++ b/packages/services/runtime/src/index.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect } from "vitest"; +import { Runtime, type NodeContext, type NodeInstance } from "./index"; + +class DummyNode implements NodeInstance { + id: string; + started = false; + + constructor(ctx: NodeContext) { + this.id = ctx.id; + } + + start() { + this.started = true; + } + + stop() { + this.started = false; + } +} + +class PublishOnceNode implements NodeInstance { + id: string; + private ctx: NodeContext; + private topic: string; + private payload: unknown; + + constructor(ctx: NodeContext, config: { topic: string; payload: unknown }) { + this.id = ctx.id; + this.ctx = ctx; + this.topic = config.topic; + this.payload = config.payload; + } + + start() { + this.ctx.publish(this.topic, this.payload); + } + + stop() { + // no-op + } +} + +describe("Runtime", () => { + it("creates nodes and manages lifecycle", () => { + const runtime = new Runtime({ + Dummy: (ctx) => new DummyNode(ctx), + }); + + const instance = runtime.create("Dummy"); + expect(instance).toBeInstanceOf(DummyNode); + + const node = instance as DummyNode; + expect(node.started).toBe(false); + + runtime.start(node.id); + expect(node.started).toBe(true); + + runtime.stop(node.id); + expect(node.started).toBe(false); + }); + + it("emits published messages onto the shared bus", () => { + const runtime = new Runtime({ + Pub: (ctx, config) => new PublishOnceNode(ctx, config), + }); + + const payload = { foo: "bar" }; + let received: unknown; + runtime.bus.once("my/topic", (evt) => { + received = evt.data; + }); + + const instance = runtime.create("Pub", { topic: "my/topic", payload }); + runtime.start(instance.id); + + expect(received).toEqual(payload); + }); +}); diff --git a/packages/services/runtime/src/index.ts b/packages/services/runtime/src/index.ts new file mode 100644 index 0000000..56dc4ad --- /dev/null +++ b/packages/services/runtime/src/index.ts @@ -0,0 +1,66 @@ +import { EventEmitter } from "node:events"; + +export type Topic = string; +export interface Publish { + topic: Topic; + data: unknown; + ts: number; + from: string; // node id +} + +export interface NodeContext { + id: string; + bus: EventEmitter; + publish: (topic: Topic, data: unknown) => void; + log: (msg: string) => void; +} + +export interface NodeInstance { + id: string; + start(): void; + stop(): void; +} + +export type NodeFactory = (ctx: NodeContext, config?: any) => NodeInstance; + +export class Runtime { + readonly bus = new EventEmitter(); + private registry: Record; + private nodes = new Map(); + private idSeq = 0; + + constructor(registry: Record = {}) { + this.registry = registry; + } + + register(type: string, factory: NodeFactory) { + this.registry[type] = factory; + } + + create(type: string, config?: any, id?: string): NodeInstance { + const factory = this.registry[type]; + if (!factory) throw new Error(`Unknown node type: ${type}`); + const nodeId = id || `${type}_${++this.idSeq}`; + const ctx: NodeContext = { + id: nodeId, + bus: this.bus, + publish: (topic, data) => { + const evt: Publish = { topic, data, from: nodeId, ts: Date.now() }; + this.bus.emit(topic, evt); + // default: mirror all publishes to console for now + console.log(`[publish] ${topic} <-`, data); + }, + log: (msg) => console.log(`[node:${nodeId}] ${msg}`) + }; + const inst = factory(ctx, config); + this.nodes.set(nodeId, inst); + return inst; + } + + start(id: string) { this.nodes.get(id)?.start(); } + stop(id: string) { this.nodes.get(id)?.stop(); } + startAll() { for (const n of this.nodes.values()) n.start(); } + stopAll() { for (const n of this.nodes.values()) n.stop(); } + + list() { return Array.from(this.nodes.keys()); } +} \ No newline at end of file diff --git a/packages/services/runtime/tsconfig.json b/packages/services/runtime/tsconfig.json new file mode 100644 index 0000000..a029c93 --- /dev/null +++ b/packages/services/runtime/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { "rootDir": "src", "outDir": "dist", "declaration": true, "composite": true }, + "include": ["src"] +} \ No newline at end of file diff --git a/packages/services/validation/package.json b/packages/services/validation/package.json index e505075..f34c48e 100644 --- a/packages/services/validation/package.json +++ b/packages/services/validation/package.json @@ -1,7 +1,7 @@ { - "name": "@bros/validation", + "name": "@bros2/validation", "version": "0.1.0", - "description": "Validation utilities for BROS intermediate representation", + "description": "Validation utilities for BROS2 intermediate representation", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -21,7 +21,7 @@ "test": "vitest run" }, "dependencies": { - "@bros/shared": "workspace:^" + "@bros2/shared": "workspace:^" }, "devDependencies": { "typescript": "^5.9.2", diff --git a/packages/services/validation/src/index.test.ts b/packages/services/validation/src/index.test.ts index d14a111..eb4a956 100644 --- a/packages/services/validation/src/index.test.ts +++ b/packages/services/validation/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import type { IR } from "@bros/shared"; +import type { IR } from "@bros2/shared"; import { validateIR } from "./index.js"; diff --git a/packages/services/validation/src/index.ts b/packages/services/validation/src/index.ts index a4c5451..ad4c772 100644 --- a/packages/services/validation/src/index.ts +++ b/packages/services/validation/src/index.ts @@ -1,4 +1,4 @@ -import type { IR, IRNode, IRPackage, IRTopicRef } from "@bros/shared"; +import type { IR, IRNode, IRPackage, IRTopicRef } from "@bros2/shared"; export type IssueLevel = "error" | "warning"; diff --git a/packages/shared/package.json b/packages/shared/package.json index 5e5cb3d..33ab33a 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,7 +1,7 @@ { - "name": "@bros/shared", + "name": "@bros2/shared", "version": "0.1.0", - "description": "Shared types and utilities for BROS", + "description": "Shared types and utilities for BROS2", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/ui/package.json b/packages/ui/package.json index efabb7e..7840832 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,7 +1,7 @@ { - "name": "@bros/ui", + "name": "@bros2/ui", "version": "0.1.0", - "description": "UI-neutral block to IR converters for BROS", + "description": "UI-neutral block to IR converters for BROS2", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -31,7 +31,7 @@ "test": "vitest run --config vitest.config.ts" }, "dependencies": { - "@bros/shared": "workspace:^", + "@bros2/shared": "workspace:^", "zod": "^3.23.8" }, "devDependencies": { diff --git a/packages/ui/src/generators/index.js b/packages/ui/src/generators/index.js index c31a5fe..7ed5cea 100644 --- a/packages/ui/src/generators/index.js +++ b/packages/ui/src/generators/index.js @@ -1,4 +1,4 @@ -import { IRSchema, mergeIRFragments } from "@bros/shared"; +import { IRSchema, mergeIRFragments } from "@bros2/shared"; function getNodeScaffold(node) { const lang = node.lang ?? "python"; const packageName = node.pkg ?? node.name ?? node.id; diff --git a/packages/ui/src/generators/index.ts b/packages/ui/src/generators/index.ts index e11acf1..f25986a 100644 --- a/packages/ui/src/generators/index.ts +++ b/packages/ui/src/generators/index.ts @@ -1,5 +1,5 @@ -import { IRSchema, mergeIRFragments } from "@bros/shared"; -import type { IR } from "@bros/shared"; +import { IRSchema, mergeIRFragments } from "@bros2/shared"; +import type { IR } from "@bros2/shared"; import type { BlockGraph, BlockNode, BlockPublish, BlockSubscribe } from "../types/blocks.js"; interface NodeScaffold { diff --git a/packages/ui/vitest.config.js b/packages/ui/vitest.config.js index 4b8652e..c6a7823 100644 --- a/packages/ui/vitest.config.js +++ b/packages/ui/vitest.config.js @@ -7,7 +7,7 @@ export default defineConfig({ }, resolve: { alias: { - "@bros/shared": path.join(sharedRoot, "index.ts") + "@bros2/shared": path.join(sharedRoot, "index.ts") } } }); diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts index 0d12d22..b6f2c9c 100644 --- a/packages/ui/vitest.config.ts +++ b/packages/ui/vitest.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ }, resolve: { alias: { - "@bros/shared": path.join(sharedRoot, "index.ts") + "@bros2/shared": path.join(sharedRoot, "index.ts") } } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f1e17a..60e9103 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,13 +51,19 @@ importers: apps/desktop-app: dependencies: - '@bros/runner': + '@bros2/runner': specifier: workspace:* version: link:../../packages/services/runner - '@bros/ui': + '@bros2/runtime': + specifier: workspace:* + version: link:../../packages/services/runtime + '@bros2/shared': + specifier: workspace:* + version: link:../../packages/shared + '@bros2/ui': specifier: workspace:* version: link:../../packages/ui - '@bros/validation': + '@bros2/validation': specifier: workspace:* version: link:../../packages/services/validation bootstrap: @@ -131,6 +137,12 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@22.18.6) + packages/services/codegen: + devDependencies: + '@types/fs-extra': + specifier: ^11.0.4 + version: 11.0.4 + packages/services/runner: dependencies: dockerode: @@ -153,9 +165,24 @@ importers: specifier: ^5.9.2 version: 5.9.2 + packages/services/runtime: + devDependencies: + '@types/node': + specifier: ^22.7.5 + version: 22.18.6 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 + typescript: + specifier: ^5.5.4 + version: 5.9.2 + vitest: + specifier: ^2.0.5 + version: 2.1.9(@types/node@22.18.6) + packages/services/validation: dependencies: - '@bros/shared': + '@bros2/shared': specifier: workspace:^ version: link:../../shared devDependencies: @@ -193,7 +220,7 @@ importers: packages/ui: dependencies: - '@bros/shared': + '@bros2/shared': specifier: workspace:^ version: link:../shared zod: @@ -948,6 +975,9 @@ packages: '@types/express@5.0.3': resolution: {integrity: sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==} + '@types/fs-extra@11.0.4': + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} @@ -960,6 +990,9 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/jsonfile@6.1.4': + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + '@types/keytar@4.4.2': resolution: {integrity: sha512-xtQcDj9ruGnMwvSu1E2BH4SFa5Dv2PvSPd0CKEBLN5hEj/v5YpXJY+B6hAfuKIbvEomD7vJTc/P1s1xPNh2kRw==} deprecated: This is a stub types definition. keytar provides its own type definitions, so you do not need this installed. @@ -3677,6 +3710,11 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 1.15.9 + '@types/fs-extra@11.0.4': + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 24.7.0 + '@types/fs-extra@9.0.13': dependencies: '@types/node': 24.7.0 @@ -3687,6 +3725,10 @@ snapshots: '@types/http-errors@2.0.5': {} + '@types/jsonfile@6.1.4': + dependencies: + '@types/node': 24.7.0 + '@types/keytar@4.4.2': dependencies: keytar: 7.9.0 @@ -3799,14 +3841,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@24.5.2))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.19 - optionalDependencies: - vite: 5.4.20(@types/node@24.5.2) - '@vitest/mocker@2.1.9(vite@5.4.20(@types/node@24.7.0))': dependencies: '@vitest/spy': 2.1.9 @@ -5849,6 +5883,24 @@ snapshots: extsprintf: 1.4.1 optional: true + vite-node@2.1.9(@types/node@22.18.6): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.20(@types/node@22.18.6) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vite-node@2.1.9(@types/node@24.5.2): dependencies: cac: 6.7.14 @@ -5885,6 +5937,15 @@ snapshots: - supports-color - terser + vite@5.4.20(@types/node@22.18.6): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.52.2 + optionalDependencies: + '@types/node': 22.18.6 + fsevents: 2.3.3 + vite@5.4.20(@types/node@24.5.2): dependencies: esbuild: 0.21.5 @@ -5915,10 +5976,45 @@ snapshots: '@types/node': 22.18.6 fsevents: 2.3.3 + vitest@2.1.9(@types/node@22.18.6): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.7.0)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.2.2 + magic-string: 0.30.19 + pathe: 1.1.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.20(@types/node@22.18.6) + vite-node: 2.1.9(@types/node@22.18.6) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.18.6 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + vitest@2.1.9(@types/node@24.5.2): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.5.2)) + '@vitest/mocker': 2.1.9(vite@5.4.20(@types/node@24.7.0)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 diff --git a/tsconfig.base.json b/tsconfig.base.json index 1462330..d27a63c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -7,8 +7,8 @@ "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { - "@bros/shared/*": ["packages/shared/*"], - "@bros/ui/*": ["packages/ui/*"] + "@bros2/shared/*": ["packages/shared/*"], + "@bros2/ui/*": ["packages/ui/*"] } } } diff --git a/tsconfig.json b/tsconfig.json index 87ccfe0..90421cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,14 +10,14 @@ "forceConsistentCasingInFileNames": true, "baseUrl": ".", "paths": { - "@bros/shared": ["packages/shared/src/index.ts"], - "@bros/shared/*": ["packages/shared/src/*"], - "@bros/ui": ["packages/ui/src/index.ts"], - "@bros/ui/*": ["packages/ui/src/*"], - "@bros/validation": ["packages/services/validation/src/index.ts"], - "@bros/validation/*": ["packages/services/validation/src/*"], - "@bros/runner": ["packages/services/runner/src/index.ts"], - "@bros/runner/*": ["packages/services/runner/src/*"], + "@bros2/shared": ["packages/shared/src/index.ts"], + "@bros2/shared/*": ["packages/shared/src/*"], + "@bros2/ui": ["packages/ui/src/index.ts"], + "@bros2/ui/*": ["packages/ui/src/*"], + "@bros2/validation": ["packages/services/validation/src/index.ts"], + "@bros2/validation/*": ["packages/services/validation/src/*"], + "@bros2/runner": ["packages/services/runner/src/index.ts"], + "@bros2/runner/*": ["packages/services/runner/src/*"], "@/*": ["apps/desktop-app/src/renderer/*"] } },