fix: Moved to manual plugin version checking and fixed some steam deck issues.

This commit is contained in:
Simeon Radivoev 2026-05-15 17:38:38 +03:00
parent 5593985884
commit 641eb2fcd5
Signed by: simeonradivoev
GPG key ID: C16C2132A7660C8E
13 changed files with 219 additions and 28 deletions

View file

@ -138,6 +138,12 @@ export async function checkLoginAndRefreshTwitch ()
export async function checkLoginAndRefreshRomm ()
{
//TODO: move to plugin logic
if (plugins.plugins['com.simeonradivoev.gameflow.romm'].config?.get('clientApiToken'))
{
return { hasLogin: true };
}
const access_token = await secrets.get({ service: 'gameflow', name: 'romm_access_token' });
if (!access_token)
{

View file

@ -44,7 +44,7 @@ export default class RommIntegration implements PluginType<SettingsType>
async getAccessToken (config: Conf<SettingsType>)
{
if (process.env.ROMM_CLIENT_TOKEN) return process.env.ROMM_CLIENT_TOKEN;
const client_token = await config.get('clientApiToken');
const client_token = config.get('clientApiToken');
if (client_token) return client_token;
return (await secrets.get({ service: 'gameflow', name: 'romm_access_token' })) ?? undefined;
}

View file

@ -2,7 +2,7 @@ import { PluginLoadingContextType, PluginType } from "@simeonradivoev/gameflow-s
import desc from './package.json';
import path, { } from 'node:path';
import { buildStoreFrontendEmulatorSystems, getAllStoreEmulatorPackages, getStoreEmulatorPackage, getStoreFolder } from "@/bun/api/store/services/gamesService";
import { Glob, pathToFileURL, sleep, which } from "bun";
import { Glob, pathToFileURL, which } from "bun";
import { and, eq } from "drizzle-orm";
import * as emulatorSchema from '@schema/emulators';

View file

@ -91,7 +91,7 @@ export class PluginManager
return true;
}
private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }, update: string | undefined)
private async reload (name: string, reloadCtx: { setProgress: (progress: number, state: string) => void; }, update: string | undefined | null)
{
const plugin = this.plugins[name];
if (plugin)
@ -149,7 +149,7 @@ export class PluginManager
for await (const id of Object.keys(this.plugins))
{
ctx.setProgress(0, `Loading ${id}`);
await this.reload(id, ctx, outdated?.[id]);
await this.reload(id, ctx, outdated.find(i => i.package === id)?.update);
}
}

View file

@ -122,19 +122,24 @@ export default async function register (pluginManager: PluginManager)
if (outdated)
{
for await (const plugin of validPlugins)
for (let i = 0; i < validPlugins.length; i++)
{
const newVersion = outdated[plugin.name];
const plugin = validPlugins[i];
const newVersion = outdated.find(i => i.package === plugin.name);
if (newVersion)
{
console.log("Plugin", plugin.name, "has update", plugin.version, "=>", newVersion);
}
console.log("Plugin", plugin.name, "has update", plugin.version, "=>", newVersion.update);
if (plugin.autoUpdate)
{
console.log("Auto Updating Plugin", plugin.name);
let response = await runBunPackageCommand(["add", `${plugin.name}@${newVersion}`, "--registry", PluginRegistry, '--omit', 'peer']);
console.log(response);
if (plugin.autoUpdate || plugin.name === '@simeonradivoev/gameflow-store')
{
console.log("Auto Updating Plugin", plugin.name);
let response = await runBunPackageCommand(["add", `${plugin.name}@${newVersion?.update}`, "--registry", PluginRegistry, '--omit', 'peer']);
console.log(response);
// Update plugin package
const newPlugin = await getPlugin(plugin.name, pluginManager);
if (newPlugin)
validPlugins[i] = newPlugin;
}
}
}
}

View file

@ -2,8 +2,8 @@ import path from 'node:path';
import os from 'node:os';
import { getStoreRootFolder } from '../store/services/gamesService';
import { PluginDescriptionType } from '@simeonradivoev/gameflow-sdk';
import { run } from 'npm-check-updates';
import { existsSync } from 'node:fs';
import { checkOutdated } from './update-check';
export function canDisable (description: PluginDescriptionType)
{
@ -16,9 +16,9 @@ export function canDisable (description: PluginDescriptionType)
export async function getUpdates ()
{
if (!existsSync(getStoreRootFolder())) return {};
const updated = await run({ packageManager: 'bun', peer: true, cwd: getStoreRootFolder(), jsonUpgraded: true, reject: ['@simeonradivoev/gameflow-sdk'] });
return updated as Record<string, string>;
if (!existsSync(getStoreRootFolder())) return [];
const results = await checkOutdated(getStoreRootFolder());
return results;
}
export function canUninstall (description: PluginDescriptionType, source: string)

View file

@ -0,0 +1,169 @@
import { semver } from "bun";
import { readFile } from "fs/promises";
import { join } from "path";
import { getOrCached } from "../cache";
import { PluginRegistry } from "@/shared/constants";
import sdkPkg from '@/packages/gameflow-sdk/package.json';
interface UpdateInfo
{
package: string,
current: string,
update: string | null,
latest: string,
sdkConstrained: boolean,
sdkRange: string,
note: string | null;
}
function parseBunOutdated (cwd: string)
{
const proc = Bun.spawnSync([process.execPath, "outdated"], {
stderr: "inherit", env: {
BUN_BE_BUN: "1",
NO_COLOR: "1",
}, cwd: cwd
});
const output = proc.stdout.toString();
const lines = output.split("\n").filter(Boolean);
const headerIndex = lines.findIndex(
(l) => l.includes("Package") && l.includes("Current")
);
if (headerIndex === -1) return [];
return lines
.slice(headerIndex + 1)
.filter((line) => !/^[-─╌| ]+$/.test(line))
.map((line) =>
{
const [, pkg, current, , latest] = line.split("|").map((c) => c.trim());
return pkg ? { package: pkg, current, latest } : null;
})
.filter(p => p !== null);
}
async function getInstalledVersion (cwd: string, pkg: string)
{
try
{
const raw = await readFile(join(cwd, "node_modules", pkg, "package.json"), "utf8");
return JSON.parse(raw).version ?? null;
} catch
{
return null;
}
}
async function fetchAllVersions (pkg: string)
{
const res = await fetch(`${PluginRegistry}/${pkg}`);
if (!res.ok) return [];
const data = await res.json();
return Object.keys(data.versions ?? {});
}
async function fetchPeerDeps (pkg: string, version: string)
{
const peerDependencies = await getOrCached(`npm-${pkg}-${version}`, async () =>
{
const res = await fetch(`${PluginRegistry}/${pkg}/${version}`);
if (!res.ok)
{
throw new Error(`Error while fetching peer deps for ${pkg} ${version} ${res.status} ${res.statusText}`);
}
const data = await res.json();
return data.peerDependencies ?? {};
}, {
//5 days
expireMs: 1000 * 60 * 60 * 24 * 5
});
return peerDependencies;
}
async function findBestVersion (pkg: string, allVersions: string[], sdkVersion: string)
{
// Sort descending so we find the highest compatible version first
const sorted = [...allVersions].sort((a, b) => semver.order(b, a));
for (const version of sorted)
{
const peers = await fetchPeerDeps(pkg, version);
const sdkRange = peers[sdkPkg.name];
if (!sdkRange)
{
// No peer dep on SDK — compatible by default
return { version, sdkRange: null };
}
if (semver.satisfies(sdkVersion, sdkRange))
{
return { version, sdkRange };
}
}
return null;
}
export async function checkOutdated (cwd: string)
{
const outdated = parseBunOutdated(cwd);
if (outdated.length === 0)
{
return [];
}
const sdkVersion = await getInstalledVersion(cwd, sdkPkg.name);
if (!sdkVersion)
{
console.error(`Could not find installed version of ${sdkPkg.name} in node_modules.`);
process.exit(1);
}
const results = await Promise.all(
outdated.map(async ({ package: pkg, current, latest }) =>
{
const allVersions = await fetchAllVersions(pkg);
// Check if the outright latest is already SDK compatible
const latestPeers = await fetchPeerDeps(pkg, latest);
const latestSdkRange = latestPeers[sdkPkg.name];
const latestCompatible =
!latestSdkRange || semver.satisfies(sdkVersion, latestSdkRange);
if (latestCompatible)
{
return {
package: pkg,
current,
update: latest,
latest,
sdkConstrained: false,
sdkRange: latestSdkRange ?? null,
note: null
} satisfies UpdateInfo as UpdateInfo;
}
const best = await findBestVersion(pkg, allVersions, sdkVersion);
return {
package: pkg,
current,
update: best?.version ?? null,
latest,
sdkConstrained: true,
sdkRange: best?.sdkRange ?? null,
note: best
? `Latest (${latest}) requires incompatible SDK range; best compatible: ${best.version}`
: `No version of ${pkg} is compatible with ${sdkPkg.name}@${sdkVersion}`,
} satisfies UpdateInfo as UpdateInfo;
})
);
return results;
}