Rework as scripts-centric, upload-only, gated beta lifecycle

Scripts become first-class: named, versioned, uploaded via the UI, optionally
scoped to a game or map. Any change that would land on scope-matched servers
(marking-required or new version of a scoped script) must first pass a beta.
The beta runs a per-server state machine — deploy -beta.gsc → wait empty →
Komodo restart → wait healthy → optionally wait for a real match end
(games_sp.log 'ExitLevel: executed') — before it's eligible for promotion.
Rollback is always available and restores from .OLD.

The shared/scripts mirror and its betas/ staging folder are gone. Every
server now has a tracked script inventory (server_scripts table), populated
only by explicit upload flows.

New: /scripts, /scripts/[id], /betas/[id] UI; /api/scripts + /api/betas +
/api/servers/[name]/scripts endpoints; scripts.ts, inventory.ts, beta.ts
(rewritten), beta-runner.ts, match-log.ts, komodoInspectHealthy().
Removed: push.ts, /api/push, BetaPanel, legacy importer.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 13:25:06 -04:00
co-authored by Claude Opus 4.7
parent 47051dc79a
commit 93d5a8e2e5
52 changed files with 2605 additions and 1689 deletions
+11 -34
View File
@@ -3,59 +3,36 @@
* Seed a test-fixtures/zombies/ tree so the app has something to point at
* during local dev on Windows/macOS (where /root/zombies doesn't exist).
*
* Since the upload-only rework, there's no shared/scripts source dir — the
* seed just creates empty server folders. Upload scripts via the /scripts UI
* to populate them.
*
* Usage:
* npm run seed:fixture
* SHARED_SCRIPTS=./test-fixtures/zombies/shared/scripts \
* SERVERS_ROOT=./test-fixtures/zombies/t5 \
* BETA_ARCHIVE_DIR=./test-fixtures/zombies/beta-archive \
* npm run dev
*/
import { mkdir, writeFile } from 'node:fs/promises';
import { mkdir } from 'node:fs/promises';
import { join } from 'node:path';
const root = process.env.FIXTURE_ROOT ?? './test-fixtures/zombies';
async function main(): Promise<void> {
const shared = join(root, 'shared/scripts');
const staging = join(shared, 'betas');
const t5 = join(root, 't5');
await mkdir(shared, { recursive: true });
await mkdir(staging, { recursive: true });
const betaArchive = join(root, 'beta-archive');
await mkdir(betaArchive, { recursive: true });
// A handful of representative GSC files
await writeFile(
join(shared, 'hud.gsc'),
'#include maps\\_utility;\n\ninit() { println("hud v1"); }\n'
);
await writeFile(
join(shared, 'zom.gsc'),
'#include maps\\_zombiemode;\n\ninit() { println("zom v1"); }\n'
);
await writeFile(
join(shared, 'perks.gsc'),
'#include maps\\_zombiemode_perks;\n\ninit() { println("perks v1"); }\n'
);
// Three servers, one already up-to-date, one empty, one with old copies
const rel = 'runtime/plutonium/storage/t5/raw/scripts/sp';
for (const name of ['t5_kino_1', 't5_ascension_1', 't5_cotd_1']) {
const dest = join(t5, name, rel);
await mkdir(dest, { recursive: true });
await mkdir(join(t5, name, rel), { recursive: true });
await mkdir(join(t5, name, 'logs'), { recursive: true });
}
// t5_kino_1 is already up-to-date
await writeFile(
join(t5, 't5_kino_1', rel, 'hud.gsc'),
'#include maps\\_utility;\n\ninit() { println("hud v1"); }\n'
);
// t5_cotd_1 has an older version of zom
await writeFile(
join(t5, 't5_cotd_1', rel, 'zom.gsc'),
'#include maps\\_zombiemode;\n\ninit() { println("zom v0"); }\n'
);
console.log(`seeded ${root}`);
console.log(` set SHARED_SCRIPTS=${shared}`);
console.log(` set SERVERS_ROOT=${t5}`);
console.log(` set BETA_ARCHIVE_DIR=${betaArchive}`);
}
main().catch((err) => {