config and plugin

This commit is contained in:
2026-09-12 19:11:44 -04:00
parent 85bcb05a7d
commit 8c40bbeff1
19 changed files with 1685 additions and 86 deletions
+43 -13
View File
@@ -1,10 +1,12 @@
import { json } from '@sveltejs/kit';
import {
createScript,
isArtifactKind,
listScripts,
listVersions,
serversMatchingScope,
ScriptError,
type ArtifactKind,
type ScopeType
} from '$lib/server/fleet/scripts.js';
import { findRunningBetaForScript } from '$lib/server/fleet/beta.js';
@@ -17,8 +19,18 @@ function scopeTypeOrNull(v: FormDataEntryValue | null): ScopeType | null {
throw new ScriptError(`invalid scope_type: ${s}`, 'invalid-scope');
}
export async function GET() {
const scripts = listScripts().map((s) => {
function kindOrDefault(v: FormDataEntryValue | string | null, fallback: ArtifactKind = 'script'): ArtifactKind {
if (v === null || v === undefined || v === '') return fallback;
const s = String(v);
if (!isArtifactKind(s)) throw new ScriptError(`invalid kind: ${s}`, 'invalid-kind');
return s;
}
export async function GET({ url }: { url: URL }) {
const kindParam = url.searchParams.get('kind');
const kindFilter = kindParam ? (isArtifactKind(kindParam) ? kindParam : null) : null;
const rows = kindFilter ? listScripts(kindFilter) : listScripts();
const scripts = rows.map((s) => {
const versions = listVersions(s.id);
const matched = s.scope_type && s.scope_value
? serversMatchingScope(s.scope_type as ScopeType, s.scope_value)
@@ -44,28 +56,45 @@ export async function GET() {
return json({ scripts });
}
// Multipart POST — creates a script + version 1 from a single file upload.
// Fields: name (text), scopeType (optional: 'game'|'map'), scopeValue
// (optional string), notes (optional), content (file). The target rel path
// defaults to the uploaded filename — T5 servers keep all scripts in a flat
// sp/ directory, so there's no sub-path to specify.
export async function POST({ request }: { request: Request }) {
// Multipart POST — creates an artifact + version 1 from either an uploaded
// file (script/plugin/baseline) or a text body (config overlay). Fields:
// name, scopeType, scopeValue, notes, kind (defaults to 'script'),
// content (file). Configs may alternatively send `text` as a form field.
export async function POST({ request, url }: { request: Request; url: URL }) {
const form = await request.formData();
const name = String(form.get('name') ?? '').trim();
const notes = form.get('notes') ? String(form.get('notes')) : undefined;
let scopeType: ScopeType | null;
let kind: ArtifactKind;
try {
scopeType = scopeTypeOrNull(form.get('scopeType'));
// Kind may come from the form or the query string (thin wrappers use ?kind=).
const kindRaw = form.get('kind') ?? url.searchParams.get('kind');
kind = kindOrDefault(kindRaw);
} catch (err) {
return json({ ok: false, error: (err as Error).message }, { status: 400 });
}
const scopeValueRaw = form.get('scopeValue');
const scopeValue = scopeValueRaw ? String(scopeValueRaw).trim() : null;
const file = form.get('content');
if (!(file instanceof File)) return json({ ok: false, error: 'content file is required' }, { status: 400 });
const targetRelPath = file.name;
const content = Buffer.from(await file.arrayBuffer());
let content: Buffer;
let uploadedFilename: string | null = null;
const file = form.get('content');
const text = form.get('text');
if (file instanceof File && file.size > 0) {
content = Buffer.from(await file.arrayBuffer());
uploadedFilename = file.name;
} else if (typeof text === 'string' && text.length > 0) {
content = Buffer.from(text, 'utf8');
} else {
return json({ ok: false, error: 'content file or text is required' }, { status: 400 });
}
// Target rel path: for script/plugin, default to the upload filename.
// For config/baseline, createScript() defaults to config.configTargetFile.
const targetRelPath = kind === 'script' || kind === 'plugin'
? uploadedFilename ?? undefined
: undefined;
try {
const { script, version } = createScript({
@@ -74,7 +103,8 @@ export async function POST({ request }: { request: Request }) {
scopeType,
scopeValue,
content,
notes
notes,
kind
});
return json({ ok: true, script, versionId: version.id });
} catch (err) {