170 lines
6.7 KiB
TypeScript
170 lines
6.7 KiB
TypeScript
import type { StaticStickerCatalog, StaticStickerCatalogItem } from "@dada/static-sticker-catalog";
|
|
|
|
export const P0A_TEXT_TEMPLATE_IDS = [
|
|
"FLOWER001", "FLOWER002", "FLOWER003", "FLOWER004", "FLOWER005", "FLOWER006", "FLOWER007", "FLOWER008",
|
|
"H001", "H002", "H003", "H004", "H005", "H006", "H007", "H008",
|
|
"TAG001", "TAG002", "TAG003", "TAG004", "TAG005", "TAG006", "TAG007", "TAG051",
|
|
"SIMPLE001", "SIMPLE002", "SIMPLE003", "SIMPLE004", "SIMPLE005", "SIMPLE006", "SIMPLE007", "SIMPLE008",
|
|
] as const;
|
|
|
|
// Derived from exact package-hash matches between the 32 frozen templates and the 86-item font panel.
|
|
export const P0A_REQUIRED_FONT_PANEL_IDS = [
|
|
"FONT005", "FONT008", "FONT011", "FONT021", "FONT022", "FONT027",
|
|
"FONT039", "FONT043", "FONT046", "FONT052", "FONT081",
|
|
] as const;
|
|
|
|
export const P0A_COLOR_CARD_IDS = ["COLOR001", "COLOR002", "COLOR008", "COLOR016"] as const;
|
|
|
|
export const P0A_DYNAMIC_STICKER_IDS = [
|
|
"DYN001", "DYN002", "DYN003", "DYN004", "DYN007",
|
|
"DYN008", "DYN011", "DYN012", "DYN015", "DYN016",
|
|
] as const;
|
|
|
|
export type RegisteredComplexFamily = "color_card" | "font_panel" | "interactive_sticker" | "text_template";
|
|
|
|
export interface RegisteredComplexAsset extends Record<string, unknown> {
|
|
canonical_id: string;
|
|
family: RegisteredComplexFamily;
|
|
font_panel_references?: string[];
|
|
release_status: string;
|
|
release_tier: string;
|
|
validation_status: string;
|
|
}
|
|
|
|
export interface ComplexRegistryManifest {
|
|
items: RegisteredComplexAsset[];
|
|
release_version: string;
|
|
schema_version: string;
|
|
}
|
|
|
|
export interface PublicComplexAsset extends RegisteredComplexAsset {
|
|
release_status: "enabled";
|
|
release_tier: "alpha_whitelist";
|
|
validation_status: "passed";
|
|
}
|
|
|
|
export interface P0aPublicManifest {
|
|
assets: {
|
|
color_cards: PublicComplexAsset[];
|
|
dynamic_stickers: PublicComplexAsset[];
|
|
font_panel_items: PublicComplexAsset[];
|
|
static_stickers: StaticStickerCatalogItem[];
|
|
text_templates: PublicComplexAsset[];
|
|
};
|
|
counts: {
|
|
color_cards: 4;
|
|
dynamic_stickers: 10;
|
|
font_panel_items: 11;
|
|
static_parts: 25;
|
|
static_stickers: 1407;
|
|
text_templates: 32;
|
|
};
|
|
release_tier: "alpha_whitelist";
|
|
release_version: string;
|
|
schema_version: "P0AAssetManifest/v1";
|
|
source_versions: {
|
|
complex: string;
|
|
static_stickers: string;
|
|
};
|
|
static_part_counts: Record<string, number>;
|
|
}
|
|
|
|
const expectedFamilyCounts: Readonly<Record<RegisteredComplexFamily, number>> = {
|
|
color_card: 16,
|
|
font_panel: 86,
|
|
interactive_sticker: 35,
|
|
text_template: 332,
|
|
};
|
|
|
|
function orderedItems(items: readonly RegisteredComplexAsset[], ids: readonly string[], family: RegisteredComplexFamily) {
|
|
const byId = new Map(items.filter((item) => item.family === family).map((item) => [item.canonical_id, item]));
|
|
return ids.map((id) => {
|
|
const item = byId.get(id);
|
|
if (!item) throw new Error(`P0-A allowlist item is not registered: ${id}`);
|
|
return item;
|
|
});
|
|
}
|
|
|
|
function publicItem(item: RegisteredComplexAsset): PublicComplexAsset {
|
|
return {
|
|
...item,
|
|
release_status: "enabled",
|
|
release_tier: "alpha_whitelist",
|
|
validation_status: "passed",
|
|
};
|
|
}
|
|
|
|
function validateFullRegistry(manifest: ComplexRegistryManifest) {
|
|
if (manifest.schema_version !== "asset-release-compiler/v1") throw new Error("complex registry schema version is unsupported");
|
|
const ids = new Set<string>();
|
|
for (const item of manifest.items) {
|
|
if (ids.has(item.canonical_id)) throw new Error(`duplicate registered asset ${item.canonical_id}`);
|
|
ids.add(item.canonical_id);
|
|
if (item.release_tier !== "full_p0") throw new Error(`${item.canonical_id} must remain in full_p0 before public projection`);
|
|
if (item.release_status === "enabled") throw new Error(`full_p0 item is enabled before atomic full release: ${item.canonical_id}`);
|
|
}
|
|
for (const [family, expected] of Object.entries(expectedFamilyCounts) as Array<[RegisteredComplexFamily, number]>) {
|
|
const actual = manifest.items.filter((item) => item.family === family).length;
|
|
if (actual !== expected) throw new Error(`${family} count mismatch: expected ${expected}, received ${actual}`);
|
|
}
|
|
}
|
|
|
|
function validateStaticCatalog(catalog: StaticStickerCatalog) {
|
|
if (catalog.count !== 1_407 || catalog.items.length !== 1_407) throw new Error("static sticker count mismatch: expected 1407");
|
|
const parts = new Set(catalog.items.map((item) => item.part));
|
|
if (parts.size !== 25 || [...parts].some((part) => part < 1 || part > 25)) throw new Error("static sticker parts must be exactly part1-part25");
|
|
if (Object.keys(catalog.part_counts).length !== 25) throw new Error("static sticker part counts must contain 25 parts");
|
|
}
|
|
|
|
function fontIdsForTemplates(templates: readonly RegisteredComplexAsset[]) {
|
|
const referenced = new Set<string>();
|
|
for (const template of templates) {
|
|
for (const fontId of template.font_panel_references ?? []) referenced.add(fontId);
|
|
}
|
|
referenced.add("FONT081");
|
|
return [...referenced].sort((left, right) => Number(left.slice(4)) - Number(right.slice(4)));
|
|
}
|
|
|
|
export function createP0aPublicManifest(input: {
|
|
complexManifest: ComplexRegistryManifest;
|
|
staticCatalog: StaticStickerCatalog;
|
|
}): P0aPublicManifest {
|
|
validateFullRegistry(input.complexManifest);
|
|
validateStaticCatalog(input.staticCatalog);
|
|
|
|
const textTemplates = orderedItems(input.complexManifest.items, P0A_TEXT_TEMPLATE_IDS, "text_template");
|
|
const derivedFontIds = fontIdsForTemplates(textTemplates);
|
|
if (JSON.stringify(derivedFontIds) !== JSON.stringify(P0A_REQUIRED_FONT_PANEL_IDS)) {
|
|
throw new Error(`P0-A referenced font panel mismatch: received ${derivedFontIds.join(",")}`);
|
|
}
|
|
const fontPanelItems = orderedItems(input.complexManifest.items, derivedFontIds, "font_panel");
|
|
const colorCards = orderedItems(input.complexManifest.items, P0A_COLOR_CARD_IDS, "color_card");
|
|
const dynamicStickers = orderedItems(input.complexManifest.items, P0A_DYNAMIC_STICKER_IDS, "interactive_sticker");
|
|
|
|
return {
|
|
assets: {
|
|
color_cards: colorCards.map(publicItem),
|
|
dynamic_stickers: dynamicStickers.map(publicItem),
|
|
font_panel_items: fontPanelItems.map(publicItem),
|
|
static_stickers: input.staticCatalog.items.map((item) => ({ ...item })),
|
|
text_templates: textTemplates.map(publicItem),
|
|
},
|
|
counts: {
|
|
color_cards: 4,
|
|
dynamic_stickers: 10,
|
|
font_panel_items: 11,
|
|
static_parts: 25,
|
|
static_stickers: 1_407,
|
|
text_templates: 32,
|
|
},
|
|
release_tier: "alpha_whitelist",
|
|
release_version: input.complexManifest.release_version,
|
|
schema_version: "P0AAssetManifest/v1",
|
|
source_versions: {
|
|
complex: input.complexManifest.release_version,
|
|
static_stickers: input.staticCatalog.release_version,
|
|
},
|
|
static_part_counts: { ...input.staticCatalog.part_counts },
|
|
};
|
|
}
|