feat: add resource schemas and validation scripts

- Introduced new resource schemas for maps, scenarios, unit sets, and turn commands using Zod.
- Implemented a script to generate JSON schemas from Zod schemas.
- Added a validation script to ensure resource JSON files conform to their respective schemas.
- Updated the logic package to export new resource schemas.
- Added new dependencies for schema generation and validation.
- Created a tools-scripts package for resource management tasks.
This commit is contained in:
2026-01-18 08:04:02 +00:00
parent e3f7758187
commit 81594a25c3
20 changed files with 452 additions and 94 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import type { MapDefinition } from '@sammo-ts/logic'; import { MapDefinitionSchema, type MapDefinition } from '@sammo-ts/logic';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
@@ -28,7 +28,7 @@ export const resolveMapDefinitionPath = (mapName: string, options?: MapLoaderOpt
export const loadMapDefinition = async (mapPath: string): Promise<MapDefinition> => { export const loadMapDefinition = async (mapPath: string): Promise<MapDefinition> => {
const raw = await readJsonFile(mapPath); const raw = await readJsonFile(mapPath);
return raw as MapDefinition; return MapDefinitionSchema.parse(raw);
}; };
export const loadMapDefinitionByName = async (mapName: string, options?: MapLoaderOptions): Promise<MapDefinition> => { export const loadMapDefinitionByName = async (mapName: string, options?: MapLoaderOptions): Promise<MapDefinition> => {
+6 -2
View File
@@ -13,7 +13,9 @@
"build": "turbo build", "build": "turbo build",
"typecheck": "turbo typecheck", "typecheck": "turbo typecheck",
"dev": "turbo dev", "dev": "turbo dev",
"build:server": "pnpm --filter ./tools/build-scripts build:server --" "build:server": "pnpm --filter ./tools/build-scripts build:server --",
"generate:resource-schemas": "pnpm --filter @sammo-ts/tools-scripts generate:resource-schemas",
"validate:resources": "pnpm --filter @sammo-ts/tools-scripts validate:resources"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
@@ -27,9 +29,11 @@
"globals": "^17.0.0", "globals": "^17.0.0",
"prettier": "^3.7.4", "prettier": "^3.7.4",
"tsdown": "^0.18.4", "tsdown": "^0.18.4",
"tsx": "^4.21.0",
"turbo": "^2.7.2", "turbo": "^2.7.2",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.51.0", "typescript-eslint": "^8.51.0",
"vue-eslint-parser": "^10.2.0" "vue-eslint-parser": "^10.2.0",
"zod-to-json-schema": "^3.25.1"
} }
} }
@@ -1,6 +1,7 @@
import { GENERAL_TURN_COMMAND_KEYS, isGeneralTurnCommandKey, type GeneralTurnCommandKey } from './general/index.js'; import { GENERAL_TURN_COMMAND_KEYS, isGeneralTurnCommandKey, type GeneralTurnCommandKey } from './general/index.js';
import { NATION_TURN_COMMAND_KEYS, isNationTurnCommandKey, type NationTurnCommandKey } from './nation/index.js'; import { NATION_TURN_COMMAND_KEYS, isNationTurnCommandKey, type NationTurnCommandKey } from './nation/index.js';
import { asStringArray, isRecord } from '@sammo-ts/common'; import { asStringArray } from '@sammo-ts/common';
import { TurnCommandProfileInputSchema } from '../../resources/turnCommandSchema.js';
export interface TurnCommandProfile { export interface TurnCommandProfile {
general: GeneralTurnCommandKey[]; general: GeneralTurnCommandKey[];
@@ -44,18 +45,20 @@ export const parseTurnCommandProfile = (
raw: unknown, raw: unknown,
fallback: TurnCommandProfile = DEFAULT_TURN_COMMAND_PROFILE fallback: TurnCommandProfile = DEFAULT_TURN_COMMAND_PROFILE
): TurnCommandProfile => { ): TurnCommandProfile => {
if (!isRecord(raw)) { const parsed = TurnCommandProfileInputSchema.safeParse(raw);
if (!parsed.success) {
return fallback; return fallback;
} }
const data = parsed.data;
return { return {
general: parseKeyList({ general: parseKeyList({
raw: raw.general, raw: data.general,
defaults: fallback.general, defaults: fallback.general,
isKey: isGeneralTurnCommandKey, isKey: isGeneralTurnCommandKey,
label: 'general', label: 'general',
}), }),
nation: parseKeyList({ nation: parseKeyList({
raw: raw.nation, raw: data.nation,
defaults: fallback.nation, defaults: fallback.nation,
isKey: isNationTurnCommandKey, isKey: isNationTurnCommandKey,
label: 'nation', label: 'nation',
+1
View File
@@ -7,6 +7,7 @@ export * from './logging/index.js';
export * from './messages/index.js'; export * from './messages/index.js';
export * from './items/index.js'; export * from './items/index.js';
export { ITEM_KEYS, createItemActionModules, createItemModuleRegistry, loadItemModules } from './items/index.js'; export { ITEM_KEYS, createItemActionModules, createItemModuleRegistry, loadItemModules } from './items/index.js';
export * from './resources/index.js';
export * from './ports/world.js'; export * from './ports/world.js';
export * from './ports/worldSnapshot.js'; export * from './ports/worldSnapshot.js';
export * from './scenario/index.js'; export * from './scenario/index.js';
+4
View File
@@ -0,0 +1,4 @@
export * from './mapSchema.js';
export * from './scenarioSchema.js';
export * from './unitSetSchema.js';
export * from './turnCommandSchema.js';
+49
View File
@@ -0,0 +1,49 @@
import { z } from 'zod';
export const MapCityStatsSchema = z.object({
population: z.number(),
agriculture: z.number(),
commerce: z.number(),
security: z.number(),
defence: z.number(),
wall: z.number(),
});
export const MapCityDefinitionSchema = z.object({
id: z.number(),
name: z.string(),
level: z.number(),
region: z.number(),
position: z.object({
x: z.number(),
y: z.number(),
}),
connections: z.array(z.number()),
max: MapCityStatsSchema,
initial: MapCityStatsSchema,
meta: z.record(z.string(), z.unknown()).optional(),
});
export const MapDefaultsSchema = z
.object({
trust: z.number(),
trade: z.number(),
supplyState: z.number(),
frontState: z.number(),
})
.partial();
export const MapDefinitionSchema = z.object({
id: z.string(),
name: z.string(),
cities: z.array(MapCityDefinitionSchema),
defaults: MapDefaultsSchema.optional(),
meta: z.record(z.string(), z.unknown()).optional(),
});
export const RegionMapSchema = z.record(z.string(), z.record(z.string(), z.string()));
export const MapResourceSchema = z.union([MapDefinitionSchema, RegionMapSchema]);
export type MapDefinitionInput = z.infer<typeof MapDefinitionSchema>;
export type RegionMapInput = z.infer<typeof RegionMapSchema>;
@@ -0,0 +1,47 @@
import { z } from 'zod';
export const ScenarioStatBlockSchema = z
.object({
total: z.number(),
min: z.number(),
max: z.number(),
npcTotal: z.number(),
npcMax: z.number(),
npcMin: z.number(),
chiefMin: z.number(),
})
.partial();
export const ScenarioDefaultsInputSchema = z.object({
stat: ScenarioStatBlockSchema.optional(),
iconPath: z.string().optional(),
});
export const ScenarioDefinitionInputSchema = z
.object({
title: z.string(),
startYear: z.number().optional(),
life: z.number().optional(),
fiction: z.number().optional(),
history: z.array(z.string()).optional(),
iconPath: z.string().optional(),
stat: ScenarioStatBlockSchema.optional(),
map: z.record(z.string(), z.unknown()).optional(),
const: z.record(z.string(), z.unknown()).optional(),
nation: z.array(z.unknown()).optional(),
diplomacy: z.array(z.unknown()).optional(),
general: z.array(z.unknown()).optional(),
general_ex: z.array(z.unknown()).optional(),
general_neutral: z.array(z.unknown()).optional(),
cities: z.array(z.unknown()).optional(),
events: z.array(z.unknown()).optional(),
initialEvents: z.array(z.unknown()).optional(),
initialActions: z.array(z.unknown()).optional(),
ignoreDefaultEvents: z.boolean().optional(),
})
.passthrough();
export const ScenarioResourceSchema = z.union([ScenarioDefaultsInputSchema, ScenarioDefinitionInputSchema]);
export type ScenarioDefaultsInput = z.infer<typeof ScenarioDefaultsInputSchema>;
export type ScenarioDefinitionInput = z.infer<typeof ScenarioDefinitionInputSchema>;
@@ -0,0 +1,8 @@
import { z } from 'zod';
export const TurnCommandProfileInputSchema = z.object({
general: z.array(z.string()),
nation: z.array(z.string()),
});
export type TurnCommandProfileInput = z.infer<typeof TurnCommandProfileInputSchema>;
@@ -0,0 +1,54 @@
import { z } from 'zod';
const numericRecordSchema = z.record(z.string(), z.number());
const numericArraySchema = z.array(z.number());
export const CrewTypeRequirementSchema = z.union([
z.object({ type: z.literal('ReqTech'), tech: z.number() }),
z.object({ type: z.literal('ReqRegions'), regions: z.array(z.string()) }),
z.object({ type: z.literal('ReqCities'), cities: z.array(z.string()) }),
z.object({ type: z.literal('ReqCitiesWithCityLevel'), level: z.number(), cities: z.array(z.string()) }),
z.object({ type: z.literal('ReqHighLevelCities'), level: z.number(), count: z.number() }),
z.object({
type: z.literal('ReqNationAux'),
key: z.string(),
op: z.string(),
value: z.union([z.number(), z.string()]),
}),
z.object({ type: z.literal('ReqMinRelYear'), year: z.number() }),
z.object({ type: z.literal('ReqChief') }),
z.object({ type: z.literal('ReqNotChief') }),
z.object({ type: z.literal('Impossible') }),
z.looseObject({ type: z.string() }),
]);
export const CrewTypeDefinitionInputSchema = z.object({
id: z.number(),
armType: z.number(),
name: z.string(),
attack: z.number(),
defence: z.number(),
speed: z.number(),
avoid: z.number(),
magicCoef: z.number(),
cost: z.number(),
rice: z.number(),
requirements: z.array(CrewTypeRequirementSchema),
attackCoef: z.union([numericRecordSchema, numericArraySchema]),
defenceCoef: z.union([numericRecordSchema, numericArraySchema]),
info: z.array(z.string()),
initSkillTrigger: z.array(z.string()).nullable(),
phaseSkillTrigger: z.array(z.string()).nullable(),
iActionList: z.array(z.string()).nullable(),
});
export const UnitSetDefinitionInputSchema = z.object({
id: z.string(),
name: z.string(),
defaultCrewTypeId: z.number().optional(),
armTypes: z.record(z.string(), z.string()).optional(),
crewTypes: z.array(CrewTypeDefinitionInputSchema).optional(),
meta: z.record(z.string(), z.unknown()).optional(),
});
export type UnitSetDefinitionInput = z.infer<typeof UnitSetDefinitionInputSchema>;
+4 -51
View File
@@ -1,6 +1,8 @@
import { z } from 'zod'; import { z } from 'zod';
import { asNullableNumber, asNullableString, asNumber, asString, asStringArray, isRecord } from '@sammo-ts/common'; import { asNullableNumber, asNullableString, asNumber, asString, asStringArray, isRecord } from '@sammo-ts/common';
import { ScenarioDefaultsInputSchema, ScenarioDefinitionInputSchema } from '../resources/scenarioSchema.js';
import type { import type {
ScenarioConfig, ScenarioConfig,
ScenarioDefaults, ScenarioDefaults,
@@ -24,56 +26,7 @@ const FALLBACK_STAT: ScenarioStatBlock = {
chiefMin: 0, chiefMin: 0,
}; };
const toRecordOrUndefined = (value: unknown): UnknownRecord | undefined => (isRecord(value) ? value : undefined);
const toArrayOrUndefined = (value: unknown): unknown[] | undefined => (Array.isArray(value) ? value : undefined);
const zRecord = z.record(z.string(), z.unknown());
const zUnknownArray = z.array(z.unknown()); const zUnknownArray = z.array(z.unknown());
const zOptionalRecord = z.preprocess(toRecordOrUndefined, zRecord.optional());
const zOptionalArray = z.preprocess(toArrayOrUndefined, zUnknownArray.optional());
const zStatInput = z
.object({
total: z.number().optional(),
min: z.number().optional(),
max: z.number().optional(),
npcTotal: z.number().optional(),
npcMax: z.number().optional(),
npcMin: z.number().optional(),
chiefMin: z.number().optional(),
})
.partial();
const zScenarioDefaults = z
.object({
stat: z.preprocess(toRecordOrUndefined, zStatInput.optional()),
iconPath: z.string().optional(),
})
.passthrough();
const zScenarioInput = z
.object({
title: z.string(),
startYear: z.number().optional(),
life: z.number().optional(),
fiction: z.number().optional(),
history: zOptionalArray,
iconPath: z.string().optional(),
stat: z.preprocess(toRecordOrUndefined, zStatInput.optional()),
map: zOptionalRecord,
const: zOptionalRecord,
nation: zOptionalArray,
diplomacy: zOptionalArray,
general: zOptionalArray,
general_ex: zOptionalArray,
general_neutral: zOptionalArray,
cities: zOptionalArray,
events: zOptionalArray,
initialEvents: zOptionalArray,
initialActions: zOptionalArray,
ignoreDefaultEvents: z.boolean().optional(),
})
.passthrough();
const parseScenarioStatBlock = (value: unknown, fallback: ScenarioStatBlock): ScenarioStatBlock => { const parseScenarioStatBlock = (value: unknown, fallback: ScenarioStatBlock): ScenarioStatBlock => {
const data = isRecord(value) ? value : {}; const data = isRecord(value) ? value : {};
@@ -211,7 +164,7 @@ const parseDiplomacyRows = (rows: unknown[]): ScenarioDiplomacy[] =>
export const parseScenarioDefaults = (raw: unknown): ScenarioDefaults => { export const parseScenarioDefaults = (raw: unknown): ScenarioDefaults => {
// 기본 시나리오 설정값을 안전하게 읽는다. // 기본 시나리오 설정값을 안전하게 읽는다.
const data = zScenarioDefaults.parse(raw); const data = ScenarioDefaultsInputSchema.parse(raw);
const stat = parseScenarioStatBlock(data.stat, FALLBACK_STAT); const stat = parseScenarioStatBlock(data.stat, FALLBACK_STAT);
const iconPath = asString(data.iconPath, '.'); const iconPath = asString(data.iconPath, '.');
return { stat, iconPath }; return { stat, iconPath };
@@ -219,7 +172,7 @@ export const parseScenarioDefaults = (raw: unknown): ScenarioDefaults => {
export const parseScenarioDefinition = (raw: unknown, defaults: ScenarioDefaults): ScenarioDefinition => { export const parseScenarioDefinition = (raw: unknown, defaults: ScenarioDefaults): ScenarioDefinition => {
// 시나리오 JSON을 런타임에서 쓰는 구조로 정규화한다. // 시나리오 JSON을 런타임에서 쓰는 구조로 정규화한다.
const data = zScenarioInput.parse(raw); const data = ScenarioDefinitionInputSchema.parse(raw);
const stat = parseScenarioStatBlock(data.stat, defaults.stat); const stat = parseScenarioStatBlock(data.stat, defaults.stat);
const mapConfig = data.map ?? {}; const mapConfig = data.map ?? {};
const constConfig = data.const ?? {}; const constConfig = data.const ?? {};
+2 -1
View File
@@ -8,6 +8,7 @@ import {
asStringArray, asStringArray,
isRecord, isRecord,
} from '@sammo-ts/common'; } from '@sammo-ts/common';
import { UnitSetDefinitionInputSchema } from '../resources/unitSetSchema.js';
const DEFAULT_REGION_MAP: Record<string, number> = { const DEFAULT_REGION_MAP: Record<string, number> = {
하북: 1, 하북: 1,
@@ -114,7 +115,7 @@ const parseCrewType = (value: unknown): CrewTypeDefinition | null => {
}; };
export const parseUnitSetDefinition = (value: unknown): UnitSetDefinition => { export const parseUnitSetDefinition = (value: unknown): UnitSetDefinition => {
const data = asRecord(value); const data = UnitSetDefinitionInputSchema.parse(value);
const id = asString(data.id, 'unknown'); const id = asString(data.id, 'unknown');
const name = asString(data.name, id); const name = asString(data.name, id);
const defaultCrewTypeId = const defaultCrewTypeId =
+19
View File
@@ -0,0 +1,19 @@
{
"name": "@sammo-ts/tools-scripts",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"generate:resource-schemas": "tsx src/generate-resource-schemas.ts",
"validate:resources": "tsx src/validate-resources.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@sammo-ts/logic": "workspace:*",
"zod-to-json-schema": "^3.25.1"
},
"devDependencies": {
"tsx": "^4.21.0",
"typescript": "^5.9.3"
}
}
@@ -0,0 +1,36 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { ZodTypeAny } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
MapResourceSchema,
ScenarioResourceSchema,
TurnCommandProfileInputSchema,
UnitSetDefinitionInputSchema,
} from '@sammo-ts/logic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
const RESOURCE_ROOT = path.join(REPO_ROOT, 'resources');
const writeSchema = async (relativePath: string, schema: ZodTypeAny, name: string): Promise<void> => {
const outputPath = path.join(RESOURCE_ROOT, relativePath);
const jsonSchema = zodToJsonSchema(schema, { name, target: 'jsonSchema7' });
const serialized = `${JSON.stringify(jsonSchema, null, 4)}\n`;
await fs.writeFile(outputPath, serialized, 'utf8');
};
const main = async (): Promise<void> => {
await writeSchema(path.join('map', 'schema.json'), MapResourceSchema, 'MapResource');
await writeSchema(path.join('scenario', 'schema.json'), ScenarioResourceSchema, 'ScenarioResource');
await writeSchema(path.join('unitset', 'schema.json'), UnitSetDefinitionInputSchema, 'UnitSetDefinition');
await writeSchema(path.join('turn-commands', 'schema.json'), TurnCommandProfileInputSchema, 'TurnCommandProfile');
};
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
@@ -0,0 +1,86 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
MapResourceSchema,
ScenarioResourceSchema,
TurnCommandProfileInputSchema,
UnitSetDefinitionInputSchema,
} from '@sammo-ts/logic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
const RESOURCE_ROOT = path.join(REPO_ROOT, 'resources');
const RESOURCE_SCHEMAS: Record<string, (value: unknown) => void> = {
map: (value) => MapResourceSchema.parse(value),
scenario: (value) => ScenarioResourceSchema.parse(value),
unitset: (value) => UnitSetDefinitionInputSchema.parse(value),
'turn-commands': (value) => TurnCommandProfileInputSchema.parse(value),
};
const listJsonFiles = async (dirPath: string): Promise<string[]> => {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
return entries
.filter((entry) => entry.isFile() && entry.name.endsWith('.json') && entry.name !== 'schema.json')
.map((entry) => entry.name)
.sort((a, b) => a.localeCompare(b));
};
const validateFolder = async (folder: string): Promise<string[]> => {
const schema = RESOURCE_SCHEMAS[folder];
if (!schema) {
return [`Unknown resource folder: ${folder}`];
}
const folderPath = path.join(RESOURCE_ROOT, folder);
const files = await listJsonFiles(folderPath);
const errors: string[] = [];
for (const fileName of files) {
const filePath = path.join(folderPath, fileName);
const raw = await fs.readFile(filePath, 'utf8');
let parsed: unknown;
try {
parsed = JSON.parse(raw) as unknown;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
errors.push(`${path.relative(REPO_ROOT, filePath)}: invalid JSON (${message})`);
continue;
}
try {
schema(parsed);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
errors.push(`${path.relative(REPO_ROOT, filePath)}: ${message}`);
}
}
return errors;
};
const main = async (): Promise<void> => {
const folders = Object.keys(RESOURCE_SCHEMAS).sort((a, b) => a.localeCompare(b));
const allErrors: string[] = [];
for (const folder of folders) {
const errors = await validateFolder(folder);
allErrors.push(...errors);
}
if (allErrors.length > 0) {
for (const error of allErrors) {
console.error(`[resource-validate] ${error}`);
}
process.exitCode = 1;
return;
}
console.log('[resource-validate] OK');
};
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
+20
View File
@@ -0,0 +1,20 @@
{
"extends": "../../tsconfig.paths.json",
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2023",
"lib": ["ES2024"],
"verbatimModuleSyntax": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"useUnknownInCatchVariables": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"],
"references": [{ "path": "../logic" }]
}
+79 -34
View File
@@ -41,6 +41,9 @@ importers:
tsdown: tsdown:
specifier: ^0.18.4 specifier: ^0.18.4
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
tsx:
specifier: ^4.21.0
version: 4.21.0
turbo: turbo:
specifier: ^2.7.2 specifier: ^2.7.2
version: 2.7.2 version: 2.7.2
@@ -53,6 +56,9 @@ importers:
vue-eslint-parser: vue-eslint-parser:
specifier: ^10.2.0 specifier: ^10.2.0
version: 10.2.0(eslint@9.39.2(jiti@2.6.1)) version: 10.2.0(eslint@9.39.2(jiti@2.6.1))
zod-to-json-schema:
specifier: ^3.25.1
version: 3.25.1(zod@4.3.5)
app/game-api: app/game-api:
dependencies: dependencies:
@@ -92,10 +98,10 @@ importers:
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
app/game-engine: app/game-engine:
dependencies: dependencies:
@@ -123,10 +129,10 @@ importers:
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
app/game-frontend: app/game-frontend:
dependencies: dependencies:
@@ -175,10 +181,10 @@ importers:
devDependencies: devDependencies:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3)) version: 6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))
autoprefixer: autoprefixer:
specifier: ^10.4.23 specifier: ^10.4.23
version: 10.4.23(postcss@8.5.6) version: 10.4.23(postcss@8.5.6)
@@ -193,7 +199,7 @@ importers:
version: 5.9.3 version: 5.9.3
vite: vite:
specifier: ^7.3.1 specifier: ^7.3.1
version: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vue-tsc: vue-tsc:
specifier: ^3.2.2 specifier: ^3.2.2
version: 3.2.2(typescript@5.9.3) version: 3.2.2(typescript@5.9.3)
@@ -245,10 +251,10 @@ importers:
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
app/gateway-frontend: app/gateway-frontend:
dependencies: dependencies:
@@ -297,10 +303,10 @@ importers:
devDependencies: devDependencies:
'@tailwindcss/vite': '@tailwindcss/vite':
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
'@vitejs/plugin-vue': '@vitejs/plugin-vue':
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3)) version: 6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))
autoprefixer: autoprefixer:
specifier: ^10.4.23 specifier: ^10.4.23
version: 10.4.23(postcss@8.5.6) version: 10.4.23(postcss@8.5.6)
@@ -315,7 +321,7 @@ importers:
version: 5.9.3 version: 5.9.3
vite: vite:
specifier: ^7.3.0 specifier: ^7.3.0
version: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vue-tsc: vue-tsc:
specifier: ^3.2.1 specifier: ^3.2.1
version: 3.2.1(typescript@5.9.3) version: 3.2.1(typescript@5.9.3)
@@ -334,7 +340,7 @@ importers:
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages/infra: packages/infra:
dependencies: dependencies:
@@ -390,10 +396,26 @@ importers:
version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3)) version: 0.18.4(synckit@0.11.11)(typescript@5.9.3)(vue-tsc@3.2.2(typescript@5.9.3))
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages/tools-scripts:
dependencies:
'@sammo-ts/logic':
specifier: workspace:*
version: link:../logic
zod-to-json-schema:
specifier: ^3.25.1
version: 3.25.1(zod@4.3.5)
devDependencies:
tsx:
specifier: ^4.21.0
version: 4.21.0
typescript:
specifier: ^5.9.3
version: 5.9.3
tools/build-scripts: {} tools/build-scripts: {}
@@ -420,10 +442,10 @@ importers:
devDependencies: devDependencies:
vite-tsconfig-paths: vite-tsconfig-paths:
specifier: ^6.0.3 specifier: ^6.0.3
version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) version: 6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
vitest: vitest:
specifier: ^4.0.16 specifier: ^4.0.16
version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages: packages:
@@ -3124,6 +3146,11 @@ packages:
tslib@2.8.1: tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
tsx@4.21.0:
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
engines: {node: '>=18.0.0'}
hasBin: true
turbo-darwin-64@2.7.2: turbo-darwin-64@2.7.2:
resolution: {integrity: sha512-dxY3X6ezcT5vm3coK6VGixbrhplbQMwgNsCsvZamS/+/6JiebqW9DKt4NwpgYXhDY2HdH00I7FWs3wkVuan4rA==} resolution: {integrity: sha512-dxY3X6ezcT5vm3coK6VGixbrhplbQMwgNsCsvZamS/+/6JiebqW9DKt4NwpgYXhDY2HdH00I7FWs3wkVuan4rA==}
cpu: [x64] cpu: [x64]
@@ -3425,6 +3452,11 @@ packages:
zeptomatch@2.0.2: zeptomatch@2.0.2:
resolution: {integrity: sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==} resolution: {integrity: sha512-H33jtSKf8Ijtb5BW6wua3G5DhnFjbFML36eFu+VdOoVY4HD9e7ggjqdM6639B+L87rjnR6Y+XeRzBXZdy52B/g==}
zod-to-json-schema@3.25.1:
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
peerDependencies:
zod: ^3.25 || ^4
zod@4.3.4: zod@4.3.4:
resolution: {integrity: sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==} resolution: {integrity: sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==}
@@ -4166,19 +4198,19 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18
'@tailwindcss/oxide-win32-x64-msvc': 4.1.18 '@tailwindcss/oxide-win32-x64-msvc': 4.1.18
'@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))': '@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
dependencies: dependencies:
'@tailwindcss/node': 4.1.18 '@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18 '@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18 tailwindcss: 4.1.18
vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
'@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))': '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
dependencies: dependencies:
'@tailwindcss/node': 4.1.18 '@tailwindcss/node': 4.1.18
'@tailwindcss/oxide': 4.1.18 '@tailwindcss/oxide': 4.1.18
tailwindcss: 4.1.18 tailwindcss: 4.1.18
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
'@tootallnate/quickjs-emscripten@0.23.0': {} '@tootallnate/quickjs-emscripten@0.23.0': {}
@@ -4314,16 +4346,16 @@ snapshots:
'@typescript-eslint/types': 8.51.0 '@typescript-eslint/types': 8.51.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
'@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3))': '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))':
dependencies: dependencies:
'@rolldown/pluginutils': 1.0.0-beta.53 '@rolldown/pluginutils': 1.0.0-beta.53
vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
'@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3))': '@vitejs/plugin-vue@6.0.3(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))(vue@3.5.26(typescript@5.9.3))':
dependencies: dependencies:
'@rolldown/pluginutils': 1.0.0-beta.53 '@rolldown/pluginutils': 1.0.0-beta.53
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
vue: 3.5.26(typescript@5.9.3) vue: 3.5.26(typescript@5.9.3)
'@vitest/expect@4.0.16': '@vitest/expect@4.0.16':
@@ -4335,13 +4367,13 @@ snapshots:
chai: 6.2.2 chai: 6.2.2
tinyrainbow: 3.0.3 tinyrainbow: 3.0.3
'@vitest/mocker@4.0.16(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2))': '@vitest/mocker@4.0.16(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))':
dependencies: dependencies:
'@vitest/spy': 4.0.16 '@vitest/spy': 4.0.16
estree-walker: 3.0.3 estree-walker: 3.0.3
magic-string: 0.30.21 magic-string: 0.30.21
optionalDependencies: optionalDependencies:
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
'@vitest/pretty-format@4.0.16': '@vitest/pretty-format@4.0.16':
dependencies: dependencies:
@@ -6035,6 +6067,13 @@ snapshots:
tslib@2.8.1: {} tslib@2.8.1: {}
tsx@4.21.0:
dependencies:
esbuild: 0.27.2
get-tsconfig: 4.13.0
optionalDependencies:
fsevents: 2.3.3
turbo-darwin-64@2.7.2: turbo-darwin-64@2.7.2:
optional: true optional: true
@@ -6117,18 +6156,18 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.9.3 typescript: 5.9.3
vite-tsconfig-paths@6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)): vite-tsconfig-paths@6.0.3(typescript@5.9.3)(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)):
dependencies: dependencies:
debug: 4.4.3 debug: 4.4.3
globrex: 0.1.2 globrex: 0.1.2
tsconfck: 3.1.6(typescript@5.9.3) tsconfck: 3.1.6(typescript@5.9.3)
optionalDependencies: optionalDependencies:
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2): vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
esbuild: 0.27.2 esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
@@ -6141,8 +6180,9 @@ snapshots:
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 2.6.1 jiti: 2.6.1
lightningcss: 1.30.2 lightningcss: 1.30.2
tsx: 4.21.0
vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2): vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
esbuild: 0.27.2 esbuild: 0.27.2
fdir: 6.5.0(picomatch@4.0.3) fdir: 6.5.0(picomatch@4.0.3)
@@ -6155,11 +6195,12 @@ snapshots:
fsevents: 2.3.3 fsevents: 2.3.3
jiti: 2.6.1 jiti: 2.6.1
lightningcss: 1.30.2 lightningcss: 1.30.2
tsx: 4.21.0
vitest@4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2): vitest@4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0):
dependencies: dependencies:
'@vitest/expect': 4.0.16 '@vitest/expect': 4.0.16
'@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)) '@vitest/mocker': 4.0.16(vite@7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
'@vitest/pretty-format': 4.0.16 '@vitest/pretty-format': 4.0.16
'@vitest/runner': 4.0.16 '@vitest/runner': 4.0.16
'@vitest/snapshot': 4.0.16 '@vitest/snapshot': 4.0.16
@@ -6176,7 +6217,7 @@ snapshots:
tinyexec: 1.0.2 tinyexec: 1.0.2
tinyglobby: 0.2.15 tinyglobby: 0.2.15
tinyrainbow: 3.0.3 tinyrainbow: 3.0.3
vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2) vite: 7.3.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
why-is-node-running: 2.3.0 why-is-node-running: 2.3.0
optionalDependencies: optionalDependencies:
'@types/node': 25.0.3 '@types/node': 25.0.3
@@ -6266,6 +6307,10 @@ snapshots:
dependencies: dependencies:
grammex: 3.1.12 grammex: 3.1.12
zod-to-json-schema@3.25.1(zod@4.3.5):
dependencies:
zod: 4.3.5
zod@4.3.4: {} zod@4.3.4: {}
zod@4.3.5: {} zod@4.3.5: {}
+7
View File
@@ -0,0 +1,7 @@
{
"$ref": "#/definitions/MapResource",
"definitions": {
"MapResource": {}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
+7
View File
@@ -0,0 +1,7 @@
{
"$ref": "#/definitions/ScenarioResource",
"definitions": {
"ScenarioResource": {}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
+7
View File
@@ -0,0 +1,7 @@
{
"$ref": "#/definitions/TurnCommandProfile",
"definitions": {
"TurnCommandProfile": {}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
+7
View File
@@ -0,0 +1,7 @@
{
"$ref": "#/definitions/UnitSetDefinition",
"definitions": {
"UnitSetDefinition": {}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}