Add Prettier and ESLint tooling (Refs #77)

This commit is contained in:
2026-10-09 15:56:13 -04:00
parent 8ed059b8d3
commit 16469e2d43
711 changed files with 99737 additions and 28286 deletions
+95 -23
View File
@@ -1,56 +1,128 @@
import { zombieMapBySlug, type ZombieGame } from './map-aliases.js';
export const autoscalingModes = ['off', 'create-destroy'] as const;
export type AutoscalingMode = typeof autoscalingModes[number];
export type AutoscalingMode = (typeof autoscalingModes)[number];
export interface ScalingSettings {
game: ZombieGame;
/** '*' applies the targets independently to every map. */
mapSlug: string;
mode: AutoscalingMode;
minimum: number; maximum: number; spares: number; emptyMinutes: number;
minimum: number;
maximum: number;
spares: number;
emptyMinutes: number;
}
export interface ScalingPolicy extends ScalingSettings {
id: string; revision: number; pausedReason: string | null; updatedAt: string;
id: string;
revision: number;
pausedReason: string | null;
updatedAt: string;
}
export interface ScalingMember {
name: string; mapSlug?: string; identity: string; state: 'occupied' | 'empty' | 'creating' | 'removing' | 'stopped' | 'unknown' | 'protected';
players: number | null; observedAt: number | null; source: string | null;
emptySince: number | null; blocker: string | null; operationId: string | null;
name: string;
mapSlug?: string;
identity: string;
state: 'occupied' | 'empty' | 'creating' | 'removing' | 'stopped' | 'unknown' | 'protected';
players: number | null;
observedAt: number | null;
source: string | null;
emptySince: number | null;
blocker: string | null;
operationId: string | null;
emptyStartedAt?: number | null;
}
export interface ScalingSnapshot {
members: ScalingMember[]; reservedNames: string[]; keyPool: { total: number; available: number }; blockers: string[];
members: ScalingMember[];
reservedNames: string[];
keyPool: { total: number; available: number };
blockers: string[];
}
export interface ScalingDecision {
action: 'create' | 'destroy' | 'wait'; reason: string; server: string | null;
desired: number; deficit: number; occupied: number; empty: number; pending: number; unknown: number;
action: 'create' | 'destroy' | 'wait';
reason: string;
server: string | null;
desired: number;
deficit: number;
occupied: number;
empty: number;
pending: number;
unknown: number;
}
export interface ScalingEvaluation {
policy: ScalingPolicy;
snapshot: ScalingSnapshot;
decision: ScalingDecision;
blockers: string[];
}
export interface ScalingHistory {
id: string;
at: number;
lastSeenAt: number;
decision: ScalingDecision;
operationId: string | null;
operationState: string | null;
}
export interface ScalingEvaluation { policy: ScalingPolicy; snapshot: ScalingSnapshot; decision: ScalingDecision; blockers: string[] }
export interface ScalingHistory { id: string; at: number; lastSeenAt: number; decision: ScalingDecision; operationId: string | null; operationState: string | null }
export interface ScalingView extends ScalingEvaluation {
history: ScalingHistory[]; lastEvaluatedAt: number | null;
totals: { observedMinutes: number; emptyLobbyHours: number; deficitMinutes: number; unknownMinutes: number };
operations: { creates: number; removals: number; cancelled: number; failed: number; readinessSamples: number; readinessMeanSeconds: number | null; readinessMaxSeconds: number | null };
history: ScalingHistory[];
lastEvaluatedAt: number | null;
totals: {
observedMinutes: number;
emptyLobbyHours: number;
deficitMinutes: number;
unknownMinutes: number;
};
operations: {
creates: number;
removals: number;
cancelled: number;
failed: number;
readinessSamples: number;
readinessMeanSeconds: number | null;
readinessMaxSeconds: number | null;
};
ownsMap: boolean;
unsettled: { id: string; action: string; state: string; stage: string }[];
}
export interface ScalingReport { policies: ScalingView[]; globalPaused: boolean; liveAvailable: boolean; removalAvailable: boolean }
export interface ScalingReport {
policies: ScalingView[];
globalPaused: boolean;
liveAvailable: boolean;
removalAvailable: boolean;
}
export const liveScaling = (mode: AutoscalingMode): boolean => mode === 'create-destroy';
export function scalingSettings(input: unknown): ScalingSettings {
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Provide an autoscaling policy.');
if (!input || typeof input !== 'object' || Array.isArray(input))
throw new Error('Provide an autoscaling policy.');
const value = input as Record<string, unknown>;
const { game, mapSlug } = value;
if ((game !== 't5' && game !== 't6') || typeof mapSlug !== 'string' || (mapSlug !== '*' && zombieMapBySlug(game, mapSlug)?.slug !== mapSlug)) throw new Error('Choose a supported game and map variant, or all maps.');
if (
(game !== 't5' && game !== 't6') ||
typeof mapSlug !== 'string' ||
(mapSlug !== '*' && zombieMapBySlug(game, mapSlug)?.slug !== mapSlug)
)
throw new Error('Choose a supported game and map variant, or all maps.');
const mode = value.mode ?? 'off';
if (!autoscalingModes.includes(mode as AutoscalingMode)) throw new Error('Choose a valid autoscaling mode.');
if (!autoscalingModes.includes(mode as AutoscalingMode))
throw new Error('Choose a valid autoscaling mode.');
const integer = (key: string, fallback: number | undefined, max: number) => {
const n = value[key] ?? fallback;
if (typeof n !== 'number' || !Number.isInteger(n) || n < 1 || n > max) throw new Error(`${key} must be an integer from 1 to ${max}.`);
if (typeof n !== 'number' || !Number.isInteger(n) || n < 1 || n > max)
throw new Error(`${key} must be an integer from 1 to ${max}.`);
return n;
};
const minimum = integer('minimum', 1, 20), maximum = integer('maximum', undefined, 20), spares = integer('spares', 1, 20);
if (minimum > maximum || spares > maximum) throw new Error('Minimum and spare target cannot exceed the maximum.');
return { game, mapSlug, mode: mode as AutoscalingMode, minimum, maximum, spares,
emptyMinutes: integer('emptyMinutes', 15, 1440) };
const minimum = integer('minimum', 1, 20),
maximum = integer('maximum', undefined, 20),
spares = integer('spares', 1, 20);
if (minimum > maximum || spares > maximum)
throw new Error('Minimum and spare target cannot exceed the maximum.');
return {
game,
mapSlug,
mode: mode as AutoscalingMode,
minimum,
maximum,
spares,
emptyMinutes: integer('emptyMinutes', 15, 1440)
};
}