feat: Bundled NW.js with appimages
feat: Implemented self update feat: Added rclone saves for emulators fix: Fixed auto focus in builds feat: Added helper cards on empty library
This commit is contained in:
parent
587956c792
commit
813785f4f3
59 changed files with 1210 additions and 480 deletions
|
|
@ -4,11 +4,11 @@ import fs from 'node:fs/promises';
|
|||
import { appBuilderPath, } from 'app-builder-bin';
|
||||
import path from 'node:path';
|
||||
import { ensureDir } from "fs-extra";
|
||||
import mustache from "mustache";
|
||||
|
||||
const APP_DIR = process.env.BUILD_DIR ?? `./build/${process.platform}`;
|
||||
const BINARY_NAME = pkg.bin;
|
||||
const ICON = "./src/mainview/public/256x256.png";
|
||||
const DESKTOP = "./flatpak/com.simeonradivoev.gameflow-deck.desktop";
|
||||
const TMP_FOLDER = ".";
|
||||
|
||||
const APP_NAME = pkg.displayName ?? pkg.name;
|
||||
|
|
@ -27,24 +27,45 @@ await fs.rename(path.join(APPDIR, `usr`, 'share', BINARY_NAME), path.join(APPDIR
|
|||
await fs.rename(path.join(APPDIR, `usr`, 'share', `libwebview-${process.arch}.so`), path.join(APPDIR, `usr`, 'lib', `libwebview-${process.arch}.so`));
|
||||
await fs.rename(path.join(APPDIR, `usr`, 'share', `7za`), path.join(APPDIR, `usr`, 'bin', `7za`));
|
||||
|
||||
await fs.writeFile(path.join(APPDIR, `${APP_ID}.desktop`), `[Desktop Entry]
|
||||
Version=${pkg.version}
|
||||
X-AppImage-Name=${APP_NAME}
|
||||
X-AppImage-Version=${pkg.version}
|
||||
X-AppImage-Arch=${process.arch}
|
||||
Name=${APP_NAME}
|
||||
Comment=${pkg.description}
|
||||
Exec=${APP_ID}.AppImage
|
||||
Icon=.DirIcon
|
||||
Type=Application
|
||||
Categories=Game;
|
||||
`);
|
||||
if (!await fs.exists('./bin/nw/nw'))
|
||||
{
|
||||
await import('./download-nw');
|
||||
}
|
||||
|
||||
await Bun.write(path.join(APPDIR, "AppRun"), `#!/bin/bash
|
||||
APPDIR="$(dirname "$(readlink -f "$0")")"
|
||||
APPIMAGE=true
|
||||
exec "$APPDIR/usr/bin/${BINARY_NAME}" "$@"
|
||||
`);
|
||||
await ensureDir(path.join(APPDIR, `usr`, 'lib', 'nw'));
|
||||
await fs.cp('./bin/nw', path.join(APPDIR, `usr`, 'lib', 'nw'), { recursive: true });
|
||||
await fs.symlink(path.join(APPDIR, `usr`, 'lib', 'nw', 'nw'), path.join(APPDIR, `usr`, `bin`, 'nw'));
|
||||
|
||||
const templateVars = {
|
||||
APP_NAME,
|
||||
VERSION: pkg.version,
|
||||
ARCH: process.arch,
|
||||
DESCRIPTION: pkg.description,
|
||||
APP_ID,
|
||||
BINARY_NAME,
|
||||
LICENSE: pkg.license
|
||||
};
|
||||
|
||||
const desktopFileTemplate = await fs.readFile('./.config/appimage/com.simeonradivoev.gameflow-deck.desktop', 'utf8');
|
||||
|
||||
const raw = await $`git tag --sort=-version:refname`.text().then(d => d.trim());
|
||||
const tags = raw.split('\n').filter(t => t.match(/^\d+\.\d+\.\d+$/));
|
||||
console.log("tags", tags);
|
||||
|
||||
console.log(">>> Updating Release History...");
|
||||
const releases = await Promise.all(tags.map(async tag =>
|
||||
{
|
||||
const date = await $`git log -1 --format=%as ${tag}`.text().then(d => d.trim());
|
||||
const version = tag.replace(/^v/, '');
|
||||
return ` <release version="${version}" date="${date}"/>`;
|
||||
}));
|
||||
|
||||
const appStreamTemplate = await fs.readFile('./.config/appimage/com.simeonradivoev.gameflow-deck.appdata.xml', 'utf8');
|
||||
await ensureDir(path.join(APPDIR, 'usr', 'share', 'metainfo'));
|
||||
await fs.writeFile(path.join(APPDIR, 'usr', 'share', 'metainfo', `${APP_ID}.appdata.xml`), mustache.render(appStreamTemplate, { ...templateVars, RELEASES: releases }));
|
||||
|
||||
const appRunTemplate = await fs.readFile(`./.config/appimage/AppRun`, 'utf8');
|
||||
await Bun.write(path.join(APPDIR, "AppRun"), mustache.render(appRunTemplate, templateVars));
|
||||
await $`chmod +x ${APPDIR}/AppRun`;
|
||||
|
||||
console.log(">>> Building AppImage...");
|
||||
|
|
@ -52,7 +73,7 @@ const config = {
|
|||
productName: pkg.displayName,
|
||||
productFilename: pkg.name,
|
||||
executableName: BINARY_NAME,
|
||||
desktopEntry: DESKTOP,
|
||||
desktopEntry: mustache.render(desktopFileTemplate, templateVars),
|
||||
icons: [
|
||||
{
|
||||
file: ICON,
|
||||
|
|
@ -67,7 +88,7 @@ const config = {
|
|||
// Remove the build dir, mainly to help with CIs
|
||||
await fs.rm(APP_DIR, { recursive: true });
|
||||
await ensureDir(APP_DIR);
|
||||
const OUTPUT = path.resolve(APP_DIR, `${APP_NAME}.AppImage`);
|
||||
const OUTPUT = path.resolve(APP_DIR, `${APP_NAME}-${process.platform}-${process.arch}.AppImage`);
|
||||
const STAGE = path.resolve(TMP_FOLDER, `${APP_ID}.stage`);
|
||||
|
||||
await ensureDir(STAGE);
|
||||
|
|
@ -86,8 +107,9 @@ const proc = Bun.spawn([
|
|||
});
|
||||
|
||||
const code = await proc.exited;
|
||||
await fs.rm(APPDIR, { recursive: true, force: true });
|
||||
await fs.rm(STAGE, { recursive: true, force: true });
|
||||
await fs.rm(APPDIR, { recursive: true, force: true });
|
||||
|
||||
if (code !== 0) process.exit(code);
|
||||
|
||||
console.log(`\n Done!`);
|
||||
|
|
@ -2,49 +2,44 @@ import EventEmitter from "events";
|
|||
import browser from '../src/bun/browser';
|
||||
import { tmpdir } from "os";
|
||||
import path from "path";
|
||||
import { createInterface } from "readline";
|
||||
import { Readable } from "stream";
|
||||
import { watch } from "fs";
|
||||
const events = new EventEmitter();
|
||||
const abortController = new AbortController();
|
||||
|
||||
process.env.WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS = "--remote-debugging-port=9222";
|
||||
process.env.NODE_ENV = "development";
|
||||
|
||||
let retries = 0;
|
||||
|
||||
function spawnServer ()
|
||||
{
|
||||
const s = Bun.spawn(["bun", '--watch', '--install=fallback', "run", "--inspect=127.0.0.1:9228/fixed-session", "./src/bun/index.ts"], {
|
||||
const s = Bun.spawn(["bun", '--install=fallback', "run", "--inspect=127.0.0.1:9228/fixed-session", "./src/bun/index.ts"], {
|
||||
env: {
|
||||
...process.env,
|
||||
HEADLESS: "true",
|
||||
},
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
stdin: "pipe",
|
||||
stdout: 'inherit',
|
||||
stderr: 'inherit',
|
||||
stdin: 'inherit',
|
||||
signal: abortController.signal,
|
||||
killSignal: 'SIGUSR1',
|
||||
ipc (message, subprocess, handle)
|
||||
{
|
||||
if (message === 'focus')
|
||||
{
|
||||
events.emit('focus');
|
||||
} else if (message === 'exitapp')
|
||||
{
|
||||
events.emit('exitapp');
|
||||
}
|
||||
},
|
||||
onExit (subprocess, exitCode, signalCode)
|
||||
{
|
||||
process.exit();
|
||||
if (exitCode !== 3)
|
||||
{
|
||||
console.log("Existing Dev With", exitCode);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
});
|
||||
const rl = createInterface({ input: Readable.fromWeb(s.stdout as any) });
|
||||
rl.on('line', e =>
|
||||
{
|
||||
if (e === 'focus')
|
||||
{
|
||||
events.emit('focus');
|
||||
} else
|
||||
{
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
const rle = createInterface({ input: Readable.fromWeb(s.stderr as any) });
|
||||
rle.on('line', e =>
|
||||
{
|
||||
console.error(e);
|
||||
});
|
||||
return s;
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +48,10 @@ function spawnBrowser ()
|
|||
try
|
||||
{
|
||||
|
||||
return browser(events, process.env.FORCE_BROWSER === "true", {
|
||||
return browser(events, {
|
||||
configPath: path.join(tmpdir(), 'gameflow'),
|
||||
isSteamDeckGameMode: false
|
||||
isSteamDeckGameMode: false,
|
||||
forceBrowser: process.env.FORCE_BROWSER === "true"
|
||||
});
|
||||
} catch (error)
|
||||
{
|
||||
|
|
@ -63,13 +59,33 @@ function spawnBrowser ()
|
|||
};
|
||||
}
|
||||
|
||||
let server = spawnServer();
|
||||
async function restart ()
|
||||
{
|
||||
if (server)
|
||||
{
|
||||
server.kill("SIGUSR1");
|
||||
await server.exited;
|
||||
server = undefined;
|
||||
console.log("Server Restarted");
|
||||
}
|
||||
|
||||
server = spawnServer();
|
||||
console.log("Server Restarted");
|
||||
}
|
||||
|
||||
watch("./src/bun", { recursive: true }, (event, filename) =>
|
||||
{
|
||||
console.log(`[watcher] ${event}: ${filename} — restarting...`);
|
||||
restart();
|
||||
});
|
||||
|
||||
let server: Bun.Subprocess | undefined = spawnServer();
|
||||
if (!process.env.HEADLESS)
|
||||
{
|
||||
spawnBrowser()?.then(async e =>
|
||||
{
|
||||
console.log("Sending exit Signal to server");
|
||||
await server.stdin.write('shutdown\n');
|
||||
await server.stdin.flush();
|
||||
if (!server) return;
|
||||
server.kill("SIGUSR1");
|
||||
await server.exited;
|
||||
});
|
||||
}
|
||||
54
scripts/download-nw.ts
Normal file
54
scripts/download-nw.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { ensureDir, remove } from "fs-extra";
|
||||
import StreamZip from "node-stream-zip";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
const VERSION = "0.110.1";
|
||||
|
||||
const platformMap: Record<string, string> = {
|
||||
"win32": "win",
|
||||
"darwin": "osx"
|
||||
};
|
||||
const extMap: Record<string, string> = {
|
||||
"win32": "zip",
|
||||
"linux": "tar.gz",
|
||||
"darwin": "zip"
|
||||
};
|
||||
|
||||
console.log("Removing old download");
|
||||
await remove('./bin/nw');
|
||||
|
||||
const downloadUrl = `https://dl.nwjs.io/v${VERSION}/nwjs-sdk-v${VERSION}-${platformMap[process.platform] ?? process.platform}-${process.arch}.${extMap[process.platform]}`;
|
||||
|
||||
console.log("Starting NW download from", downloadUrl);
|
||||
const response = await fetch(downloadUrl);
|
||||
if (!response.ok) throw new Error(response.statusText);
|
||||
const downlodPath = `./bin/nw.${extMap[process.platform]}`;
|
||||
await ensureDir('./bin');
|
||||
await Bun.write(downlodPath, response);
|
||||
console.log("Downloaded NW to", downlodPath);
|
||||
|
||||
if (downlodPath.endsWith('.zip'))
|
||||
{
|
||||
await extractZip(downlodPath, './bin');
|
||||
}
|
||||
else if (downlodPath.endsWith(".tar.gz"))
|
||||
{
|
||||
const result = spawnSync("tar", ["-xvf", downlodPath, "-C", './bin'], { stdio: "inherit" });
|
||||
if (result.status !== 0) console.error("tar extraction failed");
|
||||
}
|
||||
|
||||
console.log('Renaming to nw');
|
||||
await fs.rename(`./bin/nwjs-sdk-v${VERSION}-${platformMap[process.platform] ?? process.platform}-${process.arch}`, './bin/nw');
|
||||
await fs.rm(downlodPath);
|
||||
|
||||
async function extractZip (src: string, outDir: string)
|
||||
{
|
||||
console.log(`Extracting zip -> ${outDir}`);
|
||||
const zip = new StreamZip.async({ file: src });
|
||||
const entries = await zip.entries();
|
||||
const total = Object.keys(entries).length;
|
||||
await zip.extract(null, outDir);
|
||||
await zip.close();
|
||||
console.log(`Extracted ${total} files.`);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue