Fix blocking collection matching with large plugins
This commit is contained in:
@@ -3,12 +3,12 @@ import type { BlueprintSummary, LaunchRecipeSummary, LaunchFileSummary } from '$
|
||||
import { config, configFileForGame } from '../config.js';
|
||||
import { db } from '../db/index.js';
|
||||
import { zombieMapBySlug, type ZombieGame } from './map-aliases.js';
|
||||
import { promotedArtifacts } from './promoted-artifacts.js';
|
||||
import { promotedArtifactMetadata, promotedArtifacts } from './promoted-artifacts.js';
|
||||
import { getPromotedServerConfig, getServerMapConfig, MAP_CONFIG_FILE } from './server-config.js';
|
||||
import { getBaselineForScope, getVersion, listPromotedOverlaysForScope } from './scripts.js';
|
||||
import { getBaselineForScope, getVersionMetadata, listPromotedOverlaysForScope } from './scripts.js';
|
||||
import { sha256Buffer } from './files.js';
|
||||
|
||||
export interface LaunchRecipe {
|
||||
export interface LaunchRecipePlan {
|
||||
native?: import('../../hosts.js').NativePlacement;
|
||||
format: 1;
|
||||
game: ZombieGame;
|
||||
@@ -19,10 +19,15 @@ export interface LaunchRecipe {
|
||||
image: string;
|
||||
configFile: string;
|
||||
configSource: string;
|
||||
files: (LaunchFileSummary & { content?: string })[];
|
||||
}
|
||||
|
||||
/** Attempted launches retain their exact bytes; queued plans need only metadata. */
|
||||
export interface LaunchRecipe extends LaunchRecipePlan {
|
||||
files: (LaunchFileSummary & { content: string })[];
|
||||
}
|
||||
|
||||
export interface BlueprintEntry { count: number; recipe: LaunchRecipe }
|
||||
export interface BlueprintEntry { count: number; recipe: LaunchRecipePlan }
|
||||
interface BlueprintSelection { count: number; game: ZombieGame; mapSlug: string }
|
||||
interface BlueprintRow { id: string; name: string; description: string; created_at: string; recipe: string }
|
||||
|
||||
@@ -45,7 +50,7 @@ function summarizeBlueprint(row: BlueprintRow): BlueprintSummary {
|
||||
export function blueprintEntries(id: string): { entries: BlueprintEntry[]; blueprintName: string } {
|
||||
const row = db().prepare('SELECT * FROM server_blueprints WHERE id = ?').get(id) as BlueprintRow | undefined;
|
||||
if (!row) throw new Error('This collection is no longer available. Choose another starting point.');
|
||||
const entries = readEntries(row.recipe).map(({ count, game, mapSlug }) => ({ count, recipe: captureRecipe(game, mapSlug) }));
|
||||
const entries = readEntries(row.recipe).map(({ count, game, mapSlug }) => ({ count, recipe: captureRecipePlan(game, mapSlug) }));
|
||||
return { entries, blueprintName: row.name };
|
||||
}
|
||||
|
||||
@@ -66,22 +71,36 @@ function buildEntries(input: unknown): BlueprintSelection[] {
|
||||
}
|
||||
|
||||
export function captureRecipe(game: ZombieGame, mapSlug: string): LaunchRecipe {
|
||||
return buildRecipe(game, mapSlug, true);
|
||||
}
|
||||
|
||||
/** Preview and admission keep file references; execution captures the current bytes. */
|
||||
export function captureRecipePlan(game: ZombieGame, mapSlug: string): LaunchRecipePlan {
|
||||
return buildRecipe(game, mapSlug, false);
|
||||
}
|
||||
|
||||
function buildRecipe(game: ZombieGame, mapSlug: string, includeContent: true): LaunchRecipe;
|
||||
function buildRecipe(game: ZombieGame, mapSlug: string, includeContent: false): LaunchRecipePlan;
|
||||
function buildRecipe(game: ZombieGame, mapSlug: string, includeContent: boolean): LaunchRecipePlan {
|
||||
const map = zombieMapBySlug(game, mapSlug);
|
||||
if (!map) throw new Error('Choose a supported game and map.');
|
||||
const files: LaunchRecipe['files'] = promotedArtifacts(game, map.canonical).map(({ kind, script, version }) => ({
|
||||
const metadata = ({ kind, script, version }: ReturnType<typeof promotedArtifactMetadata>[number]): LaunchFileSummary => ({
|
||||
name: script.name, kind, path: script.target_rel_path, versionId: version.id,
|
||||
version: version.version, sha256: version.sha256, size: version.size, content: version.content.toString('base64')
|
||||
}));
|
||||
version: version.version, sha256: version.sha256, size: version.size
|
||||
});
|
||||
const files: LaunchRecipePlan['files'] = includeContent
|
||||
? promotedArtifacts(game, map.canonical).map(artifact => ({ ...metadata(artifact), content: artifact.version.content.toString('base64') }))
|
||||
: promotedArtifactMetadata(game, map.canonical).map(metadata);
|
||||
const addConfig = (name: string, path: string, content: Buffer) => files.push({
|
||||
name, path, kind: 'config', versionId: null, version: null,
|
||||
sha256: sha256Buffer(content), size: content.length, content: content.toString('base64')
|
||||
sha256: sha256Buffer(content), size: content.length, ...(includeContent ? { content: content.toString('base64') } : {})
|
||||
});
|
||||
const composed = getPromotedServerConfig(game, map.canonical, map.slug);
|
||||
let configSource = 'Plutainer defaults, seeded on first boot';
|
||||
if (composed) {
|
||||
const baseline = getBaselineForScope('game', game)!;
|
||||
const layers = [baseline, ...listPromotedOverlaysForScope('game', game), ...listPromotedOverlaysForScope('map', map.canonical)];
|
||||
configSource = layers.map(layer => `${layer.name} v${getVersion(layer.current_version_id!)!.version}`).join(' + ');
|
||||
configSource = layers.map(layer => `${layer.name} v${getVersionMetadata(layer.current_version_id!)!.version}`).join(' + ');
|
||||
addConfig('Composed server configuration', configFileForGame(game), composed);
|
||||
}
|
||||
addConfig('Selected map', MAP_CONFIG_FILE, getServerMapConfig(game, map.canonical, map.slug)!);
|
||||
@@ -90,19 +109,19 @@ export function captureRecipe(game: ZombieGame, mapSlug: string): LaunchRecipe {
|
||||
configFile: configFileForGame(game), configSource, files };
|
||||
}
|
||||
|
||||
export function summarizeRecipe(recipe: LaunchRecipe): LaunchRecipeSummary {
|
||||
export function summarizeRecipe(recipe: LaunchRecipePlan): LaunchRecipeSummary {
|
||||
const { format: _format, files, ...summary } = recipe;
|
||||
return { ...summary, files: files.map(({ content: _content, ...file }) => file),
|
||||
fingerprint: sha256Buffer(Buffer.from(JSON.stringify(recipe))) };
|
||||
}
|
||||
|
||||
export function recipeForDeployment(id: string): { recipe: LaunchRecipe; blueprintName: string | null } | null {
|
||||
export function recipeForDeployment(id: string): { recipe: LaunchRecipePlan; blueprintName: string | null } | null {
|
||||
const row = db().prepare('SELECT recipe, blueprint_name FROM server_launch_recipes WHERE deployment_id = ?')
|
||||
.get(id) as { recipe: string; blueprint_name: string | null } | undefined;
|
||||
return row ? { recipe: JSON.parse(row.recipe), blueprintName: row.blueprint_name } : null;
|
||||
}
|
||||
|
||||
export function blueprintRecipe(id: string): { recipe: LaunchRecipe; blueprintName: string } {
|
||||
export function blueprintRecipe(id: string): { recipe: LaunchRecipePlan; blueprintName: string } {
|
||||
const { entries, blueprintName } = blueprintEntries(id);
|
||||
if (entries.length !== 1 || entries[0].count !== 1) throw new Error('This collection launches multiple servers. Use the collection preview.');
|
||||
return { recipe: entries[0].recipe, blueprintName };
|
||||
@@ -122,7 +141,7 @@ function blueprintName(input: { name: unknown; description?: unknown }, exceptId
|
||||
export function saveBlueprint(input: { name: unknown; description?: unknown; previewId?: unknown; deploymentId?: unknown; entries?: unknown }): BlueprintSummary {
|
||||
const name = blueprintName(input);
|
||||
if ([input.previewId, input.deploymentId, input.entries].filter(value => value !== undefined).length !== 1) throw new Error('Choose exactly one launch preview, recorded deployment, or set of maps.');
|
||||
let recipe: LaunchRecipe | undefined;
|
||||
let recipe: LaunchRecipePlan | undefined;
|
||||
if (typeof input.previewId === 'string') {
|
||||
const row = db().prepare('SELECT recipe FROM server_launch_previews WHERE id = ? AND expires_at > ?')
|
||||
.get(input.previewId, new Date().toISOString()) as { recipe: string } | undefined;
|
||||
|
||||
Reference in New Issue
Block a user