Added cross-platform desktop packaging (#526)
This commit is contained in:
parent
3acc447a87
commit
8147d50c9f
14 changed files with 3696 additions and 25 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -12,6 +12,7 @@ vite.config.ts.timestamp-*
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
|
|
||||||
# Generated assets
|
# Generated assets
|
||||||
|
static/desktop-logo.png
|
||||||
static/favicon.ico
|
static/favicon.ico
|
||||||
static/pwa-64x64.png
|
static/pwa-64x64.png
|
||||||
static/pwa-192x192.png
|
static/pwa-192x192.png
|
||||||
|
|
|
||||||
46
README.md
46
README.md
|
|
@ -47,8 +47,8 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||||
|
|
||||||
### Desktop development (Linux)
|
### Desktop development (Linux)
|
||||||
|
|
||||||
The Electron target is a development baseline. It has no supported installers,
|
The Electron target and its unsigned packages are for development/testing.
|
||||||
release pipeline, packaging configuration, or auto-update setup.
|
Release publishing and auto-updates are not configured.
|
||||||
|
|
||||||
**Use disposable accounts only.** The current secure-storage plugin falls back to
|
**Use disposable accounts only.** The current secure-storage plugin falls back to
|
||||||
unencrypted `localStorage` on desktop. This is not secure desktop credential or
|
unencrypted `localStorage` on desktop. This is not secure desktop credential or
|
||||||
|
|
@ -92,9 +92,49 @@ headless Linux runner, use `xvfb-run -a pnpm run test:desktop`; Electron links
|
||||||
against GTK, which Playwright's chromium dependencies do not cover, so such a box
|
against GTK, which Playwright's chromium dependencies do not cover, so such a box
|
||||||
also needs `libgtk-3-0t64`. The test drops Chromium's sandbox when it runs as
|
also needs `libgtk-3-0t64`. The test drops Chromium's sandbox when it runs as
|
||||||
root, because Chromium refuses to start that way. The separate smoke
|
root, because Chromium refuses to start that way. The separate smoke
|
||||||
suite does not start a web dev server or test installers. Windows and macOS desktop
|
suite does not start a web dev server. Windows and macOS desktop
|
||||||
behavior is not verified by the Linux test. CI does not run it.
|
behavior is not verified by the Linux test. CI does not run it.
|
||||||
|
|
||||||
|
### Desktop packaging
|
||||||
|
|
||||||
|
After installing both sets of dependencies, run one of:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm run package:desktop:linux
|
||||||
|
pnpm run package:desktop:windows
|
||||||
|
pnpm run package:desktop:macos
|
||||||
|
```
|
||||||
|
|
||||||
|
Each command rebuilds production assets, copies/updates Capacitor, compiles Electron,
|
||||||
|
vendors its runtime/plugins, and invokes electron-builder without publishing or signing.
|
||||||
|
Outputs are in `electron/dist/`: Linux x64 AppImage, Windows x64 NSIS installer,
|
||||||
|
and separate macOS x64/arm64 DMGs. The root package version is authoritative.
|
||||||
|
The Capacitor app ID remains stable; `VITE_PLATFORM_NAME` supplies the product name.
|
||||||
|
Vite's `.env.local` overrides also apply; use production branding values when
|
||||||
|
building artifacts for others. Explicit `VITE_*` environment values take precedence.
|
||||||
|
`VITE_PLATFORM_LOGO` can be a local image or HTTPS image. Packaging resizes it
|
||||||
|
to 1024×1024 and stages it in ignored output.
|
||||||
|
|
||||||
|
Linux packaging requires Linux. Windows packaging from Linux uses the pinned
|
||||||
|
official `electronuserland/builder` Wine image through Docker; it only mounts a
|
||||||
|
temporary copy of the prepared Electron project. Native addons require a target-OS
|
||||||
|
ABI rebuild and cannot use this cross-build path. Native Windows preparation needs
|
||||||
|
Bash on PATH (for example, Git Bash). DMG creation requires macOS; on Linux,
|
||||||
|
`pnpm run package:desktop:macos --dir` prepares unsigned bundles for inspection only.
|
||||||
|
It does not verify macOS runtime, Gatekeeper, or signing.
|
||||||
|
|
||||||
|
To smoke-test a package, run as a non-root user with the sandbox enabled:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
FLOTILLA_DESKTOP_EXECUTABLE="/absolute/path/to/application" pnpm run test:desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the AppImage or installed executable, rather than the installer. This checks
|
||||||
|
packaged metadata, local assets, navigation, workers and CSP using a disposable
|
||||||
|
profile. Installation, reboot and uninstall still require target-OS testing.
|
||||||
|
Packages must remain development-only until OS-protected secret storage and release
|
||||||
|
signing are addressed.
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
To run your own Flotilla, it's as simple as:
|
To run your own Flotilla, it's as simple as:
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import type {CapacitorConfig} from "@capacitor/cli"
|
import type {CapacitorConfig} from "@capacitor/cli"
|
||||||
|
import {loadEnv} from "vite"
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
const config: CapacitorConfig = {
|
||||||
appId: "social.flotilla",
|
appId: "social.flotilla",
|
||||||
appName: "Flotilla",
|
appName: loadEnv(process.env.NODE_ENV || "production", process.cwd(), "VITE_").VITE_PLATFORM_NAME,
|
||||||
webDir: "build",
|
webDir: "build",
|
||||||
ios: {
|
ios: {
|
||||||
scheme: "Flotilla Chat",
|
scheme: "Flotilla Chat",
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
import {mkdtemp, rm} from "node:fs/promises"
|
import {mkdtemp, readFile, rm} 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"
|
||||||
import {_electron, expect, test} from "@playwright/test"
|
import {_electron, expect, test} from "@playwright/test"
|
||||||
|
|
||||||
test("the desktop baseline 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-"))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const executablePath: string = createRequire(import.meta.url)(
|
const packaged = process.env.FLOTILLA_DESKTOP_EXECUTABLE
|
||||||
resolve("electron/node_modules/electron"),
|
const executablePath: string =
|
||||||
)
|
packaged || createRequire(import.meta.url)(resolve("electron/node_modules/electron"))
|
||||||
const app = await _electron.launch({
|
const app = await _electron.launch({
|
||||||
executablePath,
|
executablePath,
|
||||||
// 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: process.getuid?.() !== 0,
|
chromiumSandbox: packaged ? true : process.getuid?.() !== 0,
|
||||||
args: [resolve("electron")],
|
args: [...(packaged ? [] : [resolve("electron")]), `--user-data-dir=${profile}`],
|
||||||
env: {...process.env, XDG_CONFIG_HOME: profile},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
expect(await app.evaluate(({app}) => app.getPath("userData"))).toBe(profile)
|
||||||
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)
|
||||||
|
|
@ -38,6 +38,25 @@ test("the desktop baseline renders, navigates, and keeps external pages outside"
|
||||||
).toBe(await page.locator("body").getAttribute("data-theme"))
|
).toBe(await page.locator("body").getAttribute("data-theme"))
|
||||||
const origin = await page.evaluate(() => location.origin)
|
const origin = await page.evaluate(() => location.origin)
|
||||||
expect(origin).toMatch(/^capacitor-electron:\/\//)
|
expect(origin).toMatch(/^capacitor-electron:\/\//)
|
||||||
|
expect(await page.locator('script[src*="@vite/client"]').count()).toBe(0)
|
||||||
|
|
||||||
|
if (packaged) {
|
||||||
|
const {version} = JSON.parse(await readFile("package.json", "utf8"))
|
||||||
|
expect(await app.evaluate(({app}) => app.isPackaged)).toBe(true)
|
||||||
|
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.getAppPath())).toMatch(/app\.asar$/)
|
||||||
|
expect(await app.evaluate(({app}) => app.commandLine.hasSwitch("no-sandbox"))).toBe(false)
|
||||||
|
const preferences = await app.browserWindow(page).then(window =>
|
||||||
|
window.evaluate(window => {
|
||||||
|
const {sandbox, contextIsolation, nodeIntegration} =
|
||||||
|
window.webContents.getLastWebPreferences()
|
||||||
|
return {sandbox, contextIsolation, nodeIntegration}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(preferences).toEqual({sandbox: true, contextIsolation: true, nodeIntegration: false})
|
||||||
|
await page.screenshot({path: test.info().outputPath("packaged-onboarding.png")})
|
||||||
|
}
|
||||||
|
|
||||||
await page.getByRole("button", {name: "Log in", exact: true}).click()
|
await page.getByRole("button", {name: "Log in", exact: true}).click()
|
||||||
await expect(page.getByTestId("login")).toBeVisible()
|
await expect(page.getByTestId("login")).toBeVisible()
|
||||||
|
|
|
||||||
49
electron/electron-builder.config.mjs
Normal file
49
electron/electron-builder.config.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {readFileSync} from "node:fs"
|
||||||
|
|
||||||
|
const {name, version, author} = JSON.parse(
|
||||||
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
||||||
|
)
|
||||||
|
const {appId, appName} = JSON.parse(
|
||||||
|
readFileSync(new URL("generated/capacitor.config.json", import.meta.url), "utf8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** @type {import('electron-builder').Configuration} */
|
||||||
|
export default {
|
||||||
|
appId,
|
||||||
|
productName: appName,
|
||||||
|
executableName: name,
|
||||||
|
buildVersion: version,
|
||||||
|
extraMetadata: {
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
author,
|
||||||
|
productName: appName,
|
||||||
|
description: appName,
|
||||||
|
desktopName: `${appId}.desktop`,
|
||||||
|
},
|
||||||
|
artifactName: "${productName}-${version}-${os}-${arch}.${ext}",
|
||||||
|
directories: {output: "dist", buildResources: "generated"},
|
||||||
|
icon: "generated/icon.png",
|
||||||
|
files: [
|
||||||
|
"build/main.js",
|
||||||
|
"app/**/*",
|
||||||
|
"generated/**/*",
|
||||||
|
"package.json",
|
||||||
|
{from: "vendor/node_modules", to: "node_modules"},
|
||||||
|
],
|
||||||
|
allowMissingDependencies: false,
|
||||||
|
publish: [],
|
||||||
|
toolsets: {appimage: "1.0.3"},
|
||||||
|
linux: {
|
||||||
|
target: [{target: "AppImage", arch: ["x64"]}],
|
||||||
|
category: "Network",
|
||||||
|
syncDesktopName: true,
|
||||||
|
},
|
||||||
|
win: {target: [{target: "nsis", arch: ["x64"]}], signExecutable: false},
|
||||||
|
mac: {
|
||||||
|
target: [{target: "dmg", arch: ["x64", "arm64"]}],
|
||||||
|
category: "public.app-category.social-networking",
|
||||||
|
notarize: false,
|
||||||
|
},
|
||||||
|
dmg: {sign: false},
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
|
import {join} from "node:path"
|
||||||
|
import {app} from "electron"
|
||||||
import {createCapacitorElectronApp} from "@capawesome/capacitor-electron"
|
import {createCapacitorElectronApp} from "@capawesome/capacitor-electron"
|
||||||
|
|
||||||
createCapacitorElectronApp({
|
createCapacitorElectronApp({
|
||||||
window: {width: 1200, height: 800},
|
window: {width: 1200, height: 800},
|
||||||
|
hooks: {
|
||||||
|
onWindowCreated: window => {
|
||||||
|
if (process.platform === "linux" && app.isPackaged) {
|
||||||
|
window.setIcon(join(app.getAppPath(), "generated/icon.png"))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
csp: {
|
csp: {
|
||||||
// SvelteKit's meta CSP owns scripts; the platform default would block its hashed bootstrap.
|
// SvelteKit's meta CSP owns scripts; the platform default would block its hashed bootstrap.
|
||||||
policy: "base-uri 'self'; object-src 'none'",
|
policy: "base-uri 'self'; object-src 'none'",
|
||||||
|
|
|
||||||
3426
electron/package-lock.json
generated
3426
electron/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "flotilla-electron",
|
"name": "flotilla-electron",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
|
||||||
"description": "Electron platform project for Flotilla.",
|
"description": "Electron platform project for Flotilla.",
|
||||||
"main": "build/main.js",
|
"main": "build/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"postinstall": "install-electron",
|
"postinstall": "install-electron",
|
||||||
"start": "electron ."
|
"start": "electron .",
|
||||||
|
"pack": "electron-builder --config electron-builder.config.mjs --publish never"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^43.0.0",
|
"electron": "^43.0.0",
|
||||||
|
"electron-builder": "26.16.1",
|
||||||
"typescript": "^5.5.0"
|
"typescript": "^5.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "flotilla",
|
"name": "flotilla",
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
|
"author": {"name": "Jon Staab"},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|
@ -14,13 +15,16 @@
|
||||||
"bump": "node scripts/bump-version.mjs",
|
"bump": "node scripts/bump-version.mjs",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"lint": "prettier --check src e2e playwright.config.ts capacitor.config.ts electron/*.ts scripts/dev-desktop.mjs && eslint src e2e capacitor.config.ts electron/*.ts scripts/dev-desktop.mjs",
|
"lint": "prettier --check src e2e playwright.config.ts capacitor.config.ts electron/*.ts electron/electron-builder.config.mjs scripts/dev-desktop.mjs scripts/package-desktop.mjs && eslint src e2e capacitor.config.ts electron/*.ts electron/electron-builder.config.mjs scripts/dev-desktop.mjs scripts/package-desktop.mjs",
|
||||||
"test": "playwright test",
|
"test": "playwright test",
|
||||||
"test:desktop": "playwright test --config e2e/desktop/playwright.config.ts",
|
"test:desktop": "playwright test --config e2e/desktop/playwright.config.ts",
|
||||||
"test:ui": "playwright test --ui",
|
"test:ui": "playwright test --ui",
|
||||||
"format": "git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte|css)$' | xargs -r prettier --write",
|
"format": "git diff head --name-only --diff-filter d | grep -E '(js|ts|svelte|css)$' | xargs -r prettier --write",
|
||||||
"format:all": "prettier --write src",
|
"format:all": "prettier --write src",
|
||||||
"prepare": "husky"
|
"prepare": "husky",
|
||||||
|
"package:desktop:linux": "node scripts/package-desktop.mjs linux",
|
||||||
|
"package:desktop:windows": "node scripts/package-desktop.mjs windows",
|
||||||
|
"package:desktop:macos": "node scripts/package-desktop.mjs macos"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/assets": "^3.0.5",
|
"@capacitor/assets": "^3.0.5",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,13 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
export FLOTILLA_DESKTOP=1
|
export FLOTILLA_DESKTOP=1
|
||||||
|
export NODE_ENV=production
|
||||||
|
unset FLOTILLA_DESKTOP_DEV_URL CAPACITOR_ELECTRON_DEV_SERVER_URL
|
||||||
source ./scripts/build-web.sh
|
source ./scripts/build-web.sh
|
||||||
|
|
||||||
pnpm exec cap sync @capawesome/capacitor-electron
|
export VITE_PLATFORM_NAME
|
||||||
|
# Capacitor sync swallows copy failures; its rejection handler also leaves exit 0.
|
||||||
|
node --unhandled-rejections=strict node_modules/@capacitor/cli/bin/capacitor copy @capawesome/capacitor-electron
|
||||||
|
node --unhandled-rejections=strict node_modules/@capacitor/cli/bin/capacitor update @capawesome/capacitor-electron
|
||||||
|
rm -rf electron/build
|
||||||
npm --prefix electron run build
|
npm --prefix electron run build
|
||||||
|
|
|
||||||
|
|
@ -33,4 +33,3 @@ perl -i -pe"s|{DESCRIPTION}|$VITE_PLATFORM_DESCRIPTION|g" build/index.html
|
||||||
perl -i -pe"s|{ACCENT}|$VITE_PLATFORM_ACCENT|g" build/index.html
|
perl -i -pe"s|{ACCENT}|$VITE_PLATFORM_ACCENT|g" build/index.html
|
||||||
perl -i -pe"s|{NAME}|$VITE_PLATFORM_NAME|g" build/index.html
|
perl -i -pe"s|{NAME}|$VITE_PLATFORM_NAME|g" build/index.html
|
||||||
perl -i -pe"s|{URL}|$VITE_PLATFORM_URL|g" build/index.html
|
perl -i -pe"s|{URL}|$VITE_PLATFORM_URL|g" build/index.html
|
||||||
|
|
||||||
|
|
|
||||||
122
scripts/package-desktop.mjs
Normal file
122
scripts/package-desktop.mjs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
import {spawn} from "node:child_process"
|
||||||
|
import {cp, mkdir, mkdtemp, readFile, readdir, rm} from "node:fs/promises"
|
||||||
|
import {join, resolve} from "node:path"
|
||||||
|
import {fileURLToPath} from "node:url"
|
||||||
|
import sharp from "sharp"
|
||||||
|
import {loadEnv} from "vite"
|
||||||
|
|
||||||
|
const root = fileURLToPath(new URL("../", import.meta.url))
|
||||||
|
const [target, option, ...rest] = process.argv.slice(2)
|
||||||
|
const platforms = {linux: "--linux", windows: "--win", macos: "--mac"}
|
||||||
|
const env = {...process.env, ...loadEnv("production", root, "VITE_"), NODE_ENV: "production"}
|
||||||
|
|
||||||
|
const run = (command, args, options = {}) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, {cwd: root, env, stdio: "inherit", ...options})
|
||||||
|
child.on("error", reject)
|
||||||
|
child.on("exit", (code, signal) => {
|
||||||
|
if (code === 0) resolve()
|
||||||
|
else reject(new Error(`${command} failed (${signal || code})`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Object.hasOwn(platforms, target) || rest.length || (option && option !== "--dir")) {
|
||||||
|
throw new Error("Usage: node scripts/package-desktop.mjs linux|windows|macos [--dir]")
|
||||||
|
}
|
||||||
|
delete env.FLOTILLA_DESKTOP_DEV_URL
|
||||||
|
delete env.CAPACITOR_ELECTRON_DEV_SERVER_URL
|
||||||
|
env.CSC_IDENTITY_AUTO_DISCOVERY = "false"
|
||||||
|
|
||||||
|
let logo
|
||||||
|
if (env.VITE_PLATFORM_LOGO?.startsWith("https://")) {
|
||||||
|
const response = await fetch(env.VITE_PLATFORM_LOGO, {signal: AbortSignal.timeout(30_000)})
|
||||||
|
if (!response.ok || !response.headers.get("content-type")?.startsWith("image/")) {
|
||||||
|
throw new Error("VITE_PLATFORM_LOGO must return a successful image response")
|
||||||
|
}
|
||||||
|
logo = Buffer.from(await response.arrayBuffer())
|
||||||
|
} else {
|
||||||
|
logo = await readFile(resolve(root, env.VITE_PLATFORM_LOGO))
|
||||||
|
}
|
||||||
|
env.VITE_PLATFORM_LOGO = "static/desktop-logo.png"
|
||||||
|
await sharp(logo).resize(1024, 1024).png().toFile(join(root, env.VITE_PLATFORM_LOGO))
|
||||||
|
|
||||||
|
await run("bash", ["scripts/build-desktop.sh"])
|
||||||
|
await cp(join(root, env.VITE_PLATFORM_LOGO), join(root, "electron/generated/icon.png"))
|
||||||
|
await cp(join(root, "LICENSE"), join(root, "electron/generated/LICENSE"))
|
||||||
|
await run(
|
||||||
|
process.execPath,
|
||||||
|
[join(root, "node_modules/@capawesome/capacitor-electron/dist/cli/index.js"), "vendor"],
|
||||||
|
{cwd: join(root, "electron")},
|
||||||
|
)
|
||||||
|
|
||||||
|
const args = [platforms[target], ...(option ? [option] : [])]
|
||||||
|
if (target === "macos" && option === "--dir") {
|
||||||
|
// --dir replaces configured targets, including their architectures.
|
||||||
|
args.push("--x64", "--arm64")
|
||||||
|
}
|
||||||
|
if (target === "windows" && process.platform === "linux") {
|
||||||
|
const files = await readdir(join(root, "electron/vendor"), {recursive: true})
|
||||||
|
if (files.some(file => file.endsWith(".node"))) {
|
||||||
|
throw new Error(
|
||||||
|
"Native Electron addons require a Windows ABI rebuild before packaging on Windows",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await mkdir(join(root, "electron/dist"), {recursive: true})
|
||||||
|
const directory = await mkdtemp(join(root, "electron/dist/package-"))
|
||||||
|
try {
|
||||||
|
await cp(join(root, "package.json"), join(directory, "package.json"))
|
||||||
|
for (const file of [
|
||||||
|
"package.json",
|
||||||
|
"package-lock.json",
|
||||||
|
"electron-builder.config.mjs",
|
||||||
|
"build",
|
||||||
|
"app",
|
||||||
|
"generated",
|
||||||
|
"vendor",
|
||||||
|
]) {
|
||||||
|
await cp(join(root, "electron", file), join(directory, "electron", file), {recursive: true})
|
||||||
|
}
|
||||||
|
await run("docker", [
|
||||||
|
"run",
|
||||||
|
"--rm",
|
||||||
|
"--platform",
|
||||||
|
"linux/amd64",
|
||||||
|
"--volume",
|
||||||
|
`${directory}:/project:Z`,
|
||||||
|
"--workdir",
|
||||||
|
"/project/electron",
|
||||||
|
"--env",
|
||||||
|
"USE_SYSTEM_WINE=true",
|
||||||
|
"--env",
|
||||||
|
"CSC_IDENTITY_AUTO_DISCOVERY=false",
|
||||||
|
"electronuserland/builder@sha256:41ae540902461b6cbc988987db79547fcc10cda04d2a6c6367504f59d4b37c64",
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
'owner=$1; group=$2; shift 2; npm ci --ignore-scripts && npm run pack -- "$@"; result=$?; chown -R "$owner:$group" /project; exit "$result"',
|
||||||
|
"--",
|
||||||
|
String(process.getuid()),
|
||||||
|
String(process.getgid()),
|
||||||
|
...args,
|
||||||
|
])
|
||||||
|
await cp(join(directory, "electron/dist"), join(root, "electron/dist"), {recursive: true})
|
||||||
|
} finally {
|
||||||
|
await rm(directory, {recursive: true, force: true})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await run(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"node_modules/electron-builder/cli.js",
|
||||||
|
"--config",
|
||||||
|
"electron-builder.config.mjs",
|
||||||
|
"--publish",
|
||||||
|
"never",
|
||||||
|
...args,
|
||||||
|
],
|
||||||
|
{cwd: join(root, "electron")},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await rm(join(root, "static/desktop-logo.png"), {force: true})
|
||||||
|
}
|
||||||
|
|
@ -44,9 +44,7 @@ export const PLATFORM_TERMS = env("VITE_PLATFORM_TERMS")
|
||||||
|
|
||||||
export const PLATFORM_PRIVACY = env("VITE_PLATFORM_PRIVACY")
|
export const PLATFORM_PRIVACY = env("VITE_PLATFORM_PRIVACY")
|
||||||
|
|
||||||
export const PLATFORM_LOGO = import.meta.env.PROD
|
export const PLATFORM_LOGO = env("VITE_PLATFORM_LOGO").replace(/^static/, "")
|
||||||
? PLATFORM_URL + "/logo.png"
|
|
||||||
: env("VITE_PLATFORM_LOGO").replace(/^static/, "") || PLATFORM_URL + "/logo.png"
|
|
||||||
|
|
||||||
export const PLATFORM_NAME = env("VITE_PLATFORM_NAME")
|
export const PLATFORM_NAME = env("VITE_PLATFORM_NAME")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import {
|
||||||
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
import type {Nip46ResponseWithResult} from "@welshman/signer"
|
||||||
import {Nip46Broker} from "@welshman/signer"
|
import {Nip46Broker} from "@welshman/signer"
|
||||||
import {makeSecret} from "@welshman/util"
|
import {makeSecret} from "@welshman/util"
|
||||||
import {PLATFORM_URL, PLATFORM_NAME, PLATFORM_LOGO, SIGNER_RELAYS} from "@app/env"
|
import {PLATFORM_URL, PLATFORM_NAME, SIGNER_RELAYS} from "@app/env"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
|
|
||||||
const APP_SCHEME = "social.flotilla"
|
const APP_SCHEME = "social.flotilla"
|
||||||
|
|
@ -67,7 +67,7 @@ export class Nip46Controller {
|
||||||
const url = await this.broker.makeNostrconnectUrl({
|
const url = await this.broker.makeNostrconnectUrl({
|
||||||
url: PLATFORM_URL,
|
url: PLATFORM_URL,
|
||||||
name: PLATFORM_NAME,
|
name: PLATFORM_NAME,
|
||||||
image: PLATFORM_LOGO,
|
image: PLATFORM_URL + "/logo.png",
|
||||||
perms: NIP46_PERMS,
|
perms: NIP46_PERMS,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue