import { T5_ZOMBIE_MAPS, T6_ZOMBIE_MAPS } from './map-aliases.js'; import { gameTitle } from './ui/server-labels.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 = { 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): string { return s.scope_type === 'game' ? gameTitle(s.scope_value) : 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/new?${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(); 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 }; }); }