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:
@@ -5,7 +5,7 @@ let fixture: Fixture;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.resetModules();
|
||||
fixture = await newFixture({ 'hud.gsc': 'v1' }, ['t5_kino_1', 't5_ascension_1']);
|
||||
fixture = await newFixture(['t5_kino_1', 't5_ascension_1']);
|
||||
// No integrations → sweep marks rows manual, exercising that branch.
|
||||
delete process.env.KOMODO_ADDRESS;
|
||||
delete process.env.RCON_PASSWORD;
|
||||
@@ -17,20 +17,33 @@ afterEach(async () => {
|
||||
|
||||
describe('sweep', () => {
|
||||
it('marks all pending rows manual when integrations are unset', async () => {
|
||||
const { doPush } = await import('../../src/lib/server/fleet/push.js');
|
||||
const { sweep } = await import('../../src/lib/server/pipeline/sweep.js');
|
||||
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
|
||||
const { db } = await import('../../src/lib/server/db/index.js');
|
||||
await seedServerRegistry();
|
||||
|
||||
const push = await doPush({ patterns: ['*'] });
|
||||
expect(push.deploymentId).not.toBeNull();
|
||||
// Seed a deployment row directly — the code path being tested is the
|
||||
// "integrations not configured" branch, which just walks deployment_servers.
|
||||
const depId = 'dep-test-001';
|
||||
db().prepare("INSERT INTO deployments (id, action, started_at, triggered_by) VALUES (?, 'test', ?, 'ui')")
|
||||
.run(depId, new Date().toISOString());
|
||||
db()
|
||||
.prepare(
|
||||
"INSERT INTO deployment_servers (deployment_id, server, files_changed, restart_state, attempts) VALUES (?, ?, 1, 'pending', 0)"
|
||||
)
|
||||
.run(depId, 't5_kino_1');
|
||||
db()
|
||||
.prepare(
|
||||
"INSERT INTO deployment_servers (deployment_id, server, files_changed, restart_state, attempts) VALUES (?, ?, 1, 'pending', 0)"
|
||||
)
|
||||
.run(depId, 't5_ascension_1');
|
||||
|
||||
await sweep();
|
||||
|
||||
const rows = db()
|
||||
.prepare("SELECT restart_state FROM deployment_servers WHERE deployment_id = ?")
|
||||
.all(push.deploymentId!) as Array<{ restart_state: string }>;
|
||||
.all(depId) as Array<{ restart_state: string }>;
|
||||
expect(rows.length).toBe(2);
|
||||
for (const r of rows) expect(r.restart_state).toBe('manual');
|
||||
});
|
||||
|
||||
@@ -39,7 +52,6 @@ describe('sweep', () => {
|
||||
const { seedServerRegistry } = await import('../../src/lib/server/fleet/servers.js');
|
||||
await seedServerRegistry();
|
||||
|
||||
// No deployments → no pending rows. Should return zero counts.
|
||||
const result = await sweep();
|
||||
expect(result).toEqual({ checked: 0, restarted: 0, held: 0, failed: 0 });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user