Add GitHub plugin release imports and update discovery
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { importPluginRelease } from '$lib/server/fleet/plugin-releases.js';
|
||||
import { GithubReleaseError } from '$lib/server/integrations/github-releases.js';
|
||||
import { ScriptError } from '$lib/server/fleet/scripts.js';
|
||||
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
let input;
|
||||
try { input = await request.json(); } catch { return json({ error: 'Invalid JSON body.' }, { status: 400 }); }
|
||||
if (!input || typeof input !== 'object' || Array.isArray(input)) return json({ error: 'Import details are required.' }, { status: 400 });
|
||||
if ((input.scopeType != null && input.scopeType !== 'game' && input.scopeType !== 'map') ||
|
||||
(input.scopeValue != null && typeof input.scopeValue !== 'string')) {
|
||||
return json({ error: 'Invalid plugin scope.' }, { status: 400 });
|
||||
}
|
||||
try { return json({ ok: true, ...await importPluginRelease(input) }); }
|
||||
catch (error) {
|
||||
if (error instanceof GithubReleaseError) return json({ error: error.message }, { status: error.status });
|
||||
if (error instanceof ScriptError) return json({ error: error.message }, { status: 409 });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getGithubRelease, GithubReleaseError } from '$lib/server/integrations/github-releases.js';
|
||||
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
try { return json({ release: await getGithubRelease(url.searchParams.get('url') ?? '') }); }
|
||||
catch (error) {
|
||||
if (error instanceof GithubReleaseError) return json({ error: error.message }, { status: error.status });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { listScripts } from '$lib/server/fleet/scripts.js';
|
||||
import { checkPluginUpdate } from '$lib/server/fleet/plugin-releases.js';
|
||||
import { GithubReleaseError } from '$lib/server/integrations/github-releases.js';
|
||||
import type { PluginUpdate } from '$lib/plugin-github.js';
|
||||
|
||||
export async function GET({ url }: { url: URL }) {
|
||||
const raw = url.searchParams.get('pluginId');
|
||||
if (raw !== null) {
|
||||
const id = Number(raw);
|
||||
if (!Number.isSafeInteger(id) || id < 1) return json({ error: 'Invalid plugin id.' }, { status: 400 });
|
||||
try { return json({ updates: [await checkPluginUpdate(id)] }); }
|
||||
catch (error) {
|
||||
if (error instanceof GithubReleaseError) return json({ error: error.message }, { status: error.status });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const plugins = listScripts('plugin');
|
||||
const updates: PluginUpdate[] = [];
|
||||
// Bound outbound concurrency for large fleets; repository requests share a five-minute cache.
|
||||
for (let i = 0; i < plugins.length; i += 4) {
|
||||
updates.push(...await Promise.all(plugins.slice(i, i + 4).map(p => checkPluginUpdate(p.id))));
|
||||
}
|
||||
return json({ updates });
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
type ScopeType
|
||||
} from '$lib/server/fleet/scripts.js';
|
||||
import { findRunningBetaForScript } from '$lib/server/fleet/beta.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import type { PluginSource } from '$lib/plugin-github.js';
|
||||
|
||||
function parseId(idRaw: string): number | null {
|
||||
const n = Number.parseInt(idRaw, 10);
|
||||
@@ -27,6 +29,9 @@ export async function GET({ params }: { params: { id: string } }) {
|
||||
sha256: v.sha256,
|
||||
size: v.size,
|
||||
notes: v.notes,
|
||||
source: script.kind === 'plugin'
|
||||
? db().prepare('SELECT * FROM plugin_imports WHERE version_id = ?').get(v.id) as PluginSource | undefined ?? null
|
||||
: null,
|
||||
state: v.state,
|
||||
uploaded_at: v.uploaded_at
|
||||
}));
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
let poll: ReturnType<typeof setInterval> | null = null;
|
||||
onMount(() => {
|
||||
load();
|
||||
void load().then(seedImportedPlugin).catch(error => { flash = (error as Error).message; });
|
||||
poll = setInterval(refreshBetas, 5000);
|
||||
});
|
||||
onDestroy(() => {
|
||||
@@ -102,6 +102,26 @@
|
||||
return eligibleScripts.filter((s) => !takenIds.has(s.id));
|
||||
}
|
||||
|
||||
async function seedImportedPlugin(): Promise<void> {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
if (!query.has('pluginId')) return;
|
||||
const pluginId = Number(query.get('pluginId'));
|
||||
const versionId = Number(query.get('versionId'));
|
||||
if (!eligibleScripts.some(s => s.id === pluginId && s.kind === 'plugin')) {
|
||||
flash = 'This plugin is already promoted or has a running beta. Open its detail page to review its status.';
|
||||
return;
|
||||
}
|
||||
picks = [{ key: nextKey++, scriptId: pluginId, versionId: null }];
|
||||
await onPickScript(0);
|
||||
if (!versionsForRow(picks[0]).some(v => v.id === versionId)) {
|
||||
picks[0].versionId = null;
|
||||
flash = 'The imported version is no longer available for testing. Choose a version to continue.';
|
||||
return;
|
||||
}
|
||||
picks[0].versionId = versionId;
|
||||
picks = [...picks];
|
||||
}
|
||||
|
||||
async function onPickScript(rowIdx: number): Promise<void> {
|
||||
flash = null;
|
||||
const pick = picks[rowIdx];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import PluginGithubImport from '$ui/components/PluginGithubImport.svelte';
|
||||
import PluginUpdates from '$ui/components/PluginUpdates.svelte';
|
||||
import { artifactVersionLabel } from '$lib/artifact-version.js';
|
||||
import Card from '$ui/components/primitives/Card.svelte';
|
||||
import Badge from '$ui/components/primitives/Badge.svelte';
|
||||
@@ -31,6 +33,7 @@
|
||||
interface ScopeOptions { games: string[]; mapsByGame: Record<string, string[]>; }
|
||||
|
||||
let plugins = $state<ArtifactRow[]>([]);
|
||||
let importCount = $state(0);
|
||||
let loading = $state(true);
|
||||
let flash = $state<string | null>(null);
|
||||
let uploading = $state(false);
|
||||
@@ -149,6 +152,9 @@
|
||||
<div class="page">
|
||||
<SectionHeader title="Plugins" subtitle="Native game plugins deployed alongside scripts." />
|
||||
|
||||
<PluginGithubImport onimport={async () => { importCount++; await load(); }} />
|
||||
{#key importCount}<PluginUpdates />{/key}
|
||||
|
||||
<Card padding="lg">
|
||||
<h2 class="panel-title">Upload a plugin</h2>
|
||||
{#if successBanner}
|
||||
|
||||
Reference in New Issue
Block a user