diff --git a/app/game-engine/src/scenario/mapLoader.ts b/app/game-engine/src/scenario/mapLoader.ts index d289b8f..9704e47 100644 --- a/app/game-engine/src/scenario/mapLoader.ts +++ b/app/game-engine/src/scenario/mapLoader.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; 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 __dirname = path.dirname(__filename); @@ -28,7 +28,7 @@ export const resolveMapDefinitionPath = (mapName: string, options?: MapLoaderOpt export const loadMapDefinition = async (mapPath: string): Promise => { const raw = await readJsonFile(mapPath); - return raw as MapDefinition; + return MapDefinitionSchema.parse(raw); }; export const loadMapDefinitionByName = async (mapName: string, options?: MapLoaderOptions): Promise => { diff --git a/package.json b/package.json index d6b0410..03eb6e0 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "build": "turbo build", "typecheck": "turbo typecheck", "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": { "@eslint/js": "^9.39.2", @@ -27,9 +29,11 @@ "globals": "^17.0.0", "prettier": "^3.7.4", "tsdown": "^0.18.4", + "tsx": "^4.21.0", "turbo": "^2.7.2", "typescript": "^5.9.3", "typescript-eslint": "^8.51.0", - "vue-eslint-parser": "^10.2.0" + "vue-eslint-parser": "^10.2.0", + "zod-to-json-schema": "^3.25.1" } } diff --git a/packages/logic/src/actions/turn/commandProfile.ts b/packages/logic/src/actions/turn/commandProfile.ts index 80f6811..58bd409 100644 --- a/packages/logic/src/actions/turn/commandProfile.ts +++ b/packages/logic/src/actions/turn/commandProfile.ts @@ -1,6 +1,7 @@ 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 { asStringArray, isRecord } from '@sammo-ts/common'; +import { asStringArray } from '@sammo-ts/common'; +import { TurnCommandProfileInputSchema } from '../../resources/turnCommandSchema.js'; export interface TurnCommandProfile { general: GeneralTurnCommandKey[]; @@ -44,18 +45,20 @@ export const parseTurnCommandProfile = ( raw: unknown, fallback: TurnCommandProfile = DEFAULT_TURN_COMMAND_PROFILE ): TurnCommandProfile => { - if (!isRecord(raw)) { + const parsed = TurnCommandProfileInputSchema.safeParse(raw); + if (!parsed.success) { return fallback; } + const data = parsed.data; return { general: parseKeyList({ - raw: raw.general, + raw: data.general, defaults: fallback.general, isKey: isGeneralTurnCommandKey, label: 'general', }), nation: parseKeyList({ - raw: raw.nation, + raw: data.nation, defaults: fallback.nation, isKey: isNationTurnCommandKey, label: 'nation', diff --git a/packages/logic/src/index.ts b/packages/logic/src/index.ts index e2f40a4..9b8e34c 100644 --- a/packages/logic/src/index.ts +++ b/packages/logic/src/index.ts @@ -7,6 +7,7 @@ export * from './logging/index.js'; export * from './messages/index.js'; export * 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/worldSnapshot.js'; export * from './scenario/index.js'; diff --git a/packages/logic/src/resources/index.ts b/packages/logic/src/resources/index.ts new file mode 100644 index 0000000..2215a89 --- /dev/null +++ b/packages/logic/src/resources/index.ts @@ -0,0 +1,4 @@ +export * from './mapSchema.js'; +export * from './scenarioSchema.js'; +export * from './unitSetSchema.js'; +export * from './turnCommandSchema.js'; diff --git a/packages/logic/src/resources/mapSchema.ts b/packages/logic/src/resources/mapSchema.ts new file mode 100644 index 0000000..339d434 --- /dev/null +++ b/packages/logic/src/resources/mapSchema.ts @@ -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; +export type RegionMapInput = z.infer; diff --git a/packages/logic/src/resources/scenarioSchema.ts b/packages/logic/src/resources/scenarioSchema.ts new file mode 100644 index 0000000..f84abd6 --- /dev/null +++ b/packages/logic/src/resources/scenarioSchema.ts @@ -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; +export type ScenarioDefinitionInput = z.infer; diff --git a/packages/logic/src/resources/turnCommandSchema.ts b/packages/logic/src/resources/turnCommandSchema.ts new file mode 100644 index 0000000..4763556 --- /dev/null +++ b/packages/logic/src/resources/turnCommandSchema.ts @@ -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; diff --git a/packages/logic/src/resources/unitSetSchema.ts b/packages/logic/src/resources/unitSetSchema.ts new file mode 100644 index 0000000..cdaff9f --- /dev/null +++ b/packages/logic/src/resources/unitSetSchema.ts @@ -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; diff --git a/packages/logic/src/scenario/parseScenario.ts b/packages/logic/src/scenario/parseScenario.ts index 7a33706..9d446c6 100644 --- a/packages/logic/src/scenario/parseScenario.ts +++ b/packages/logic/src/scenario/parseScenario.ts @@ -1,6 +1,8 @@ import { z } from 'zod'; import { asNullableNumber, asNullableString, asNumber, asString, asStringArray, isRecord } from '@sammo-ts/common'; +import { ScenarioDefaultsInputSchema, ScenarioDefinitionInputSchema } from '../resources/scenarioSchema.js'; + import type { ScenarioConfig, ScenarioDefaults, @@ -24,56 +26,7 @@ const FALLBACK_STAT: ScenarioStatBlock = { 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 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 data = isRecord(value) ? value : {}; @@ -211,7 +164,7 @@ const parseDiplomacyRows = (rows: unknown[]): ScenarioDiplomacy[] => export const parseScenarioDefaults = (raw: unknown): ScenarioDefaults => { // 기본 시나리오 설정값을 안전하게 읽는다. - const data = zScenarioDefaults.parse(raw); + const data = ScenarioDefaultsInputSchema.parse(raw); const stat = parseScenarioStatBlock(data.stat, FALLBACK_STAT); const iconPath = asString(data.iconPath, '.'); return { stat, iconPath }; @@ -219,7 +172,7 @@ export const parseScenarioDefaults = (raw: unknown): ScenarioDefaults => { export const parseScenarioDefinition = (raw: unknown, defaults: ScenarioDefaults): ScenarioDefinition => { // 시나리오 JSON을 런타임에서 쓰는 구조로 정규화한다. - const data = zScenarioInput.parse(raw); + const data = ScenarioDefinitionInputSchema.parse(raw); const stat = parseScenarioStatBlock(data.stat, defaults.stat); const mapConfig = data.map ?? {}; const constConfig = data.const ?? {}; diff --git a/packages/logic/src/world/unitSet.ts b/packages/logic/src/world/unitSet.ts index dd8f3d2..420db32 100644 --- a/packages/logic/src/world/unitSet.ts +++ b/packages/logic/src/world/unitSet.ts @@ -8,6 +8,7 @@ import { asStringArray, isRecord, } from '@sammo-ts/common'; +import { UnitSetDefinitionInputSchema } from '../resources/unitSetSchema.js'; const DEFAULT_REGION_MAP: Record = { 하북: 1, @@ -114,7 +115,7 @@ const parseCrewType = (value: unknown): CrewTypeDefinition | null => { }; export const parseUnitSetDefinition = (value: unknown): UnitSetDefinition => { - const data = asRecord(value); + const data = UnitSetDefinitionInputSchema.parse(value); const id = asString(data.id, 'unknown'); const name = asString(data.name, id); const defaultCrewTypeId = diff --git a/packages/tools-scripts/package.json b/packages/tools-scripts/package.json new file mode 100644 index 0000000..673d267 --- /dev/null +++ b/packages/tools-scripts/package.json @@ -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" + } +} diff --git a/packages/tools-scripts/src/generate-resource-schemas.ts b/packages/tools-scripts/src/generate-resource-schemas.ts new file mode 100644 index 0000000..43f231f --- /dev/null +++ b/packages/tools-scripts/src/generate-resource-schemas.ts @@ -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 => { + 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 => { + 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; +}); diff --git a/packages/tools-scripts/src/validate-resources.ts b/packages/tools-scripts/src/validate-resources.ts new file mode 100644 index 0000000..e498527 --- /dev/null +++ b/packages/tools-scripts/src/validate-resources.ts @@ -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 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 => { + 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 => { + 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 => { + 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; +}); diff --git a/packages/tools-scripts/tsconfig.json b/packages/tools-scripts/tsconfig.json new file mode 100644 index 0000000..9e2913a --- /dev/null +++ b/packages/tools-scripts/tsconfig.json @@ -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" }] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e022219..f005474 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: tsdown: 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)) + tsx: + specifier: ^4.21.0 + version: 4.21.0 turbo: specifier: ^2.7.2 version: 2.7.2 @@ -53,6 +56,9 @@ importers: vue-eslint-parser: specifier: ^10.2.0 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: 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)) vite-tsconfig-paths: 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: 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: 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)) vite-tsconfig-paths: 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: 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: dependencies: @@ -175,10 +181,10 @@ importers: devDependencies: '@tailwindcss/vite': 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': 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: specifier: ^10.4.23 version: 10.4.23(postcss@8.5.6) @@ -193,7 +199,7 @@ importers: version: 5.9.3 vite: 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: specifier: ^3.2.2 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)) vite-tsconfig-paths: 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: 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: dependencies: @@ -297,10 +303,10 @@ importers: devDependencies: '@tailwindcss/vite': 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': 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: specifier: ^10.4.23 version: 10.4.23(postcss@8.5.6) @@ -315,7 +321,7 @@ importers: version: 5.9.3 vite: 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: specifier: ^3.2.1 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)) vitest: 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: 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)) vite-tsconfig-paths: 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: 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: {} @@ -420,10 +442,10 @@ importers: devDependencies: vite-tsconfig-paths: 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: 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: @@ -3124,6 +3146,11 @@ packages: tslib@2.8.1: 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: resolution: {integrity: sha512-dxY3X6ezcT5vm3coK6VGixbrhplbQMwgNsCsvZamS/+/6JiebqW9DKt4NwpgYXhDY2HdH00I7FWs3wkVuan4rA==} cpu: [x64] @@ -3425,6 +3452,11 @@ packages: zeptomatch@2.0.2: 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: resolution: {integrity: sha512-Zw/uYiiyF6pUT1qmKbZziChgNPRu+ZRneAsMUDU6IwmXdWt5JwcUfy2bvLOCUtz5UniaN/Zx5aFttZYbYc7O/A==} @@ -4166,19 +4198,19 @@ snapshots: '@tailwindcss/oxide-win32-arm64-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: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 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: '@tailwindcss/node': 4.1.18 '@tailwindcss/oxide': 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': {} @@ -4314,16 +4346,16 @@ snapshots: '@typescript-eslint/types': 8.51.0 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: '@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) - '@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: '@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) '@vitest/expect@4.0.16': @@ -4335,13 +4367,13 @@ snapshots: chai: 6.2.2 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: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 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': dependencies: @@ -6035,6 +6067,13 @@ snapshots: 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: optional: true @@ -6117,18 +6156,18 @@ snapshots: optionalDependencies: 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: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) 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: - supports-color - 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: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -6141,8 +6180,9 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 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: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -6155,11 +6195,12 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 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: '@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/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -6176,7 +6217,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 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 optionalDependencies: '@types/node': 25.0.3 @@ -6266,6 +6307,10 @@ snapshots: dependencies: 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.5: {} diff --git a/resources/map/schema.json b/resources/map/schema.json new file mode 100644 index 0000000..d3a6890 --- /dev/null +++ b/resources/map/schema.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/MapResource", + "definitions": { + "MapResource": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/resources/scenario/schema.json b/resources/scenario/schema.json new file mode 100644 index 0000000..a14fb07 --- /dev/null +++ b/resources/scenario/schema.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/ScenarioResource", + "definitions": { + "ScenarioResource": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/resources/turn-commands/schema.json b/resources/turn-commands/schema.json new file mode 100644 index 0000000..f1e2737 --- /dev/null +++ b/resources/turn-commands/schema.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/TurnCommandProfile", + "definitions": { + "TurnCommandProfile": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/resources/unitset/schema.json b/resources/unitset/schema.json new file mode 100644 index 0000000..8470aa3 --- /dev/null +++ b/resources/unitset/schema.json @@ -0,0 +1,7 @@ +{ + "$ref": "#/definitions/UnitSetDefinition", + "definitions": { + "UnitSetDefinition": {} + }, + "$schema": "http://json-schema.org/draft-07/schema#" +}