diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index ee9cf88..2aa3295 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,14 +1,16 @@ # Release matrix — INERT until act_runner instances with these labels are # registered on git.teknes.es. Each OS builds its own artifacts (electrobun # cannot cross-compile: packaging needs codesign/hdiutil on macOS and -# rcedit/PowerShell on Windows). +# rcedit/PowerShell on Windows). No mac-x64: Electrobun 2.x has no macOS x64 +# core. # # Runner prerequisites: -# macos — arm64 Mac: bun, Xcode CLT, Rosetta 2 (softwareupdate -# --install-rosetta), network to github.com (electrobun downloads -# its per-arch core binaries on first build) -# windows — Windows 11 x64: bun, network to github.com; WebView2 runtime is -# preinstalled on Win 11 +# macos — Apple Silicon Mac: bun, Xcode CLT, network to github.com and the +# Electrobun/Hutch release hosts (`bun install` runs `electrobun +# prepare`; the first build per Electrobun version fills ~/.hutch +# with Hutch, the core, Cottontail and the pinned Bun, ~200 MB) +# windows — Windows 11 x64: bun, the same network access; WebView2 runtime +# is preinstalled on Win 11 # # Trigger: push a tag like build-62 (git tag build-$(git rev-list --count HEAD)). name: release @@ -26,7 +28,6 @@ jobs: fetch-depth: 0 # full history — the build number is the commit count - run: bun install --frozen-lockfile - run: bun scripts/release.ts --target mac-arm64 - - run: bun scripts/release.ts --target mac-x64 - uses: actions/upload-artifact@v3 with: name: mac diff --git a/.gitignore b/.gitignore index a46f740..5f2d73e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ ui/dist/ ui/.angular/ src/shared/version.gen.ts release/ +# Hutch-generated (electrobun 2.x): the projected SDK devkit and build scratch +.hutch/ +.cottontail-tmp/ diff --git a/README.md b/README.md index aa449e8..56dcf4f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Dense reference material lives in [docs/](docs/): [architecture](docs/architectu | Layer | Choice | | ---------- | ------------------------------------------------------------- | | Runtime | Bun (main process, all SSH/SFTP + file IO) | -| Desktop | Electrobun 1.18 (native webview, typed RPC bridge) | +| Desktop | Electrobun 2.0 via Hutch (native webview, typed RPC bridge) | | Frontend | Angular 22 (standalone, signals, zoneless) in `ui/` | | Styling | Tailwind CSS v4, Oxide-derived tokens (no component library) | | Icons | @ng-icons/tdesign-icons | @@ -83,6 +83,10 @@ ui/src/app/ Angular components + signal services Details and diagnostics in [platform notes](docs/platform-notes.md). - No Node install required — a preload shim (`scripts/node-version-shim.ts`) satisfies the Angular CLI's Node version check while running under Bun. +- Network on the first `bun install`: Electrobun 2.x's npm package is a + bootstrap that downloads its build tool (Hutch), the Electrobun core and the + pinned Bun runtime into `~/.hutch` (~200 MB), and projects the SDK into the + gitignored `.hutch/devkit/`. ## Setup @@ -107,22 +111,19 @@ Versioning is Sublime-style: **Build n**, where n is the commit count `src/shared/version.gen.ts` by every build script (and `postinstall`). One artifact per invocation, on the matching host — electrobun cannot -cross-compile between OSes: +cross-compile (Hutch builds for its own OS and arch): ```sh -bun run release -- --target mac-arm64 # on the Mac (native) -bun run release -- --target mac-x64 # on the same arm64 Mac, via Rosetta 2 +bun run release -- --target mac-arm64 # on an Apple Silicon Mac bun run release -- --target win-x64 # on a Windows 11 x64 machine bun run release -- --target linux-x64 # on a Linux x64 machine ``` Artifacts land in `release//miniSCP-Build-.*` — a `.dmg` -per mac arch; on Windows a `-Setup.zip` installer **and** a `-portable.zip` -(unzip anywhere and run); on Linux a portable `.tar.gz` (unpack and run -`miniSCP.sh`, which sets the NVIDIA workaround then execs `bin/launcher`). -The mac-x64 build fetches electrobun's x64 CLI once -into `~/.cache/miniscp/` and runs it under Rosetta so the whole pipeline -believes it is on an Intel Mac. CI: `.gitea/workflows/release.yml` runs the +on the Mac; on Windows a `-Setup.zip` installer **and** a `-portable.zip` +(unzip anywhere and run); on Linux Electrobun's `-Setup.tar.gz` installer. +There is no Intel Mac build: Electrobun 2.x publishes no macOS x64 core. +CI: `.gitea/workflows/release.yml` runs the same script on a macos + windows runner matrix once act_runners are registered; trigger by pushing a `build-` tag. @@ -137,7 +138,7 @@ compiled from `assets/icon.iconset` into `AppIcon.icns` automatically. For a distributable (optimized, with auto-update channel) build, use `bunx electrobun build --env=stable` (see the -[Electrobun docs](https://framework.blackboard.sh/electrobun/) for signing, +[Electrobun docs](https://electrobun.dev/electrobun/) for signing, notarization, and release hosting). ## Rebranding @@ -233,11 +234,12 @@ electrobun APIs the app uses are implemented on Windows; known issues are upstream: - **HiDPI blur** ([electrobun#324](https://github.com/blackboardsh/electrobun/issues/324)): - WebView2 renders at 1x on scaled displays until upstream adds DPI awareness. + WebView2 rendered at 1x on scaled displays on Electrobun 1.18; 2.x ships + per-monitor DPI setup — to re-check on the first 2.x Windows build. - **Notifications** use legacy balloon style, not Windows toast. - **Unsigned binaries**: both the Setup.exe installer and the portable exe - trigger SmartScreen/Mark-of-the-Web warnings on first run. No Start Menu - entry or uninstaller (installer unpacks to `%LOCALAPPDATA%`). + trigger SmartScreen/Mark-of-the-Web warnings on first run. The installer + unpacks to `%LOCALAPPDATA%` and, since Electrobun 2.x, ships an uninstaller. - `connections.json` is protected with an `icacls` current-user ACL instead of POSIX 0600 (which is meaningless on Windows). @@ -249,7 +251,8 @@ portable zip runs from any folder. ## Linux (beta) -Linux x64 is supported as a **beta** target and builds a portable tarball. +Linux x64 is supported as a **beta** target and ships Electrobun's own +installer (`-Setup.tar.gz`). One hard requirement: the AppIndicator library listed under [Prerequisites](#prerequisites) — electrobun links it for the tray, so without it the app aborts at startup with `ERR_DLOPEN_FAILED` rather than merely losing @@ -260,12 +263,12 @@ its tray icon. HTML, so nothing is missing in practice. - **Tray visibility** depends on the desktop: KDE, XFCE, MATE and Cinnamon show it; GNOME 3.26+ needs the AppIndicator extension. The app works either way. -- **Unsigned and unpackaged**: no AppImage, `.deb`, desktop entry or icon - installation — unpack the tarball and run `miniSCP.sh`. +- **Unsigned**, and no AppImage or `.deb` — unpack the `-Setup.tar.gz` and run + its `installer`. - **NVIDIA GPUs** need `WEBKIT_DISABLE_DMABUF_RENDERER=1` or the window renders - black. Every launch path (dev scripts and the packaged `miniSCP.sh` wrapper) - sets it automatically when an NVIDIA driver is detected — see - [gotchas](docs/gotchas.md#linux) if you add a new way to launch the app. + black. The dev scripts set it automatically when an NVIDIA driver is + detected; an installed build needs it set by hand (e.g. in its desktop + entry) — see [gotchas](docs/gotchas.md#linux). Full dependency table, diagnostics and the first-boot smoke test: [platform notes](docs/platform-notes.md). @@ -273,17 +276,17 @@ Full dependency table, diagnostics and the first-boot smoke test: ## Known issues - **Rare native crash under load.** A long-running session (multiple remote - connections, extended uptime) can hit a Bun panic — a segfault inside Bun + connections, extended uptime) could hit a Bun panic — a segfault inside Bun 1.3.13's FFI thread-safe callbacks executing Electrobun's native→worker bridge (`FFI_Callback_threadsafe_call` → `web_worker.spin` in the decoded crash report). This is an upstream Bun × Electrobun bug, not app code; a - restart recovers cleanly and in-flight work isn't corrupted. Expected to - resolve when Electrobun bumps its pinned Bun (currently 1.3.13; 1.3.14+ - available). Bun's own crash reporter link is printed to the console on panic. + restart recovers cleanly and in-flight work isn't corrupted. Electrobun 2.0.2 + pins Bun 1.4.0, which may have fixed it — not yet confirmed under load. Bun's + own crash reporter link is printed to the console on panic. ## Not in v1 Drag-and-drop, file preview/edit, transfer resume, symlink following (symlinks are listed but skipped by transfers), OS keychain, tab persistence -across restarts, Linux desktop integration (AppImage/.deb/.desktop entry — -the release is a portable tarball). +across restarts, Linux AppImage/.deb packages (the release is Electrobun's +Setup installer). diff --git a/bun.lock b/bun.lock index 15dd5b0..4aac6b5 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,6 @@ "": { "name": "bunscp", "dependencies": { - "electrobun": "1.18.1", "ssh-config": "^5.2.0", "ssh2": "^1.17.0", }, @@ -13,6 +12,7 @@ "@resvg/resvg-js": "^2.6.2", "@types/bun": "latest", "@types/ssh2": "^1.15.5", + "electrobun": "2.0.2", "png-to-ico": "^3.0.2", "typescript": "~5.9.3", }, @@ -133,8 +133,6 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], @@ -253,8 +251,6 @@ "@lmdb/lmdb-win32-x64": ["@lmdb/lmdb-win32-x64@3.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw=="], - "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="], - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], @@ -475,8 +471,6 @@ "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.2", "@tailwindcss/oxide": "4.3.2", "postcss": "^8.5.15", "tailwindcss": "4.3.2" } }, "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g=="], - "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="], - "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], @@ -497,7 +491,7 @@ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agent-base": ["agent-base@9.0.0", "", {}, "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -513,14 +507,10 @@ "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], - "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], - "basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="], - "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], "beasties": ["beasties@0.4.2", "", { "dependencies": { "css-select": "^6.0.0", "css-what": "^7.0.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3", "postcss-safe-parser": "^7.0.1" } }, "sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw=="], @@ -583,18 +573,12 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="], - "css-select": ["css-select@6.0.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "nth-check": "^2.1.1" } }, "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw=="], "css-what": ["css-what@7.0.0", "", {}, "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ=="], - "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], @@ -611,7 +595,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electrobun": ["electrobun@1.18.1", "", { "dependencies": { "@babylonjs/core": "^7.45.0", "@types/bun": "^1.3.8", "png-to-ico": "^2.1.8", "proxy-agent": "^6.5.0", "rcedit": "^4.0.1", "three": "^0.165.0" }, "bin": { "electrobun": "bin/electrobun.cjs" } }, "sha512-tgZ+WKGskn/1/Y5i1mpVCCkgRa1O31Tz7MIArBTK44GfPzT43uOq9c/HvxdQGrLeZV8ZvjK1lQrwqSXCgh6tvA=="], + "electrobun": ["electrobun@2.0.2", "", { "bin": { "electrobun": "bin/electrobun.cjs" } }, "sha512-UDs2iTNs6qYCzdWiFch/0DtxrsijNDr1WFDQfRmSA3Iouv0iEzyCF5mh4uDxkwNnAbuwFyow36EiVTYP4DRzig=="], "electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="], @@ -639,14 +623,6 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="], - - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - - "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - - "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], @@ -695,8 +671,6 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="], - "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], @@ -737,8 +711,6 @@ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], @@ -751,8 +723,6 @@ "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], - "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], @@ -807,7 +777,7 @@ "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], - "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -859,8 +829,6 @@ "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - "netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="], - "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], "node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], @@ -903,10 +871,6 @@ "p-map": ["p-map@7.0.5", "", {}, "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA=="], - "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="], - - "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="], - "pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], @@ -945,18 +909,12 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="], - - "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], - "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="], - "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], @@ -1041,8 +999,6 @@ "tar": ["tar@7.5.19", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="], - "three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="], - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], @@ -1105,10 +1061,10 @@ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@npmcli/agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "@npmcli/agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], @@ -1145,18 +1101,18 @@ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "electrobun/png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="], - "hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], - "https-proxy-agent/agent-base": ["agent-base@9.0.0", "", {}, "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA=="], + "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], "log-update/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -1165,16 +1121,14 @@ "ora/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], - "pac-proxy-agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "png-to-ico/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], - "proxy-agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + "socks-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "ui/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], @@ -1187,16 +1141,10 @@ "@angular/compiler-cli/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], - "electrobun/png-to-ico/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="], - - "electrobun/png-to-ico/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="], - "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], diff --git a/docs/architecture.md b/docs/architecture.md index 3a18589..9e74d4d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,15 +6,21 @@ Dense reference for development and debugging. Companion files: ## Process model -- **Bun main process** (`src/bun/`): electrobun runs it inside a WebWorker of - its native launcher. Owns every privileged capability: filesystem, SSH, - window management, native menus/dialogs/tray, the connection/settings stores. +- **Bun main process** (`src/bun/`): the native launcher runs electrobun's + `main.js` on the Bun the 2.x devkit pins (1.4.0, shipped in the bundle — + `build.mainProcess: "bun"`), which starts our bundle in a Worker. Imports the + SDK as `electrobun/main` (projected into `.hutch/devkit/`). Owns every + privileged capability: filesystem, SSH, window management, native + menus/dialogs/tray, the connection/settings stores. - **Webviews** (`ui/` Angular): one per window, system WebKit on macOS / WebView2 on Windows / WebKitGTK on Linux. Zero privileged access — everything goes through RPC. - **RPC transport**: one WebSocket server in the bun process, multiplexed by - `webviewId` (each webview auto-connects using natively injected globals). - Requests are typed by the `AppRPC` schema in `src/shared/rpc.ts`. + `webviewId` (each webview auto-connects using natively injected globals; + packets AES-GCM-encrypted per webview). 2.x Electroview queues sends while + its `hostSocket` connects and falls back to a native bridge when the socket + is unavailable. Requests are typed by the `AppRPC` schema in + `src/shared/rpc.ts`. ### The one-schema rule @@ -39,7 +45,9 @@ Settings window). `rpc.send.*` on an instance targets exactly its own webview. exist until after construction). `AppDeps.events` is a mutable object of no-op functions, reassigned in `src/bun/index.ts` after everything exists: `sessionClosed`, `connectionsChanged`, `openEditor`, `settingsChanged`, -`openConnectionManager`, `openSettings`, `showAbout`. +`openConnectionManager`, `openSettings`, `showAbout`, `applyUpdate` (the +update restart lives in index.ts beside the before-quit guard it must get past — +see build-and-release.md "Auto-updater"). ## Window kinds @@ -50,9 +58,9 @@ scale so the first paint is already right. `ui/src/main.ts` picks the root component from `APP_MODE`; all four roots use selector `app-root` (index.html hosts only that tag). `chromeOptions()` is the ONLY place the native frame is decided, spread by the main window and `openSecondaryWindow`: macOS `hiddenInset` + -`trafficLightOffset {x:12,y:13}` (the UI renders an `h-10 pl-24` drag-region -header via `IS_MAC_CHROME`); other platforms keep the native titlebar and a -compact `h-9` non-drag header. +`trafficLightOffset {x:12,y:4}` (2.x semantics — see gotchas; the UI renders an +`h-10 pl-24` drag-region header via `IS_MAC_CHROME`); other platforms keep the +native titlebar and a compact `h-9` non-drag header. `WindowRegistry` (same file): keyed window map with dedupe/activate, cascade placement off an anchor frame, focus tracking. electrobun's emitter @@ -67,8 +75,9 @@ through a concrete session, and `local` is one shared session anyway). Re-opening focuses the existing window. The webview fetches its own content (`readFile`) — `openEditor` just creates the window with ctx in the preload. -**Close safety** (electrobun window `close` is NOT cancellable — verified, no -`allow` response like `before-quit` has): +**Close safety** (designed when electrobun window `close` was NOT cancellable — +1.18.1 had no `allow` response like `before-quit`; 2.x adds a cancellable +`will-close` for the titlebar close, not used yet — see gotchas): 1. ⌘W (routed via the app menu → `editorCloseRequest`) / Close button → styled in-window Save/Don't Save/Cancel; the confirm path calls `editorClose` which sets `suppressPrompt` before `win.close()`. @@ -92,7 +101,9 @@ not `findInPage`, which is a shim on WebView2). ### Connection-manager window (`src/bun/manager-window.ts`) Single instance, centered over main, **modal to main only** (editors stay -usable). No native modality exists, so three layers approximate it: bun +usable). No native modality exists (re-checked on 2.0.2: no parent/owner/sheet +option or input lock in any SDK or the core's exports; `styleMask.DocModalWindow` +is only the cosmetic NSWindow bit), so three layers approximate it: bun bounces main's `focus` event back to the manager; the main webview renders a click-swallowing overlay (`managerState` message); main drops its keyboard shortcuts and bun drops main-targeted menu actions while open. Connect / @@ -208,8 +219,9 @@ component; tokens → `ui/src/styles.css`. through plain callback registries, never signals**: a signal consumed from an effect that also writes signals re-triggers itself (this froze the whole app once), and signal coalescing drops progress snapshots. All requests are - gated on the socket-open promise — calls fired during boot would otherwise - be silently lost. `request` and `send` are ready-gated Proxies. + gated on the socket-open promise (`electroview.hostSocket`) — on 1.18.1 calls + fired during boot were silently lost; 2.x Electroview queues them itself, so + the gate is belt-and-braces now. `request` and `send` are ready-gated Proxies. - `panels.service.ts` — `PanelState` (per panel): sessionId, fsKey, cwd, entries, sort, cursor, selection, type-to-filter. `sorted` computed: filter → dirs-first sort (name/size/mtime numeric, kind/rights/owner/group diff --git a/docs/build-and-release.md b/docs/build-and-release.md index 7d7f975..747b31b 100644 --- a/docs/build-and-release.md +++ b/docs/build-and-release.md @@ -14,81 +14,101 @@ semver-shaped (`0.1.`) for electrobun's config/updater; `BRANDING.buildLabel` ## Dev loop -- `bun run dev` — gen-version → scripts/dev.ts: starts `ng serve` (HMR) and - `electrobun dev --watch`, with `DEV_SERVER_URL` pointing the webviews at the - dev server (only honored on the dev channel and only if the server answers). -- `bun run build` — gen-version → Angular build → `electrobun build` - (**dev channel** — fine for boot checks, NOT a release). +- `bun run dev` — gen-version → `electrobun prepare` → scripts/dev.ts: starts + `ng serve` (HMR) and `electrobun dev --watch`, with `DEV_SERVER_URL` pointing + the webviews at the dev server (only honored on the dev channel and only if + the server answers). +- `bun run build` — gen-version → `electrobun prepare` → Angular build → + `electrobun build` (**dev channel** — fine for boot checks, NOT a release). - `scripts/node-version-shim.ts` (preloaded via `bunfig.toml`, root AND ui/) fakes a Node version the Angular CLI accepts under Bun. Both preloads are load-bearing. -- `scripts/fix-electrobun-cli.ts` re-signs electrobun's prebuilt CLI binary, - whose upstream ad-hoc signature is invalid — macOS SIGKILLs it and the app - silently never launches, with exit code 0 (see [gotchas.md](gotchas.md)). It - runs from `postinstall` **and** at the start of `dev`/`start`/`build`/ - `release`, because the CLI is downloaded lazily on first use rather than at - install time; when the binary is missing it provokes that download first, so - a fresh checkout self-heals. Equally load-bearing — removing it means - `bun run start` does nothing at all. +- **Electrobun 2.x is driven by Hutch.** The `electrobun` devDependency (exact + `2.0.2`) is only a bootstrap: its first command downloads the paired Hutch + (0.27.1) from the Electrobun GitHub release, checks its SHA-256 against + `hutch-artifacts.json`, caches it under `~/.hutch/npm/electrobun//`, and + forwards every `electrobun …` to `hutch electrobun …`. Hutch then fetches the + Electrobun core, its build-time Cottontail and the pinned Bun 1.4.0 (the + runtime the app ships) into `~/.hutch` (~200 MB on a fresh machine) and + projects the SDK into the gitignored `.hutch/devkit/` (plus a gitignored + `.cottontail-tmp/` scratch dir). The devDependency version IS the toolchain + pin: moving Electrobun = bump it + `bun install`. No Node needed — the + bootstrap's `#!/usr/bin/env node` runs under `bunx`. +- `bunx electrobun prepare` (re)projects `.hutch/devkit`. It runs from + `postinstall` **and** at the start of `dev`/`start`/`build`/`release` (~0.5 s + when current — the slot the v1 CLI re-sign step held; that step is gone, + Hutch's binaries carry valid signatures). tsc (the root tsconfig extends + `.hutch/devkit/tsconfig.json`) and the Angular build / `ng serve` (the + `electrobun/view` path in `ui/tsconfig.json`) resolve the SDK there *before* + any electrobun command would project it; without it both fail on unresolved + `electrobun/*` imports. Bun stays the package manager: Hutch's own resolver + (`hutch.lock`, no lifecycle scripts) only runs on `hutch install`, which + nothing calls — hence no `hutch.config.ts`. ## Release builds -`bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64]` +`bun scripts/release.ts [--target mac-arm64|win-x64|linux-x64]` (default: host). -Steps: gen-version → fix-electrobun-cli → gen-icons → Angular build → -`electrobun build --env=stable` → copy/rename artifacts into +Steps: gen-version → electrobun prepare → gen-icons → Angular build → empty +`artifacts/` → `electrobun build --env=stable` → copy/rename the installers into `release//miniSCP-Build-.*`. Facts the script encodes: -- **`--env=stable` is mandatory for releases** — the CLI defaults to the dev - channel (different app name `miniSCP-dev`, no distributable artifacts). -- **No OS cross-compile.** The CLI builds for its host (its `targets` config - field is unread in 1.18.1); packaging needs host tools (codesign/hdiutil on - macOS, rcedit/PowerShell on Windows). Hence one script run per artifact, on - the matching machine; `win-x64` on a non-Windows host and `linux-x64` on a - non-Linux host both error out. -- **mac-x64 from the arm64 Mac (Rosetta)**: the script downloads electrobun's - x64 CLI binary once (`electrobun-cli-darwin-x64.tar.gz` from the GitHub - release matching the installed version, cached at - `~/.cache/miniscp/electrobun-cli-x64-/`) and runs it directly — - Rosetta translates it, the CLI sees `process.arch === "x64"`, downloads the - `dist-macos-x64` core into the electrobun package dir on first use, and - packages a genuine x64 app. Note: Bun's `fetch`→`Bun.write` streaming hung - on the GitHub redirect chain; the script uses `curl -fsSL` deliberately. -- **Both CLI binaries need re-signing.** The x64 CLI fetched for the Rosetta - path carries the same broken upstream signature as the arm64 one, so - `x64Cli()` re-signs it after download *and* on cache reuse; the native path - is covered by the `fix-electrobun-cli.ts` step up front. -- **Core binary downloads**: the installed npm package ships mac-arm64 - binaries only; other `dist--` cores are fetched from - `github.com/blackboardsh/electrobun/releases` at build time — network to - GitHub is a build-time requirement on every fresh machine/runner. -- **Artifact shapes** (stable channel, in `artifacts/`): macOS → - `stable-macos--miniSCP.dmg` + `.app.tar.zst` + `update.json` - (updater feed); Windows → `stable-win-x64-miniSCP-Setup.zip` - (self-extracting installer → `%LOCALAPPDATA%`) plus the script's own - **portable zip** made from the assembled app folder under - `build/stable-win-x64/` (layout to confirm on the first real Windows build — - the step warns instead of failing); Linux → only - `stable-linux-x64-miniSCP.tar.zst` + `update.json`, i.e. the updater feed and - nothing installable, so the script's **`.tar.gz` of the assembled app folder** - under `build/stable-linux-x64/` IS the release artifact (same best-effort - warn). No AppImage/.deb/.desktop — unpack and run the `miniSCP.sh` wrapper the - script writes beside `bin/launcher` (it applies the NVIDIA/DMABUF workaround, - which the app cannot set for itself — see gotchas.md). +- **`--env=stable` is mandatory for releases** — `electrobun build` defaults to + the dev channel (different app name `miniSCP-dev`, no distributable artifacts). +- **No cross-compile, and no Intel Mac.** Hutch builds for its host OS *and* + arch only; packaging needs host tools (codesign/hdiutil on macOS, + rcedit/PowerShell on Windows). Hence one script run per artifact, on the + matching machine; `win-x64` on a non-Windows host and `linux-x64` on a + non-Linux host both error out. **Electrobun 2.x publishes no macOS x64 core + (nor an x64 Hutch)**, so mac-x64 was dropped — the v1 trick of running the x64 + CLI under Rosetta has nothing left to run. `--target mac-x64`, and any build + on an Intel Mac, fail with that message. +- **Runtime downloads**: on first use per Electrobun version Hutch fetches the + platform core from `github.com/blackboardsh/electrobun/releases` + (SHA-256-checked against `electrobun-artifacts.json`), plus Cottontail and the + pinned Bun, into `~/.hutch` — network is a build-time requirement on every + fresh machine/runner. +- **`artifacts/` is emptied before the build** — `site/deploy.sh` publishes it + verbatim, so it must hold exactly this build (stale files, e.g. v1-named + installers, would otherwise ship). +- **Artifact shapes** (stable channel, in `artifacts/`): 2.x installers carry + **no channel prefix**; only the updater feed keeps `stable---` (the + names v1.18.1+ clients request). macOS → `macos-arm64-miniSCP.dmg` + + `stable-macos-arm64-miniSCP.app.tar.zst` + `stable-macos-arm64-update.json` + (verified); Windows → `win-x64-miniSCP-Setup.zip` (setup exe + hidden payload) + + the `stable-win-x64-*` feed, plus the script's own **portable zip** of + `build/stable-win-x64/miniSCP` (warns instead of failing if that folder is + missing); Linux → `linux-x64-miniSCP-Setup.tar.gz` (a self-extracting + `installer` + README) + the `stable-linux-x64-*` feed. The official Linux + installer IS the release artifact — the v1 portable tarball and its + `miniSCP.sh` NVIDIA wrapper are gone, so an installed build on NVIDIA needs + `WEBKIT_DISABLE_DMABUF_RENDERER=1` from the user (see gotchas.md). The + Windows/Linux names come from the 2.x docs — confirm on the first real 2.x + build there. A build that can reach the previous release at + `release.baseUrl` also emits `stable---.patch`. - **Linux builds need the AppIndicator dev libraries present to RUN** (not to - build) — see platform-notes.md; a packaged tarball started on a machine + build) — see platform-notes.md; an installed build started on a machine without them dies at `dlopen`. - The script filters artifacts by app name, so stale artifacts from older brandings don't get collected. +- **v1 → 2.x bridge**: Windows users on 0.1.78 (the only feed live on + miniscp.com/dl) update through their v1 updater because `app.name`, + `app.identifier` and `release.baseUrl` are unchanged, the build is + `--env=stable`, and `artifacts/` is published unrenamed. 2.x keeps the v1 + top-level `update.json` fields (`version`/`hash`/`platform`/`arch`) and the + physical `stable` state root, so user data stays put. Unverified until the + first 2.x Windows release reaches a v1 install. ## CI (Gitea Actions) -`.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos + windows -runner matrix, each running the same release script; inert until act_runners -with those labels are registered. Needs `fetch-depth: 0` (the build number is -the commit count). Runner prereqs are commented in the workflow file. +`.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos (mac-arm64) + +windows (win-x64) runner matrix, each running the same release script; inert +until act_runners with those labels are registered. Needs `fetch-depth: 0` (the +build number is the commit count); `bun install --frozen-lockfile` already +prepares the devkit (postinstall). Runner prereqs are commented in the workflow +file. ## Download host (`site/` + Caddy) @@ -96,27 +116,31 @@ the commit count). Runner prereqs are commented in the workflow file. internal box (plain HTTP `:80`, no domain/TLS). Deploys to `/srv/miniscp/`. - `site/index.html` — pure static, no JS, tinygrad-minimal. Download buttons link to **fixed - stable filenames** (`/dl/stable-macos-arm64-miniSCP.dmg`, …) so the page never changes per - release. Logo/screenshot `` slots are commented in, images live in `site/assets/`. + stable filenames** (`/dl/macos-arm64-miniSCP.dmg`, `/dl/win-x64-miniSCP-Setup.zip`, the portable + zip) so the page never changes per release. 2.x dropped the `stable-` prefix from installer + names (the feed kept it), so the v1 `stable-…-Setup.zip`/`.dmg` installers still on the server + are stale — delete them by hand. Logo/screenshot `` slots are commented in, images live in + `site/assets/`. - `site/Caddyfile` — one flat `/dl/*` folder with `file_server browse` + range requests, and per-type `Cache-Control`: `*.patch` immutable (unique hashed names), `*update.json` `no-cache` - (updater must see new versions), installers (`*.dmg *.zip *.tar.zst`) `max-age=300` (stable - names overwritten each release). Landing page has **no** SPA fallback → unknown paths 404. + (updater must see new versions), installers (`*.dmg *.zip *.tar.zst *.tar.gz`) `max-age=300` + (stable names overwritten each release). Landing page has **no** SPA fallback → unknown paths 404. Path matcher gotcha: use suffix globs like `*update.json`, not `*/*update.json`. - **One flat folder, no per-build dirs** — this layout IS electrobun's `artifacts/` feed, so it - doubles as the (future) updater `baseUrl`. Publishing = drop this machine's `artifacts/stable-*` - into `/dl/`; installers overwrite in place, `.patch` files accumulate (never delete them, or - older installs lose their incremental-update trail). **Tradeoff:** only the latest full + doubles as the updater `baseUrl`. Publishing = drop this machine's whole `artifacts/` into + `/dl/`, unrenamed; installers overwrite in place, `.patch` files accumulate (never delete them, + or older installs lose their incremental-update trail). **Tradeoff:** only the latest full installer is downloadable; old full versions are overwritten. -- `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/stable-*` - and renames the build-numbered win portable zip to the stable `stable-win-x64-miniSCP-portable.zip`. - `MINISCP_DL_DIR` overrides the target (stage locally, then rsync to the server). +- `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/*` + verbatim and renames the build-numbered win portable zip to the stable + `stable-win-x64-miniSCP-portable.zip`. `MINISCP_DL_DIR` overrides the target (stage locally, + then rsync to the server). - `release.baseUrl = https://miniscp.com/dl` is set in `electrobun.config.ts`; `miniscp.com` is fronted by an external proxy for TLS (Caddy itself stays on `:80` behind it). The value is baked into `version.json` at build time, so only builds made after it was added check for updates. - With `generatePatch` (default true), release builds now need **build-time network to - `miniscp.com/dl`** so electrobun can fetch the currently-published version to diff a `.patch` - (the first build after enabling it has nothing to diff and just ships the full artifacts). + With `generatePatch` (default true), release builds need **build-time network to + `miniscp.com/dl`** so Hutch can fetch the currently-published version to diff a `.patch` (a + target with no published feed yet — currently mac and Linux — just ships the full artifacts). ## Auto-updater (app side) @@ -128,16 +152,21 @@ Wired on top of the download host above. See the "Auto-updater" section in `Updater.onStatusChange` streams `download-progress` (0–100) → `updateStatus{downloading}`. `checkForUpdate` self-guards the `dev` channel, so `bun run dev` never shows the pill. - **RPC** ([src/shared/rpc.ts](../src/shared/rpc.ts)): requests `checkForUpdate` / `downloadUpdate` / - `applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`); - push message `updateStatus`. + `applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`; + `applyUpdate` goes through `events.applyUpdate` in index.ts); push message `updateStatus`. +- **`applyUpdate()` asks the before-quit guard first (2.x).** With transfers running or dirty + editors the guard vetoes and shows its Quit Anyway / Cancel box. `events.applyUpdate` marks the + quit as update-initiated, so Quit Anyway re-runs `Updater.applyUpdate()` (a plain `Utils.quit()` + would drop the downloaded update); Cancel leaves the pill at "Restart to apply". - **UI**: `UpdaterService` ([ui/src/app/core/updater.service.ts](../ui/src/app/core/updater.service.ts)) holds signals (`state`/`version`/`progress`); the tab-bar "update pill" ([tab-bar.ts](../ui/src/app/skins/shadlike/tab-bar/tab-bar.ts)) is one accent (`bg-primary`) control that announces → fills as a progress bar while downloading → "Restart to apply". `state` is `available → downloading → ready` (ready derived from the `downloadUpdate` response). App-menu "Check for Updates…" ([menu.ts](../src/bun/menu.ts)) routes a `check-for-updates` menu action into `UpdaterService.check()`. -- **Unsigned is fine:** `applyUpdate()` strips the macOS quarantine xattr before relaunch, so the - swapped bundle isn't Gatekeeper-blocked. +- **Unsigned was fine on 1.18.1:** `applyUpdate()` stripped the macOS quarantine xattr before + relaunch. 2.x hands the swap to a native post-exit helper (transactional replace, restores the + old app on failure) — re-check Gatekeeper on the first unsigned 2.x mac update. ## Icons @@ -149,8 +178,13 @@ Rerun after touching either SVG; outputs are committed (build inputs). ## electrobun config notes -`electrobun.config.ts`: app identity comes from `BRANDING`; `build.bun.external` -must keep `["cpu-features", "*.node"]` (ssh2's optional native accelerators — -bundling them breaks the build; runtime falls back to pure JS); -`build.copy` ships the Angular dist and tray assets into `views://`; -`bundleCEF: false` everywhere (system WebKit / WebView2). +`electrobun.config.ts`: app identity comes from `BRANDING`; **`build.mainProcess: +"bun"` is load-bearing** — 2.x defaults to Cottontail, which ignores the whole +`build.bun` block (verified: without the line, `bunx electrobun config +--env=stable` resolves `mainProcess: "cottontail"` with no externals); +`build.bun.external` must keep `["cpu-features", "*.node"]` (ssh2's optional +native accelerators — bundling them breaks the build; runtime falls back to pure +JS; a 2.x stable bundle keeps both as runtime `require`s); `build.copy` ships the +Angular dist and tray assets into `views://`; `bundleCEF: false` everywhere +(system WebKit / WebView2). `bunx electrobun config --env=stable` prints the +merged config Hutch will build with. diff --git a/docs/gotchas.md b/docs/gotchas.md index c40e1a9..e5808ab 100644 --- a/docs/gotchas.md +++ b/docs/gotchas.md @@ -87,6 +87,8 @@ touching the adjacent code. `webviewGetPageZoom` returns a hardcoded `1.0` from `.rodata`, so the failure is unobservable through the API — set 2.0, read back "1.0 = success". The JSDoc's "(WebKit only)" means macOS WKWebView. This is why the scale is CSS. + (Disassembled on 1.18.1; 2.0.2's zoom fixes are CEF-only — re-check the + native Linux wrapper before trusting it.) - **Everything the UI sizes with must be rem.** The scale works by setting the root font-size, so a px value freezes while everything around it grows. The px reservoirs were the panel-chrome tokens (`--row-h` etc.) and 93 @@ -101,25 +103,39 @@ touching the adjacent code. ## Electrobun -- **The prebuilt CLI binary ships with a broken ad-hoc signature**, so macOS - SIGKILLs it at exec and `bun run start` prints nothing and exits **0** — - `bin/electrobun.cjs` spawns the real binary and forwards - `process.exit(code || 0)`, and a signal death has `code === null`. The binary - is a Bun single-file executable whose payload is appended *after* the linker - signed it, so `codesign -v` reports "code or signature have been modified"; - the arm64 and x64 CLI tarballs are both affected upstream (confirmed by - hashing a fresh download against the shipped copy — it is not local - corruption, and re-installing does not help). `scripts/fix-electrobun-cli.ts` - re-seals it with `codesign --force --sign -`. It runs from `postinstall` - **and** ahead of every electrobun invocation, because the wrapper downloads - the binary **lazily on first use**, not at install time — postinstall alone - would no-op on a fresh checkout. It also fixes `.cache/electrobun`, which the - wrapper restores `bin/` from. Never read a silent `exit 0` from the - electrobun CLI as "nothing happened": run `codesign -v` on it first. -- **Window `close` is not cancellable** (response type `{}`; only the - app-level `before-quit` supports `event.response = {allow:false}`). All - unsaved-state guards must either intercept app-controlled close paths - (⌘W, buttons) or survive the close (editor draft mirroring). +- **The `electrobun` npm package (2.x) is a bootstrap whose every export is a + throwing stub** ("Electrobun 2.x APIs come from the Hutch devkit, not + node_modules"). Every `electrobun/*` import must resolve through the + projected `.hutch/devkit/`: the root tsconfig extends + `.hutch/devkit/tsconfig.json`, and `ui/tsconfig.json` maps `electrobun/view` + to `../.hutch/devkit/api/browser/index.ts` (the exact export-map target). Let + an import fall through to `node_modules/electrobun` and the webview throws at + load. tsc and the UI build fail on unresolved `electrobun/*` imports until + `bunx electrobun prepare` has run (postinstall and every dev/build script do + it). Never point anything into `~/.hutch` — that store is Hutch-owned. +- **`build.mainProcess` defaults to Cottontail in 2.x**, and Cottontail ignores + the whole `build.bun` block — the ssh2 externals included, silently (verified + with `bunx electrobun config --env=stable`). `mainProcess: "bun"` in + `electrobun.config.ts` is load-bearing. +- **The browser SDK is raw TS compiled inside the Angular program**, so the UI's + strictness flags apply to upstream code: 2.0.2's `createTransport().send` + returns on one path only, which is why `ui/tsconfig.json` has + `noImplicitReturns: false`. Its `import "./global.d.ts"` also prints "File + '…/global.d.ts' not found in TypeScript compilation" on every build — benign, + and adding the file to the program does not silence it. +- **Window `close` is not cancellable, but 2.x added `will-close`.** The `close` + event's response type is `{}`; `will-close` (fired by the titlebar/traffic-light + close and `win.requestClose()`) honors `event.response = {allow:false}`, and + `win.close()` skips it. The editor close safety predates this: all + unsaved-state guards still intercept app-controlled close paths (⌘W, buttons) + or survive the close (editor draft mirroring) — a `will-close` prompt is a + possible follow-up. +- **`Updater.applyUpdate()` asks the `before-quit` handlers first (2.x)**, so the + transfers/unsaved guard can veto an update restart. Its "Quit Anyway" must + re-run `Updater.applyUpdate()` (`events.applyUpdate` flags the quit as + update-initiated) — a plain `Utils.quit()` there quits without installing. + `Utils.quit()` runs the same approval, which is why the guard checks + `quitConfirmed` first. - **⌘W in any secondary window arrives as the MAIN window's `close-tab` menu action** (macOS: the app menu intercepts it before the webview sees the key). bun must check the focused secondary window first — the Settings @@ -127,8 +143,10 @@ touching the adjacent code. check makes ⌘W close a main-window tab behind it. - **One rpc instance per window** — BrowserView binding steals the rpc's transport; a shared instance silently sends to the wrong webview. -- **RPC before socket open is silently lost.** Everything gates on the - socket-open promise (RpcService `ready`). +- **RPC before socket open was silently lost on 1.18.1.** 2.x Electroview queues + sends while its `hostSocket` (v1: `bunSocket`) is CONNECTING and falls back to + the native bridge; RpcService still gates everything on the socket-open + promise (`ready`) as belt-and-braces. - The emitter has no `off()`. Window event closures must not capture records you want garbage collected. - `electrobun build` defaults to the **dev** channel; release builds need @@ -140,12 +158,13 @@ touching the adjacent code. - `ApplicationMenu` is macOS-only in our usage; accelerators on Windows are single-character only. - **`ContextMenu.showContextMenu` is a silent no-op on Linux** (electrobun's own - `dist/api/bun/proc/linux.md` says so; the app also prints "Application menus are - not supported on Linux" at boot). Because the UI suppresses the webview's own - right-click menu, that left EVERY panel context menu dead on Linux — no error, - nothing happens. The menu is plain HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on - every platform; don't reintroduce the native one for "nativeness" on mac/win - without keeping a Linux path. + `.hutch/devkit/api/sdks/main/proc/linux.md` says so, still in 2.0.2; the app + also prints "Application menus are not supported on Linux" at boot). Because + the UI suppresses the webview's own right-click menu, that left EVERY panel + context menu dead on Linux — no error, nothing happens. The menu is plain + HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on every platform; + don't reintroduce the native one for "nativeness" on mac/win without keeping + a Linux path. - **Window-open flash prevention is three-layer** — remove any one and the white/inverted blink returns: (1) windows are created `hidden: true` and revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms @@ -161,7 +180,8 @@ touching the adjacent code. URL, never hardcode `views://`. - Upstream Bun panic (`FFI_Callback_threadsafe_call` → `web_worker.spin`): rare segfault under long sessions; upstream electrobun×Bun issue, restart - recovers, nothing app-side to fix. + recovers, nothing app-side to fix. Seen on Bun 1.3.13 (Electrobun 1.18.1); + 2.0.2 ships Bun 1.4.0 — unconfirmed whether that fixed it. - **Preloads and frames are construction-time only.** The preload is a persistent `WebKitUserScript` at DOCUMENT_START, frozen at webview construction — `webview.loadURL()` re-runs the OLD one @@ -171,13 +191,25 @@ touching the adjacent code. correct live over RPC. Recreating the main window is not an escape hatch: `exitOnLastWindowClosed`, push closures and the tray capture it, and there is no `off()`. +- **`trafficLightOffset` changed meaning in 2.x.** 1.18.1 nudged the buttons + by the offset but clamped them inside the stock titlebar, so `{12, 13}` really + meant "as low as the titlebar allows" (left 21 / top 18 on macOS 26). 2.x adds + the full offset to macOS's default position (9/9 in the 32pt Tahoe titlebar) + and grows the titlebar to fit — the same numbers dropped the lights 4pt. + `chromeOptions()` now passes `{12, 4}`: left 21 / top 13, the 14pt buttons + vertically centred in the 40px `h-10` header (1.18.1's clamped spot, top 18, + was 5pt low). The default shifts with macOS's titlebar metrics (28pt / 7 + before macOS 26), so re-check after an OS release. `win.setWindowButtonPosition(x, top)` is the absolute + alternative (re-applied on resize/fullscreen) if per-version drift matters. - **Drag regions ignore `preventDefault`.** The preload starts a window move from a bubble-phase `mousedown` on `document` and never reads `defaultPrevented`; a `pointerdown` handler doesn't stop it either (the - compat `mousedown` still fires). `isAppRegionDrag` checks - `.electrobun-webkit-app-region-no-drag` first — that class on the element is - the only reliable suppressor (`dist/api/bun/preload/dragRegions.ts`). -- **No resize, maximize-event or double-click-maximize primitives (1.18.1).** + compat `mousedown` still fires). `isAppRegionDragTarget` lets a `no-drag` + region win — the `.electrobun-webkit-app-region-no-drag` class (or, since + 2.x, `app-region: no-drag` CSS) on the element is the only reliable + suppressor (`.hutch/devkit/api/preload/dragRegions.ts`; re-checked on 2.0.2). +- **No resize, maximize-event or double-click-maximize primitives (1.18.1, still + absent from 2.0.2's `BrowserWindow`).** `titleBarStyle: "hidden"` drops the whole frame (Cocoa `Titled` — the only way to remove the traffic lights; `gtk_window_set_decorated(false)` on Linux) and with it the WM resize borders; nothing restores them (no @@ -204,12 +236,15 @@ touching the adjacent code. - **The app cannot apply this to itself.** WebKit reads the variable when it forks its web process, and **Bun's `process.env` writes do not propagate to child processes** (verified: a spawned child sees it unset). Setting it in - `src/bun/index.ts` — even in a module imported before `electrobun/bun` — + `src/bun/index.ts` — even in a module imported before `electrobun/main` — does nothing. It must be in the environment before the app process starts. - - Hence every launch path injects it: `scripts/linux-gpu.ts` (`launchEnv()`) - for `bun run dev` / `bun run start`, and the `miniSCP.sh` wrapper that - `release.ts` writes beside `bin/launcher` in the tarball. **A new launch - path must call `launchEnv()` or it will render black on NVIDIA.** + - Only the dev launch paths inject it: `scripts/linux-gpu.ts` (`launchEnv()`) + for `bun run dev` / `bun run start`. **A new dev launch path must call + `launchEnv()` or it will render black on NVIDIA.** The release is the + official 2.x Setup installer now (the v1 portable tarball and its + `miniSCP.sh` wrapper are gone), so an installed build on NVIDIA needs the + user to launch it with `WEBKIT_DISABLE_DMABUF_RENDERER=1` (e.g. in the + desktop entry's `Exec`). - Gated on `/proc/driver/nvidia` so Intel/AMD keep hardware compositing, and an explicit `WEBKIT_DISABLE_DMABUF_RENDERER` from the user always wins. - A "does it boot?" log check does NOT catch this — grep for the GBM line, or @@ -296,7 +331,14 @@ touching the adjacent code. breaks `ng`. - ssh2's optional native deps must stay `external: ["cpu-features", "*.node"]` in `electrobun.config.ts` — bundling them breaks the build; ssh2 falls back - to pure JS at runtime. + to pure JS at runtime. (Inert without `mainProcess: "bun"` — see Electrobun.) +- **2.x installers dropped the `stable-` prefix; the updater feed kept it.** + `macos-arm64-miniSCP.dmg` / `win-x64-miniSCP-Setup.zip` / + `linux-x64-miniSCP-Setup.tar.gz` next to `stable---update.json`, + `.tar.zst` and `.patch`. Anything that globs `artifacts/stable-*` misses the + installers — release.ts collects by `--`, deploy.sh publishes + `artifacts/*` verbatim (release.ts empties `artifacts/` first), and the site + links the new names. - `bun run --cwd ui build` (flag order matters; `bun --cwd ui run build` does not do what you want). - `version.gen.ts` is gitignored and regenerated by postinstall + every build diff --git a/docs/platform-notes.md b/docs/platform-notes.md index 9306135..dd9ca72 100644 --- a/docs/platform-notes.md +++ b/docs/platform-notes.md @@ -1,8 +1,9 @@ # Platform notes — macOS / Windows / Linux behavior matrix -macOS is the primary target; Windows 11+ x64 and Linux x64 are **beta** targets -(win-arm64 and linux-arm64 deferred). Every platform seam carries a -`PORTABILITY:` comment at the branch site. +macOS (Apple Silicon) is the primary target; Windows 11+ x64 and Linux x64 are +**beta** targets (win-arm64 and linux-arm64 deferred). **Intel Macs were +dropped** with the move to Electrobun 2.x, which publishes no macOS x64 core. +Every platform seam carries a `PORTABILITY:` comment at the branch site. ## Seam inventory (where platform branches live) @@ -81,7 +82,8 @@ ldd build/dev-linux-x64/*/bin/libNativeWrapper.so | grep 'not found' `libasar.so => not found` in that output is **expected and harmless** — it ships in the same `bin/` folder and resolves at load time; only an unresolved -`libayatana-appindicator3.so.1` is fatal. +`libayatana-appindicator3.so.1` is fatal. (Hard link and path verified on +1.18.1 — re-check both on the first 2.x Linux build.) **Tray visibility** is a separate matter from linking: GNOME 3.26+ hides the legacy tray, so the icon needs the @@ -92,23 +94,26 @@ either way — the tray is a convenience, and every action it offers exists in-a ## Linux known issues (upstream electrobun) - **No native menus of any kind.** Application menus and `showContextMenu` are - both no-ops (electrobun's `dist/api/bun/proc/linux.md`; the app logs + both no-ops (electrobun's `.hutch/devkit/api/sdks/main/proc/linux.md`, still in + 2.0.2; the app logs "Application menus are not supported on Linux" at boot). The app menubar was already macOS-only, and the panel context menus are now HTML on every platform, so nothing is missing at the UI level — but do not add a feature that depends on a native menu. - **NVIDIA renders black without the DMABUF workaround.** WebKitGTK can't allocate GBM buffers on the proprietary driver, so the window paints nothing - while the log looks perfectly healthy. Every launch path sets - `WEBKIT_DISABLE_DMABUF_RENDERER=1` when `/proc/driver/nvidia` exists — - `scripts/linux-gpu.ts` for dev, the `miniSCP.sh` wrapper in the tarball for - releases. The app can't do it itself (Bun's `process.env` writes don't reach - child processes); see [gotchas.md](gotchas.md#linux). + while the log looks perfectly healthy. The dev launches + (`scripts/linux-gpu.ts`) set `WEBKIT_DISABLE_DMABUF_RENDERER=1` when + `/proc/driver/nvidia` exists; an installed build (the official Setup + installer) does not — launch it with that variable set (e.g. in the desktop + entry's `Exec`). The app can't do it itself (Bun's `process.env` writes don't + reach child processes); see [gotchas.md](gotchas.md#linux). - **Benign startup noise**: a deprecation warning pointing at `libayatana-appindicator-glib`. Not fatal. -- **Unsigned, unpackaged**: the release artifact is a portable tarball — no - AppImage, `.deb`, `.desktop` entry or icon installation. Unpack and run - `miniSCP.sh` (which execs `bin/launcher`). +- **Unsigned; Electrobun's own installer**: the release artifact is + `linux-x64-miniSCP-Setup.tar.gz` — a self-extracting `installer` + README + (2.x does the desktop-entry packaging and ships an uninstaller); no AppImage + or `.deb`. Layout unverified until the first 2.x Linux build. ## First-Linux-boot smoke test @@ -130,21 +135,24 @@ either way — the tray is a convenience, and every action it offers exists in-a (temp download → save → auto-upload) with the inotify watcher. 6. Move to Trash lands the item in the desktop's trash; connections.json is 0600. 7. `bun scripts/release.ts --target linux-x64` produces - `release/linux-x64/miniSCP-Build-linux-x64.tar.gz`; unpack it elsewhere and - run `miniSCP.sh`. + `release/linux-x64/miniSCP-Build-linux-x64-Setup.tar.gz`; unpack it + elsewhere, run its `installer`, then launch the installed app (on NVIDIA with + `WEBKIT_DISABLE_DMABUF_RENDERER=1`). ## Windows known issues (upstream electrobun, documented not fixed) - **HiDPI blur** — WebView2 renders at 1x on scaled displays - (blackboardsh/electrobun#324, open). Most user-visible defect; wait for - upstream DPI awareness. + (blackboardsh/electrobun#324, seen on 1.18.1). Most user-visible defect; 2.x + lists per-monitor DPI setup among its Windows fixes — re-check on the first + 2.x Windows build. - **Notifications** are legacy balloon style (`Shell_NotifyIcon`), not Windows toasts. - **Unsigned binaries** — Setup.exe and the portable exe both trigger - SmartScreen / Mark-of-the-Web on first run. No Authenticode support in - electrobun 1.18.1 (signtool post-hook or electrobun-builder are options - later). Installer unpacks to `%LOCALAPPDATA%`, desktop shortcut only — no - Start Menu entry or uninstaller. + SmartScreen / Mark-of-the-Web on first run. No Authenticode support (2.x: + "Windows release signing is not currently part of Hutch's packaging + pipeline"; a signtool post-hook is an option later). Installer unpacks to + `%LOCALAPPDATA%`, desktop shortcut only; 2.x installer builds add an + uninstaller (`%LOCALAPPDATA%\\\uninstall.exe`). - `hiddenInset` degrades to `hidden` on Windows — irrelevant here because non-mac windows use the native titlebar. - `findInPage` is a `window.find()` shim on WebView2 — irrelevant here, the @@ -155,10 +163,11 @@ either way — the tray is a convenience, and every action it offers exists in-a Code paths written blind (no Windows box was available); verify in order: 1. Build: `bun scripts/release.ts --target win-x64` on the Windows machine — - core binaries download from GitHub releases on first run; Setup.zip AND - the portable zip appear under `release/win-x64/` (the portable step warns - instead of failing if the assembled-app folder layout differs — fix the - `appDir` path in release.ts if so). + Hutch, the core and the pinned Bun download into `~/.hutch` on first run; + Setup.zip AND the portable zip appear under `release/win-x64/` (the portable + step warns instead of failing if the assembled-app folder layout differs — + fix the `appDir` path in release.ts if so). A v1 0.1.78 install must pick up + the published 2.x build through its updater (the bridge release). 2. App boots, tray icon renders colored, window has a native titlebar. 3. Drives dropdown in each panel lists `C:\` (+ others); navigating to a drive root hides the `'..'` row; the path bar shows consistent `\` paths. @@ -175,5 +184,5 @@ Code paths written blind (no Windows box was available); verify in order: user after saving a connection. 9. Editor/manager/Settings windows open, modal bounce works, Ctrl+, opens Settings and About shows from its About section. -10. Known-issue confirmation: HiDPI blur on a scaled display (expected until - upstream fixes it). +10. Known-issue check: HiDPI blur on a scaled display (seen on 1.18.1 — 2.x's + per-monitor DPI setup may have fixed it). diff --git a/electrobun.config.ts b/electrobun.config.ts index 72fa78b..d7bc69e 100644 --- a/electrobun.config.ts +++ b/electrobun.config.ts @@ -16,6 +16,9 @@ export default { baseUrl: "https://miniscp.com/dl", }, build: { + // Load-bearing: 2.x defaults to Cottontail, which ignores the whole + // `bun` block below — the ssh2 externals included. + mainProcess: "bun", bun: { entrypoint: "src/bun/index.ts", // ssh2's optional native accelerators must never be bundled; its diff --git a/package.json b/package.json index 70c1040..c10c96c 100644 --- a/package.json +++ b/package.json @@ -7,16 +7,15 @@ "ui" ], "scripts": { - "dev": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun scripts/dev.ts", + "dev": "bun scripts/gen-version.ts && bunx electrobun prepare && bun scripts/dev.ts", "dev:ui": "bun run --cwd ui start", "build:ui": "bun run --cwd ui build", - "build": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun run build:ui && bunx electrobun build", - "start": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun run build:ui && bun scripts/start.ts", - "postinstall": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts", + "build": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bunx electrobun build", + "start": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bun scripts/start.ts", + "postinstall": "bun scripts/gen-version.ts && bunx electrobun prepare", "release": "bun scripts/release.ts" }, "dependencies": { - "electrobun": "1.18.1", "ssh-config": "^5.2.0", "ssh2": "^1.17.0" }, @@ -24,6 +23,7 @@ "@resvg/resvg-js": "^2.6.2", "@types/bun": "latest", "@types/ssh2": "^1.15.5", + "electrobun": "2.0.2", "png-to-ico": "^3.0.2", "typescript": "~5.9.3" }, diff --git a/scripts/fix-electrobun-cli.ts b/scripts/fix-electrobun-cli.ts deleted file mode 100644 index 7b8b437..0000000 --- a/scripts/fix-electrobun-cli.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Re-signs electrobun's prebuilt CLI binary, which ships with a BROKEN ad-hoc -// signature: it is a Bun single-file executable whose payload is appended after -// the linker signed it, so `codesign -v` reports "code or signature have been -// modified" and the macOS kernel SIGKILLs it at exec. The failure is invisible: -// electrobun's bin/electrobun.cjs wrapper spawns the real binary and forwards -// `process.exit(code || 0)` — a signal death has code === null, so `bun run -// start` printed nothing and exited 0 while never launching the app. -// `codesign --force --sign -` re-seals it ad-hoc and it runs normally. -// -// The binary is downloaded LAZILY by that wrapper on first use, not by install, -// so this runs both from postinstall and ahead of every electrobun invocation. -// Idempotent, and a no-op off macOS. -import { existsSync } from "node:fs"; -import path from "node:path"; - -const ROOT = path.join(import.meta.dir, ".."); - -/** true if `bin` has (or now has) a signature the kernel will accept. */ -export function ensureSigned(bin: string): boolean { - if (process.platform !== "darwin" || !existsSync(bin)) return true; - const valid = () => Bun.spawnSync(["codesign", "-v", bin], { stderr: "ignore" }).success; - if (valid()) return true; - const signed = Bun.spawnSync(["codesign", "--force", "--sign", "-", bin], { stderr: "pipe" }); - if (!signed.success || !valid()) { - console.warn(`[electrobun-cli] could not re-sign ${path.relative(ROOT, bin)} — the app will not launch`); - return false; - } - console.log(`[electrobun-cli] re-signed ${path.relative(ROOT, bin)} (upstream ad-hoc signature is invalid)`); - return true; -} - -if (import.meta.main) { - const pkgDir = path.join(ROOT, "node_modules", "electrobun"); - const bin = path.join(pkgDir, "bin", "electrobun"); - - // The wrapper downloads the CLI on first run and only then execs it, so one - // throwaway invocation leaves a downloaded (still unsigned) binary behind - // even though that exec is killed. - if (process.platform === "darwin" && existsSync(pkgDir) && !existsSync(bin)) { - Bun.spawnSync(["bunx", "electrobun", "--version"], { cwd: ROOT, stdout: "ignore", stderr: "ignore" }); - } - - // .cache is what the wrapper restores bin/ from, so both copies need fixing. - const ok = [bin, path.join(pkgDir, ".cache", "electrobun")].map(ensureSigned).every(Boolean); - if (!ok) process.exit(1); -} diff --git a/scripts/linux-gpu.ts b/scripts/linux-gpu.ts index 032dcb6..77e0d56 100644 --- a/scripts/linux-gpu.ts +++ b/scripts/linux-gpu.ts @@ -7,9 +7,10 @@ // It has to be in the environment BEFORE the app process starts: WebKit reads // it when it forks its web process, and Bun's `process.env` writes do NOT // propagate to children (verified — a child sees the variable unset), so the -// app cannot set this for itself from inside src/bun/. Every launch path has to -// inject it: the dev scripts here, and the wrapper script that release.ts ships -// beside the packaged launcher. +// app cannot set this for itself from inside src/bun/. Only the dev launch +// paths (`bun run dev` / `bun run start`) inject it; an installed build (the +// official Linux Setup installer) needs the user to set +// WEBKIT_DISABLE_DMABUF_RENDERER=1 themselves on NVIDIA. // // Scoped to NVIDIA so Intel/AMD keep hardware compositing, and an explicit // value from the user's environment always wins. diff --git a/scripts/release.ts b/scripts/release.ts index 41241e9..ced8041 100644 --- a/scripts/release.ts +++ b/scripts/release.ts @@ -1,24 +1,21 @@ // Build one release artifact for one target, on the matching host: -// bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64] -// Default target = the host. electrobun cannot cross-compile OSes (its -// packaging needs host tools: codesign/hdiutil on mac, rcedit/PowerShell on -// Windows), so win-x64 must run on a Windows machine. mac-x64 IS buildable on -// an arm64 Mac: we fetch electrobun's x64 CLI binary and run it under -// Rosetta 2 — it sees an x64 host, pulls the x64 core, and packages mac-x64. -// Artifacts land in release//miniSCP-Build-.. -import { cpSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"; -import os from "node:os"; +// bun scripts/release.ts [--target mac-arm64|win-x64|linux-x64] +// Default target = the host. electrobun cannot cross-compile (Hutch builds for +// its host OS and arch only; packaging needs host tools: codesign/hdiutil on +// mac, rcedit/PowerShell on Windows), so win-x64 must run on a Windows machine. +// There is no mac-x64: Electrobun 2.x publishes no macOS x64 core at all. +// Artifacts land in release//miniSCP-Build-. +import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs"; import path from "node:path"; -import { ensureSigned } from "./fix-electrobun-cli"; const ROOT = path.join(import.meta.dir, ".."); -const TARGETS = ["mac-arm64", "mac-x64", "win-x64", "linux-x64"] as const; +const TARGETS = ["mac-arm64", "win-x64", "linux-x64"] as const; type Target = (typeof TARGETS)[number]; function hostTarget(): Target { if (process.platform === "win32") return "win-x64"; if (process.platform === "linux") return "linux-x64"; - return process.arch === "arm64" ? "mac-arm64" : "mac-x64"; + return "mac-arm64"; } function fail(message: string): never { @@ -39,8 +36,10 @@ function run(cmd: string[], opts: { cwd?: string } = {}): void { // ---- target selection + host guards ---- const argIndex = process.argv.indexOf("--target"); -const requested = (argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget()) as Target; -if (!TARGETS.includes(requested)) fail(`unknown target "${requested}" (${TARGETS.join(", ")})`); +const arg = argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget(); +if (arg === "mac-x64") fail("mac-x64 was dropped — Electrobun 2.x publishes no macOS x64 core."); +if (!TARGETS.includes(arg as Target)) fail(`unknown target "${arg}" (${TARGETS.join(", ")})`); +const requested = arg as Target; if (requested === "win-x64" && process.platform !== "win32") { fail("win-x64 must be built on a Windows machine/runner — electrobun cannot cross-compile."); @@ -48,15 +47,14 @@ if (requested === "win-x64" && process.platform !== "win32") { if (requested === "linux-x64" && process.platform !== "linux") { fail("linux-x64 must be built on a Linux machine/runner — electrobun cannot cross-compile."); } -if (requested.startsWith("mac") && process.platform !== "darwin") { - fail(`${requested} must be built on macOS.`); +if (requested === "mac-arm64" && (process.platform !== "darwin" || process.arch !== "arm64")) { + fail("mac-arm64 must be built on an Apple Silicon Mac (Electrobun 2.x has no macOS x64 build)."); } -const viaRosetta = requested === "mac-x64" && process.arch === "arm64"; -// ---- prepare: version, icons, UI bundle ---- +// ---- prepare: version, devkit, icons, UI bundle ---- run(["bun", "scripts/gen-version.ts"]); -run(["bun", "scripts/fix-electrobun-cli.ts"]); +run(["bunx", "electrobun", "prepare"]); run(["bun", "scripts/gen-icons.ts"]); run(["bun", "run", "--cwd", "ui", "build"]); @@ -64,104 +62,47 @@ const { BUILD } = await import("../src/shared/version.gen"); const { BRANDING } = await import("../src/shared/branding"); const buildName = `Build${BUILD.number}`; -// ---- electrobun build (native, or x64 CLI under Rosetta) ---- +// ---- electrobun build ---- -async function x64Cli(): Promise { - const pkg = await Bun.file(path.join(ROOT, "node_modules", "electrobun", "package.json")).json(); - const dir = path.join(os.homedir(), ".cache", "miniscp", `electrobun-cli-x64-${pkg.version}`); - const bin = path.join(dir, "electrobun"); - // Same broken upstream ad-hoc signature as the arm64 CLI (see - // scripts/fix-electrobun-cli.ts) — unsigned it is SIGKILLed on exec. - if (existsSync(bin)) { - if (!ensureSigned(bin)) fail(`x64 CLI at ${bin} has an unusable code signature`); - return bin; - } - console.log(`[release] downloading electrobun x64 CLI ${pkg.version} (one-time)…`); - mkdirSync(dir, { recursive: true }); - const url = `https://github.com/blackboardsh/electrobun/releases/download/v${pkg.version}/electrobun-cli-darwin-x64.tar.gz`; - const tarball = path.join(dir, "cli.tar.gz"); - run(["curl", "-fsSL", "-o", tarball, url]); - run(["tar", "-xzf", tarball, "-C", dir]); - if (!existsSync(bin)) fail(`x64 CLI tarball did not contain "electrobun" (${dir})`); - run(["chmod", "+x", bin]); - if (!ensureSigned(bin)) fail(`x64 CLI at ${bin} has an unusable code signature`); - return bin; -} - -if (viaRosetta) { - // x64 Mach-O runs under Rosetta transparently; it sees process.arch === x64 - const rosetta = Bun.spawnSync(["arch", "-x86_64", "true"]); - if (!rosetta.success) { - fail("Rosetta 2 is not installed — run: softwareupdate --install-rosetta --agree-to-license"); - } - run([await x64Cli(), "build", "--env=stable"]); -} else { - run(["bunx", "electrobun", "build", "--env=stable"]); -} +// Start from an empty artifacts/: site/deploy.sh publishes it verbatim (the +// updater contract), so an earlier build's files must not ride along. +const artifactsDir = path.join(ROOT, "artifacts"); +rmSync(artifactsDir, { recursive: true, force: true }); +run(["bunx", "electrobun", "build", "--env=stable"]); // ---- collect artifacts ---- const [osName, archName] = - requested === "win-x64" - ? ["win", "x64"] - : requested === "linux-x64" - ? ["linux", "x64"] - : ["macos", requested.slice(4)]; -const prefix = `stable-${osName}-${archName}-`; -const artifactsDir = path.join(ROOT, "artifacts"); + requested === "win-x64" ? ["win", "x64"] : requested === "linux-x64" ? ["linux", "x64"] : ["macos", "arm64"]; +// 2.x installers carry no channel prefix (macos-arm64-miniSCP.dmg); only the +// update feed (stable---*) keeps it, for v1-compatible clients. +const prefix = `${osName}-${archName}-`; const outDir = path.join(ROOT, "release", requested); mkdirSync(outDir, { recursive: true }); const produced = readdirSync(artifactsDir).filter( (f) => f.startsWith(prefix) && f.includes(BRANDING.appName), ); -if (!produced.length) fail(`no ${prefix}* artifacts found in artifacts/ — did the build run?`); +if (!produced.length) fail(`no ${prefix}* installer found in artifacts/ — did the build run?`); const out: string[] = []; for (const file of produced) { - const ext = file.endsWith(".dmg") ? ".dmg" : file.endsWith("-Setup.zip") ? "-Setup.zip" : null; - if (!ext) continue; // skip the raw .app.tar.zst + update.json + const ext = [".dmg", "-Setup.zip", "-Setup.tar.gz"].find((e) => file.endsWith(e)); + if (!ext) continue; const name = `${BRANDING.appName}-${buildName}-${requested}${ext}`; cpSync(path.join(artifactsDir, file), path.join(outDir, name)); out.push(name); } -// Windows / Linux: package the assembled app folder directly. On Windows that -// complements the Setup.exe installer (portable = unzip anywhere and run); on -// Linux it is the ONLY artifact, since electrobun emits just the .tar.zst -// updater blob there — no installer, AppImage or .deb. Best-effort: the folder -// layout is confirmed by the first real build on each OS. -if (requested === "win-x64" || requested === "linux-x64") { - const appDir = path.join(ROOT, "build", `stable-${osName}-${archName}`, BRANDING.appName); +// Windows: also zip the assembled app folder — the portable build (unzip +// anywhere and run) next to the Setup installer. Best-effort: the folder +// layout is confirmed by the first real 2.x Windows build. +if (requested === "win-x64") { + const appDir = path.join(ROOT, "build", "stable-win-x64", BRANDING.appName); if (existsSync(appDir)) { - if (requested === "win-x64") { - const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`); - run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]); - out.push(path.basename(portable)); - } else { - // Ship a wrapper beside the launcher: the packaged app never goes - // through scripts/start.ts, and the NVIDIA/DMABUF workaround has to be - // in the environment BEFORE the process starts (see scripts/linux-gpu.ts). - const wrapper = path.join(appDir, `${BRANDING.appName}.sh`); - writeFileSync( - wrapper, - [ - "#!/bin/sh", - "# WebKitGTK's DMABUF renderer cannot allocate GBM buffers on the NVIDIA", - "# proprietary driver — the window renders black. Disable it there only.", - 'if [ -z "$WEBKIT_DISABLE_DMABUF_RENDERER" ] && [ -e /proc/driver/nvidia ]; then', - " export WEBKIT_DISABLE_DMABUF_RENDERER=1", - "fi", - 'exec "$(dirname "$(readlink -f "$0")")/bin/launcher" "$@"', - "", - ].join("\n"), - { mode: 0o755 }, - ); - const tarball = path.join(outDir, `${BRANDING.appName}-${buildName}-linux-x64.tar.gz`); - // -C the parent so the archive unpacks into one named folder - run(["tar", "-czf", tarball, "-C", path.dirname(appDir), path.basename(appDir)]); - out.push(path.basename(tarball)); - } + const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`); + run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]); + out.push(path.basename(portable)); } else { console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`); } diff --git a/site/Caddyfile b/site/Caddyfile index c1c2e07..74cc07b 100644 --- a/site/Caddyfile +++ b/site/Caddyfile @@ -19,7 +19,7 @@ header @feed Cache-Control "no-cache" # Installers/payloads keep stable names but get overwritten each release. - @pkg path *.dmg *.zip *.tar.zst + @pkg path *.dmg *.zip *.tar.zst *.tar.gz header @pkg Cache-Control "public, max-age=300" # browse => directory listing; file_server serves range requests diff --git a/site/README.md b/site/README.md index ae1e93d..53f0aff 100644 --- a/site/README.md +++ b/site/README.md @@ -42,7 +42,7 @@ electrobun can't cross-compile, so each OS/arch is built on its own machine. On machine, after producing artifacts, drop them into the shared `dl/` folder: ```bash -bun scripts/release.ts # or --target mac-x64 / win-x64 +bun scripts/release.ts # or --target win-x64 / linux-x64 site/deploy.sh # -> /srv/miniscp/dl ``` @@ -56,14 +56,16 @@ rsync -av ./_dl/ user@host:/srv/miniscp/dl/ That's the whole workflow: **build, drop the artifacts, done.** Installers keep stable names and overwrite in place, so the download links on the page never change. -> **Don't delete old `*.patch` files.** They're the incremental-update trail for the (upcoming) +> **Don't delete old `*.patch` files.** They're the incremental-update trail for the > built-in updater — removing them forces older installs to re-download the full app. ## Notes - Downloads live under `/dl/` and are browsable at `http:///dl/`. -- The page links to fixed, stable filenames (`stable-macos-arm64-miniSCP.dmg`, …) so it stays - static and always points at the latest build. +- The page links to fixed, stable filenames (`macos-arm64-miniSCP.dmg`, `win-x64-miniSCP-Setup.zip`, + …) so it stays static and always points at the latest build. Electrobun 2.x names installers + without the `stable-` prefix; only the updater feed (`stable---update.json`, `.tar.zst`, + `.patch`) keeps it. Installers left over under the old v1 `stable-…` names are stale — delete them. - Builds are currently unsigned — the FAQ on the page covers the macOS `xattr` step and the Windows SmartScreen prompt. -- `/dl/` is also the intended `release.baseUrl` for the future electrobun auto-updater. +- `/dl/` is also the electrobun auto-updater's `release.baseUrl`. diff --git a/site/deploy.sh b/site/deploy.sh index 2c962c7..d44ff5c 100755 --- a/site/deploy.sh +++ b/site/deploy.sh @@ -3,7 +3,7 @@ set -euo pipefail # Publish this machine's built artifacts into the served flat download folder. # # Run from the repo root AFTER building for this host's target, e.g.: -# bun scripts/release.ts # or --target mac-x64 / win-x64 +# bun scripts/release.ts # or --target win-x64 / linux-x64 # site/deploy.sh # # electrobun cannot cross-compile, so each OS/arch is built (and deployed) on its @@ -19,12 +19,15 @@ set -euo pipefail dl="${MINISCP_DL_DIR:-/srv/miniscp/dl}" mkdir -p "$dl" -# electrobun's flat artifacts (stable---*): installers, update payloads, -# update.json, and (once release.baseUrl is set) .patch files. -if compgen -G 'artifacts/stable-*' >/dev/null; then - cp -f artifacts/stable-* "$dl"/ +# electrobun's flat artifacts, verbatim — the updater contract, never rename them: +# the update feed (stable---update.json, .tar.zst, .patch) plus the +# installers, which 2.x names without the channel prefix (macos-arm64-miniSCP.dmg, +# win-x64-miniSCP-Setup.zip). release.ts empties artifacts/ first, so it holds +# exactly this build. +if compgen -G 'artifacts/*' >/dev/null; then + cp -f artifacts/* "$dl"/ else - echo "deploy: WARN — no artifacts/stable-* found (did scripts/release.ts run?)" >&2 + echo "deploy: WARN — artifacts/ is empty (did scripts/release.ts run?)" >&2 fi # Windows portable zip is a release.ts extra (build-numbered) → give it a stable name. diff --git a/site/index.html b/site/index.html index 37cc29c..e2ab93a 100644 --- a/site/index.html +++ b/site/index.html @@ -215,9 +215,8 @@

Download

Always the latest stable build.

Looking for everything? browse all files.

@@ -226,8 +225,8 @@

FAQ

Which platforms are supported?
-
macOS (Apple Silicon and Intel) and Windows 11+ (x64). macOS is the primary target; - Windows is currently in beta.
+
macOS (Apple Silicon) and Windows 11+ (x64). macOS is the primary target; + Windows is currently in beta. Intel Macs are not supported.
macOS says the app is "damaged and can't be opened".
The builds aren't code-signed yet, so macOS quarantines them on download. Clear the diff --git a/src/bun/editor-windows.ts b/src/bun/editor-windows.ts index f6ef3e1..8399123 100644 --- a/src/bun/editor-windows.ts +++ b/src/bun/editor-windows.ts @@ -1,4 +1,4 @@ -import { Utils, type BrowserWindow } from "electrobun/bun"; +import { Utils, type BrowserWindow } from "electrobun/main"; import { BRANDING } from "../shared/branding"; import type { EditorOpenContext } from "../shared/rpc"; import type { AppSettings } from "../shared/types"; diff --git a/src/bun/index.ts b/src/bun/index.ts index 7b96edc..1bf774c 100644 --- a/src/bun/index.ts +++ b/src/bun/index.ts @@ -1,4 +1,4 @@ -import Electrobun, { ApplicationMenu, BrowserWindow, Updater, Utils } from "electrobun/bun"; +import Electrobun, { ApplicationMenu, BrowserWindow, Updater, Utils } from "electrobun/main"; import { BRANDING } from "../shared/branding"; import type { EditorOpenContext } from "../shared/rpc"; import { effectiveConcurrency, type AppSettings } from "../shared/types"; @@ -55,6 +55,7 @@ const events = { openConnectionManager: () => {}, openSettings: () => {}, showAbout: () => {}, + applyUpdate: async () => {}, }; const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events }); // late-bound like events: a watched local dir changed -> tell the main webview @@ -211,11 +212,24 @@ engine.onUpdate = (snapshot) => { // don't quit out from under running transfers or unsaved editors let quitConfirmed = false; +// Updater.applyUpdate() asks this guard first (2.x), so remember the quit is +// update-initiated: "Quit Anyway" must re-run the apply, or a plain quit would +// drop the downloaded update. +let applyingUpdate = false; +events.applyUpdate = async () => { + applyingUpdate = true; + try { + await Updater.applyUpdate(); + } finally { + applyingUpdate = false; + } +}; Electrobun.events.on("before-quit", (event) => { const transfers = engine.activeJobCount(); const dirtyEditors = editors.dirtyCount(); if (quitConfirmed || (transfers === 0 && dirtyEditors === 0)) return; (event as { response: { allow: boolean } }).response = { allow: false }; + const updating = applyingUpdate; // read now: it clears once the vetoed apply returns void (async () => { const parts = [ transfers > 0 && `${transfers} transfer${transfers === 1 ? " is" : "s are"} still running`, @@ -233,7 +247,10 @@ Electrobun.events.on("before-quit", (event) => { }); if (response === 0) { quitConfirmed = true; - Utils.quit(); + // the trailing quit is a no-op once the apply is quitting, and still + // quits if it bails (no prepared download) + if (updating) void Updater.applyUpdate().finally(() => Utils.quit()); + else Utils.quit(); } })(); }); diff --git a/src/bun/manager-window.ts b/src/bun/manager-window.ts index db0b76d..bd4a9e1 100644 --- a/src/bun/manager-window.ts +++ b/src/bun/manager-window.ts @@ -1,4 +1,4 @@ -import type { BrowserWindow } from "electrobun/bun"; +import type { BrowserWindow } from "electrobun/main"; import type { AppSettings } from "../shared/types"; import { createManagerRPC } from "./rpc-handlers"; import type { ConnectionStore } from "./store/connections"; diff --git a/src/bun/menu.ts b/src/bun/menu.ts index 0cf7412..84f22d6 100644 --- a/src/bun/menu.ts +++ b/src/bun/menu.ts @@ -1,4 +1,4 @@ -import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/bun"; +import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/main"; import { BRANDING } from "../shared/branding"; import type { SavedConnection } from "../shared/types"; import { t } from "./i18n"; diff --git a/src/bun/rpc-handlers.ts b/src/bun/rpc-handlers.ts index 7cdfeb2..dc8e862 100644 --- a/src/bun/rpc-handlers.ts +++ b/src/bun/rpc-handlers.ts @@ -1,4 +1,4 @@ -import { BrowserView, Updater, Utils } from "electrobun/bun"; +import { BrowserView, Updater, Utils } from "electrobun/main"; import type { AppRPC, EditorOpenContext } from "../shared/rpc"; import type { AppSettings, Result } from "../shared/types"; import { readFileText, writeFileText, type EditManager } from "./edits"; @@ -33,6 +33,8 @@ export interface AppDeps { openConnectionManager: () => void; openSettings: () => void; showAbout: () => void; + /** "Restart to apply" — lives next to the before-quit guard it must get past. */ + applyUpdate: () => Promise; }; } @@ -352,7 +354,7 @@ export function createAppRPC({ applyUpdate: () => wrap(async () => { - await Updater.applyUpdate(); // relaunches + quits; may never resolve + await events.applyUpdate(); // relaunches + quits; may never resolve return {}; }), }, diff --git a/src/bun/settings-window.ts b/src/bun/settings-window.ts index c609d1f..511ac5b 100644 --- a/src/bun/settings-window.ts +++ b/src/bun/settings-window.ts @@ -1,4 +1,4 @@ -import type { BrowserWindow } from "electrobun/bun"; +import type { BrowserWindow } from "electrobun/main"; import type { AppSettings } from "../shared/types"; import { t } from "./i18n"; import { createSettingsRPC } from "./rpc-handlers"; diff --git a/src/bun/tray.ts b/src/bun/tray.ts index a906839..08b58ec 100644 --- a/src/bun/tray.ts +++ b/src/bun/tray.ts @@ -1,4 +1,4 @@ -import { Tray, type BrowserWindow } from "electrobun/bun"; +import { Tray, type BrowserWindow } from "electrobun/main"; /** * Menu-bar / notification-area icon. Clicking it brings the app window to the diff --git a/src/bun/windows.ts b/src/bun/windows.ts index 185e905..c27bf4a 100644 --- a/src/bun/windows.ts +++ b/src/bun/windows.ts @@ -1,4 +1,4 @@ -import { BrowserWindow } from "electrobun/bun"; +import { BrowserWindow } from "electrobun/main"; import type { AppSettings } from "../shared/types"; /** @@ -44,10 +44,15 @@ export function buildPreload( * lights overlay it, which is what IS_MAC_CHROME reserves pl-24 for); other * platforms keep the native titlebar. Constructor-only: there is no API to * re-decorate a live window. + * + * electrobun 2.x adds trafficLightOffset to macOS's default button position + * (9/9 in the 32pt macOS 26 titlebar) and grows the titlebar to fit — 1.18.1 + * clamped the shift inside the stock titlebar instead. {12, 4} puts the 14pt + * buttons at left 21 / top 13: vertically centred in the 40px h-10 header. */ export function chromeOptions() { return process.platform === "darwin" - ? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } } + ? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 4 } } : {}; } diff --git a/src/types/shims.d.ts b/src/types/shims.d.ts deleted file mode 100644 index 9e42ed2..0000000 --- a/src/types/shims.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// electrobun ships raw .ts sources whose WGPU adapter imports "three" without -// bundled types; we never use that path, so give tsc an any-typed stand-in. -declare module "three"; diff --git a/tsconfig.json b/tsconfig.json index 10a2797..4573aaa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,5 @@ { + "extends": "./.hutch/devkit/tsconfig.json", "compilerOptions": { "target": "ES2022", "useDefineForClassFields": false, @@ -16,6 +17,6 @@ "emitDecoratorMetadata": false, "skipLibCheck": true }, - "include": ["src"], + "include": ["src", "electrobun.config.ts"], "exclude": ["node_modules", "dist", "build", "../../package/dist"] } diff --git a/ui/src/app/core/context-menu.service.ts b/ui/src/app/core/context-menu.service.ts index d0d5836..721fc22 100644 --- a/ui/src/app/core/context-menu.service.ts +++ b/ui/src/app/core/context-menu.service.ts @@ -53,10 +53,10 @@ const trashKey = (): string => (IS_WINDOWS ? 'ctx.moveToRecycleBin' : 'ctx.moveT /** * Builds and opens the panel right-click menus. This used to be a native * electrobun ContextMenu on the bun side, but `showContextMenu` is a documented - * no-op on Linux (electrobun's own dist/api/bun/proc/linux.md), which left the - * whole right-click surface dead there. The menu is plain HTML now — one code - * path on every platform — and still emits the exact same action strings, so - * the dispatcher in app.ts is unchanged. + * no-op on Linux (electrobun's own .hutch/devkit/api/sdks/main/proc/linux.md), + * which left the whole right-click surface dead there. The menu is plain HTML + * now — one code path on every platform — and still emits the exact same + * action strings, so the dispatcher in app.ts is unchanged. */ @Injectable({ providedIn: 'root' }) export class ContextMenuService { diff --git a/ui/src/app/core/rpc.service.ts b/ui/src/app/core/rpc.service.ts index 96c17d4..ee9cfae 100644 --- a/ui/src/app/core/rpc.service.ts +++ b/ui/src/app/core/rpc.service.ts @@ -76,9 +76,10 @@ export class RpcService { }); const electroview = new Electroview({ rpc: this.rpc }); - // Requests fired before the RPC WebSocket finishes connecting are lost, so - // everything below gates on it being open (services fire calls at startup). - const socket = electroview.bunSocket; + // Gate everything below on the RPC WebSocket being open (services fire calls + // at startup). v1 silently lost pre-open sends; 2.x Electroview queues them + // and falls back to the native bridge, so this is now just belt-and-braces. + const socket = electroview.hostSocket; if (socket && socket.readyState !== WebSocket.OPEN) { this.ready = new Promise((resolve) => { socket.addEventListener('open', () => resolve(), { once: true }); diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 31103d2..f53d883 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -8,14 +8,20 @@ UI layer (services, platform, formatters). @core is an ALIAS rather than a relative path on purpose: skin components live at varying depths under app/skins//, and an alias keeps their imports identical wherever a - component is moved to. */ + component is moved to. + electrobun/view -> the SDK Hutch projects into .hutch/devkit (the npm + `electrobun` package only exports a throwing stub). Exact target from the + devkit's export map; `bunx electrobun prepare` must have run. */ "paths": { "@shared/*": ["../src/shared/*"], - "@core/*": ["./src/app/core/*"] + "@core/*": ["./src/app/core/*"], + "electrobun/view": ["../.hutch/devkit/api/browser/index.ts"] }, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, - "noImplicitReturns": true, + /* Off because the devkit's browser SDK is raw TS compiled inside this + program, and 2.0.2's transport send() returns on only one path. */ + "noImplicitReturns": false, "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "isolatedModules": true,