Refresh script library with batch uploads and beta handoff

This commit is contained in:
2026-09-18 09:57:57 -04:00
parent b2ec01daf5
commit ec51e38312
9 changed files with 576 additions and 382 deletions
+55
View File
@@ -0,0 +1,55 @@
import { T5_ZOMBIE_MAPS, T6_ZOMBIE_MAPS } from './map-aliases.js';
export interface LibraryVersion {
id: number; version: number; sha256: string; size: number;
state: 'untested' | 'tested' | 'test-failed'; uploaded_at: string;
}
export interface LibraryScript {
id: number; name: string; target_rel_path: string;
scope_type: 'game' | 'map' | null; scope_value: string | null;
current_version_id: number | null;
currentVersion: LibraryVersion | null; latestVersion: LibraryVersion | null;
versionCount: number; servers: string[]; runningBetaId: number | null;
}
export type ScriptStatus = 'untested' | 'testing' | 'tested' | 'failed' | 'promoted' | 'empty';
export const statusLabels: Record<ScriptStatus, string> = {
untested: 'Needs testing', testing: 'Beta active', tested: 'Test passed',
failed: 'Test failed', promoted: 'Promoted', empty: 'No versions'
};
export function scriptStatus(s: LibraryScript): ScriptStatus {
if (s.runningBetaId !== null) return 'testing';
if (!s.latestVersion) return 'empty';
if (s.latestVersion.id === s.current_version_id) return 'promoted';
return s.latestVersion.state === 'test-failed' ? 'failed' : s.latestVersion.state;
}
export function canTest(s: LibraryScript): boolean {
return s.runningBetaId === null && s.latestVersion !== null && s.latestVersion.id !== s.current_version_id;
}
export function mapLabel(canonical: string): string {
const matches = [...T5_ZOMBIE_MAPS, ...T6_ZOMBIE_MAPS].filter(m => m.canonical === canonical);
return matches.length > 1 ? `${matches[0].friendly} + ${matches.length - 1} variants` : matches[0]?.friendly ?? canonical;
}
export function scopeLabel(s: Pick<LibraryScript, 'scope_type' | 'scope_value'>): string {
return s.scope_type === 'game' ? (s.scope_value ?? '').toUpperCase()
: s.scope_type === 'map' ? mapLabel(s.scope_value ?? '') : 'Unscoped';
}
export interface BetaSelection { scriptId: number; versionId: number; }
export function betaSelectionHref(selections: BetaSelection[]): string {
const query = new URLSearchParams();
for (const s of selections) query.append('artifact', `${s.scriptId}:${s.versionId}`);
return `/betas?${query}`;
}
export function parseBetaSelection(query: URLSearchParams): BetaSelection[] {
const values = query.getAll('artifact');
if (!values.length && query.has('pluginId')) values.push(`${query.get('pluginId')}:${query.get('versionId')}`);
const seen = new Set<number>();
return values.map(value => {
if (!/^[1-9]\d*:[1-9]\d*$/.test(value)) throw new Error('The beta selection link is invalid. Choose artifacts below.');
const [scriptId, versionId] = value.split(':').map(Number);
if (!Number.isSafeInteger(scriptId) || !Number.isSafeInteger(versionId) || seen.has(scriptId)) {
throw new Error('The beta selection link contains invalid or repeated artifacts. Choose artifacts below.');
}
seen.add(scriptId);
return { scriptId, versionId };
});
}