diff --git a/.claude/launch.json b/.claude/launch.json index 58096b2..d9bb6a5 100644 --- a/.claude/launch.json +++ b/.claude/launch.json @@ -4,8 +4,14 @@ { "name": "ui-preview", "runtimeExecutable": "python3", - "runtimeArgs": ["-m", "http.server", "8765", "--directory", "dist"], + "runtimeArgs": ["-m", "http.server", "8765", "--directory", "apps/desktop/dist"], "port": 8765 + }, + { + "name": "web", + "runtimeExecutable": "pnpm", + "runtimeArgs": ["--filter", "@frameloader/web", "dev", "--strictPort"], + "port": 5180 } ] } diff --git a/README.md b/README.md index 027a306..df8989d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Frameloader +[www.frameloader.com](https://www.frameloader.com) + Drop an APK, a Windows app, or a Linux ARM64 build onto Frameloader and it shows up in your Steam Frame's library, under Non-Steam, ready to launch. Open source, cross-platform (macOS, Windows, Linux), and built on the same mechanism Valve's own SteamOS Devkit Client uses. **Drop it in. It's in your library.** @@ -32,19 +34,34 @@ Logs for Android titles stream straight into the app (row → Logs). The Activit ## Development +This is a pnpm workspace: + +| Path | What it is | +|---|---| +| `apps/desktop` | The Electron app (TypeScript, esbuild, electron-builder) | +| `apps/web` | The website (plain HTML and CSS, built with Vite) | +| `packages/tokens` | Shared color, type and shape tokens used by both | + ```bash pnpm install -pnpm start # build and run Electron -pnpm dev # esbuild in watch mode (run `electron .` separately) +pnpm start # build and run the desktop app +pnpm dev:web # website dev server on http://localhost:5180 pnpm typecheck pnpm test +pnpm build:web # static site in apps/web/dist pnpm dist:mac # or dist:win / dist:linux ``` -The renderer can be previewed in a normal browser with simulated data: serve `dist/` and open `index.html?mock=connected` (also `fresh`, `empty`, `offline`, `nolepton`, `fail`). +The desktop app bundles all of its runtime dependencies into `dist/main.cjs`, so packages contain no `node_modules`. + +The renderer can be previewed in a normal browser with simulated data: serve `apps/desktop/dist/` and open `index.html?mock=connected` (also `fresh`, `empty`, `offline`, `nolepton`, `fail`). `pnpm --filter @frameloader/desktop screenshots` regenerates the website's screenshots from that mock, in light and dark. `pnpm install` must be allowed to run the `electron` and `esbuild` build scripts (see `pnpm-workspace.yaml`). If Electron's binary is missing, run `node node_modules/electron/install.js`. +## Privacy + +Frameloader has no analytics, telemetry, crash reporting, accounts or update checks. Its only network connections are to your headset on your local network: SSH, Valve's devkit pairing service on port 32000, and mDNS discovery. A remembered password is encrypted with the operating system's secure storage. The website loads no third-party scripts, fonts or cookies. + ## Troubleshooting - **"Couldn't find frame.local."** Frameloader resolves names itself over mDNS, then the OS resolver, then by browsing for the devkit service, then the last known IP. If all fail: the Frame is asleep (it drops off the network), Developer Mode is off, or the network blocks multicast (guest/isolated Wi-Fi). Typing the IP from Quick Settings always works. @@ -60,4 +77,4 @@ The renderer can be previewed in a normal browser with simulated data: serve `di - [Lepton](https://gitlab.steamos.cloud/frame-public/lepton) (MIT) for documenting how Android titles are launched. - [Frame Control](https://github.com/saphid/frame-control) (MIT) for field notes that verified each runtime on real hardware. -MIT licensed. +MIT licensed. If Frameloader saves you some time, you can [buy me a coffee](https://buymeacoffee.com/travelerdev). diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..c3b82ef --- /dev/null +++ b/TERMS.md @@ -0,0 +1,33 @@ +# Frameloader terms of use + +Last updated: October 7, 2026 + +Frameloader is free, open-source software released under the [MIT License](LICENSE). By using it you accept these terms. They're short on purpose. + +## No warranty + +Frameloader is provided "as is", without warranty of any kind, express or implied, including any warranty that it is fit for a particular purpose, works with your headset, or is free of bugs. + +## Use at your own risk + +Frameloader connects to your Steam Frame over your local network using Developer Mode, copies files to it, and registers them with Steam. Sideloaded apps may not run, may misbehave, or may crash your headset. You are responsible for what you install and for backing up anything you care about. + +## Limitation of liability + +To the fullest extent the law allows, the authors and contributors are not liable for any damage or loss arising from your use of Frameloader, including damage to devices, lost data, or actions taken on your accounts. + +## Your responsibilities + +Only install software you have the right to use, and follow its license. Steam's and Valve's own terms still apply to your headset and your Steam account. + +## Not affiliated with Valve + +Frameloader is an independent project. Valve, Steam, and Steam Frame are trademarks of Valve Corporation. + +## Privacy + +Frameloader collects no data. It has no analytics, telemetry, or accounts, and it only talks to your headset on your local network. + +## Changes + +These terms may be updated. The current version is always at https://www.frameloader.com/terms.html. diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..bde944c --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,98 @@ +{ + "name": "@frameloader/desktop", + "productName": "Frameloader", + "version": "0.1.0", + "description": "Drop an APK, Windows app, or Linux build onto your Steam Frame and it shows up in your Steam library.", + "license": "MIT", + "author": "Zack ", + "private": true, + "main": "dist/main.cjs", + "type": "module", + "scripts": { + "build": "node scripts/build.mjs", + "dev": "node scripts/build.mjs --watch", + "start": "pnpm build && electron .", + "typecheck": "tsc -p tsconfig.main.json --noEmit && tsc -p tsconfig.renderer.json --noEmit", + "test": "pnpm build && node --test dist/test/*.test.cjs", + "dist": "pnpm build && electron-builder --publish never", + "dist:mac": "pnpm build && electron-builder --mac --arm64 --publish never", + "dist:win": "pnpm build && electron-builder --win --x64 --publish never", + "dist:linux": "pnpm build && electron-builder --linux --x64 --arm64 --publish never", + "screenshots": "pnpm build && electron scripts/screenshot.cjs" + }, + "devDependencies": { + "@frameloader/tokens": "workspace:*", + "@types/multicast-dns": "^7.2.4", + "@types/node": "^24.0.0", + "@types/ssh2": "^1.15.6", + "@types/yauzl": "^2.10.3", + "app-info-parser": "^1.1.6", + "electron": "44.5.1", + "electron-builder": "^26.15.3", + "esbuild": "^0.28.2", + "multicast-dns": "^7.2.5", + "ssh2": "^1.17.0", + "typescript": "^7.0.2", + "yauzl": "^3.2.0" + }, + "build": { + "appId": "dev.traveler.frameloader", + "productName": "Frameloader", + "directories": { + "output": "release", + "buildResources": "build" + }, + "files": [ + "dist/**/*", + "package.json", + "!dist/test/**", + "!dist/**/*.map" + ], + "extraResources": [ + { + "from": "vendor/devkit-utils", + "to": "devkit-utils", + "filter": [ + "**/*", + "!**/__pycache__/**" + ] + } + ], + "mac": { + "category": "public.app-category.utilities", + "target": [ + "dmg", + "zip" + ], + "artifactName": "Frameloader-mac-${arch}.${ext}" + }, + "win": { + "target": [ + "nsis", + "zip" + ], + "artifactName": "Frameloader-win-${arch}.${ext}" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true + }, + "linux": { + "target": [ + "AppImage", + "deb" + ], + "category": "Utility", + "artifactName": "Frameloader-linux-${arch}.${ext}" + }, + "electronVersion": "44.5.1" + }, + "homepage": "https://www.frameloader.com", + "repository": { + "type": "git", + "url": "https://github.com/travelerdev/frameloader.git", + "directory": "apps/desktop" + }, + "dependencies": {} +} diff --git a/scripts/build.mjs b/apps/desktop/scripts/build.mjs similarity index 72% rename from scripts/build.mjs rename to apps/desktop/scripts/build.mjs index dad9dc0..41edaf3 100644 --- a/scripts/build.mjs +++ b/apps/desktop/scripts/build.mjs @@ -1,9 +1,13 @@ // Builds main + preload (Node/CJS) and the renderer (browser) with esbuild. // `node scripts/build.mjs --watch` rebuilds on change. import { build, context } from "esbuild"; -import { cpSync, mkdirSync, readdirSync } from "node:fs"; +import { cpSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; import { join } from "node:path"; +const require = createRequire(import.meta.url); +const TOKENS = require.resolve("@frameloader/tokens/tokens.css"); + const watch = process.argv.includes("--watch"); const prod = process.env.NODE_ENV === "production"; @@ -14,7 +18,9 @@ const nodeCommon = { target: "node22", sourcemap: !prod, minify: false, - external: ["electron", "ssh2", "cpu-features", "app-info-parser", "yauzl"], + // Everything except Electron is bundled, so the packaged app needs no node_modules. + // ssh2's optional native helpers are loaded in try/catch and fall back to pure JS. + external: ["electron", "cpu-features", "*.node"], logLevel: "info", }; @@ -47,7 +53,8 @@ const configs = [ function copyStatic() { mkdirSync("dist", { recursive: true }); cpSync("src/renderer/index.html", "dist/index.html"); - cpSync("src/renderer/styles.css", "dist/styles.css"); + // Shared tokens first, then the app's own styles. + writeFileSync("dist/styles.css", readFileSync(TOKENS, "utf8") + "\n" + readFileSync("src/renderer/styles.css", "utf8")); } copyStatic(); @@ -59,6 +66,7 @@ if (watch) { fsWatch("src/renderer", { recursive: true }, (_e, f) => { if (f && (f.endsWith(".html") || f.endsWith(".css"))) copyStatic(); }); + fsWatch(TOKENS, () => copyStatic()); console.log("watching…"); } else { await Promise.all(configs.map((c) => build(c))); diff --git a/apps/desktop/scripts/screenshot.cjs b/apps/desktop/scripts/screenshot.cjs new file mode 100644 index 0000000..8a8f5e0 --- /dev/null +++ b/apps/desktop/scripts/screenshot.cjs @@ -0,0 +1,56 @@ +// Renders the UI against the mock bridge and saves PNGs for the website. +// Usage: pnpm screenshots (runs `electron scripts/screenshot.cjs` after a build) +const { app, BrowserWindow, nativeTheme } = require("electron"); +const { mkdirSync, writeFileSync } = require("node:fs"); +const { join } = require("node:path"); + +const OUT = join(__dirname, "..", "..", "web", "public", "screenshots"); +const PAGE = join(__dirname, "..", "dist", "index.html"); +const W = 960; +const H = 640; + +const shots = [ + { name: "home", query: "mock=connected&shot=1", prepare: "" }, + { + name: "review", + query: "mock=connected&shot=1", + prepare: `[...document.querySelectorAll("button")].find(b => b.textContent.includes("Choose file"))?.click();`, + }, +]; + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +// Each shot opens and closes a window; don't let Electron quit in between. +app.on("window-all-closed", () => undefined); + +app.whenReady().then(async () => { + mkdirSync(OUT, { recursive: true }); + for (const theme of ["light", "dark"]) { + nativeTheme.themeSource = theme; + for (const shot of shots) { + const win = new BrowserWindow({ width: W, height: H, show: false, useContentSize: true, paintWhenInitiallyHidden: true }); + const query = Object.fromEntries(new URLSearchParams(shot.query)); + try { + await win.loadFile(PAGE, { query }); + } catch (e) { + console.error("load failed", shot.name, theme, e.message); + win.destroy(); + continue; + } + await sleep(1600); + if (shot.prepare) { + await win.webContents.executeJavaScript(shot.prepare); + await sleep(1200); + } + // Hide the scrollbar and the empty macOS titlebar strip for a clean frame. + await win.webContents.insertCSS("::-webkit-scrollbar{display:none} body{--titlebar:0px !important}"); + await sleep(300); + const img = await win.webContents.capturePage(undefined, { stayHidden: true }); + const file = join(OUT, `${shot.name}-${theme}.png`); + writeFileSync(file, img.toPNG()); + console.log("wrote", file, img.getSize()); + win.destroy(); + } + } + app.quit(); +}); diff --git a/src/main/activity.ts b/apps/desktop/src/main/activity.ts similarity index 100% rename from src/main/activity.ts rename to apps/desktop/src/main/activity.ts diff --git a/src/main/config.ts b/apps/desktop/src/main/config.ts similarity index 100% rename from src/main/config.ts rename to apps/desktop/src/main/config.ts diff --git a/src/main/devkitPair.ts b/apps/desktop/src/main/devkitPair.ts similarity index 100% rename from src/main/devkitPair.ts rename to apps/desktop/src/main/devkitPair.ts diff --git a/src/main/devkitUtils.ts b/apps/desktop/src/main/devkitUtils.ts similarity index 100% rename from src/main/devkitUtils.ts rename to apps/desktop/src/main/devkitUtils.ts diff --git a/src/main/discovery.ts b/apps/desktop/src/main/discovery.ts similarity index 100% rename from src/main/discovery.ts rename to apps/desktop/src/main/discovery.ts diff --git a/src/main/errors.ts b/apps/desktop/src/main/errors.ts similarity index 100% rename from src/main/errors.ts rename to apps/desktop/src/main/errors.ts diff --git a/src/main/index.ts b/apps/desktop/src/main/index.ts similarity index 100% rename from src/main/index.ts rename to apps/desktop/src/main/index.ts diff --git a/src/main/installer.ts b/apps/desktop/src/main/installer.ts similarity index 100% rename from src/main/installer.ts rename to apps/desktop/src/main/installer.ts diff --git a/src/main/logcat.ts b/apps/desktop/src/main/logcat.ts similarity index 100% rename from src/main/logcat.ts rename to apps/desktop/src/main/logcat.ts diff --git a/src/main/payload/apk.ts b/apps/desktop/src/main/payload/apk.ts similarity index 100% rename from src/main/payload/apk.ts rename to apps/desktop/src/main/payload/apk.ts diff --git a/src/main/payload/binary.ts b/apps/desktop/src/main/payload/binary.ts similarity index 100% rename from src/main/payload/binary.ts rename to apps/desktop/src/main/payload/binary.ts diff --git a/src/main/payload/index.ts b/apps/desktop/src/main/payload/index.ts similarity index 100% rename from src/main/payload/index.ts rename to apps/desktop/src/main/payload/index.ts diff --git a/src/main/preflight.ts b/apps/desktop/src/main/preflight.ts similarity index 100% rename from src/main/preflight.ts rename to apps/desktop/src/main/preflight.ts diff --git a/src/main/runtimes.ts b/apps/desktop/src/main/runtimes.ts similarity index 100% rename from src/main/runtimes.ts rename to apps/desktop/src/main/runtimes.ts diff --git a/src/main/session.ts b/apps/desktop/src/main/session.ts similarity index 100% rename from src/main/session.ts rename to apps/desktop/src/main/session.ts diff --git a/src/main/ssh.ts b/apps/desktop/src/main/ssh.ts similarity index 100% rename from src/main/ssh.ts rename to apps/desktop/src/main/ssh.ts diff --git a/src/main/titles.ts b/apps/desktop/src/main/titles.ts similarity index 100% rename from src/main/titles.ts rename to apps/desktop/src/main/titles.ts diff --git a/src/preload/index.ts b/apps/desktop/src/preload/index.ts similarity index 100% rename from src/preload/index.ts rename to apps/desktop/src/preload/index.ts diff --git a/src/renderer/actions.ts b/apps/desktop/src/renderer/actions.ts similarity index 100% rename from src/renderer/actions.ts rename to apps/desktop/src/renderer/actions.ts diff --git a/src/renderer/api.ts b/apps/desktop/src/renderer/api.ts similarity index 100% rename from src/renderer/api.ts rename to apps/desktop/src/renderer/api.ts diff --git a/src/renderer/dom.ts b/apps/desktop/src/renderer/dom.ts similarity index 100% rename from src/renderer/dom.ts rename to apps/desktop/src/renderer/dom.ts diff --git a/src/renderer/index.html b/apps/desktop/src/renderer/index.html similarity index 100% rename from src/renderer/index.html rename to apps/desktop/src/renderer/index.html diff --git a/src/renderer/index.ts b/apps/desktop/src/renderer/index.ts similarity index 93% rename from src/renderer/index.ts rename to apps/desktop/src/renderer/index.ts index 30ed0f9..b49ab76 100644 --- a/src/renderer/index.ts +++ b/apps/desktop/src/renderer/index.ts @@ -37,7 +37,7 @@ function render(s: State): void { if (s.panel) for (const el of panelView(s)) root.appendChild(el); if (s.devicesSheet) for (const el of devicesView(s)) root.appendChild(el); if (s.toast) root.appendChild(h("div", { class: `notice ${s.toast.kind}`, "data-anim": "toast", style: "position:fixed;left:50%;bottom:48px;transform:translateX(-50%);z-index:40;box-shadow:0 8px 24px rgba(16,24,40,.15);max-width:min(560px,90vw)" }, s.toast.text)); - if (isMock) root.appendChild(h("div", { class: "chip warn", style: "position:fixed;top:8px;left:50%;transform:translateX(-50%);z-index:50" }, "Preview mode: no headset, simulated data")); + if (isMock && !new URLSearchParams(location.search).has("shot")) root.appendChild(h("div", { class: "chip warn", style: "position:fixed;top:8px;left:50%;transform:translateX(-50%);z-index:50" }, "Preview mode: no headset, simulated data")); // Entrance animations only when an element first appears, not on every re-render. root.querySelectorAll("[data-anim]").forEach((el) => { if (!mounted.has(el.dataset.anim!)) el.classList.add("anim-in"); diff --git a/src/renderer/mock.ts b/apps/desktop/src/renderer/mock.ts similarity index 100% rename from src/renderer/mock.ts rename to apps/desktop/src/renderer/mock.ts diff --git a/src/renderer/state.ts b/apps/desktop/src/renderer/state.ts similarity index 100% rename from src/renderer/state.ts rename to apps/desktop/src/renderer/state.ts diff --git a/src/renderer/styles.css b/apps/desktop/src/renderer/styles.css similarity index 92% rename from src/renderer/styles.css rename to apps/desktop/src/renderer/styles.css index d00e74b..d515fca 100644 --- a/src/renderer/styles.css +++ b/apps/desktop/src/renderer/styles.css @@ -1,49 +1,5 @@ -:root { - --page: #f5f6f8; - --card: #ffffff; - --card-2: #f9fafb; - --text: #1b1f24; - --text-2: #5c6470; - --text-3: #8a919c; - --border: #e3e6ea; - --border-2: #d3d7dd; - --accent: #2e6be6; - --accent-hover: #255cc9; - --accent-soft: #e9f0fd; - --success: #1f9d55; - --success-soft: #e6f6ec; - --warning: #b7791f; - --warning-soft: #fdf3e1; - --danger: #d23f3f; - --danger-soft: #fdeaea; - --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; - --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, Roboto, sans-serif; - --radius: 12px; - --radius-sm: 8px; - --shadow: 0 1px 2px rgba(16, 24, 40, 0.04); - --titlebar: 0px; -} -@media (prefers-color-scheme: dark) { - :root { - --page: #121417; - --card: #1b1e23; - --card-2: #20242a; - --text: #e8eaed; - --text-2: #a4abb5; - --text-3: #6f7782; - --border: #2a2f37; - --border-2: #363c46; - --accent: #5b8df0; - --accent-hover: #74a0f3; - --accent-soft: #1d2a45; - --success: #3fbf74; - --success-soft: #16301f; - --warning: #d9a441; - --warning-soft: #332a14; - --danger: #ef6b6b; - --danger-soft: #3a1c1c; - } -} +/* Color, type and shape tokens come from @frameloader/tokens (prepended at build). */ +:root { --titlebar: 0px; } body.mac { --titlebar: 38px; } * { box-sizing: border-box; } diff --git a/src/renderer/views/connect.ts b/apps/desktop/src/renderer/views/connect.ts similarity index 95% rename from src/renderer/views/connect.ts rename to apps/desktop/src/renderer/views/connect.ts index c9bbf40..ebc2fff 100644 --- a/src/renderer/views/connect.ts +++ b/apps/desktop/src/renderer/views/connect.ts @@ -1,4 +1,6 @@ import { h, icon } from "../dom"; + +const TERMS_URL = "https://www.frameloader.com/terms.html"; import { store, type State } from "../state"; import { cancelPair, connectSaved, connectWithPassword, pair, useDiscovered } from "../actions"; @@ -109,6 +111,13 @@ export function connectView(s: State): HTMLElement { found, f.mode === "pair" ? pairBlock : passwordBlock, f.error ? h("div", { class: "notice bad" }, f.error) : null, + h( + "p", + { class: "help" }, + "Frameloader is free software with no warranty. By connecting, you accept the ", + h("a", { href: TERMS_URL, target: "_blank", rel: "noreferrer" }, "terms of use"), + " and use it at your own risk.", + ), h( "div", { class: "row spread wrap" }, diff --git a/src/renderer/views/devices.ts b/apps/desktop/src/renderer/views/devices.ts similarity index 100% rename from src/renderer/views/devices.ts rename to apps/desktop/src/renderer/views/devices.ts diff --git a/src/renderer/views/drawer.ts b/apps/desktop/src/renderer/views/drawer.ts similarity index 100% rename from src/renderer/views/drawer.ts rename to apps/desktop/src/renderer/views/drawer.ts diff --git a/src/renderer/views/home.ts b/apps/desktop/src/renderer/views/home.ts similarity index 100% rename from src/renderer/views/home.ts rename to apps/desktop/src/renderer/views/home.ts diff --git a/src/renderer/views/install.ts b/apps/desktop/src/renderer/views/install.ts similarity index 100% rename from src/renderer/views/install.ts rename to apps/desktop/src/renderer/views/install.ts diff --git a/src/renderer/views/panel.ts b/apps/desktop/src/renderer/views/panel.ts similarity index 100% rename from src/renderer/views/panel.ts rename to apps/desktop/src/renderer/views/panel.ts diff --git a/src/renderer/views/review.ts b/apps/desktop/src/renderer/views/review.ts similarity index 100% rename from src/renderer/views/review.ts rename to apps/desktop/src/renderer/views/review.ts diff --git a/src/shared/format.ts b/apps/desktop/src/shared/format.ts similarity index 100% rename from src/shared/format.ts rename to apps/desktop/src/shared/format.ts diff --git a/src/shared/gameid.ts b/apps/desktop/src/shared/gameid.ts similarity index 100% rename from src/shared/gameid.ts rename to apps/desktop/src/shared/gameid.ts diff --git a/src/shared/ipc.ts b/apps/desktop/src/shared/ipc.ts similarity index 100% rename from src/shared/ipc.ts rename to apps/desktop/src/shared/ipc.ts diff --git a/src/types/app-info-parser.d.ts b/apps/desktop/src/types/app-info-parser.d.ts similarity index 100% rename from src/types/app-info-parser.d.ts rename to apps/desktop/src/types/app-info-parser.d.ts diff --git a/test/discovery.test.ts b/apps/desktop/test/discovery.test.ts similarity index 100% rename from test/discovery.test.ts rename to apps/desktop/test/discovery.test.ts diff --git a/test/fixtures/termux-window.apk b/apps/desktop/test/fixtures/termux-window.apk similarity index 100% rename from test/fixtures/termux-window.apk rename to apps/desktop/test/fixtures/termux-window.apk diff --git a/test/payload.test.ts b/apps/desktop/test/payload.test.ts similarity index 100% rename from test/payload.test.ts rename to apps/desktop/test/payload.test.ts diff --git a/tsconfig.base.json b/apps/desktop/tsconfig.base.json similarity index 100% rename from tsconfig.base.json rename to apps/desktop/tsconfig.base.json diff --git a/tsconfig.json b/apps/desktop/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/desktop/tsconfig.json diff --git a/tsconfig.main.json b/apps/desktop/tsconfig.main.json similarity index 100% rename from tsconfig.main.json rename to apps/desktop/tsconfig.main.json diff --git a/tsconfig.renderer.json b/apps/desktop/tsconfig.renderer.json similarity index 100% rename from tsconfig.renderer.json rename to apps/desktop/tsconfig.renderer.json diff --git a/vendor/devkit-utils/LICENSE b/apps/desktop/vendor/devkit-utils/LICENSE similarity index 100% rename from vendor/devkit-utils/LICENSE rename to apps/desktop/vendor/devkit-utils/LICENSE diff --git a/vendor/devkit-utils/README.md b/apps/desktop/vendor/devkit-utils/README.md similarity index 100% rename from vendor/devkit-utils/README.md rename to apps/desktop/vendor/devkit-utils/README.md diff --git a/vendor/devkit-utils/VALVE-README.md b/apps/desktop/vendor/devkit-utils/VALVE-README.md similarity index 100% rename from vendor/devkit-utils/VALVE-README.md rename to apps/desktop/vendor/devkit-utils/VALVE-README.md diff --git a/vendor/devkit-utils/devkit_utils/__init__.py b/apps/desktop/vendor/devkit-utils/devkit_utils/__init__.py similarity index 100% rename from vendor/devkit-utils/devkit_utils/__init__.py rename to apps/desktop/vendor/devkit-utils/devkit_utils/__init__.py diff --git a/vendor/devkit-utils/devkit_utils/resolve.py b/apps/desktop/vendor/devkit-utils/devkit_utils/resolve.py similarity index 100% rename from vendor/devkit-utils/devkit_utils/resolve.py rename to apps/desktop/vendor/devkit-utils/devkit_utils/resolve.py diff --git a/vendor/devkit-utils/steam-client-create-shortcut b/apps/desktop/vendor/devkit-utils/steam-client-create-shortcut similarity index 100% rename from vendor/devkit-utils/steam-client-create-shortcut rename to apps/desktop/vendor/devkit-utils/steam-client-create-shortcut diff --git a/vendor/devkit-utils/steam-devkit-rpc b/apps/desktop/vendor/devkit-utils/steam-devkit-rpc similarity index 100% rename from vendor/devkit-utils/steam-devkit-rpc rename to apps/desktop/vendor/devkit-utils/steam-devkit-rpc diff --git a/vendor/devkit-utils/steamos-delete b/apps/desktop/vendor/devkit-utils/steamos-delete similarity index 100% rename from vendor/devkit-utils/steamos-delete rename to apps/desktop/vendor/devkit-utils/steamos-delete diff --git a/vendor/devkit-utils/steamos-get-status b/apps/desktop/vendor/devkit-utils/steamos-get-status similarity index 100% rename from vendor/devkit-utils/steamos-get-status rename to apps/desktop/vendor/devkit-utils/steamos-get-status diff --git a/vendor/devkit-utils/steamos-list-games b/apps/desktop/vendor/devkit-utils/steamos-list-games similarity index 100% rename from vendor/devkit-utils/steamos-list-games rename to apps/desktop/vendor/devkit-utils/steamos-list-games diff --git a/vendor/devkit-utils/steamos-prepare-upload b/apps/desktop/vendor/devkit-utils/steamos-prepare-upload similarity index 100% rename from vendor/devkit-utils/steamos-prepare-upload rename to apps/desktop/vendor/devkit-utils/steamos-prepare-upload diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..8634791 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,166 @@ + + + + + + Frameloader: sideload apps onto your Steam Frame + + + + + + + + + + + + + + + +
+
+ For Steam Frame · Free and open source +

Drag. Drop. Play.

+

+ How do you sideload APK Android apps, Windows exe apps, Linux apps, and more to your Steam Frame for free + without installing sketchy closed-source shareware? You use Frameloader. +

+ +

macOS, Windows, and Linux · MIT licensed · Fully LAN

+ +
+
+ + + Frameloader's home screen: a drop zone above a list of three apps installed on the headset, each with a Play button. + +
+
+ + +
+
+
+

How to get loading

+
    +
  1. + 1 +
    Turn on Developer ModeSteam Settings → System → Enable Developer Mode, then set a user password under Developer.
    +
  2. +
  3. + 2 +
    PairOpen Settings → Developer → Pair new host on the headset, click Pair in Frameloader, and approve.
    +
  4. +
  5. + 3 +
    DropFrom then on it's drag, drop, Install. The title appears under Non-Steam in your library.
    +
  6. +
+

+ Frameloader automates the sideloading process that Valve built for developers to test apps on the Frame. + This lets you get apps into the Frame's library without needing to mess around with the terminal yourself every time. +

+
+
+
+ + + Reviewing an Android app before installing: its name, details like arm64 and VR app, the Lepton runtime, and an Install to Frame button. + +
+
+
+ +
+
+
+
+
+

Actually private - no analytics, no tracking, nothing.

+

Frameloader is a tiny piece of freeware that solves one problem - getting apps from your computer to your Steam Frame.

+
+
+
    +
  • No analytics, no telemetry, no crash reporting. Frameloader doesn't count you, profile you, or phone home.
  • +
  • No account. Nothing to sign up for and nothing to sign in to.
  • +
  • Talks only to your Frame. Its only connections are to the headset on your own network: SSH, Valve's pairing service, and local discovery.
  • +
  • This website doesn't track you either. No cookies, no analytics, no third-party fonts or scripts.
  • +
+

Don't take our word for it: read the code.

+
+
+ +
+
+

Open source, top to bottom

+

Yes it was written with the help of Claude, but it's also a straight forward bit of code. The whole app (and this site) is MIT licensed and you can read it, fork it, file an issue, send a PR.

+
+ +
+ +
+
+
+

Frameloader is 100% free and open source

+

+ I'm Zack - I made this because I wanted to solve this problem without having to + install sketchy closed-source software or dig into terminal all afternoon. +

+
+ + + Buy me a coffee + +
+
+
+ +
+ +
+ + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..88f0b74 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,21 @@ +{ + "name": "@frameloader/web", + "version": "0.1.0", + "private": true, + "description": "The Frameloader website.", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "typecheck": "true" + }, + "dependencies": { + "@frameloader/tokens": "workspace:*" + }, + "devDependencies": { + "vite": "^8.3.3" + }, + "homepage": "https://www.frameloader.com" +} diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..2f86c35 --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/apps/web/public/robots.txt b/apps/web/public/robots.txt new file mode 100644 index 0000000..4911c7d --- /dev/null +++ b/apps/web/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://www.frameloader.com/sitemap.xml diff --git a/apps/web/public/screenshots/home-dark.png b/apps/web/public/screenshots/home-dark.png new file mode 100644 index 0000000..f7ad97a Binary files /dev/null and b/apps/web/public/screenshots/home-dark.png differ diff --git a/apps/web/public/screenshots/home-light.png b/apps/web/public/screenshots/home-light.png new file mode 100644 index 0000000..0d5ea15 Binary files /dev/null and b/apps/web/public/screenshots/home-light.png differ diff --git a/apps/web/public/screenshots/review-dark.png b/apps/web/public/screenshots/review-dark.png new file mode 100644 index 0000000..b3872d0 Binary files /dev/null and b/apps/web/public/screenshots/review-dark.png differ diff --git a/apps/web/public/screenshots/review-light.png b/apps/web/public/screenshots/review-light.png new file mode 100644 index 0000000..b4675c0 Binary files /dev/null and b/apps/web/public/screenshots/review-light.png differ diff --git a/apps/web/public/sitemap.xml b/apps/web/public/sitemap.xml new file mode 100644 index 0000000..b237a94 --- /dev/null +++ b/apps/web/public/sitemap.xml @@ -0,0 +1,5 @@ + + + https://www.frameloader.com/ + https://www.frameloader.com/terms.html + diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css new file mode 100644 index 0000000..a73365c --- /dev/null +++ b/apps/web/src/styles.css @@ -0,0 +1,127 @@ +@import "@frameloader/tokens/tokens.css"; + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + font: 16px/1.6 var(--sans); + color: var(--text); + background: var(--page); + -webkit-font-smoothing: antialiased; +} +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } +h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.015em; line-height: 1.2; } +h1 { font-size: clamp(36px, 6vw, 56px); letter-spacing: -0.025em; } +h2 { font-size: clamp(24px, 3.2vw, 30px); } +h3 { font-size: 17px; } +p { margin: 0; } +.muted { color: var(--text-2); } +.small { font-size: 14px; } +svg { width: 18px; height: 18px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; } + +.wrap { width: 100%; max-width: 1040px; margin: 0 auto; padding: 0 16px; } +@media (min-width: 720px) { .wrap { padding: 0 24px; } } + +/* Nav */ +.nav { position: sticky; top: 0; z-index: 10; background: color-mix(in srgb, var(--page) 85%, transparent); backdrop-filter: saturate(1.4) blur(12px); border-bottom: 1px solid var(--border); } +.nav-inner { display: flex; align-items: center; justify-content: space-between; height: 60px; gap: 16px; } +.brand { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 16px; color: var(--text); } +.brand:hover { text-decoration: none; } +.logo { width: 28px; height: 28px; border-radius: 8px; background: var(--accent); color: #fff; display: grid; place-items: center; } +.logo svg { width: 17px; height: 17px; stroke-width: 2.2; } +.nav-links { display: flex; align-items: center; gap: 20px; font-size: 15px; } +.nav-links > a:not(.btn) { color: var(--text-2); } +.nav-links > a:not(.btn):hover { color: var(--text); text-decoration: none; } +@media (max-width: 640px) { .nav-links > a:not(.btn) { display: none; } } + +/* Buttons and chips */ +.btn { + display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px; border-radius: var(--radius-sm); + border: 1px solid var(--border-2); background: var(--card); color: var(--text); font-weight: 500; font-size: 15px; line-height: 1.2; + transition: background 120ms, border-color 120ms; +} +.btn:hover { background: var(--card-2); text-decoration: none; } +.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; } +.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); } +.btn.sm { padding: 6px 12px; font-size: 14px; } +.btn.lg { padding: 12px 20px; font-size: 16px; } +.chip { display: inline-flex; align-items: center; padding: 4px 12px; border-radius: 999px; font-size: 13.5px; font-weight: 500; } +.chip.accent { color: var(--accent); background: var(--accent-soft); } + +/* Hero */ +.hero { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 20px; padding-top: 72px; padding-bottom: 24px; } +.lede { font-size: clamp(17px, 2.2vw, 19px); color: var(--text-2); max-width: 620px; } +.cta { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; margin-top: 4px; } + +/* App window frame around screenshots */ +.window { + margin: 36px 0 0; width: 100%; max-width: 960px; border-radius: 14px; overflow: hidden; background: var(--card); + border: 1px solid var(--border-2); box-shadow: 0 24px 60px -20px rgba(16, 24, 40, 0.25), 0 2px 6px rgba(16, 24, 40, 0.05); +} +.window-bar { height: 34px; display: flex; align-items: center; gap: 8px; padding: 0 14px; border-bottom: 1px solid var(--border); background: var(--card); } +.window-bar span { width: 11px; height: 11px; border-radius: 50%; background: var(--border-2); } +.window img { display: block; width: 100%; height: auto; } + +/* Sections */ +.section { padding-top: 88px; } +.section-head { display: flex; flex-direction: column; gap: 8px; margin-bottom: 28px; max-width: 640px; } +.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); } +.grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); } +.grid.two { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); } +@media (max-width: 380px) { .grid, .grid.two { grid-template-columns: 1fr; } } +.feature { padding: 22px; display: flex; flex-direction: column; gap: 10px; } +.feature p { color: var(--text-2); font-size: 15px; } +.icon { width: 38px; height: 38px; border-radius: 10px; background: var(--accent-soft); color: var(--accent); display: grid; place-items: center; } +.icon.big { width: 48px; height: 48px; border-radius: 12px; flex: none; } +.icon.big svg { width: 24px; height: 24px; } + +/* Steps */ +.split { display: grid; gap: 40px; align-items: center; grid-template-columns: 1fr; } +@media (min-width: 880px) { .split { grid-template-columns: 0.9fr 1.1fr; } } +.steps { display: flex; flex-direction: column; gap: 20px; } +.steps ol { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; } +.steps li { display: flex; gap: 14px; align-items: flex-start; } +.steps li b { display: block; } +.steps li span:not(.num) { color: var(--text-2); font-size: 15px; } +.num { width: 28px; height: 28px; border-radius: 50%; background: var(--accent-soft); color: var(--accent); display: grid; place-items: center; font-size: 14px; font-weight: 600; flex: none; margin-top: 1px; } +.small-window { margin: 0; } + +/* Privacy */ +.privacy { padding: clamp(22px, 4vw, 40px); display: flex; flex-direction: column; gap: 24px; } +.privacy-head { display: flex; gap: 18px; align-items: flex-start; } +.privacy-head > div:last-child { display: flex; flex-direction: column; gap: 6px; } +.checks { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px 32px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } +@media (max-width: 380px) { .checks { grid-template-columns: 1fr; } } +.checks li { position: relative; padding-left: 30px; color: var(--text-2); font-size: 15px; } +.checks li b { color: var(--text); } +.checks li::before { + content: ""; position: absolute; left: 0; top: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--success-soft); + -webkit-mask: none; +} +.checks li::after { + content: ""; position: absolute; left: 7px; top: 7px; width: 5px; height: 9px; border: solid var(--success); border-width: 0 2px 2px 0; transform: rotate(45deg); +} + +/* Open source */ +.link-card { padding: 22px; display: flex; flex-direction: column; gap: 10px; color: var(--text); } +a.link-card:hover { text-decoration: none; border-color: var(--accent); } +.link-card p { font-size: 15px; } +.more { color: var(--accent); font-weight: 500; font-size: 15px; margin-top: auto; } + +/* Footer */ +.footer { margin-top: 88px; border-top: 1px solid var(--border); background: var(--card); } +.footer-inner { padding-top: 40px; padding-bottom: 40px; display: flex; flex-direction: column; gap: 28px; } +.support { + padding: clamp(24px, 4vw, 40px); display: flex; gap: 24px; align-items: center; justify-content: space-between; flex-wrap: wrap; + background: var(--accent-soft); border-color: color-mix(in srgb, var(--accent) 35%, transparent); +} +.support-text { display: flex; flex-direction: column; gap: 10px; max-width: 620px; } +.support-text p { font-size: 16px; } +.footer-meta { display: flex; flex-direction: column; gap: 6px; font-size: 14px; color: var(--text-2); } + +/* Terms page */ +.legal { max-width: 720px; padding-top: 56px; display: flex; flex-direction: column; gap: 18px; } +.legal h2 { font-size: 19px; margin-top: 12px; } +.legal p, .legal li { color: var(--text-2); } +.legal ul { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 6px; } diff --git a/apps/web/terms.html b/apps/web/terms.html new file mode 100644 index 0000000..0ac6482 --- /dev/null +++ b/apps/web/terms.html @@ -0,0 +1,62 @@ + + + + + + Frameloader terms of use + + + + + + + + + +
+

Terms of use

+

Last updated: October 7, 2026

+

Frameloader is free, open-source software released under the MIT License. By using it you accept these terms. They're short on purpose.

+ +

No warranty

+

Frameloader is provided "as is", without warranty of any kind, express or implied, including any warranty that it is fit for a particular purpose, works with your headset, or is free of bugs.

+ +

Use at your own risk

+

Frameloader connects to your Steam Frame over your local network using Developer Mode, copies files to it, and registers them with Steam. Sideloaded apps may not run, may misbehave, or may crash your headset. You are responsible for what you install and for backing up anything you care about.

+ +

Limitation of liability

+

To the fullest extent the law allows, the authors and contributors are not liable for any damage or loss arising from your use of Frameloader, including damage to devices, lost data, or actions taken on your accounts.

+ +

Your responsibilities

+

Only install software you have the right to use, and follow its license. Steam's and Valve's own terms still apply to your headset and your Steam account.

+ +

Not affiliated with Valve

+

Frameloader is an independent project. Valve, Steam, and Steam Frame are trademarks of Valve Corporation.

+ +

Privacy

+

Frameloader collects no data. It has no analytics, telemetry, or accounts, and it only talks to your headset on your local network.

+ +

Changes

+

These terms may be updated. The current version is always at frameloader.com/terms.html.

+
+ +
+ +
+ + diff --git a/apps/web/vite.config.js b/apps/web/vite.config.js new file mode 100644 index 0000000..cb23e7d --- /dev/null +++ b/apps/web/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from "vite"; +import { resolve } from "node:path"; + +export default defineConfig({ + // Relative asset URLs so the built site works from any path (GitHub Pages, a subfolder, a file share). + base: "./", + server: { port: 5180 }, + build: { + outDir: "dist", + assetsInlineLimit: 0, + rollupOptions: { + input: { + main: resolve(import.meta.dirname, "index.html"), + terms: resolve(import.meta.dirname, "terms.html"), + }, + }, + }, +}); diff --git a/package.json b/package.json index 1adde8d..b5fdd60 100644 --- a/package.json +++ b/package.json @@ -1,89 +1,17 @@ { - "name": "frameloader", - "productName": "Frameloader", - "version": "0.1.0", - "description": "Drop an APK, Windows app, or Linux build onto your Steam Frame and it shows up in your Steam library.", - "license": "MIT", - "author": "Zack ", + "name": "frameloader-monorepo", "private": true, - "main": "dist/main.cjs", - "type": "module", + "license": "MIT", + "packageManager": "pnpm@11.24.0", "scripts": { - "build": "node scripts/build.mjs", - "dev": "node scripts/build.mjs --watch", - "start": "pnpm build && electron .", - "typecheck": "tsc -p tsconfig.main.json --noEmit && tsc -p tsconfig.renderer.json --noEmit", - "test": "pnpm build && node --test dist/test/*.test.cjs", - "dist": "pnpm build && electron-builder --publish never", - "dist:mac": "pnpm build && electron-builder --mac --arm64 --publish never", - "dist:win": "pnpm build && electron-builder --win --x64 --publish never", - "dist:linux": "pnpm build && electron-builder --linux --x64 --arm64 --publish never" - }, - "dependencies": { - "app-info-parser": "^1.1.6", - "multicast-dns": "^7.2.5", - "ssh2": "^1.17.0", - "yauzl": "^3.2.0" - }, - "devDependencies": { - "@types/multicast-dns": "^7.2.4", - "@types/node": "^24.0.0", - "@types/ssh2": "^1.15.6", - "@types/yauzl": "^2.10.3", - "electron": "^44.5.1", - "electron-builder": "^26.15.3", - "esbuild": "^0.28.2", - "typescript": "^7.0.2" - }, - "build": { - "appId": "dev.traveler.frameloader", - "productName": "Frameloader", - "directories": { - "output": "release", - "buildResources": "build" - }, - "files": [ - "dist/**/*", - "package.json", - "!dist/test/**" - ], - "extraResources": [ - { - "from": "vendor/devkit-utils", - "to": "devkit-utils", - "filter": [ - "**/*", - "!**/__pycache__/**" - ] - } - ], - "mac": { - "category": "public.app-category.utilities", - "target": [ - "dmg", - "zip" - ], - "artifactName": "Frameloader-mac-${arch}.${ext}" - }, - "win": { - "target": [ - "nsis", - "zip" - ], - "artifactName": "Frameloader-win-${arch}.${ext}" - }, - "nsis": { - "oneClick": false, - "perMachine": false, - "allowToChangeInstallationDirectory": true - }, - "linux": { - "target": [ - "AppImage", - "deb" - ], - "category": "Utility", - "artifactName": "Frameloader-linux-${arch}.${ext}" - } + "build": "pnpm -r build", + "typecheck": "pnpm -r typecheck", + "test": "pnpm --filter @frameloader/desktop test", + "start": "pnpm --filter @frameloader/desktop start", + "dev:web": "pnpm --filter @frameloader/web dev", + "build:web": "pnpm --filter @frameloader/web build", + "dist:mac": "pnpm --filter @frameloader/desktop dist:mac", + "dist:win": "pnpm --filter @frameloader/desktop dist:win", + "dist:linux": "pnpm --filter @frameloader/desktop dist:linux" } } diff --git a/packages/tokens/package.json b/packages/tokens/package.json new file mode 100644 index 0000000..780e795 --- /dev/null +++ b/packages/tokens/package.json @@ -0,0 +1,11 @@ +{ + "name": "@frameloader/tokens", + "version": "0.1.0", + "private": true, + "description": "Shared color, type and shape tokens for the Frameloader app and website.", + "license": "MIT", + "exports": { + "./tokens.css": "./tokens.css" + }, + "files": ["tokens.css"] +} diff --git a/packages/tokens/tokens.css b/packages/tokens/tokens.css new file mode 100644 index 0000000..1b7d3e1 --- /dev/null +++ b/packages/tokens/tokens.css @@ -0,0 +1,46 @@ +/* Frameloader design tokens, shared by the desktop app and the website. */ +:root { + --page: #f5f6f8; + --card: #ffffff; + --card-2: #f9fafb; + --text: #1b1f24; + --text-2: #5c6470; + --text-3: #8a919c; + --border: #e3e6ea; + --border-2: #d3d7dd; + --accent: #2e6be6; + --accent-hover: #255cc9; + --accent-soft: #e9f0fd; + --success: #1f9d55; + --success-soft: #e6f6ec; + --warning: #b7791f; + --warning-soft: #fdf3e1; + --danger: #d23f3f; + --danger-soft: #fdeaea; + --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, Roboto, sans-serif; + --radius: 12px; + --radius-sm: 8px; + --shadow: 0 1px 2px rgba(16, 24, 40, 0.04); +} +@media (prefers-color-scheme: dark) { + :root { + --page: #121417; + --card: #1b1e23; + --card-2: #20242a; + --text: #e8eaed; + --text-2: #a4abb5; + --text-3: #6f7782; + --border: #2a2f37; + --border-2: #363c46; + --accent: #5b8df0; + --accent-hover: #74a0f3; + --accent-soft: #1d2a45; + --success: #3fbf74; + --success-soft: #16301f; + --warning: #d9a441; + --warning-soft: #332a14; + --danger: #ef6b6b; + --danger-soft: #3a1c1c; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9ef46d..5f3001d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,21 +6,13 @@ settings: importers: - .: - dependencies: - app-info-parser: - specifier: ^1.1.6 - version: 1.1.6 - multicast-dns: - specifier: ^7.2.5 - version: 7.2.5 - ssh2: - specifier: ^1.17.0 - version: 1.17.0 - yauzl: - specifier: ^3.2.0 - version: 3.4.0 + .: {} + + apps/desktop: devDependencies: + '@frameloader/tokens': + specifier: workspace:* + version: link:../../packages/tokens '@types/multicast-dns': specifier: ^7.2.4 version: 7.2.4 @@ -33,8 +25,11 @@ importers: '@types/yauzl': specifier: ^2.10.3 version: 2.10.3 + app-info-parser: + specifier: ^1.1.6 + version: 1.1.6 electron: - specifier: ^44.5.1 + specifier: 44.5.1 version: 44.5.1(supports-color@7.2.0) electron-builder: specifier: ^26.15.3 @@ -42,9 +37,30 @@ importers: esbuild: specifier: ^0.28.2 version: 0.28.2 + multicast-dns: + specifier: ^7.2.5 + version: 7.2.5 + ssh2: + specifier: ^1.17.0 + version: 1.17.0 typescript: specifier: ^7.0.2 version: 7.0.2 + yauzl: + specifier: ^3.2.0 + version: 3.4.0 + + apps/web: + dependencies: + '@frameloader/tokens': + specifier: workspace:* + version: link:../../packages/tokens + devDependencies: + vite: + specifier: ^8.3.3 + version: 8.3.3(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.7.0) + + packages/tokens: {} packages: @@ -271,6 +287,9 @@ packages: resolution: {integrity: sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==} engines: {node: '>= 20.19.0'} + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + '@peculiar/asn1-schema@2.10.0': resolution: {integrity: sha512-GhokD41lV4gQrrLYm3wCkHfBOnJrnhDMgt4XeMW8gzfE1UdJqIuSwsE+ggf82XBjUXRJylcm+KIGQFa4utIVLw==} engines: {node: '>=14'} @@ -286,6 +305,105 @@ packages: resolution: {integrity: sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==} engines: {node: '>=14.18.0'} + '@rolldown/binding-android-arm-eabi@1.2.12': + resolution: {integrity: sha512-dB/a1214qKfHMXCpgqR4OZT+jS4kTyEXbQGJPqzobt5EwH5rX080pxE37alt3RzvR1bf1Yz/yGqRfrYAxuPw0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.12': + resolution: {integrity: sha512-7KHFgQ5VJxIHcLlrwrc3Xbds7oTNQT7Pgi9gQCJKrd2VGab/UksIOYp6VD8MzCstGxOKMgNamPwUCfxPdP1OHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.12': + resolution: {integrity: sha512-3YIhqHD96nA5SaYNRBR16HnGv4oavZvXfD/ayHM+oYZ0WD/8lBAtf6zQua4kEyAvpqrluKXl0lnOBoiNby7x9w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.12': + resolution: {integrity: sha512-UuuJ35MFw4gmFOrE9pEqIV+K3syIKveph+Qc1/ljHZVdoDW4pz/JHR/eMVom+TZGl/5OOvGJOWaOCVt3ZfqhxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.12': + resolution: {integrity: sha512-uMvssit0a4W+/7D8CbHUvG719mH3R2jwXAlh/XcPvuHTE0g++LymF88DCGNX0HM2rBOn0xrzgXktIB6fLSJBTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + resolution: {integrity: sha512-XcFu0R0xWnwzSf4IQgFH1rJIckPN1pLy2R+4r9IDB7Yfu/ys9cVqfa4pBrMHj7a3gl8mIR4nRNPg0e5IvEVs6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + resolution: {integrity: sha512-260UrKgn8tz39ak+SMDOirKzr7V04M9dWPw5llW00SwBivCZoWcRBKV1d8cXnRkUmSZA3BdiUmBHWk7734Ulpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.12': + resolution: {integrity: sha512-5YK1I9SqDkbPgc1IA8BgDl34suqUS2q0KWnBrirm0E51YjOs6eo6dV6jbQfNE/argHRSvd0QUGgtpIoYx+WWpw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + resolution: {integrity: sha512-Rkcrmp7eFRg74yL5fXEU91JEWbdEPLevWwGtXpmhbjlD1StScbWTmO94Bhly+Mo+ketKYkdmM1vNUKeWSlx8cQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + resolution: {integrity: sha512-qvK4DuAsQc2BSjlx+Xr+IzOIvvxbGZqxFwdWfG6F518Erj0GGISyQbJ6pIappnOxlNPzNHvo/L0BwB30GZ+zVw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.12': + resolution: {integrity: sha512-Q9uLBO53Xd4QIq1WOycVQyPP1O4HhraEV2qqb3uTrnVw6QZih9duY4vNXOivL1xoUS1/z+W8eF4NMfl2a8Sdjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.12': + resolution: {integrity: sha512-3IBxWFMjbOZskDPKv8Lf9BCnahlKuHthWkYnyIxOH/QcJrFcS4EmcenthApkwr/5+nEqZlLzeYbxeMaX7A5u4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.12': + resolution: {integrity: sha512-xtX61xg4LKPkPWilZU1ynKClz5Gj4bf74LML4r3eVLWumKnGjoEr1OSHQhMdbBDoYTi+yjrujvpZe2pUnqCrrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + resolution: {integrity: sha512-At7fPB6PCaIjzgIhEZFxuT+BBFqiQibJDT4d3PhiR3f4E7bbMZF4aKblbFfEM3sETRDd1YiQx/+U/g/B/ou5Ew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.12': + resolution: {integrity: sha512-WIw2haVKwjuYdXkHaoC0mF8Le71TuCBxjrdKqLbJGctbBABj+ClfmNvtbOnzpq3RokNo5+V1qhtSzJyXorsklQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -701,6 +819,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -858,6 +980,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1016,6 +1143,80 @@ packages: lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -1100,6 +1301,11 @@ packages: nan@2.29.0: resolution: {integrity: sha512-GlGk3HIvitbvs+LT3g6XUP1kpirKNvmDFwF/bmo6XNWSb/eYEs/O4bfgIEIXCZ+lIOTS5xNwDvSGMw6FJdAhtA==} + nanoid@3.3.20: + resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + node-abi@4.35.0: resolution: {integrity: sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==} engines: {node: '>=22.12.0'} @@ -1169,6 +1375,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + postcss@8.5.29: + resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} + engines: {node: ^10 || ^12 || >=14} + postject@1.0.0-alpha.6: resolution: {integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==} engines: {node: '>=14.0.0'} @@ -1244,6 +1454,11 @@ packages: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} + rolldown@1.2.12: + resolution: {integrity: sha512-8wafseiaG80xmXSfqidUNqZcylTlhmPZZt+za2m+js2sFZ8dTNlhIOV2WcbIPx2hgwPBJpEUGFAMZ9bgBBLTSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -1297,6 +1512,10 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + source-map-js@1.2.2: + resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} + engines: {node: '>=0.10.0'} + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -1423,6 +1642,49 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite@8.3.3: + resolution: {integrity: sha512-cTAldKPImjg6c+gk48U19POPn3GCBzZwpdsN8ZMEEcbpes+6/wvfqUd0C2y3qYY4wsj8PwgFwrZ/1jBPVttMSg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.7.1 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + webcrypto-core@1.9.2: resolution: {integrity: sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==} @@ -1679,6 +1941,8 @@ snapshots: '@noble/hashes@2.4.0': {} + '@oxc-project/types@0.152.0': {} + '@peculiar/asn1-schema@2.10.0': dependencies: '@peculiar/utils': 2.0.3 @@ -1701,6 +1965,53 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.9.2 + '@rolldown/binding-android-arm-eabi@1.2.12': + optional: true + + '@rolldown/binding-android-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.12': + optional: true + + '@rolldown/binding-darwin-x64@1.2.12': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.12': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.12': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.12': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.12': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.12': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.12': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.12': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + '@sindresorhus/is@4.6.0': {} '@szmarczak/http-timer@4.0.6': @@ -2114,6 +2425,8 @@ snapshots: delayed-stream@1.0.0: {} + detect-libc@2.1.2: {} + detect-node@2.1.0: optional: true @@ -2344,6 +2657,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} get-caller-file@2.0.5: {} @@ -2520,6 +2836,55 @@ snapshots: lazy-val@1.0.5: {} + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + lodash@4.18.1: {} long@3.2.0: {} @@ -2587,6 +2952,8 @@ snapshots: nan@2.29.0: optional: true + nanoid@3.3.20: {} + node-abi@4.35.0: dependencies: semver: 7.8.5 @@ -2656,6 +3023,12 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + postcss@8.5.29: + dependencies: + nanoid: 3.3.20 + picocolors: 1.1.1 + source-map-js: 1.2.2 + postject@1.0.0-alpha.6: dependencies: commander: 9.5.0 @@ -2737,6 +3110,27 @@ snapshots: sprintf-js: 1.1.3 optional: true + rolldown@1.2.12: + dependencies: + '@oxc-project/types': 0.152.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.12 + '@rolldown/binding-android-arm64': 1.2.12 + '@rolldown/binding-darwin-arm64': 1.2.12 + '@rolldown/binding-darwin-x64': 1.2.12 + '@rolldown/binding-freebsd-x64': 1.2.12 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.12 + '@rolldown/binding-linux-arm64-gnu': 1.2.12 + '@rolldown/binding-linux-arm64-musl': 1.2.12 + '@rolldown/binding-linux-ppc64-gnu': 1.2.12 + '@rolldown/binding-linux-s390x-gnu': 1.2.12 + '@rolldown/binding-linux-x64-gnu': 1.2.12 + '@rolldown/binding-linux-x64-musl': 1.2.12 + '@rolldown/binding-openharmony-arm64': 1.2.12 + '@rolldown/binding-win32-arm64-msvc': 1.2.12 + '@rolldown/binding-win32-x64-msvc': 1.2.12 + safe-buffer@5.1.2: {} safer-buffer@2.1.2: {} @@ -2775,6 +3169,8 @@ snapshots: dependencies: semver: 7.8.5 + source-map-js@1.2.2: {} + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -2921,6 +3317,19 @@ snapshots: util-deprecate@1.0.2: {} + vite@8.3.3(@types/node@24.19.1)(esbuild@0.28.2)(jiti@2.7.0): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.29 + rolldown: 1.2.12 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.19.1 + esbuild: 0.28.2 + fsevents: 2.3.3 + jiti: 2.7.0 + webcrypto-core@1.9.2: dependencies: '@peculiar/asn1-schema': 2.10.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 340557a..40b1cb3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,6 @@ +packages: + - apps/* + - packages/* nodeLinker: hoisted allowBuilds: electron: true @@ -5,3 +8,5 @@ allowBuilds: ssh2: true cpu-features: true electron-winstaller: false +minimumReleaseAgeExclude: + - vite@8.3.3