feat: add community publishing and news syndication
Validate and deploy production / validate (push) Successful in 23s
Validate and deploy staging / validate (push) Successful in 50s
Validate and deploy production / deploy (push) Failing after 40s
Validate and deploy staging / deploy (push) Failing after 7s

This commit is contained in:
jb
2026-08-01 21:42:09 -04:00
parent b47e2588e9
commit dd84dff055
288 changed files with 83254 additions and 38351 deletions
+116 -78
View File
@@ -1,100 +1,138 @@
export const WEAPON_MOD_SLOT_TYPES = [
'Optics',
'Muzzle',
'Underbarrel',
'Magazine',
'Barrel',
'Upper Receiver',
'Lower Receiver',
'Trigger'
'Optics',
'Muzzle',
'Underbarrel',
'Magazine',
'Barrel',
'Upper Receiver',
'Lower Receiver',
'Trigger',
] as const;
export type WeaponModSlotConfig = {
tier: number;
slots: string[];
tier: number;
slots: string[];
};
export function normalizeWeaponModSlotPayload(payload: unknown): WeaponModSlotConfig[] {
if (!Array.isArray(payload)) return [];
const byTier = new Map<number, Set<string>>();
for (const row of payload) {
if (!row || typeof row !== 'object') continue;
const tier = Number('tier' in row ? row.tier : Number.NaN);
if (!Number.isInteger(tier) || tier < 0 || tier > 2) continue;
const slots = 'slots' in row && Array.isArray(row.slots) ? row.slots.map((slot: unknown) => String(slot)) : [];
const collected = byTier.get(tier) ?? new Set<string>();
for (const slot of slots) collected.add(slot);
byTier.set(tier, collected);
}
return [...byTier.entries()]
.sort(([left], [right]) => left - right)
.map(([tier, slots]) => ({ tier, slots: [...slots] }));
export function normalizeWeaponModSlotPayload(
payload: unknown,
): WeaponModSlotConfig[] {
if (!Array.isArray(payload)) return [];
const byTier = new Map<number, Set<string>>();
for (const row of payload) {
if (!row || typeof row !== 'object') continue;
const tier = Number('tier' in row ? row.tier : Number.NaN);
if (!Number.isInteger(tier) || tier < 0 || tier > 2) continue;
const slots =
'slots' in row && Array.isArray(row.slots)
? row.slots.map((slot: unknown) => String(slot))
: [];
const collected = byTier.get(tier) ?? new Set<string>();
for (const slot of slots) collected.add(slot);
byTier.set(tier, collected);
}
return [...byTier.entries()]
.sort(([left], [right]) => left - right)
.map(([tier, slots]) => ({ tier, slots: [...slots] }));
}
export function requiredWeaponModSlotCount(tier: number): number {
return tier === 0 ? 4 : 5;
return tier === 0 ? 4 : 5;
}
export function sharedWeaponModSlots(configurations: WeaponModSlotConfig[]): string[] {
return [...new Set(
configurations
.filter((row) => Number(row.tier) === 1 || Number(row.tier) === 2)
.flatMap((row) => row.slots)
)];
export function sharedWeaponModSlots(
configurations: WeaponModSlotConfig[],
): string[] {
return [
...new Set(
configurations
.filter((row) => Number(row.tier) === 1 || Number(row.tier) === 2)
.flatMap((row) => row.slots),
),
];
}
export function excludedTierZeroWeaponModSlot(
sharedSlots: string[],
configurations: WeaponModSlotConfig[]
sharedSlots: string[],
configurations: WeaponModSlotConfig[],
): string {
const tierZeroSlots = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const excluded = sharedSlots.filter((slot) => !tierZeroSlots.includes(slot));
return excluded.length === 1 && tierZeroSlots.every((slot) => sharedSlots.includes(slot)) ? excluded[0] : '';
const tierZeroSlots =
configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const excluded = sharedSlots.filter((slot) => !tierZeroSlots.includes(slot));
return excluded.length === 1 &&
tierZeroSlots.every((slot) => sharedSlots.includes(slot))
? excluded[0]
: '';
}
export function expandSharedWeaponModSlots(sharedSlots: string[], tierZeroExcludedSlot: string): WeaponModSlotConfig[] {
const uniqueSharedSlots = [...new Set(sharedSlots)];
return [
{
tier: 0,
slots: tierZeroExcludedSlot
? uniqueSharedSlots.filter((slot) => slot !== tierZeroExcludedSlot)
: []
},
{ tier: 1, slots: [...uniqueSharedSlots] },
{ tier: 2, slots: [...uniqueSharedSlots] }
];
export function expandSharedWeaponModSlots(
sharedSlots: string[],
tierZeroExcludedSlot: string,
): WeaponModSlotConfig[] {
const uniqueSharedSlots = [...new Set(sharedSlots)];
return [
{
tier: 0,
slots: tierZeroExcludedSlot
? uniqueSharedSlots.filter((slot) => slot !== tierZeroExcludedSlot)
: [],
},
{ tier: 1, slots: [...uniqueSharedSlots] },
{ tier: 2, slots: [...uniqueSharedSlots] },
];
}
export function weaponModSlotsComplete(nativeTier: number, configurations: WeaponModSlotConfig[]): boolean {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
if (configured?.slots.length !== requiredWeaponModSlotCount(tier)) return false;
}
if (nativeTier <= 1) {
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
const tierTwo = configurations.find((row) => Number(row.tier) === 2)?.slots ?? [];
if (tierOne.some((slot) => !tierTwo.includes(slot)) || tierTwo.some((slot) => !tierOne.includes(slot))) return false;
}
if (nativeTier === 0) {
const tierZero = configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const tierOne = configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
if (tierZero.some((slot) => !tierOne.includes(slot))) return false;
}
return true;
export function weaponModSlotsComplete(
nativeTier: number,
configurations: WeaponModSlotConfig[],
): boolean {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
if (configured?.slots.length !== requiredWeaponModSlotCount(tier))
return false;
}
if (nativeTier <= 1) {
const tierOne =
configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
const tierTwo =
configurations.find((row) => Number(row.tier) === 2)?.slots ?? [];
if (
tierOne.some((slot) => !tierTwo.includes(slot)) ||
tierTwo.some((slot) => !tierOne.includes(slot))
)
return false;
}
if (nativeTier === 0) {
const tierZero =
configurations.find((row) => Number(row.tier) === 0)?.slots ?? [];
const tierOne =
configurations.find((row) => Number(row.tier) === 1)?.slots ?? [];
if (tierZero.some((slot) => !tierOne.includes(slot))) return false;
}
return true;
}
export function validateWeaponModSlots(nativeTier: number, configurations: WeaponModSlotConfig[]): string | null {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
const slots = configured?.slots ?? [];
const required = requiredWeaponModSlotCount(tier);
if (slots.some((slot) => !WEAPON_MOD_SLOT_TYPES.includes(slot as (typeof WEAPON_MOD_SLOT_TYPES)[number]))) {
return `Tier ${tier} includes an unavailable Weapon Mod slot.`;
}
if (slots.length > required) {
return `Tier ${tier} supports at most ${required} unique Weapon Mod slots.`;
}
}
return null;
export function validateWeaponModSlots(
nativeTier: number,
configurations: WeaponModSlotConfig[],
): string | null {
for (let tier = nativeTier; tier <= 2; tier += 1) {
const configured = configurations.find((row) => Number(row.tier) === tier);
const slots = configured?.slots ?? [];
const required = requiredWeaponModSlotCount(tier);
if (
slots.some(
(slot) =>
!WEAPON_MOD_SLOT_TYPES.includes(
slot as (typeof WEAPON_MOD_SLOT_TYPES)[number],
),
)
) {
return `Tier ${tier} includes an unavailable Weapon Mod slot.`;
}
if (slots.length > required) {
return `Tier ${tier} supports at most ${required} unique Weapon Mod slots.`;
}
}
return null;
}