Add desktop auto-updates and Linux icon integration (#621)
This commit is contained in:
parent
05c7d963c2
commit
fcba6b197c
6 changed files with 216 additions and 38 deletions
39
README.md
39
README.md
|
|
@ -78,7 +78,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions and workflow.
|
||||||
### Desktop development (Linux)
|
### Desktop development (Linux)
|
||||||
|
|
||||||
The Electron target and its unsigned packages are for development and testing. Release publishing
|
The Electron target and its unsigned packages are for development and testing. Release publishing
|
||||||
and auto-updates are not configured. Desktop secrets are encrypted using the OS keyring or keychain.
|
and production signing remain prerequisites for public distribution. Desktop secrets are encrypted
|
||||||
|
using the OS keyring or keychain.
|
||||||
If protected storage is unavailable or unreadable, the app warns and keeps secrets only in
|
If protected storage is unavailable or unreadable, the app warns and keeps secrets only in
|
||||||
memory until it closes, leaving the saved file untouched. Unlock or configure the OS keyring
|
memory until it closes, leaving the saved file untouched. Unlock or configure the OS keyring
|
||||||
and restart to restore persistence. Linux’s insecure `basic_text` backend is never used.
|
and restart to restore persistence. Linux’s insecure `basic_text` backend is never used.
|
||||||
|
|
@ -133,12 +134,19 @@ pnpm run package:desktop:macos
|
||||||
Each command rebuilds production assets, copies and updates Capacitor, compiles Electron, vendors
|
Each command rebuilds production assets, copies and updates Capacitor, compiles Electron, vendors
|
||||||
its runtime and plugins, and invokes electron-builder without publishing or signing. Outputs land in
|
its runtime and plugins, and invokes electron-builder without publishing or signing. Outputs land in
|
||||||
`electron/dist/`: a Linux x64 AppImage, a Windows x64 NSIS installer, and separate macOS x64 and
|
`electron/dist/`: a Linux x64 AppImage, a Windows x64 NSIS installer, and separate macOS x64 and
|
||||||
arm64 DMGs. The root package version is authoritative, the Capacitor app ID stays stable, and
|
arm64 DMGs and ZIPs. The root package version is authoritative, the Capacitor app ID stays stable, and
|
||||||
`VITE_PLATFORM_NAME` supplies the product name. Vite's `.env.local` overrides apply, and explicit
|
`VITE_PLATFORM_NAME` supplies the product name. Vite's `.env.local` overrides apply, and explicit
|
||||||
`VITE_*` environment values take precedence. Use production branding values when building artifacts
|
`VITE_*` environment values take precedence. Use production branding values when building artifacts
|
||||||
for others. `VITE_PLATFORM_LOGO` can be a local or HTTPS image, which packaging resizes to 1024×1024
|
for others. `VITE_PLATFORM_LOGO` can be a local or HTTPS image, which packaging resizes to 1024×1024
|
||||||
and stages in ignored output.
|
and stages in ignored output.
|
||||||
|
|
||||||
|
Window and Dock icons use the bundled branding image, including during local runs. The default
|
||||||
|
`VITE_PLATFORM_LOGO=static/logo.png` is the official logo; custom branding remains supported.
|
||||||
|
On Linux, packaged startup registers a hidden desktop entry and a persistent icon under `XDG_DATA_HOME`
|
||||||
|
(normally `~/.local/share`) so Wayland docks can identify the app. Existing user or system launchers
|
||||||
|
are preserved, and development runs never write an entry. The generated entry follows the AppImage
|
||||||
|
path, including immediately after an update renames it. Windows uses the executable icon resources.
|
||||||
|
|
||||||
Linux packaging requires Linux. Windows packaging from Linux uses the pinned official
|
Linux packaging requires Linux. Windows packaging from Linux uses the pinned official
|
||||||
`electronuserland/builder` Wine image through Docker, mounting only a temporary copy of the prepared
|
`electronuserland/builder` Wine image through Docker, mounting only a temporary copy of the prepared
|
||||||
Electron project. Native addons need a target-OS ABI rebuild and cannot use this cross-build path.
|
Electron project. Native addons need a target-OS ABI rebuild and cannot use this cross-build path.
|
||||||
|
|
@ -156,6 +164,33 @@ Use the AppImage or installed executable rather than the installer. This checks
|
||||||
local assets, navigation, workers, and CSP using a disposable profile. Installation, reboot, and
|
local assets, navigation, workers, and CSP using a disposable profile. Installation, reboot, and
|
||||||
uninstall still require target-OS testing.
|
uninstall still require target-OS testing.
|
||||||
|
|
||||||
|
### Desktop updates
|
||||||
|
|
||||||
|
Packaged apps check once at startup, download available updates, and install them on normal quit.
|
||||||
|
There is no update notification or custom updater UI. Unpackaged development runs do not check.
|
||||||
|
Update errors are logged and leave the app usable. The generic feed is configured in
|
||||||
|
`electron/electron-builder.config.mjs`; electron-builder generates `app-update.yml` and the
|
||||||
|
`latest*.yml` files alongside the installers, including when packaging with `--publish never`.
|
||||||
|
|
||||||
|
Before public desktop auto-updates ship, the release pipeline must guarantee that **every stable
|
||||||
|
release selected by Gitea's latest route includes all desktop updater metadata and matching
|
||||||
|
artifacts**. A newer mobile-only release would break the desktop feed. Production acceptance also
|
||||||
|
requires platform signing and native Windows/macOS testing.
|
||||||
|
The existing release task does not yet upload updater metadata or macOS ZIPs; that is follow-up work.
|
||||||
|
Until it does, the public feed returns 404 for channel metadata and packaged startup logs an update
|
||||||
|
check failure. This PR provides packaging and runtime support, not an operational public update feed.
|
||||||
|
Do not publish desktop builds to users until the publishing follow-up is complete.
|
||||||
|
|
||||||
|
For local updater QA, use disposable copies with temporary A/B versions and an isolated user-data
|
||||||
|
directory. In those copies only, point the builder's generic feed at a local HTTP server. Package
|
||||||
|
both versions and serve B's generated metadata and artifacts. Run AppImage A, wait for B to
|
||||||
|
download, quit normally, and relaunch the installed AppImage to verify its version and saved state.
|
||||||
|
Check that preferences, protected secrets, tray controls, and notification activation survive.
|
||||||
|
Also exercise unavailable/missing files, interrupted downloads, invalid checksums/versions, and
|
||||||
|
an already-current version; failed updates must not install. Never bypass integrity checks.
|
||||||
|
On macOS, build both architectures together and verify the single generated manifest references
|
||||||
|
both ZIPs with matching hashes. Do not hand-create or merge updater manifests.
|
||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
`pnpm release` takes a tagged commit and ships it everywhere: the web bundle and native projects,
|
`pnpm release` takes a tagged commit and ships it everywhere: the web bundle and native projects,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {mkdtemp, readFile, rm} from "node:fs/promises"
|
import {mkdir, mkdtemp, readFile, rm, writeFile} from "node:fs/promises"
|
||||||
import {createRequire} from "node:module"
|
import {createRequire} from "node:module"
|
||||||
import {tmpdir} from "node:os"
|
import {tmpdir} from "node:os"
|
||||||
import {join, resolve} from "node:path"
|
import {join, resolve} from "node:path"
|
||||||
|
|
@ -19,13 +19,23 @@ declare global {
|
||||||
|
|
||||||
test("the desktop app renders, navigates, and keeps external pages outside", async () => {
|
test("the desktop app renders, navigates, and keeps external pages outside", async () => {
|
||||||
const profile = await mkdtemp(join(tmpdir(), "flotilla-desktop-"))
|
const profile = await mkdtemp(join(tmpdir(), "flotilla-desktop-"))
|
||||||
|
const env = {...process.env, XDG_DATA_HOME: join(profile, "data")}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const packaged = process.env.FLOTILLA_DESKTOP_EXECUTABLE
|
const packaged = process.env.FLOTILLA_DESKTOP_EXECUTABLE
|
||||||
|
const {appId} = JSON.parse(await readFile("electron/generated/capacitor.config.json", "utf8"))
|
||||||
|
const entryPath = join(env.XDG_DATA_HOME, "applications", `${appId}.desktop`)
|
||||||
|
const existingEntry =
|
||||||
|
"[Desktop Entry]\nExec=/installed/app.AppImage\nX-Electron-Generated=true\n"
|
||||||
|
if (!packaged && process.platform === "linux") {
|
||||||
|
await mkdir(join(env.XDG_DATA_HOME, "applications"), {recursive: true})
|
||||||
|
await writeFile(entryPath, existingEntry)
|
||||||
|
}
|
||||||
const executablePath: string =
|
const executablePath: string =
|
||||||
packaged || createRequire(import.meta.url)(resolve("electron/node_modules/electron"))
|
packaged || createRequire(import.meta.url)(resolve("electron/node_modules/electron"))
|
||||||
let app = await _electron.launch({
|
let app = await _electron.launch({
|
||||||
executablePath,
|
executablePath,
|
||||||
|
env,
|
||||||
// Chromium refuses to start as root with its sandbox on, which is what a CI container is.
|
// Chromium refuses to start as root with its sandbox on, which is what a CI container is.
|
||||||
chromiumSandbox: packaged ? true : process.getuid?.() !== 0,
|
chromiumSandbox: packaged ? true : process.getuid?.() !== 0,
|
||||||
args: [...(packaged ? [] : [resolve("electron")]), `--user-data-dir=${profile}`],
|
args: [...(packaged ? [] : [resolve("electron")]), `--user-data-dir=${profile}`],
|
||||||
|
|
@ -33,6 +43,13 @@ test("the desktop app renders, navigates, and keeps external pages outside", asy
|
||||||
|
|
||||||
try {
|
try {
|
||||||
expect(await app.evaluate(({app}) => app.getPath("userData"))).toBe(profile)
|
expect(await app.evaluate(({app}) => app.getPath("userData"))).toBe(profile)
|
||||||
|
if (packaged && process.platform === "linux") {
|
||||||
|
const entry = await readFile(entryPath, "utf8")
|
||||||
|
expect(entry).toContain(`Icon=${join(env.XDG_DATA_HOME, "icons", `${appId}.png`)}`)
|
||||||
|
expect(entry).not.toContain("/tmp/.mount_")
|
||||||
|
} else if (process.platform === "linux") {
|
||||||
|
expect(await readFile(entryPath, "utf8")).toBe(existingEntry)
|
||||||
|
}
|
||||||
const mainWindows = () =>
|
const mainWindows = () =>
|
||||||
app.windows().filter(page => page.url().startsWith("capacitor-electron://"))
|
app.windows().filter(page => page.url().startsWith("capacitor-electron://"))
|
||||||
await expect.poll(() => mainWindows().length).toBe(1)
|
await expect.poll(() => mainWindows().length).toBe(1)
|
||||||
|
|
@ -45,7 +62,9 @@ test("the desktop app renders, navigates, and keeps external pages outside", asy
|
||||||
const heading = page.getByRole("heading")
|
const heading = page.getByRole("heading")
|
||||||
|
|
||||||
await expect(heading).toBeVisible()
|
await expect(heading).toBeVisible()
|
||||||
await expect(page).toHaveTitle((await heading.textContent())!.replace(/^Welcome to |!$/g, ""))
|
await expect(page).toHaveTitle(
|
||||||
|
(await heading.textContent())!.replace(/^Welcome to\s*|!$/g, ""),
|
||||||
|
)
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => getComputedStyle(document.documentElement).colorScheme),
|
await page.evaluate(() => getComputedStyle(document.documentElement).colorScheme),
|
||||||
).toBe(await page.locator("body").getAttribute("data-theme"))
|
).toBe(await page.locator("body").getAttribute("data-theme"))
|
||||||
|
|
@ -59,6 +78,23 @@ test("the desktop app renders, navigates, and keeps external pages outside", asy
|
||||||
expect(await app.evaluate(({app}) => app.getName())).toBe(await page.title())
|
expect(await app.evaluate(({app}) => app.getName())).toBe(await page.title())
|
||||||
expect(await app.evaluate(({app}) => app.getVersion())).toBe(version)
|
expect(await app.evaluate(({app}) => app.getVersion())).toBe(version)
|
||||||
expect(await app.evaluate(({app}) => app.getAppPath())).toMatch(/app\.asar$/)
|
expect(await app.evaluate(({app}) => app.getAppPath())).toMatch(/app\.asar$/)
|
||||||
|
const updater = await app.evaluate(async ({app}) => {
|
||||||
|
const {createRequire} = process.getBuiltinModule("node:module")
|
||||||
|
const {readFile} = process.getBuiltinModule("node:fs/promises")
|
||||||
|
const {join} = process.getBuiltinModule("node:path")
|
||||||
|
const require = createRequire(join(app.getAppPath(), "package.json"))
|
||||||
|
return {
|
||||||
|
version: require("electron-updater").autoUpdater.currentVersion.version,
|
||||||
|
config: require("js-yaml").load(
|
||||||
|
await readFile(join(app.getAppPath(), "..", "app-update.yml"), "utf8"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(updater.version).toBe(version)
|
||||||
|
expect(updater.config).toMatchObject({
|
||||||
|
provider: "generic",
|
||||||
|
url: "https://gitea.coracle.social/coracle/flotilla/releases/download/latest/",
|
||||||
|
})
|
||||||
expect(await app.evaluate(({app}) => app.commandLine.hasSwitch("no-sandbox"))).toBe(false)
|
expect(await app.evaluate(({app}) => app.commandLine.hasSwitch("no-sandbox"))).toBe(false)
|
||||||
const preferences = await app.browserWindow(page).then(window =>
|
const preferences = await app.browserWindow(page).then(window =>
|
||||||
window.evaluate(window => {
|
window.evaluate(window => {
|
||||||
|
|
@ -144,26 +180,27 @@ test("the desktop app renders, navigates, and keeps external pages outside", asy
|
||||||
expect(typeof contract.decrypted.shouldReEncrypt).toBe("boolean")
|
expect(typeof contract.decrypted.shouldReEncrypt).toBe("boolean")
|
||||||
const fixture = "packaged-desktop-secret"
|
const fixture = "packaged-desktop-secret"
|
||||||
await page.evaluate(async value => {
|
await page.evaluate(async value => {
|
||||||
await window.Capacitor.Plugins.DesktopSecureStorage.set({key: "session", value})
|
await window.Capacitor.Plugins.DesktopSecureStorage.set({key: "desktop-smoke", value})
|
||||||
}, fixture)
|
}, fixture)
|
||||||
expect((await readFile(join(profile, "secure-storage.bin"))).includes(fixture)).toBe(false)
|
expect((await readFile(join(profile, "secure-storage.bin"))).includes(fixture)).toBe(false)
|
||||||
|
|
||||||
await app.close()
|
await app.close()
|
||||||
app = await _electron.launch({
|
app = await _electron.launch({
|
||||||
executablePath,
|
executablePath,
|
||||||
|
env,
|
||||||
chromiumSandbox: true,
|
chromiumSandbox: true,
|
||||||
args: [`--user-data-dir=${profile}`],
|
args: [`--user-data-dir=${profile}`],
|
||||||
})
|
})
|
||||||
await expect
|
await expect.poll(() => mainWindows().length).toBe(1)
|
||||||
.poll(async () => {
|
const [relaunched] = mainWindows()
|
||||||
const relaunched = app
|
await relaunched.waitForFunction(() =>
|
||||||
.windows()
|
Boolean(window.Capacitor?.Plugins?.DesktopSecureStorage),
|
||||||
.find(candidate => candidate.url().startsWith("capacitor-electron://"))
|
)
|
||||||
return relaunched?.evaluate(async () =>
|
expect(
|
||||||
window.Capacitor.Plugins.DesktopSecureStorage.get({key: "session"}),
|
await relaunched.evaluate(() =>
|
||||||
)
|
window.Capacitor.Plugins.DesktopSecureStorage.get({key: "desktop-smoke"}),
|
||||||
})
|
),
|
||||||
.toEqual({value: fixture})
|
).toEqual({value: fixture})
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await app.close()
|
await app.close()
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,10 @@ export default {
|
||||||
{from: "vendor/node_modules", to: "node_modules"},
|
{from: "vendor/node_modules", to: "node_modules"},
|
||||||
],
|
],
|
||||||
allowMissingDependencies: false,
|
allowMissingDependencies: false,
|
||||||
publish: [],
|
publish: {
|
||||||
|
provider: "generic",
|
||||||
|
url: "https://gitea.coracle.social/coracle/flotilla/releases/download/latest/",
|
||||||
|
},
|
||||||
toolsets: {appimage: "1.0.3"},
|
toolsets: {appimage: "1.0.3"},
|
||||||
linux: {
|
linux: {
|
||||||
target: [{target: "AppImage", arch: ["x64"]}],
|
target: [{target: "AppImage", arch: ["x64"]}],
|
||||||
|
|
@ -41,7 +44,10 @@ export default {
|
||||||
},
|
},
|
||||||
win: {target: [{target: "nsis", arch: ["x64"]}], signExecutable: false},
|
win: {target: [{target: "nsis", arch: ["x64"]}], signExecutable: false},
|
||||||
mac: {
|
mac: {
|
||||||
target: [{target: "dmg", arch: ["x64", "arm64"]}],
|
target: [
|
||||||
|
{target: "dmg", arch: ["x64", "arm64"]},
|
||||||
|
{target: "zip", arch: ["x64", "arm64"]},
|
||||||
|
],
|
||||||
category: "public.app-category.social-networking",
|
category: "public.app-category.social-networking",
|
||||||
notarize: false,
|
notarize: false,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,28 @@
|
||||||
import {readFileSync} from "node:fs"
|
import {copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync} from "node:fs"
|
||||||
import {join} from "node:path"
|
import {join} from "node:path"
|
||||||
import {app, Menu, Tray} from "electron"
|
import {app, Menu, Tray} from "electron"
|
||||||
|
import {autoUpdater} from "electron-updater"
|
||||||
import {createCapacitorElectronApp} from "@capawesome/capacitor-electron"
|
import {createCapacitorElectronApp} from "@capawesome/capacitor-electron"
|
||||||
|
|
||||||
const {appId, appName} = JSON.parse(
|
const {appId, appName} = JSON.parse(
|
||||||
readFileSync(join(app.getAppPath(), "generated/capacitor.config.json"), "utf8"),
|
readFileSync(join(app.getAppPath(), "generated/capacitor.config.json"), "utf8"),
|
||||||
)
|
)
|
||||||
|
const iconPath = join(
|
||||||
|
app.getAppPath(),
|
||||||
|
app.isPackaged ? "generated/icon.png" : "app/pwa-192x192.png",
|
||||||
|
)
|
||||||
let tray: Tray | undefined
|
let tray: Tray | undefined
|
||||||
let quitting = false
|
let quitting = false
|
||||||
|
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
if (app.isPackaged) {
|
||||||
|
void autoUpdater
|
||||||
|
.checkForUpdates()
|
||||||
|
.then(result => result?.downloadPromise)
|
||||||
|
.catch(error => console.error("Update check failed", error))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const destroyTray = () => {
|
const destroyTray = () => {
|
||||||
tray?.destroy()
|
tray?.destroy()
|
||||||
tray = undefined
|
tray = undefined
|
||||||
|
|
@ -25,6 +39,54 @@ createCapacitorElectronApp({
|
||||||
beforeReady: () => {
|
beforeReady: () => {
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
app.setAppUserModelId(appId)
|
app.setAppUserModelId(appId)
|
||||||
|
} else if (process.platform === "linux" && app.isPackaged) {
|
||||||
|
app.setDesktopName(`${appId}.desktop`)
|
||||||
|
try {
|
||||||
|
const data = process.env.XDG_DATA_HOME || join(app.getPath("home"), ".local/share")
|
||||||
|
const applications = join(data, "applications")
|
||||||
|
const entry = join(applications, `${appId}.desktop`)
|
||||||
|
const marker = "X-Electron-Generated=true"
|
||||||
|
const installed = (process.env.XDG_DATA_DIRS || "/usr/local/share:/usr/share")
|
||||||
|
.split(":")
|
||||||
|
.some(directory => existsSync(join(directory, "applications", `${appId}.desktop`)))
|
||||||
|
if (!installed && (!existsSync(entry) || readFileSync(entry, "utf8").includes(marker))) {
|
||||||
|
const icons = join(data, "icons")
|
||||||
|
const icon = join(icons, `${appId}.png`)
|
||||||
|
mkdirSync(applications, {recursive: true})
|
||||||
|
mkdirSync(icons, {recursive: true})
|
||||||
|
copyFileSync(iconPath, icon)
|
||||||
|
const escape = (value: string) =>
|
||||||
|
value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r")
|
||||||
|
const writeEntry = (executable: string) => {
|
||||||
|
const command = `"${executable.replace(/[\\"`$]/g, "\\$&").replace(/%/g, "%%")}"`
|
||||||
|
// Wayland shells resolve icons through desktop entries, not BrowserWindow.setIcon.
|
||||||
|
writeFileSync(
|
||||||
|
entry,
|
||||||
|
[
|
||||||
|
"[Desktop Entry]",
|
||||||
|
"Type=Application",
|
||||||
|
`Name=${escape(appName)}`,
|
||||||
|
`Exec=${escape(command)}`,
|
||||||
|
`Icon=${escape(icon)}`,
|
||||||
|
`StartupWMClass=${appId}`,
|
||||||
|
"NoDisplay=true",
|
||||||
|
marker,
|
||||||
|
"",
|
||||||
|
].join("\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
writeEntry(process.env.APPIMAGE || process.execPath)
|
||||||
|
autoUpdater.on("appimage-filename-updated", (destination: string) => {
|
||||||
|
try {
|
||||||
|
writeEntry(destination)
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Could not update the desktop launcher", error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Could not register the desktop icon", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onWindowCreated: window => {
|
onWindowCreated: window => {
|
||||||
|
|
@ -51,13 +113,7 @@ createCapacitorElectronApp({
|
||||||
: process.platform === "win32"
|
: process.platform === "win32"
|
||||||
? "icon.ico"
|
? "icon.ico"
|
||||||
: "icon.png"
|
: "icon.png"
|
||||||
tray = new Tray(
|
tray = new Tray(app.isPackaged ? join(app.getAppPath(), "generated", icon) : iconPath)
|
||||||
join(
|
|
||||||
app.getAppPath(),
|
|
||||||
app.isPackaged ? "generated" : "app",
|
|
||||||
app.isPackaged ? icon : "pwa-192x192.png",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
tray.setToolTip(appName)
|
tray.setToolTip(appName)
|
||||||
tray.setContextMenu(
|
tray.setContextMenu(
|
||||||
Menu.buildFromTemplate([
|
Menu.buildFromTemplate([
|
||||||
|
|
@ -67,8 +123,10 @@ createCapacitorElectronApp({
|
||||||
)
|
)
|
||||||
tray.on("click", show)
|
tray.on("click", show)
|
||||||
window.on("closed", destroyTray)
|
window.on("closed", destroyTray)
|
||||||
if (process.platform === "linux" && app.isPackaged) {
|
if (process.platform === "darwin") {
|
||||||
window.setIcon(join(app.getAppPath(), "generated/icon.png"))
|
app.dock?.setIcon(iconPath)
|
||||||
|
} else if (process.platform === "linux") {
|
||||||
|
window.setIcon(iconPath)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
61
electron/package-lock.json
generated
61
electron/package-lock.json
generated
|
|
@ -6,6 +6,9 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "flotilla-electron",
|
"name": "flotilla-electron",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"electron-updater": "6.8.9"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^43.0.0",
|
"electron": "^43.0.0",
|
||||||
"electron-builder": "26.16.1",
|
"electron-builder": "26.16.1",
|
||||||
|
|
@ -795,7 +798,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/asn1js": {
|
"node_modules/asn1js": {
|
||||||
|
|
@ -951,7 +953,6 @@
|
||||||
"version": "9.7.0",
|
"version": "9.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
|
||||||
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
|
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
|
|
@ -1210,7 +1211,6 @@
|
||||||
"version": "4.4.3",
|
"version": "4.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "^2.1.3"
|
||||||
|
|
@ -1520,6 +1520,34 @@
|
||||||
"mime": "^2.5.2"
|
"mime": "^2.5.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/electron-updater": {
|
||||||
|
"version": "6.8.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
|
||||||
|
"integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"builder-util-runtime": "9.7.0",
|
||||||
|
"fs-extra": "^10.1.0",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
|
"lazy-val": "^1.0.5",
|
||||||
|
"lodash.escaperegexp": "^4.1.2",
|
||||||
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"semver": "~7.7.3",
|
||||||
|
"tiny-typed-emitter": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/electron-updater/node_modules/semver": {
|
||||||
|
"version": "7.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||||
|
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-winstaller": {
|
"node_modules/electron-winstaller": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
|
||||||
|
|
@ -1808,7 +1836,6 @@
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.2.0",
|
"graceful-fs": "^4.2.0",
|
||||||
|
|
@ -2034,7 +2061,6 @@
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/has-flag": {
|
"node_modules/has-flag": {
|
||||||
|
|
@ -2256,7 +2282,6 @@
|
||||||
"version": "4.3.2",
|
"version": "4.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
|
||||||
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
|
"integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -2314,7 +2339,6 @@
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
|
||||||
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
|
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
|
|
@ -2337,7 +2361,6 @@
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
|
|
@ -2347,6 +2370,19 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.escaperegexp": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/lodash.isequal": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||||
|
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lowercase-keys": {
|
"node_modules/lowercase-keys": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||||
|
|
@ -2507,7 +2543,6 @@
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/node-abi": {
|
"node_modules/node-abi": {
|
||||||
|
|
@ -3067,7 +3102,6 @@
|
||||||
"version": "1.6.1",
|
"version": "1.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz",
|
||||||
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
|
"integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=11.0.0"
|
"node": ">=11.0.0"
|
||||||
|
|
@ -3330,6 +3364,12 @@
|
||||||
"semver": "bin/semver"
|
"semver": "bin/semver"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tiny-typed-emitter": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
|
@ -3434,7 +3474,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,8 @@
|
||||||
"electron": "^43.0.0",
|
"electron": "^43.0.0",
|
||||||
"electron-builder": "26.16.1",
|
"electron-builder": "26.16.1",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"electron-updater": "6.8.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue