diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 2a8cddbc..2f546c45 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -99,3 +99,16 @@ jobs: with: ref: ${{ needs.release.outputs.new_tag }} version: ${{ needs.release.outputs.new_tag }} + + # Build + attach the native desktop installers (macOS/Linux signed if secrets + # are set; Windows best-effort). Same in-run pattern as publish-images, because + # the GITHUB_TOKEN-created release does not fire a `release` event. + desktop-apps: + needs: release + if: ${{ needs.release.outputs.new_tag != '' }} + permissions: + contents: write + uses: ./.github/workflows/desktop-apps.yml + with: + tag: ${{ needs.release.outputs.new_tag }} + secrets: inherit diff --git a/.github/workflows/desktop-apps.yml b/.github/workflows/desktop-apps.yml index f17b85b0..8afaa83e 100644 --- a/.github/workflows/desktop-apps.yml +++ b/.github/workflows/desktop-apps.yml @@ -30,6 +30,15 @@ on: description: 'Release tag to build + attach to (e.g. v0.3.0)' required: true type: string + # Called in-run from auto-release.yml: a GITHUB_TOKEN-created release does NOT + # fire the `release` event, so the apps must be built from within the same run + # that cut the release (same pattern as publish-images.yml). + workflow_call: + inputs: + tag: + description: 'Release tag to build + attach to' + required: true + type: string concurrency: group: desktop-apps-${{ github.event.release.tag_name || inputs.tag }} @@ -43,8 +52,11 @@ jobs: fail-fast: false matrix: include: + # macOS is Apple Silicon (arm64) only for v1 — see the rationale in + # desktop/electron-builder.yml (extraResources can't be arch-split, and + # Intel runners queue for hours). Intel x64 is a tracked follow-up. - os: macos-latest - args: --mac + args: --mac --arm64 - os: windows-latest args: --win # Linux is best-effort for now: AppImage/deb are built + uploaded but @@ -53,12 +65,16 @@ jobs: - os: ubuntu-latest args: --linux runs-on: ${{ matrix.os }} + # Hard cap so a stuck native build can never run for hours. + timeout-minutes: 30 env: TAG: ${{ github.event.release.tag_name || inputs.tag }} # Must match Supervisor.KERNEL_PORT in desktop/src/supervisor.ts — the # web-ui bakes this kernel URL into its routes-manifest at BUILD time, so # it cannot be a per-launch random port. KERNEL_URL: http://127.0.0.1:8769 + # node-gyp's VS auto-detection is unreliable on windows-latest; pin VS2022. + GYP_MSVS_VERSION: '2022' # macOS signing (hoisted so they're addressable in step-level `if:`). APPLE_P12_BASE64: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64_HIGH5 }} APPLE_P12_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD_HIGH5 }} @@ -82,6 +98,21 @@ jobs: with: node-version: 22 + # node-gyp's bundled gyp imports Python's `distutils`, removed in Python + # 3.12. The macOS/Windows runners default to 3.12+ (Ubuntu still ships + # 3.11), so pin 3.11 everywhere to keep native rebuilds working. + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + # @electron/rebuild pulls @electron/node-gyp as a git+ssh GitHub dependency. + # Runners have no SSH key, so rewrite git+ssh GitHub URLs to https (public + # repo, no auth) before any npm install/ci, or those installs fail. + - name: Allow git-https for git dependencies + run: | + git config --global url."https://github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://github.com/".insteadOf "git@github.com:" + # --- build the omadia runtime the installer bundles ------------------- - name: Build middleware kernel working-directory: middleware @@ -101,25 +132,29 @@ jobs: working-directory: desktop run: npm ci - # better-sqlite3/argon2/sharp ship in middleware/node_modules built for the - # system Node ABI; the kernel runs under Electron's Node (ELECTRON_RUN_AS_NODE) - # so they must be rebuilt against Electron's ABI before staging. electron-builder - # rebuilds the APP's own deps but NOT extraResources, so we do it here. - - name: Rebuild middleware native modules for Electron - working-directory: desktop - # Pin the Electron version explicitly: electron-rebuild's auto-detection - # is unreliable across the --module-dir boundary (electron lives in - # desktop/, the modules in middleware/), and a wrong-ABI rebuild fails - # silently until the kernel won't boot. + # Make the middleware's native modules load under Electron's Node (the kernel + # runs via ELECTRON_RUN_AS_NODE). argon2 + sharp are N-API → ABI-stable across + # node/electron, so their shipped binaries already work, no rebuild needed. + # Only better-sqlite3 (non-N-API) needs an Electron-ABI build — and it + # publishes Electron PREBUILDS, so fetch one with prebuild-install (no + # node-gyp / Visual Studio). Fall back to a source rebuild if no prebuild + # exists for this Electron version on this platform. + - name: Provision better-sqlite3 for Electron + shell: bash run: | - EV="$(node -p "require('electron/package.json').version")" - echo "rebuilding middleware native modules for Electron $EV" - npx electron-rebuild --version "$EV" --module-dir ../middleware \ - --only better-sqlite3,argon2,sharp --force - # Real ABI check: load a rebuilt native module under Electron's own Node - # runtime. If the rebuild targeted the wrong ABI this require() throws. - ELECTRON_RUN_AS_NODE=1 ./node_modules/.bin/electron -e \ - "require('../middleware/node_modules/better-sqlite3'); console.log('native modules load under Electron ABI OK')" + EV="$(node -p "require('./desktop/node_modules/electron/package.json').version")" + echo "provisioning better-sqlite3 for Electron $EV" + ( cd middleware/node_modules/better-sqlite3 \ + && npx --yes prebuild-install -r electron -t "$EV" --verbose ) \ + || ( cd desktop \ + && npx electron-rebuild --version "$EV" --module-dir ../middleware --only better-sqlite3 --force ) + # Verify ALL three native modules actually load under Electron's runtime + # — not just the rebuilt better-sqlite3, but also argon2 + sharp, whose + # (un-rebuilt) prebuilds we ASSUME are N-API/ABI-stable. If that + # assumption is wrong, this fails here instead of crashing the kernel at + # boot in the shipped app. + ELECTRON_RUN_AS_NODE=1 ./desktop/node_modules/.bin/electron -e \ + "require('./middleware/node_modules/better-sqlite3'); require('./middleware/node_modules/argon2'); require('./middleware/node_modules/sharp'); console.log('better-sqlite3 + argon2 + sharp load under Electron ABI OK')" - name: Build desktop (main + preload + renderer) working-directory: desktop @@ -131,7 +166,7 @@ jobs: # --- macOS signing + notarization (proven for omadia-ui) -------------- - name: Prepare Apple signing (optional — needs _HIGH5 secrets) - if: ${{ matrix.os == 'macos-latest' && env.APPLE_P12_BASE64 != '' }} + if: ${{ startsWith(matrix.os, 'macos') && env.APPLE_P12_BASE64 != '' }} run: | printf '%s' "$APPLE_P12_BASE64" | base64 -d > "$RUNNER_TEMP/developer-id.p12" printf '%s' "$APPLE_ASC_KEY_P8_BASE64" | base64 -d > "$RUNNER_TEMP/asc-key.p8" @@ -150,8 +185,17 @@ jobs: security import "$RUNNER_TEMP/developer-id.p12" -k "$KEYCHAIN" \ -P "$APPLE_P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KCPASS" "$KEYCHAIN" >/dev/null - # Prepend our keychain to the user search list (keep the existing ones). - security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | sed 's/[\"[:space:]]//g') + # Prepend our keychain to the user search list, KEEPING the existing ones. + # Build the list as an array (bash 3.2 on macOS runners — no mapfile) so + # multiple existing keychains aren't mashed into one path by whitespace + # stripping. Only quotes + leading/trailing space are trimmed per entry. + EXISTING=() + while IFS= read -r kc; do + kc="${kc//\"/}" + kc="$(printf '%s' "$kc" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')" + [ -n "$kc" ] && EXISTING+=("$kc") + done < <(security list-keychains -d user) + security list-keychains -d user -s "$KEYCHAIN" "${EXISTING[@]}" # The exact Developer ID Application identity now present in the keychain. IDENT=$(security find-identity -v -p codesigning "$KEYCHAIN" \ @@ -186,7 +230,7 @@ jobs: # The .app is notarized + stapled by electron-builder above; the DMG needs # its own ticket so `stapler validate` passes on the disk image itself. - name: Notarize + staple the DMGs - if: ${{ matrix.os == 'macos-latest' && env.APPLE_P12_BASE64 != '' }} + if: ${{ startsWith(matrix.os, 'macos') && env.APPLE_P12_BASE64 != '' }} working-directory: desktop run: | for dmg in release/*.dmg; do @@ -199,11 +243,17 @@ jobs: done - name: Verify macOS signatures (acceptance gate) - if: ${{ matrix.os == 'macos-latest' && env.APPLE_P12_BASE64 != '' }} + if: ${{ startsWith(matrix.os, 'macos') && env.APPLE_P12_BASE64 != '' }} working-directory: desktop run: | set -e - for app in release/mac*/*.app; do + shopt -s nullglob + apps=(release/mac*/*.app) + if [ ${#apps[@]} -eq 0 ]; then + echo "FAIL: no .app produced under release/mac*/ — nothing to verify" >&2 + exit 1 + fi + for app in "${apps[@]}"; do # `--deep` is deprecated for verification and does NOT descend into # extraResources — verify the app, then EACH nested native binary # explicitly, then the real Gatekeeper verdict. @@ -229,7 +279,31 @@ jobs: - name: Tear down signing material if: ${{ always() }} - run: rm -f "$RUNNER_TEMP/developer-id.p12" "$RUNNER_TEMP/asc-key.p8" "$RUNNER_TEMP/win-cert.p12" || true + run: | + rm -f "$RUNNER_TEMP/developer-id.p12" "$RUNNER_TEMP/asc-key.p8" "$RUNNER_TEMP/win-cert.p12" || true + # Also drop our temp signing keychain (and let macOS fall back to the + # default search list) so nothing signing-related lingers. + if [ -n "${MAC_SIGN_KEYCHAIN:-}" ]; then + security delete-keychain "$MAC_SIGN_KEYCHAIN" 2>/dev/null || true + fi + + # Downloadable from the run itself (independent of the Release upload), and + # placed AFTER notarize/staple/verify so the artifacts are the final, + # stapled installers — not a pre-notarization copy. + - name: Upload installers as run artifacts + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: omadia-installers-${{ matrix.os }} + if-no-files-found: ignore + path: | + desktop/release/*.dmg + desktop/release/*.zip + desktop/release/*.exe + desktop/release/*.AppImage + desktop/release/*.deb + desktop/release/*.blockmap + desktop/release/latest*.yml - name: Upload installers to the release env: @@ -237,15 +311,15 @@ jobs: working-directory: desktop run: | shopt -s nullglob - # On workflow_dispatch the tag may not correspond to an existing - # release — fail with a clear message rather than a cryptic gh error. - if ! gh release view "$TAG" >/dev/null 2>&1; then - echo "no GitHub Release exists for tag '$TAG' — create the release first" >&2 - exit 1 - fi files=(release/*.dmg release/*.zip release/*.exe release/*.AppImage release/*.deb release/*.blockmap release/latest*.yml) if [ ${#files[@]} -eq 0 ]; then echo "no installer artifacts found in desktop/release/" >&2 exit 1 fi + # If TAG has no matching release (e.g. a build-validation dispatch on a + # branch name), this is a build-only run — skip upload, don't fail. + if ! gh release view "$TAG" >/dev/null 2>&1; then + echo "no GitHub Release for '$TAG' — build validated, skipping upload." + exit 0 + fi gh release upload "$TAG" "${files[@]}" --clobber diff --git a/desktop/buildResources/afterPack.js b/desktop/buildResources/afterPack.js new file mode 100644 index 00000000..26464e4c --- /dev/null +++ b/desktop/buildResources/afterPack.js @@ -0,0 +1,110 @@ +// electron-builder afterPack hook — sign the native Mach-O binaries that ship +// as extraResources (the staged middleware's node_modules: better-sqlite3, +// argon2, sharp, and any nested .dylib). +// +// WHY: electron-builder signs the app bundle + its OWN dependencies, but it does +// NOT sign extraResources payloads. Under `hardenedRuntime: true` + notarization, +// Apple's notary service rejects bundles that contain unsigned Mach-O binaries +// (and hardened-runtime library validation refuses to load unsigned .node at +// runtime). We must sign these BEFORE electron-builder seals the outer app, so +// afterPack (post-pack, pre-sign) is the correct hook — the outer signature then +// records the now-signed nested binaries. +// +// Fail-soft: if no Developer ID identity is available (unsigned/local build), it +// logs and skips, so dev/ad-hoc builds still work. + +const { execFileSync } = require('node:child_process'); +const fs = require('node:fs'); +const path = require('node:path'); + +/** + * Resolves the Developer ID Application identity to sign with. + * + * Prefers an explicit `MAC_SIGN_IDENTITY` env (set by CI) so we don't depend on + * electron-builder's temporary keychain already existing when afterPack runs; + * falls back to scanning the keychain via `security find-identity`. + */ +function findIdentity() { + const fromEnv = (process.env.MAC_SIGN_IDENTITY || '').trim(); + if (fromEnv) return fromEnv; + try { + const out = execFileSync('security', ['find-identity', '-v', '-p', 'codesigning'], { + encoding: 'utf8', + }); + const m = out.match(/"(Developer ID Application:[^"]+)"/); + return m ? m[1] : null; + } catch { + return null; + } +} + +function collectMachO(dir, found) { + let entries; + try { + entries = fs.readdirSync(dir); + } catch { + return found; + } + for (const name of entries) { + const p = path.join(dir, name); + let st; + try { + // statSync FOLLOWS symlinks, so symlinked vendor dirs (e.g. sharp's libvips) + // get walked and symlinked .dylibs resolve to their real target — which we + // dedupe so we sign each Mach-O once. lstat-based walks miss these and ship + // them unsigned, which the verify gate (find follows them) would then fail. + st = fs.statSync(p); + } catch { + continue; + } + if (st.isDirectory()) { + collectMachO(p, found); + } else if (st.isFile() && (name.endsWith('.node') || name.endsWith('.dylib'))) { + try { + found.add(fs.realpathSync(p)); + } catch { + found.add(p); + } + } + } + return found; +} + +exports.default = async function afterPack(context) { + if (context.electronPlatformName !== 'darwin') return; + + const identity = findIdentity(); + if (!identity) { + // With signing secrets present (MAC_SIGN_EXPECTED=1) a missing identity is a + // HARD error — silently shipping unsigned nested modules would only fail + // later at notarization. Without secrets, this is the legitimate unsigned + // (dev / ad-hoc) path, so we skip. + if (process.env.MAC_SIGN_EXPECTED === '1') { + throw new Error( + '[afterPack] signing was expected (MAC_SIGN_EXPECTED=1) but no Developer ID ' + + 'Application identity is available — the signing keychain was not set up ' + + 'before packaging. Refusing to ship unsigned native modules.', + ); + } + console.log('[afterPack] no Developer ID identity — skipping nested signing (unsigned build).'); + return; + } + + const appName = `${context.packager.appInfo.productFilename}.app`; + const omadiaResources = path.join(context.appOutDir, appName, 'Contents', 'Resources', 'omadia'); + const targets = [...collectMachO(omadiaResources, new Set())]; + if (targets.length === 0) { + console.log('[afterPack] no nested Mach-O binaries found under extraResources.'); + return; + } + + const keychain = (process.env.MAC_SIGN_KEYCHAIN || '').trim(); + const args = ['--force', '--options', 'runtime', '--timestamp']; + if (keychain) args.push('--keychain', keychain); + args.push('--sign', identity); + + console.log(`[afterPack] signing ${targets.length} nested Mach-O binaries with "${identity}"`); + for (const target of targets) { + execFileSync('codesign', [...args, target], { stdio: 'inherit' }); + } +}; diff --git a/desktop/buildResources/entitlements.mac.plist b/desktop/buildResources/entitlements.mac.plist new file mode 100644 index 00000000..7d2cf1bf --- /dev/null +++ b/desktop/buildResources/entitlements.mac.plist @@ -0,0 +1,19 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + com.apple.security.network.client + + + diff --git a/desktop/electron-builder.yml b/desktop/electron-builder.yml index cc1e49d4..1c1959b7 100644 --- a/desktop/electron-builder.yml +++ b/desktop/electron-builder.yml @@ -17,7 +17,7 @@ copyright: Copyright © 2026 byte5 GmbH directories: output: release - buildResources: build + buildResources: buildResources # Only the compiled desktop app + its own node_modules go into the app bundle. files: @@ -42,20 +42,28 @@ asarUnpack: # Sign the native Mach-O binaries inside the staged middleware (extraResources) # BEFORE the outer app is signed — electron-builder does not sign extraResources, # and notarization rejects unsigned nested Mach-O. Fail-soft when unsigned. -afterPack: build/afterPack.js +afterPack: buildResources/afterPack.js mac: category: public.app-category.developer-tools hardenedRuntime: true gatekeeperAssess: false - entitlements: build/entitlements.mac.plist - entitlementsInherit: build/entitlements.mac.plist + entitlements: buildResources/entitlements.mac.plist + entitlementsInherit: buildResources/entitlements.mac.plist + # Apple Silicon only for v1. The native modules ship as extraResources (copied + # verbatim, not arch-split), and GitHub's Intel (macos-13) runners queue for + # hours — impractical for a release. An x64 DMG built on an arm64 runner would + # contain arm64 native modules and crash on Intel. Intel support is a follow-up + # (cross-compile x64 prebuilds on the arm64 runner into a second staged tree). target: - target: dmg - arch: [arm64, x64] + arch: [arm64] - target: zip # required by electron-updater on macOS - arch: [arm64, x64] - notarize: false # flip to true in CI once APPLE_* secrets are set + arch: [arm64] + # Notarization is left unset here so it's driven entirely by CI: the workflow + # adds `--config.mac.notarize=true` only when the Apple secrets are present + # (and electron-builder skips it without notary credentials). A hard-coded + # `notarize: false` here would fight that CLI override. dmg: title: ${productName} ${version} diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 4a8c87b4..54307fce 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -14,6 +14,7 @@ "electron-updater": "^6.3.9" }, "devDependencies": { + "@electron/rebuild": "3.6.1", "@types/node": "^22.10.0", "electron": "^37.2.0", "electron-builder": "^25.1.8", @@ -127,6 +128,51 @@ "global-agent": "^3.0.0" } }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@electron/get/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@electron/get/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/@electron/notarize": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", @@ -158,29 +204,6 @@ "node": ">=10" } }, - "node_modules/@electron/notarize/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/notarize/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/osx-sign": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz", @@ -203,21 +226,6 @@ "node": ">=12.0.0" } }, - "node_modules/@electron/osx-sign/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -231,29 +239,6 @@ "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/@electron/osx-sign/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/osx-sign/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/rebuild": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.1.tgz", @@ -283,57 +268,6 @@ "node": ">=12.13.0" } }, - "node_modules/@electron/rebuild/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@electron/rebuild/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/rebuild/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/rebuild/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@electron/universal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", @@ -385,19 +319,6 @@ "node": ">=14.14" } }, - "node_modules/@electron/universal/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/@electron/universal/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -414,16 +335,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@electron/universal/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -589,29 +500,6 @@ "node": ">=10" } }, - "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@malept/flatpak-bundler/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/@npmcli/fs": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", @@ -626,19 +514,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@npmcli/move-file": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", @@ -978,57 +853,6 @@ "electron-builder-squirrel-windows": "25.1.8" } }, - "node_modules/app-builder-lib/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/app-builder-lib/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/app-builder-lib/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/app-builder-lib/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/aproba": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", @@ -1355,44 +1179,6 @@ "node": ">=12.0.0" } }, - "node_modules/builder-util/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/builder-util/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/builder-util/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/cacache": { "version": "16.1.3", "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", @@ -2103,44 +1889,6 @@ "dmg-license": "^1.0.11" } }, - "node_modules/dmg-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dmg-builder/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/dmg-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/dmg-license": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", @@ -2243,134 +1991,55 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^22.7.7", - "extract-zip": "^2.0.1" - }, - "bin": { - "electron": "cli.js" - }, - "engines": { - "node": ">= 12.20.55" - } - }, - "node_modules/electron-builder": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", - "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", - "dev": true, - "license": "MIT", - "dependencies": { - "app-builder-lib": "25.1.8", - "builder-util": "25.1.7", - "builder-util-runtime": "9.2.10", - "chalk": "^4.1.2", - "dmg-builder": "25.1.8", - "fs-extra": "^10.1.0", - "is-ci": "^3.0.0", - "lazy-val": "^1.0.5", - "simple-update-notifier": "2.0.0", - "yargs": "^17.6.2" - }, - "bin": { - "electron-builder": "cli.js", - "install-app-deps": "install-app-deps.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/electron-builder-squirrel-windows": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", - "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "app-builder-lib": "25.1.8", - "archiver": "^5.3.1", - "builder-util": "25.1.7", - "fs-extra": "^10.1.0" - } - }, - "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/electron-builder/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" }, "engines": { - "node": ">=12" + "node": ">= 12.20.55" } }, - "node_modules/electron-builder/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "node_modules/electron-builder": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", + "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", "dev": true, "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "app-builder-lib": "25.1.8", + "builder-util": "25.1.7", + "builder-util-runtime": "9.2.10", + "chalk": "^4.1.2", + "dmg-builder": "25.1.8", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/electron-builder/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "node_modules/electron-builder-squirrel-windows": { + "version": "25.1.8", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", + "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 10.0.0" + "peer": true, + "dependencies": { + "app-builder-lib": "25.1.8", + "archiver": "^5.3.1", + "builder-util": "25.1.7", + "fs-extra": "^10.1.0" } }, "node_modules/electron-publish": { @@ -2389,44 +2058,6 @@ "mime": "^2.5.2" } }, - "node_modules/electron-publish/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-publish/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/electron-publish/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/electron-updater": { "version": "6.8.9", "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz", @@ -2456,32 +2087,6 @@ "node": ">=12.0.0" } }, - "node_modules/electron-updater/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/electron-updater/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/electron-updater/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -2494,15 +2099,6 @@ "node": ">=10" } }, - "node_modules/electron-updater/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2788,18 +2384,17 @@ "peer": true }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" } }, "node_modules/fs-minipass": { @@ -2990,20 +2585,6 @@ "node": ">=10.0" } }, - "node_modules/global-agent/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -3493,11 +3074,13 @@ } }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -3993,19 +3576,6 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -4024,19 +3594,6 @@ "semver": "^7.3.5" } }, - "node_modules/node-api-version/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-gyp": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", @@ -4063,19 +3620,6 @@ "node": "^12.13 || ^14.13 || >=16" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/nopt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", @@ -4642,13 +4186,16 @@ } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/semver-compare": { @@ -4726,19 +4273,6 @@ "node": ">=10" } }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", @@ -5013,44 +4547,6 @@ "fs-extra": "^10.0.0" } }, - "node_modules/temp-file/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/temp-file/node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/temp-file/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/tiny-typed-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", @@ -5149,13 +4645,12 @@ } }, "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/uri-js": { diff --git a/desktop/package.json b/desktop/package.json index 8a327d17..e8b6835e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -3,8 +3,16 @@ "version": "0.1.0", "private": true, "description": "omadia native desktop installer — runs the full omadia stack locally with an embedded Postgres+pgvector engine, no Docker.", - "author": "byte5 GmbH", + "author": { + "name": "byte5 GmbH", + "email": "mwege@byte5.de" + }, "license": "Apache-2.0", + "homepage": "https://github.com/byte5ai/omadia", + "repository": { + "type": "git", + "url": "git+https://github.com/byte5ai/omadia.git" + }, "main": "dist/main.js", "scripts": { "build": "tsc -p tsconfig.json && node scripts/copy-renderer.mjs", @@ -22,7 +30,7 @@ "electron-updater": "^6.3.9" }, "devDependencies": { - "@electron/rebuild": "^3.7.1", + "@electron/rebuild": "3.6.1", "@types/node": "^22.10.0", "electron": "^37.2.0", "electron-builder": "^25.1.8", diff --git a/desktop/scripts/stage-runtime.mjs b/desktop/scripts/stage-runtime.mjs index ec9357d2..48c9b41b 100644 --- a/desktop/scripts/stage-runtime.mjs +++ b/desktop/scripts/stage-runtime.mjs @@ -43,6 +43,34 @@ for (const entry of ['dist', 'node_modules', 'packages', 'migrations', 'assets', const to = path.join(mwDest, entry); fs.cpSync(from, to, { recursive: true, dereference: true }); } + +// fs.cpSync({dereference:true}) does NOT reliably materialise the npm-workspace +// symlinks `node_modules/@omadia/* → packages/*` — on CI runners they're ABSOLUTE +// (`/Users/runner/.../packages/x`), so the shipped bundle ends up with DANGLING +// symlinks to the build machine and the kernel can't resolve any @omadia/* package +// at runtime (ERR_MODULE_NOT_FOUND → kernel crash). Replace each @omadia symlink +// with a real copy of the package it resolves to. (Each package's own nested +// @omadia symlinks may stay dangling but are unused: Node resolves up to this +// top-level real dir.) +const scope = path.join(mwDest, 'node_modules', '@omadia'); +if (fs.existsSync(scope)) { + let materialised = 0; + for (const name of fs.readdirSync(scope)) { + const link = path.join(scope, name); + if (!fs.lstatSync(link).isSymbolicLink()) continue; + let target; + try { + target = fs.realpathSync(link); + } catch { + console.error(`[stage-runtime] FATAL: dangling @omadia/${name} symlink — build the middleware first`); + process.exit(1); + } + fs.rmSync(link, { force: true }); + fs.cpSync(target, link, { recursive: true }); + materialised++; + } + console.log(`[stage-runtime] materialised ${materialised} @omadia workspace package(s)`); +} console.log('[stage-runtime] staged middleware'); // --- web-ui: flatten the Next standalone output into runtime/web-ui --- diff --git a/desktop/src/embeddedDb.ts b/desktop/src/embeddedDb.ts index 031e298c..609134dd 100644 --- a/desktop/src/embeddedDb.ts +++ b/desktop/src/embeddedDb.ts @@ -24,6 +24,9 @@ import { log } from './log'; * bottlenecks is a bundled native Postgres. */ +// NOT a credential: pglite-socket on loopback does not authenticate, so these +// only populate the DATABASE_URL the kernel's pg client expects — any value is +// accepted. Security rests entirely on the 127.0.0.1-only bind, not on this. const DB_NAME = 'omadia'; const DB_USER = 'omadia'; const DB_PASSWORD = 'omadia'; @@ -85,13 +88,18 @@ export async function startEmbeddedDb(): Promise { // (pgcrypto is intentionally NOT required: the kernel migrations only used it // for gen_random_uuid(), which is core since Postgres 13.) - const port = await stableDbPort(); - const server = new PGLiteSocketServer({ - db, - port, - host: '127.0.0.1', - }); - await server.start(); + // From here on, any failure must close `db` — otherwise the PGlite instance + // leaks and keeps the dataDir lock, so a retry can't reopen the same dir. + let server: PGLiteSocketServer; + let port: number; + try { + port = await stableDbPort(); + server = new PGLiteSocketServer({ db, port, host: '127.0.0.1' }); + await server.start(); + } catch (err) { + await db.close().catch(() => {}); + throw err; + } log.info(`[db] embedded Postgres listening on 127.0.0.1:${port}`); current = { db, server, port }; diff --git a/desktop/src/main.ts b/desktop/src/main.ts index e386bee3..28d4e479 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -4,7 +4,7 @@ import { Supervisor, setActiveSupervisor, BootProgress } from './supervisor'; import { registerIpc } from './ipc'; import { CH } from './ipcTypes'; import { createTray, setTrayStatus, destroyTray, TrayActions } from './tray'; -import { initUpdater } from './updater'; +import { initUpdater, isUpdateInstalling } from './updater'; import { isSetupComplete } from './setupState'; import { log } from './log'; @@ -184,6 +184,10 @@ if (!gotLock) { app.on('before-quit', (e) => { quitting = true; destroyTray(); + // During an update install, electron-updater drives the quit and runs the + // installer on `will-quit`. It already stopped the supervisor, so we must + // NOT preventDefault + app.exit() here — that would bypass the install. + if (isUpdateInstalling()) return; if (quitHandled || !supervisor) return; // Block the quit just long enough to flush + close the embedded DB and // terminate the children cleanly, then exit for real. diff --git a/desktop/src/secrets.ts b/desktop/src/secrets.ts index f64e7297..e1777d3b 100644 --- a/desktop/src/secrets.ts +++ b/desktop/src/secrets.ts @@ -52,7 +52,16 @@ function load(): SecretsBlob { } } cache = { vaultKey: generateVaultKey(), providerKeys: {} }; - persist(); + try { + persist(); + } catch (err) { + // Fail-closed: if we couldn't persist (e.g. OS encryption unavailable in a + // packaged build), do NOT leave the unpersisted key cached — a later call + // would otherwise return it past the `if (cache)` short-circuit and bypass + // the fail-closed guard, diverging from whatever gets persisted next launch. + cache = null; + throw err; + } return cache; } diff --git a/desktop/src/supervisor.ts b/desktop/src/supervisor.ts index 6b67358a..4b1a1c3b 100644 --- a/desktop/src/supervisor.ts +++ b/desktop/src/supervisor.ts @@ -11,7 +11,6 @@ import { import { findFreePorts, isPortFree } from './ports'; import { startEmbeddedDb, EmbeddedDb } from './embeddedDb'; import { vaultKey, allProviderKeys } from './secrets'; -import { readSetup } from './setupState'; import { log } from './log'; export type BootPhase = @@ -126,16 +125,18 @@ export class Supervisor extends EventEmitter { this.progress('ready', 'omadia is ready.'); return this.uiUrl; } catch (err) { - // Roll back any partially-started children so a retry starts from a clean - // slate (and so we don't leak processes or hold the PGlite lock). - await this.teardownChildren(); - this.state = 'idle'; + // If a concurrent stop()/restart() superseded this boot (it bumped the + // generation), it now owns teardown + the state — do NOT double-tear-down + // or stomp its state. Only clean up when we're still the live generation. + if (gen === this.generation) { + await this.teardownChildren(); + this.state = 'idle'; + } throw err; } } private kernelEnv(port: number): NodeJS.ProcessEnv { - const setup = readSetup(); const env: NodeJS.ProcessEnv = { ...process.env, ELECTRON_RUN_AS_NODE: '1', @@ -159,7 +160,6 @@ export class Supervisor extends EventEmitter { // v1 wires only persistence + LLM. Embeddings (in-process), diagrams (hosted), // and the filesystem attachment store are later milestones; leaving their env // unset means the kernel degrades gracefully rather than failing. - void setup; return env; } diff --git a/desktop/src/updater.ts b/desktop/src/updater.ts index ec1ea0b1..e9ea6303 100644 --- a/desktop/src/updater.ts +++ b/desktop/src/updater.ts @@ -3,8 +3,15 @@ import { autoUpdater } from 'electron-updater'; import fs from 'node:fs'; import path from 'node:path'; import { embeddedDbDir, snapshotDir } from './paths'; +import { getActiveSupervisor } from './supervisor'; import { log } from './log'; +let installing = false; +/** True once the user accepted an update and we're handing off to the installer. */ +export function isUpdateInstalling(): boolean { + return installing; +} + /** * Auto-update via electron-updater against GitHub Releases. * @@ -28,12 +35,6 @@ export function initUpdater(): void { ); autoUpdater.on('update-downloaded', async (info) => { log.info(`[updater] downloaded ${info.version}`); - try { - snapshotDbDir(info.version); - } catch (err) { - log.error(`[updater] snapshot failed, not auto-installing: ${String(err)}`); - return; - } const { response } = await dialog.showMessageBox({ type: 'info', buttons: ['Restart now', 'Later'], @@ -41,11 +42,24 @@ export function initUpdater(): void { cancelId: 1, title: 'Update ready', message: `omadia ${info.version} is ready to install.`, - detail: 'Your data has been snapshotted. omadia will restart to apply the update.', + detail: 'omadia will close, back up your local data, and restart to apply the update.', }); - if (response === 0) { - autoUpdater.quitAndInstall(); + if (response !== 0) return; + + // Quiesce the stack FIRST so the embedded DB is flushed + closed before we + // copy its directory — a live cpSync could capture a torn, unrestorable + // snapshot. Then snapshot, then hand off to the installer. + installing = true; + try { + const sup = getActiveSupervisor(); + if (sup) await sup.stop(); + snapshotDbDir(info.version); + } catch (err) { + log.error(`[updater] pre-install stop/snapshot failed (installing anyway): ${String(err)}`); } + // quitAndInstall drives the app quit itself; main's before-quit checks + // isUpdateInstalling() and steps aside so the installer isn't bypassed. + autoUpdater.quitAndInstall(); }); autoUpdater.checkForUpdates().catch((err) => log.error(`[updater] check failed: ${String(err)}`));