From 4329cbbbcfe3ebb7d29a6ea63f636e6b7f7becdd Mon Sep 17 00:00:00 2001 From: hided62 Date: Wed, 12 Aug 2026 23:34:23 +0000 Subject: [PATCH 1/5] build: run TypeScript 6 and 7 side by side --- AGENTS.md | 6 +- README.md | 4 +- app/game-api/package.json | 2 +- app/game-api/tsconfig.json | 1 - app/game-engine/package.json | 2 +- app/game-frontend/package.json | 2 +- app/game-frontend/tsconfig.json | 1 - app/gateway-api/package.json | 2 +- app/gateway-frontend/package.json | 2 +- app/gateway-frontend/tsconfig.json | 1 - app/release-controller/package.json | 2 +- docs/architecture/typescript-version.md | 45 +- package.json | 7 +- packages/common/package.json | 2 +- packages/infra/package.json | 2 +- packages/logic/package.json | 2 +- packages/tools-scripts/package.json | 4 +- pnpm-lock.yaml | 569 ++++++++++++++++-------- pnpm-workspace.yaml | 2 +- tools/build-scripts/package.json | 4 +- tools/build-scripts/tsconfig.json | 1 - tools/check-typescript-toolchain.mjs | 38 ++ tools/integration-tests/package.json | 2 +- tools/legacy-db-migration/package.json | 4 +- tools/test-dbms/tsconfig.json | 1 - tsconfig.base.json | 1 - tsconfig.paths.json | 29 +- 27 files changed, 499 insertions(+), 239 deletions(-) create mode 100644 tools/check-typescript-toolchain.mjs diff --git a/AGENTS.md b/AGENTS.md index 339fbd58..f2eb4582 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -316,8 +316,10 @@ docs에서 확인해 주세요. 존재하지 않는 명령을 오래된 report ## 코드 스타일 -- TypeScript는 workspace 전체에서 정확히 `6.0.2`를 사용해 주세요. package-local - 다른 버전을 추가하지 말아 주세요. +- Vite, Vue/Volar와 compiler API 소비자는 workspace 전체에서 정확히 + TypeScript `6.0.3`을 사용해 주세요. CLI project build/typecheck는 root의 + `@typescript/native` alias로 고정한 TypeScript 7 `tsc`를 사용하며 package-local + 다른 compiler version이나 직접 `tsc` 호출을 추가하지 말아 주세요. - TypeScript/JSON/Vue SFC는 기존 4-space 스타일을 유지해 주세요. - public API는 명시적 타입을 사용하고 `any`, 불필요하게 넓은 `unknown`, `as unknown as` 우회를 피해 주세요. diff --git a/README.md b/README.md index 3bfa7389..72274ee6 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,8 @@ input-event 결과를 transaction으로 반영합니다. Redis pub/sub과 SSE는 ## 도구 체인 -- pnpm `11.17.0`, Turbo -- TypeScript `6.0.2` +- pnpm `11.21.0`, Turbo +- TypeScript `6.0.3` compiler API와 TypeScript `7.0.2` native `tsc` - Fastify, tRPC, zod - Vue 3, Pinia, Vue Router, Vite - PostgreSQL, Prisma, Redis diff --git a/app/game-api/package.json b/app/game-api/package.json index 7a7194c9..70c17b2c 100644 --- a/app/game-api/package.json +++ b/app/game-api/package.json @@ -23,7 +23,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b app/game-api/tsconfig.json" }, "devDependencies": { "@types/sanitize-html": "2.16.1", diff --git a/app/game-api/tsconfig.json b/app/game-api/tsconfig.json index fd0712c2..f18f113d 100644 --- a/app/game-api/tsconfig.json +++ b/app/game-api/tsconfig.json @@ -3,7 +3,6 @@ "compilerOptions": { "outDir": "dist", "composite": true, - "baseUrl": ".", "paths": { "@sammo-ts/common": [ "../../packages/common/src/index.ts" diff --git a/app/game-engine/package.json b/app/game-engine/package.json index 8c1d6c3e..0531cd16 100644 --- a/app/game-engine/package.json +++ b/app/game-engine/package.json @@ -99,7 +99,7 @@ "lint:fix": "eslint . --fix", "profile:npc-unification-memory": "node scripts/profile-npc-unification-memory.mjs", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b app/game-engine/tsconfig.json" }, "dependencies": { "@prisma/client": "^7.9.1", diff --git a/app/game-frontend/package.json b/app/game-frontend/package.json index 0a9ad20a..228a482c 100644 --- a/app/game-frontend/package.json +++ b/app/game-frontend/package.json @@ -56,7 +56,7 @@ "autoprefixer": "^10.5.4", "postcss": "8.5.26", "tailwindcss": "^4.3.3", - "typescript": "6.0.2", + "typescript": "6.0.3", "vite": "^8.2.1", "vue-tsc": "^3.3.9" } diff --git a/app/game-frontend/tsconfig.json b/app/game-frontend/tsconfig.json index bb471ef1..24f317d9 100644 --- a/app/game-frontend/tsconfig.json +++ b/app/game-frontend/tsconfig.json @@ -16,7 +16,6 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": ".", "paths": { "@/*": ["./src/*"], "@sammo-ts/common": ["../../packages/common/src/index.ts"], diff --git a/app/gateway-api/package.json b/app/gateway-api/package.json index 8df78aa2..a93281ca 100644 --- a/app/gateway-api/package.json +++ b/app/gateway-api/package.json @@ -21,7 +21,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b app/gateway-api/tsconfig.json" }, "devDependencies": { "@types/sanitize-html": "2.16.1", diff --git a/app/gateway-frontend/package.json b/app/gateway-frontend/package.json index 7f094fd2..4e90bea6 100644 --- a/app/gateway-frontend/package.json +++ b/app/gateway-frontend/package.json @@ -40,7 +40,7 @@ "pg": "^8.23.0", "postcss": "8.5.26", "tailwindcss": "^4.3.3", - "typescript": "6.0.2", + "typescript": "6.0.3", "vite": "^8.2.1", "vue-tsc": "^3.3.9" } diff --git a/app/gateway-frontend/tsconfig.json b/app/gateway-frontend/tsconfig.json index 6c3f5e52..64066432 100644 --- a/app/gateway-frontend/tsconfig.json +++ b/app/gateway-frontend/tsconfig.json @@ -20,7 +20,6 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/app/release-controller/package.json b/app/release-controller/package.json index 18bb4f86..8e6e1a73 100644 --- a/app/release-controller/package.json +++ b/app/release-controller/package.json @@ -14,7 +14,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b app/release-controller/tsconfig.json" }, "dependencies": { "@sammo-ts/gateway-api": "workspace:*", diff --git a/docs/architecture/typescript-version.md b/docs/architecture/typescript-version.md index 92eef26b..59fac460 100644 --- a/docs/architecture/typescript-version.md +++ b/docs/architecture/typescript-version.md @@ -2,36 +2,49 @@ ## 버전 -Workspace 전체는 TypeScript `6.0.2`를 정확한 버전으로 사용합니다. -`pnpm-workspace.yaml`의 override가 transitive resolution에도 같은 버전을 -적용합니다. +Workspace의 Vite, Vue/Volar, typescript-eslint와 compiler API 소비자는 +TypeScript `6.0.3`을 정확한 버전으로 사용합니다. `pnpm-workspace.yaml`의 +override가 transitive resolution에도 같은 버전을 적용합니다. + +CLI project build/typecheck는 TypeScript `7.0.2` native compiler를 사용합니다. +루트의 `@typescript/native`는 `npm:typescript@7.0.2` alias이며, `tsc7` script가 +그 package의 `tsc` binary를 명시적으로 실행합니다. 각 package의 `tsc -b`나 +`tsc -p`는 PATH의 우연한 binary 선택에 의존하지 않고 `pnpm -w tsc7 ...`를 +통해 이 script를 호출합니다. Vue SFC 검사는 계속 `vue-tsc`와 TypeScript 6.0.3 +API를 사용합니다. + +TypeScript 7의 공식 side-by-side 안내에 있는 `@typescript/typescript6` package는 +현재 6.0.2까지만 배포되어 있으므로, 6.0.3 API는 일반 `typescript` package로 +설치합니다. 두 package가 모두 `tsc` 이름을 제공할 수 있는 PATH 충돌을 피하기 +위해 project script는 반드시 명시적인 `tsc7` entry point를 사용합니다. +`pnpm check:typescript-toolchain`은 root와 두 frontend의 API resolution 및 native +CLI version을 함께 검사합니다. 적용 범위는 다음과 같습니다. -- 루트 개발 도구 -- `app/*` -- `packages/*` -- `tools/*` +- 루트와 frontend의 Vite/Vue/compiler API: TypeScript 6.0.3 +- `app/*`, `packages/*`, `tools/*`의 project build/typecheck: TypeScript 7.0.2 -Package별 다른 TypeScript 버전, caret range, alias와 fallback compiler를 -추가하지 않습니다. `pnpm-lock.yaml`에는 승인된 한 버전만 resolve되어야 -합니다. Ref PHP 저장소 `../ref/sam`의 도구 체인은 이 workspace 정책에 +Package별 다른 TypeScript 버전이나 caret range를 추가하지 않습니다. 승인된 +두 compiler의 역할은 `typescript` 6.0.3 API와 `@typescript/native` 7.0.2 CLI로 +고정합니다. Ref PHP 저장소 `../ref/sam`의 도구 체인은 이 workspace 정책에 포함되지 않습니다. -공유 `tsconfig.base.json`은 TypeScript 6의 `baseUrl` deprecation 경고에 -대해 `ignoreDeprecations: "6.0"`을 사용합니다. Package별 suppression을 -추가하지 않습니다. +공유 tsconfig와 package tsconfig는 TypeScript 7에서 제거된 `baseUrl`을 +사용하지 않습니다. `paths` target은 각 설정 파일 기준의 명시적 상대경로로 +작성하여 TypeScript 6, TypeScript 7과 Vite의 경로 해석을 일치시킵니다. ## 버전 변경 Compiler major version은 workspace 전체를 한 변경으로 갱신합니다. 다음 항목을 모두 확인해 주세요. -1. 저장소 script가 사용하는 compiler API +1. 저장소 script가 사용하는 TypeScript 7 CLI와 TypeScript 6 compiler API 2. `typescript-eslint`, `vue-tsc`, tsdown, Vite와 Prisma 도구 지원 -3. 모든 manifest와 lockfile의 단일 version resolution +3. 모든 manifest와 lockfile에서 승인된 두 역할의 exact version resolution 4. `CI=1 pnpm typecheck`, `pnpm lint`, `pnpm build`, `pnpm test` 5. `pnpm check:legacy:general`, `pnpm check:legacy:nation` -6. 별도 compiler 설치나 alias 없이 실행되는 비교 도구 +6. `tsc7 --version`, `vue-tsc --version`과 `import('typescript').version`의 + 실제 binary/API resolution 검증되지 않은 compiler major version을 package 한 곳에만 적용하지 않습니다. diff --git a/package.json b/package.json index 6c796252..f118a9fa 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "test:integration:conditional": "./tools/run-conditional-integration.sh", "build": "turbo build", "typecheck": "turbo typecheck", + "tsc7": "node_modules/@typescript/native/bin/tsc", "dev": "turbo dev", "build:server": "pnpm --filter ./tools/build-scripts build:server --", "generate:resource-schemas": "pnpm --filter @sammo-ts/tools-scripts generate:resource-schemas", @@ -23,10 +24,11 @@ "check:legacy:general": "node tools/compare-command-constraints.mjs --include '^General/' --check && node tools/compare-command-logs.mjs --include '^General/' --mode action --check && node tools/compare-general-turn-contracts.mjs --check", "check:ref-compat-markers": "node tools/check-ref-compat-markers.mjs", "check:architecture": "node tools/check-package-boundaries.mjs", + "check:typescript-toolchain": "node tools/check-typescript-toolchain.mjs", "test:architecture": "node --test tools/check-package-boundaries.test.mjs", "test:image-sync": "node --test tools/sync-image-repository.test.mjs", "test:e2e:frontend-legacy": "playwright test --config tools/frontend-legacy-parity/playwright.config.mjs --tsconfig tools/frontend-legacy-parity/tsconfig.json", - "typecheck:e2e:frontend-legacy": "tsc -p tools/frontend-legacy-parity/tsconfig.json --noEmit", + "typecheck:e2e:frontend-legacy": "pnpm tsc7 -p tools/frontend-legacy-parity/tsconfig.json --noEmit", "test:e2e:main-front-status-live": "node tools/frontend-legacy-parity/run-main-front-status-live.mjs", "test:e2e:main-records-live": "node tools/frontend-legacy-parity/run-main-records-live.mjs", "migrate:legacy": "pnpm --filter @sammo-ts/legacy-db-migration migrate", @@ -49,7 +51,8 @@ "prettier": "^3.9.6", "tsdown": "^0.22.14", "turbo": "^2.10.9", - "typescript": "6.0.2", + "@typescript/native": "npm:typescript@7.0.2", + "typescript": "6.0.3", "typescript-eslint": "^8.67.0", "vitepress": "1.6.4", "vue-eslint-parser": "^10.4.1" diff --git a/packages/common/package.json b/packages/common/package.json index 5871ab5f..ff7563d7 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -29,7 +29,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b packages/common/tsconfig.json" }, "dependencies": { "@noble/hashes": "^2.0.1", diff --git a/packages/infra/package.json b/packages/infra/package.json index 66f56ef9..91338992 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -12,7 +12,7 @@ "lint:fix": "eslint . --fix", "test": "node -e \"console.log('test not configured')\"", "prisma:generate": "pnpm prisma:generate:game && pnpm prisma:generate:gateway", - "typecheck": "tsc -b", + "typecheck": "pnpm -w tsc7 -b packages/infra/tsconfig.json", "prisma:generate:game": "prisma generate --schema prisma/game.prisma", "prisma:generate:gateway": "prisma generate --schema prisma/gateway.prisma", "prisma:migrate:deploy:game": "prisma migrate deploy --schema prisma/game.prisma", diff --git a/packages/logic/package.json b/packages/logic/package.json index aa01f619..29ed782f 100644 --- a/packages/logic/package.json +++ b/packages/logic/package.json @@ -18,7 +18,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b packages/logic/tsconfig.json" }, "dependencies": { "@sammo-ts/common": "workspace:*", diff --git a/packages/tools-scripts/package.json b/packages/tools-scripts/package.json index 0efd40b7..570ff4a0 100644 --- a/packages/tools-scripts/package.json +++ b/packages/tools-scripts/package.json @@ -6,7 +6,7 @@ "scripts": { "generate:resource-schemas": "tsx src/generate-resource-schemas.ts", "validate:resources": "tsx src/validate-resources.ts", - "typecheck": "tsc -p tsconfig.json --noEmit" + "typecheck": "pnpm -w tsc7 -p packages/tools-scripts/tsconfig.json --noEmit" }, "dependencies": { "@sammo-ts/logic": "workspace:*", @@ -15,6 +15,6 @@ "devDependencies": { "@types/node": "^26.2.0", "tsx": "^4.23.12", - "typescript": "6.0.2" + "typescript": "6.0.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e95a3e4..b3a2e056 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: postcss: 8.5.26 - typescript: 6.0.2 + typescript: 6.0.3 importers: @@ -23,10 +23,13 @@ importers: version: 26.2.0 '@typescript-eslint/eslint-plugin': specifier: ^8.67.0 - version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + version: 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/parser': specifier: ^8.67.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript/native': + specifier: npm:typescript@7.0.2 + version: typescript@7.0.2 eslint: specifier: ^10.8.1 version: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) @@ -38,7 +41,7 @@ importers: version: 5.5.6(eslint-config-prettier@10.1.8(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(prettier@3.9.6) eslint-plugin-vue: specifier: ^10.10.0 - version: 10.10.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)) + version: 10.10.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)) globals: specifier: ^17.11.0 version: 17.11.0 @@ -47,19 +50,19 @@ importers: version: 3.9.6 tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) turbo: specifier: ^2.10.9 version: 2.10.9 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 typescript-eslint: specifier: ^8.67.0 - version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + version: 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) vitepress: specifier: 1.6.4 - version: 1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(lightningcss@1.33.0)(postcss@8.5.26)(search-insights@2.17.3)(typescript@6.0.2) + version: 1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(lightningcss@1.33.0)(postcss@8.5.26)(search-insights@2.17.3)(typescript@6.0.3) vue-eslint-parser: specifier: ^10.4.1 version: 10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) @@ -86,7 +89,7 @@ importers: version: link:../../packages/logic '@trpc/server': specifier: ^11.8.1 - version: 11.8.1(typescript@6.0.2) + version: 11.8.1(typescript@6.0.3) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -114,7 +117,7 @@ importers: version: 2.16.1 tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -123,7 +126,7 @@ importers: dependencies: '@prisma/client': specifier: ^7.9.1 - version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2) + version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3) '@sammo-ts/common': specifier: workspace:* version: link:../../packages/common @@ -142,7 +145,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -172,16 +175,16 @@ importers: version: 3.18.0 '@tiptap/vue-3': specifier: ^3.5.0 - version: 3.18.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(vue@3.5.26(typescript@6.0.2)) + version: 3.18.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(vue@3.5.26(typescript@6.0.3)) '@trpc/client': specifier: ^11.8.1 - version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.2))(typescript@6.0.2) + version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.3))(typescript@6.0.3) '@trpc/server': specifier: ^11.8.1 - version: 11.8.1(typescript@6.0.2) + version: 11.8.1(typescript@6.0.3) '@vueuse/core': specifier: ^14.1.0 - version: 14.1.0(vue@3.5.26(typescript@6.0.2)) + version: 14.1.0(vue@3.5.26(typescript@6.0.3)) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -193,13 +196,13 @@ importers: version: 3.0.1 pinia: specifier: ^3.0.4 - version: 3.0.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2)) + version: 3.0.4(typescript@6.0.3)(vue@3.5.26(typescript@6.0.3)) vue: specifier: ^3.5.26 - version: 3.5.26(typescript@6.0.2) + version: 3.5.26(typescript@6.0.3) vue-router: specifier: ^4.6.4 - version: 4.6.4(vue@3.5.26(typescript@6.0.2)) + version: 4.6.4(vue@3.5.26(typescript@6.0.3)) zod: specifier: ^4.3.5 version: 4.3.5 @@ -221,7 +224,7 @@ importers: version: 4.3.3(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) '@vitejs/plugin-vue': specifier: ^6.0.8 - version: 6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.2)) + version: 6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.3)) autoprefixer: specifier: ^10.5.4 version: 10.5.4(postcss@8.5.26) @@ -232,14 +235,14 @@ importers: specifier: ^4.3.3 version: 4.3.3 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 vite: specifier: ^8.2.1 version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12) vue-tsc: specifier: ^3.3.9 - version: 3.3.9(typescript@6.0.2) + version: 3.3.9(typescript@6.0.3) app/gateway-api: dependencies: @@ -251,7 +254,7 @@ importers: version: 9.0.0 '@prisma/client': specifier: ^7.9.1 - version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2) + version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3) '@sammo-ts/common': specifier: workspace:* version: link:../../packages/common @@ -266,7 +269,7 @@ importers: version: link:../../packages/logic '@trpc/server': specifier: ^11.8.1 - version: 11.8.1(typescript@6.0.2) + version: 11.8.1(typescript@6.0.3) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -297,7 +300,7 @@ importers: version: 2.16.1 tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -309,13 +312,13 @@ importers: version: link:../../packages/common '@trpc/client': specifier: ^11.8.1 - version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.2))(typescript@6.0.2) + version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.3))(typescript@6.0.3) '@trpc/server': specifier: ^11.8.1 - version: 11.8.1(typescript@6.0.2) + version: 11.8.1(typescript@6.0.3) '@vueuse/core': specifier: ^13.9.0 - version: 13.9.0(vue@3.5.26(typescript@6.0.2)) + version: 13.9.0(vue@3.5.26(typescript@6.0.3)) date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -327,13 +330,13 @@ importers: version: 3.0.1 pinia: specifier: ^3.0.4 - version: 3.0.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2)) + version: 3.0.4(typescript@6.0.3)(vue@3.5.26(typescript@6.0.3)) vue: specifier: ^3.5.26 - version: 3.5.26(typescript@6.0.2) + version: 3.5.26(typescript@6.0.3) vue-router: specifier: ^4.6.4 - version: 4.6.4(vue@3.5.26(typescript@6.0.2)) + version: 4.6.4(vue@3.5.26(typescript@6.0.3)) zod: specifier: ^4.3.5 version: 4.3.5 @@ -361,7 +364,7 @@ importers: version: 8.21.0 '@vitejs/plugin-vue': specifier: ^6.0.8 - version: 6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.2)) + version: 6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.3)) autoprefixer: specifier: ^10.5.4 version: 10.5.4(postcss@8.5.26) @@ -375,14 +378,14 @@ importers: specifier: ^4.3.3 version: 4.3.3 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 vite: specifier: ^8.2.1 version: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12) vue-tsc: specifier: ^3.3.9 - version: 3.3.9(typescript@6.0.2) + version: 3.3.9(typescript@6.0.3) app/release-controller: dependencies: @@ -395,7 +398,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -414,7 +417,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -426,7 +429,7 @@ importers: version: 7.9.1 '@prisma/client': specifier: ^7.9.1 - version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2) + version: 7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3) '@prisma/client-runtime-utils': specifier: ^7.9.1 version: 7.9.1 @@ -451,10 +454,10 @@ importers: version: 17.4.2 prisma: specifier: ^7.9.1 - version: 7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2) + version: 7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3) tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) packages/logic: dependencies: @@ -473,7 +476,7 @@ importers: devDependencies: tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -494,8 +497,8 @@ importers: specifier: ^4.23.12 version: 4.23.12 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 tools/build-scripts: {} @@ -518,7 +521,7 @@ importers: version: link:../../packages/infra '@trpc/client': specifier: ^11.8.1 - version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.2))(typescript@6.0.2) + version: 11.8.1(@trpc/server@11.8.1(typescript@6.0.3))(typescript@6.0.3) devDependencies: vitest: specifier: ^4.1.10 @@ -541,13 +544,13 @@ importers: version: 8.21.0 tsdown: specifier: ^0.22.14 - version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)) + version: 0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)) tsx: specifier: ^4.23.12 version: 4.23.12 typescript: - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 vitest: specifier: ^4.1.10 version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12)) @@ -1352,7 +1355,7 @@ packages: engines: {node: ^20.19 || ^22.12 || >=24.0} peerDependencies: prisma: '*' - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: prisma: optional: true @@ -2112,12 +2115,12 @@ packages: resolution: {integrity: sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q==} peerDependencies: '@trpc/server': 11.8.1 - typescript: 6.0.2 + typescript: 6.0.3 '@trpc/server@11.8.1': resolution: {integrity: sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@turbo/darwin-64@2.10.9': resolution: {integrity: sha512-Jh+pTGXLNz8+1tkUU13TI/f+ZOI+OvC4YbHi1H+57iSpLt5DR3xgptd+4sA07RdjdRR/RX/01uQQu2OkbzIefA==} @@ -2245,20 +2248,20 @@ packages: peerDependencies: '@typescript-eslint/parser': ^8.67.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/parser@8.67.0': resolution: {integrity: sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/project-service@8.67.0': resolution: {integrity: sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/scope-manager@8.67.0': resolution: {integrity: sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==} @@ -2268,14 +2271,14 @@ packages: resolution: {integrity: sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/type-utils@8.67.0': resolution: {integrity: sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/types@8.67.0': resolution: {integrity: sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==} @@ -2285,19 +2288,139 @@ packages: resolution: {integrity: sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/utils@8.67.0': resolution: {integrity: sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: 6.0.2 + typescript: 6.0.3 '@typescript-eslint/visitor-keys@8.67.0': resolution: {integrity: sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@ungap/structured-clone@1.3.3': resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} @@ -2388,7 +2511,7 @@ packages: '@volar/typescript@2.4.28': resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: typescript: optional: true @@ -4070,7 +4193,7 @@ packages: pinia@3.0.4: resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 vue: ^3.5.11 peerDependenciesMeta: typescript: @@ -4184,7 +4307,7 @@ packages: hasBin: true peerDependencies: better-sqlite3: '>=9.0.0' - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: better-sqlite3: optional: true @@ -4376,7 +4499,7 @@ packages: '@typescript/native-preview': '*' '@volar/typescript': ~2.4.0 rolldown: ^1.0.0 - typescript: 6.0.2 + typescript: 6.0.3 vue-tsc: ~3.2.0 || ~3.3.0 peerDependenciesMeta: '@typescript/native-preview': @@ -4623,7 +4746,7 @@ packages: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 tsdown@0.22.14: resolution: {integrity: sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==} @@ -4636,7 +4759,7 @@ packages: '@vitejs/devtools': '*' publint: ^0.3.8 tsx: '*' - typescript: 6.0.2 + typescript: 6.0.3 unplugin-unused: ^0.5.0 unrun: '*' peerDependenciesMeta: @@ -4690,13 +4813,18 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: 6.0.2 + typescript: 6.0.3 - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -4746,7 +4874,7 @@ packages: valibot@1.4.2: resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: typescript: optional: true @@ -4910,12 +5038,12 @@ packages: resolution: {integrity: sha512-TS3Y1ux/IRoE8OCP2PpACAeOseuIs0UvWrcr7u+w3PmfY+SlCfEf8zjrBgnQksHUgLpthi5vHlffcQTQTdPBZA==} hasBin: true peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 vue@3.5.26: resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: typescript: optional: true @@ -4923,7 +5051,7 @@ packages: vue@3.5.41: resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==} peerDependencies: - typescript: 6.0.2 + typescript: 6.0.3 peerDependenciesMeta: typescript: optional: true @@ -5657,12 +5785,12 @@ snapshots: '@prisma/client-runtime-utils@7.9.1': {} - '@prisma/client@7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2))(typescript@6.0.2)': + '@prisma/client@7.9.1(prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@prisma/client-runtime-utils': 7.9.1 optionalDependencies: - prisma: 7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2) - typescript: 6.0.2 + prisma: 7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + typescript: 6.0.3 '@prisma/config@7.9.1': dependencies: @@ -5677,7 +5805,7 @@ snapshots: '@prisma/debug@7.9.1': {} - '@prisma/dev@0.24.17(typescript@6.0.2)': + '@prisma/dev@0.24.17(typescript@6.0.3)': dependencies: '@electric-sql/pglite': 0.4.3 '@electric-sql/pglite-socket': 0.1.3(@electric-sql/pglite@0.4.3) @@ -5692,7 +5820,7 @@ snapshots: proper-lockfile: 4.1.2 remeda: 2.33.4 std-env: 3.10.0 - valibot: 1.4.2(typescript@6.0.2) + valibot: 1.4.2(typescript@6.0.3) zeptomatch: 2.1.0 transitivePeerDependencies: - typescript @@ -6276,26 +6404,26 @@ snapshots: '@tiptap/extensions': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) '@tiptap/pm': 3.18.0 - '@tiptap/vue-3@3.18.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(vue@3.5.26(typescript@6.0.2))': + '@tiptap/vue-3@3.18.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0)(vue@3.5.26(typescript@6.0.3))': dependencies: '@floating-ui/dom': 1.7.5 '@tiptap/core': 3.18.0(@tiptap/pm@3.18.0) '@tiptap/pm': 3.18.0 - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) optionalDependencies: '@tiptap/extension-bubble-menu': 3.18.0(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) '@tiptap/extension-floating-menu': 3.18.0(@floating-ui/dom@1.7.5)(@tiptap/core@3.18.0(@tiptap/pm@3.18.0))(@tiptap/pm@3.18.0) '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@6.0.2))(typescript@6.0.2)': + '@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@6.0.3))(typescript@6.0.3)': dependencies: - '@trpc/server': 11.8.1(typescript@6.0.2) - typescript: 6.0.2 + '@trpc/server': 11.8.1(typescript@6.0.3) + typescript: 6.0.3 - '@trpc/server@11.8.1(typescript@6.0.2)': + '@trpc/server@11.8.1(typescript@6.0.3)': dependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@turbo/darwin-64@2.10.9': optional: true @@ -6406,40 +6534,40 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.67.0 - '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) ignore: 7.0.6 natural-compare: 1.4.0 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@7.2.0) eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/project-service@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.2) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) '@typescript-eslint/types': 8.67.0 debug: 4.4.3(supports-color@7.2.0) - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6448,47 +6576,47 @@ snapshots: '@typescript-eslint/types': 8.67.0 '@typescript-eslint/visitor-keys': 8.67.0 - '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.2)': + '@typescript-eslint/tsconfig-utils@8.67.0(typescript@6.0.3)': dependencies: - typescript: 6.0.2 + typescript: 6.0.3 - '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) debug: 4.4.3(supports-color@7.2.0) eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color '@typescript-eslint/types@8.67.0': {} - '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.67.0(supports-color@7.2.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.2) + '@typescript-eslint/project-service': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.67.0(typescript@6.0.3) '@typescript-eslint/types': 8.67.0 '@typescript-eslint/visitor-keys': 8.67.0 debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 - ts-api-utils: 2.5.0(typescript@6.0.2) - typescript: 6.0.2 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': + '@typescript-eslint/utils@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.67.0 '@typescript-eslint/types': 8.67.0 - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -6497,6 +6625,66 @@ snapshots: '@typescript-eslint/types': 8.67.0 eslint-visitor-keys: 5.0.1 + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + '@ungap/structured-clone@1.3.3': {} '@visx/curve@4.0.1-alpha.0': @@ -6576,16 +6764,16 @@ snapshots: d3-time-format: 4.1.0 internmap: 2.0.3 - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.2))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.3))': dependencies: vite: 5.4.21(@types/node@26.2.0)(lightningcss@1.33.0) - vue: 3.5.41(typescript@6.0.2) + vue: 3.5.41(typescript@6.0.3) - '@vitejs/plugin-vue@6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.2))': + '@vitejs/plugin-vue@6.0.8(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12))(vue@3.5.26(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.12) - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) '@vitest/expect@4.1.10': dependencies: @@ -6634,13 +6822,13 @@ snapshots: '@volar/source-map@2.4.28': {} - '@volar/typescript@2.4.28(typescript@6.0.2)': + '@volar/typescript@2.4.28(typescript@6.0.3)': dependencies: '@volar/language-core': 2.4.28 path-browserify: 1.0.1 vscode-uri: 3.1.0 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 '@vue/compiler-core@3.5.26': dependencies: @@ -6782,11 +6970,11 @@ snapshots: '@vue/shared': 3.5.41 csstype: 3.2.3 - '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@6.0.2))': + '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@6.0.3))': dependencies: '@vue/compiler-ssr': 3.5.26 '@vue/shared': 3.5.26 - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) '@vue/server-renderer@3.5.41': dependencies: @@ -6798,34 +6986,34 @@ snapshots: '@vue/shared@3.5.41': {} - '@vueuse/core@12.8.2(typescript@6.0.2)': + '@vueuse/core@12.8.2(typescript@6.0.3)': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 12.8.2 - '@vueuse/shared': 12.8.2(typescript@6.0.2) - vue: 3.5.41(typescript@6.0.2) + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.41(typescript@6.0.3) transitivePeerDependencies: - typescript - '@vueuse/core@13.9.0(vue@3.5.26(typescript@6.0.2))': + '@vueuse/core@13.9.0(vue@3.5.26(typescript@6.0.3))': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 13.9.0 - '@vueuse/shared': 13.9.0(vue@3.5.26(typescript@6.0.2)) - vue: 3.5.26(typescript@6.0.2) + '@vueuse/shared': 13.9.0(vue@3.5.26(typescript@6.0.3)) + vue: 3.5.26(typescript@6.0.3) - '@vueuse/core@14.1.0(vue@3.5.26(typescript@6.0.2))': + '@vueuse/core@14.1.0(vue@3.5.26(typescript@6.0.3))': dependencies: '@types/web-bluetooth': 0.0.21 '@vueuse/metadata': 14.1.0 - '@vueuse/shared': 14.1.0(vue@3.5.26(typescript@6.0.2)) - vue: 3.5.26(typescript@6.0.2) + '@vueuse/shared': 14.1.0(vue@3.5.26(typescript@6.0.3)) + vue: 3.5.26(typescript@6.0.3) - '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@6.0.2)': + '@vueuse/integrations@12.8.2(focus-trap@7.8.0)(typescript@6.0.3)': dependencies: - '@vueuse/core': 12.8.2(typescript@6.0.2) - '@vueuse/shared': 12.8.2(typescript@6.0.2) - vue: 3.5.41(typescript@6.0.2) + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/shared': 12.8.2(typescript@6.0.3) + vue: 3.5.41(typescript@6.0.3) optionalDependencies: focus-trap: 7.8.0 transitivePeerDependencies: @@ -6837,19 +7025,19 @@ snapshots: '@vueuse/metadata@14.1.0': {} - '@vueuse/shared@12.8.2(typescript@6.0.2)': + '@vueuse/shared@12.8.2(typescript@6.0.3)': dependencies: - vue: 3.5.41(typescript@6.0.2) + vue: 3.5.41(typescript@6.0.3) transitivePeerDependencies: - typescript - '@vueuse/shared@13.9.0(vue@3.5.26(typescript@6.0.2))': + '@vueuse/shared@13.9.0(vue@3.5.26(typescript@6.0.3))': dependencies: - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) - '@vueuse/shared@14.1.0(vue@3.5.26(typescript@6.0.2))': + '@vueuse/shared@14.1.0(vue@3.5.26(typescript@6.0.3))': dependencies: - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) '@yuku-codegen/binding-android-arm64@0.8.4': optional: true @@ -7419,7 +7607,7 @@ snapshots: optionalDependencies: eslint-config-prettier: 10.1.8(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) - eslint-plugin-vue@10.10.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)): + eslint-plugin-vue@10.10.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(vue-eslint-parser@10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)): dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0)) eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) @@ -7430,7 +7618,7 @@ snapshots: vue-eslint-parser: 10.4.1(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0) xml-name-validator: 5.0.0 optionalDependencies: - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint-scope@9.1.2: dependencies: @@ -8241,12 +8429,12 @@ snapshots: dependencies: safe-buffer: 5.2.1 - pinia@3.0.4(typescript@6.0.2)(vue@3.5.26(typescript@6.0.2)): + pinia@3.0.4(typescript@6.0.3)(vue@3.5.26(typescript@6.0.3)): dependencies: '@vue/devtools-api': 7.7.9 - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 pino-abstract-transport@2.0.0: dependencies: @@ -8392,16 +8580,16 @@ snapshots: prettier@3.9.6: {} - prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.2): + prisma@7.9.1(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(typescript@6.0.3): dependencies: '@prisma/config': 7.9.1 - '@prisma/dev': 0.24.17(typescript@6.0.2) + '@prisma/dev': 0.24.17(typescript@6.0.3) '@prisma/engines': 7.9.1 '@prisma/studio-core': 0.33.0(@types/react@19.2.18)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) mysql2: 3.15.3 postgres: 3.4.7 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -8627,7 +8815,7 @@ snapshots: robust-predicates@3.0.3: {} - rolldown-plugin-dts@0.27.14(@volar/typescript@2.4.28(typescript@6.0.2))(rolldown@1.2.3)(typescript@6.0.2)(vue-tsc@3.3.9(typescript@6.0.2)): + rolldown-plugin-dts@0.27.14(@volar/typescript@2.4.28(typescript@6.0.3))(rolldown@1.2.3)(typescript@6.0.3)(vue-tsc@3.3.9(typescript@6.0.3)): dependencies: dts-resolver: 3.0.0 get-tsconfig: 5.0.0-beta.5 @@ -8637,9 +8825,9 @@ snapshots: yuku-codegen: 0.8.4 yuku-parser: 0.8.4 optionalDependencies: - '@volar/typescript': 2.4.28(typescript@6.0.2) - typescript: 6.0.2 - vue-tsc: 3.3.9(typescript@6.0.2) + '@volar/typescript': 2.4.28(typescript@6.0.3) + typescript: 6.0.3 + vue-tsc: 3.3.9(typescript@6.0.3) transitivePeerDependencies: - oxc-resolver @@ -8921,11 +9109,11 @@ snapshots: trim-lines@3.0.1: {} - ts-api-utils@2.5.0(typescript@6.0.2): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 6.0.2 + typescript: 6.0.3 - tsdown@0.22.14(@volar/typescript@2.4.28(typescript@6.0.2))(tsx@4.23.12)(typescript@6.0.2)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.2)): + tsdown@0.22.14(@volar/typescript@2.4.28(typescript@6.0.3))(tsx@4.23.12)(typescript@6.0.3)(unrun@0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13))(vue-tsc@3.3.9(typescript@6.0.3)): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -8936,7 +9124,7 @@ snapshots: obug: 2.1.4 picomatch: 4.0.5 rolldown: 1.2.3 - rolldown-plugin-dts: 0.27.14(@volar/typescript@2.4.28(typescript@6.0.2))(rolldown@1.2.3)(typescript@6.0.2)(vue-tsc@3.3.9(typescript@6.0.2)) + rolldown-plugin-dts: 0.27.14(@volar/typescript@2.4.28(typescript@6.0.3))(rolldown@1.2.3)(typescript@6.0.3)(vue-tsc@3.3.9(typescript@6.0.3)) tinyexec: 1.3.0 tinyglobby: 0.2.17 tree-kill: 1.2.2 @@ -8944,7 +9132,7 @@ snapshots: verkit: 0.3.2 optionalDependencies: tsx: 4.23.12 - typescript: 6.0.2 + typescript: 6.0.3 unrun: 0.2.22(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)(synckit@0.11.13) transitivePeerDependencies: - '@typescript/native-preview' @@ -8982,18 +9170,41 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2): + typescript-eslint@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.2) - '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/eslint-plugin': 8.67.0(@typescript-eslint/parser@8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3))(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.67.0(supports-color@7.2.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.67.0(eslint@10.8.1(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.3) eslint: 10.8.1(jiti@2.7.0)(supports-color@7.2.0) - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - typescript@6.0.2: {} + typescript@6.0.3: {} + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 uc.micro@2.1.0: {} @@ -9049,9 +9260,9 @@ snapshots: util-deprecate@1.0.2: {} - valibot@1.4.2(typescript@6.0.2): + valibot@1.4.2(typescript@6.0.3): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 verkit@0.3.2: {} @@ -9089,7 +9300,7 @@ snapshots: jiti: 2.7.0 tsx: 4.23.12 - vitepress@1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(lightningcss@1.33.0)(postcss@8.5.26)(search-insights@2.17.3)(typescript@6.0.2): + vitepress@1.6.4(@algolia/client-search@5.56.0)(@types/node@26.2.0)(lightningcss@1.33.0)(postcss@8.5.26)(search-insights@2.17.3)(typescript@6.0.3): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.56.0)(search-insights@2.17.3) @@ -9098,17 +9309,17 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.2)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@26.2.0)(lightningcss@1.33.0))(vue@3.5.41(typescript@6.0.3)) '@vue/devtools-api': 7.7.10 '@vue/shared': 3.5.41 - '@vueuse/core': 12.8.2(typescript@6.0.2) - '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@6.0.2) + '@vueuse/core': 12.8.2(typescript@6.0.3) + '@vueuse/integrations': 12.8.2(focus-trap@7.8.0)(typescript@6.0.3) focus-trap: 7.8.0 mark.js: 8.11.1 minisearch: 7.2.0 shiki: 2.5.0 vite: 5.4.21(@types/node@26.2.0)(lightningcss@1.33.0) - vue: 3.5.41(typescript@6.0.2) + vue: 3.5.41(typescript@6.0.3) optionalDependencies: postcss: 8.5.26 transitivePeerDependencies: @@ -9187,28 +9398,28 @@ snapshots: transitivePeerDependencies: - supports-color - vue-router@4.6.4(vue@3.5.26(typescript@6.0.2)): + vue-router@4.6.4(vue@3.5.26(typescript@6.0.3)): dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.26(typescript@6.0.2) + vue: 3.5.26(typescript@6.0.3) - vue-tsc@3.3.9(typescript@6.0.2): + vue-tsc@3.3.9(typescript@6.0.3): dependencies: - '@volar/typescript': 2.4.28(typescript@6.0.2) + '@volar/typescript': 2.4.28(typescript@6.0.3) '@vue/language-core': 3.3.9 - typescript: 6.0.2 + typescript: 6.0.3 - vue@3.5.26(typescript@6.0.2): + vue@3.5.26(typescript@6.0.3): dependencies: '@vue/compiler-dom': 3.5.26 '@vue/compiler-sfc': 3.5.26 '@vue/runtime-dom': 3.5.26 - '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@6.0.2)) + '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@6.0.3)) '@vue/shared': 3.5.26 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 - vue@3.5.41(typescript@6.0.2): + vue@3.5.41(typescript@6.0.3): dependencies: '@vue/compiler-dom': 3.5.41 '@vue/compiler-sfc': 3.5.41 @@ -9216,7 +9427,7 @@ snapshots: '@vue/server-renderer': 3.5.41 '@vue/shared': 3.5.41 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 w3c-keyname@2.2.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 11b1d988..cb5015e7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,7 +5,7 @@ packages: overrides: postcss: 8.5.26 - typescript: 6.0.2 + typescript: 6.0.3 allowBuilds: '@prisma/engines': true diff --git a/tools/build-scripts/package.json b/tools/build-scripts/package.json index 0933e1d5..6e91b364 100644 --- a/tools/build-scripts/package.json +++ b/tools/build-scripts/package.json @@ -4,11 +4,11 @@ "version": "0.0.0", "type": "module", "scripts": { - "build": "tsc -p tsconfig.json", + "build": "pnpm -w tsc7 -p tools/build-scripts/tsconfig.json", "dev": "node -e \"console.log('dev not configured')\"", "lint": "node -e \"console.log('lint not configured')\"", "test": "node -e \"console.log('test not configured')\"", - "typecheck": "tsc -b", + "typecheck": "pnpm -w tsc7 -b tools/build-scripts/tsconfig.json", "build:server": "node ./build-server.mjs" } } diff --git a/tools/build-scripts/tsconfig.json b/tools/build-scripts/tsconfig.json index f94e66c7..2c11d91b 100644 --- a/tools/build-scripts/tsconfig.json +++ b/tools/build-scripts/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "baseUrl": "../..", "outDir": "dist", "rootDir": "src", "composite": true diff --git a/tools/check-typescript-toolchain.mjs b/tools/check-typescript-toolchain.mjs new file mode 100644 index 00000000..a1a6f740 --- /dev/null +++ b/tools/check-typescript-toolchain.mjs @@ -0,0 +1,38 @@ +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const workspaceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const expectedApiVersion = '6.0.3'; +const expectedCliVersion = '7.0.2'; + +const consumers = [ + ['root', 'package.json'], + ['game frontend', 'app/game-frontend/package.json'], + ['gateway frontend', 'app/gateway-frontend/package.json'], +]; + +for (const [label, packagePath] of consumers) { + const requireFromConsumer = createRequire(path.join(workspaceRoot, packagePath)); + const version = requireFromConsumer('typescript').version; + if (version !== expectedApiVersion) { + throw new Error(`${label} resolved TypeScript API ${version}; expected ${expectedApiVersion}`); + } +} + +const nativeTscPath = path.join(workspaceRoot, 'node_modules/@typescript/native/bin/tsc'); +const nativeResult = spawnSync(process.execPath, [nativeTscPath, '--version'], { + cwd: workspaceRoot, + encoding: 'utf8', +}); +if (nativeResult.status !== 0) { + throw new Error(nativeResult.stderr || nativeResult.stdout || 'TypeScript 7 tsc failed'); +} + +const cliVersion = nativeResult.stdout.trim().replace(/^Version\s+/, ''); +if (cliVersion !== expectedCliVersion) { + throw new Error(`native tsc resolved ${cliVersion}; expected ${expectedCliVersion}`); +} + +console.log(`TypeScript API ${expectedApiVersion}; native tsc ${expectedCliVersion}`); diff --git a/tools/integration-tests/package.json b/tools/integration-tests/package.json index 1754e7db..c0be8f1a 100644 --- a/tools/integration-tests/package.json +++ b/tools/integration-tests/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "test:integration": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b" + "typecheck": "pnpm -w tsc7 -b tools/integration-tests/tsconfig.json" }, "dependencies": { "@sammo-ts/common": "workspace:*", diff --git a/tools/legacy-db-migration/package.json b/tools/legacy-db-migration/package.json index 5e243037..4f53c8cc 100644 --- a/tools/legacy-db-migration/package.json +++ b/tools/legacy-db-migration/package.json @@ -11,7 +11,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run --config vitest.config.ts", - "typecheck": "tsc -b", + "typecheck": "pnpm -w tsc7 -b tools/legacy-db-migration/tsconfig.json", "migrate": "tsx src/cli.ts" }, "dependencies": { @@ -23,7 +23,7 @@ "@types/pg": "^8.21.0", "tsdown": "^0.22.14", "tsx": "^4.23.12", - "typescript": "6.0.2", + "typescript": "6.0.3", "vitest": "^4.1.10" } } diff --git a/tools/test-dbms/tsconfig.json b/tools/test-dbms/tsconfig.json index c3edd9a7..3af198ee 100644 --- a/tools/test-dbms/tsconfig.json +++ b/tools/test-dbms/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "baseUrl": "../..", "noEmit": true, "types": ["node"] }, diff --git a/tsconfig.base.json b/tsconfig.base.json index 01c24d06..ac7e186d 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -8,7 +8,6 @@ "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, - "ignoreDeprecations": "6.0", "declaration": true, "declarationMap": true, "sourceMap": true diff --git a/tsconfig.paths.json b/tsconfig.paths.json index 50c574ab..ee096f82 100644 --- a/tsconfig.paths.json +++ b/tsconfig.paths.json @@ -1,22 +1,21 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", "paths": { - "@sammo-ts/common": ["packages/common/src/index.ts"], - "@sammo-ts/common/*": ["packages/common/src/*"], - "@sammo-ts/infra": ["packages/infra/src/index.ts"], - "@sammo-ts/infra/*": ["packages/infra/src/*"], - "@sammo-ts/logic": ["packages/logic/src/index.ts"], - "@sammo-ts/logic/*": ["packages/logic/src/*"], - "@sammo-ts/logic/resources": ["packages/logic/src/resources/index.ts"], - "@sammo-ts/logic/resources/*": ["packages/logic/src/resources/*"], - "@sammo-ts/gateway-api": ["app/gateway-api/src/index.ts"], - "@sammo-ts/gateway-api/*": ["app/gateway-api/src/*"], - "@sammo-ts/game-api": ["app/game-api/src/index.ts"], - "@sammo-ts/game-api/*": ["app/game-api/src/*"], - "@sammo-ts/game-engine": ["app/game-engine/src/index.ts"], - "@sammo-ts/game-engine/*": ["app/game-engine/src/*"] + "@sammo-ts/common": ["./packages/common/src/index.ts"], + "@sammo-ts/common/*": ["./packages/common/src/*"], + "@sammo-ts/infra": ["./packages/infra/src/index.ts"], + "@sammo-ts/infra/*": ["./packages/infra/src/*"], + "@sammo-ts/logic": ["./packages/logic/src/index.ts"], + "@sammo-ts/logic/*": ["./packages/logic/src/*"], + "@sammo-ts/logic/resources": ["./packages/logic/src/resources/index.ts"], + "@sammo-ts/logic/resources/*": ["./packages/logic/src/resources/*"], + "@sammo-ts/gateway-api": ["./app/gateway-api/src/index.ts"], + "@sammo-ts/gateway-api/*": ["./app/gateway-api/src/*"], + "@sammo-ts/game-api": ["./app/game-api/src/index.ts"], + "@sammo-ts/game-api/*": ["./app/game-api/src/*"], + "@sammo-ts/game-engine": ["./app/game-engine/src/index.ts"], + "@sammo-ts/game-engine/*": ["./app/game-engine/src/*"] } } } From 7ac9035bbc618c60993378b7b0ebd63759802e36 Mon Sep 17 00:00:00 2001 From: hided62 Date: Thu, 13 Aug 2026 00:00:27 +0000 Subject: [PATCH 2/5] fix: render Ref-compatible GUI display names --- app/game-api/src/router/archive/index.ts | 46 ++- app/game-api/src/router/general/index.ts | 64 +++- .../nation/endpoints/getBattleCenter.ts | 60 +++- .../router/nation/endpoints/getGeneralList.ts | 20 +- app/game-api/src/router/nation/shared.ts | 12 +- app/game-api/src/router/public/index.ts | 327 +++++++++--------- app/game-api/src/router/world/directory.ts | 3 +- app/game-api/src/services/gameDisplayNames.ts | 138 ++++++++ app/game-api/test/archiveRouter.test.ts | 10 +- app/game-api/test/gameDisplayNames.test.ts | 32 ++ .../test/inGameMenuPermissions.test.ts | 55 +++ .../test/nationGeneralSecretRouter.test.ts | 3 + app/game-engine/src/turn/selectPoolService.ts | 11 + app/game-frontend/e2e/inGameInfo.spec.ts | 13 + app/game-frontend/e2e/inGameMenus.spec.ts | 107 +++++- app/game-frontend/e2e/pastPlays.spec.ts | 11 + .../src/components/main/CityBasicCard.vue | 7 +- .../src/components/main/GeneralBasicCard.vue | 9 +- .../src/components/main/NationBasicCard.vue | 12 +- app/game-frontend/src/utils/nationFormat.ts | 25 +- app/game-frontend/src/views/AuctionView.vue | 8 +- .../src/views/BattleCenterView.vue | 6 +- app/game-frontend/src/views/MyPageView.vue | 50 ++- .../src/views/NationGeneralsView.vue | 11 +- .../src/views/NationInfoView.vue | 5 +- .../src/views/NationListView.vue | 14 +- app/game-frontend/src/views/PastPlaysView.vue | 2 +- .../src/views/SelectGeneralView.vue | 14 +- app/game-frontend/test/nationFormat.test.ts | 20 ++ 29 files changed, 834 insertions(+), 261 deletions(-) create mode 100644 app/game-api/src/services/gameDisplayNames.ts create mode 100644 app/game-api/test/gameDisplayNames.test.ts create mode 100644 app/game-frontend/test/nationFormat.test.ts diff --git a/app/game-api/src/router/archive/index.ts b/app/game-api/src/router/archive/index.ts index 50bc33c3..275be3b7 100644 --- a/app/game-api/src/router/archive/index.ts +++ b/app/game-api/src/router/archive/index.ts @@ -3,7 +3,9 @@ import { z } from 'zod'; import { asRecord } from '@sammo-ts/common'; +import { resolveOfficerLevelName, sanitizeInternalDisplayCode } from '../../services/gameDisplayNames.js'; import { readOnlyAuthedProcedure, router } from '../../trpc.js'; +import { loadTraitNames } from '../nation/shared.js'; const numberOrNull = (value: unknown): number | null => typeof value === 'number' && Number.isFinite(value) ? value : null; @@ -87,6 +89,31 @@ export const archiveRouter = router({ nationByServerAndId.set(key, nation); } } + const archivedRoles = generals.map((general) => { + const data = asRecord(general.data); + const role = asRecord(data.role); + return { + personal: displayTextOrNull(data.personalCode ?? data.personal ?? role.personality), + special: displayTextOrNull(data.specialCode ?? data.special ?? role.specialDomestic), + special2: displayTextOrNull(data.special2Code ?? data.special2 ?? role.specialWar), + }; + }); + const [personalityNames, domesticNames, warNames] = await Promise.all([ + loadTraitNames( + archivedRoles.map((role) => role.personal), + 'personality' + ), + loadTraitNames( + archivedRoles.map((role) => role.special), + 'domestic' + ), + loadTraitNames( + archivedRoles.map((role) => role.special2), + 'war' + ), + ]); + const displayRole = (value: string | null, names: Awaited>): string | null => + value ? (names.get(value)?.name ?? sanitizeInternalDisplayCode(value)) : null; const seasons = new Map< string, @@ -110,6 +137,7 @@ export const archiveRouter = router({ experience: number | null; dedication: number | null; officerLevel: number | null; + officerLevelText: string | null; personal: string | null; special: string | null; special2: string | null; @@ -118,7 +146,7 @@ export const archiveRouter = router({ } >(); - for (const general of generals) { + for (const [generalIndex, general] of generals.entries()) { const game = gameByServer.get(general.serverId); let season = seasons.get(general.serverId); if (!season) { @@ -136,10 +164,12 @@ export const archiveRouter = router({ const data = asRecord(general.data); const stats = asRecord(data.stats); - const role = asRecord(data.role); const nationId = firstNumber(data, 'nationId', 'nation') ?? 0; const nation = nationByServerAndId.get(`${general.serverId}:${nationId}`); const nationData = asRecord(nation?.data); + const officerLevel = firstNumber(data, 'officerLevel', 'officer_level'); + const nationLevel = firstNumber(nationData, 'level', 'nationLevel'); + const archivedRole = archivedRoles[generalIndex]!; season.generals.push({ generalNo: general.generalNo, name: general.name, @@ -152,10 +182,14 @@ export const archiveRouter = router({ intel: firstNumber(data, 'intel', 'intelligence') ?? numberOrNull(stats.intelligence), experience: numberOrNull(data.experience), dedication: numberOrNull(data.dedication), - officerLevel: firstNumber(data, 'officerLevel', 'officer_level'), - personal: displayTextOrNull(data.personalCode ?? data.personal ?? role.personality), - special: displayTextOrNull(data.specialCode ?? data.special ?? role.specialDomestic), - special2: displayTextOrNull(data.special2Code ?? data.special2 ?? role.specialWar), + officerLevel, + officerLevelText: + officerLevel === null + ? null + : resolveOfficerLevelName(officerLevel, nationLevel === null ? undefined : nationLevel), + personal: displayRole(archivedRole.personal, personalityNames), + special: displayRole(archivedRole.special, domesticNames), + special2: displayRole(archivedRole.special2, warNames), historyCount: parseHistory(data.history).length, }); } diff --git a/app/game-api/src/router/general/index.ts b/app/game-api/src/router/general/index.ts index 0ca59a00..da826725 100644 --- a/app/game-api/src/router/general/index.ts +++ b/app/game-api/src/router/general/index.ts @@ -17,6 +17,16 @@ import { import { ConflictingTurnDaemonCommandError } from '../../daemon/databaseTransport.js'; import { resolveAccessWindows } from '../../services/generalAccess.js'; import { adjustAccountIconForUser } from '../../services/accountIconSync.js'; +import { + loadCrewTypeDisplayNames, + loadItemDisplayNames, + resolveCityLevelName, + resolveDedicationLevelName, + resolveNationLevelName, + resolveOfficerLevelName, + resolveRegionName, + sanitizeInternalDisplayCode, +} from '../../services/gameDisplayNames.js'; import { getMyGeneral } from '../shared/general.js'; import { loadTraitNames, resolveNationNotice, type TraitNameMap } from '../nation/shared.js'; @@ -155,7 +165,7 @@ const resolveTraitDisplayName = (code: string, names: TraitNameMap): string => { return loadedName; } // Ref는 class getName()을 표시하므로 로더가 모르는 선택적 특기도 raw namespace는 노출하지 않는다. - return code.replace(/^che_(?:event_)?/u, ''); + return sanitizeInternalDisplayCode(code); }; const resolveUserSettings = (meta: Record) => { @@ -244,7 +254,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => { return null; } - const [city, nation, worldState] = await Promise.all([ + const [city, queriedNation, worldState] = await Promise.all([ general.cityId > 0 ? ctx.db.city.findUnique({ where: { id: general.cityId }, @@ -291,18 +301,36 @@ export const getGeneralContext = async (ctx: GameApiContext) => { : Promise.resolve(NEUTRAL_NATION_CONTEXT), ctx.db.worldState.findFirst({ select: { config: true } }), ]); + const nation = queriedNation ?? NEUTRAL_NATION_CONTEXT; - const [personalityNames, domesticNames, warNames] = await Promise.all([ + const [capitalCity, cityNation] = await Promise.all([ + nation.capitalCityId + ? ctx.db.city.findUnique({ where: { id: nation.capitalCityId }, select: { name: true } }) + : Promise.resolve(null), + city && city.nationId > 0 + ? ctx.db.nation.findUnique({ where: { id: city.nationId }, select: { name: true } }) + : Promise.resolve(null), + ]); + const [personalityNames, domesticNames, warNames, nationTypeNames, crewTypeNames, itemNames] = await Promise.all([ loadTraitNames([general.personalCode], 'personality'), loadTraitNames([general.specialCode], 'domestic'), loadTraitNames([general.special2Code], 'war'), + loadTraitNames([nation.typeCode], 'nation'), + loadCrewTypeDisplayNames(worldState, ctx.profile.id), + loadItemDisplayNames([general.horseCode, general.weaponCode, general.bookCode, general.itemCode]), ]); const metaRecord = asRecord(general.meta); const worldConfig = asRecord(worldState?.config); const constValues = asRecord(worldConfig.const ?? worldConfig.consts); + const maxDedicationLevel = readNumber(constValues.maxDedLevel, 30); const settings = resolveUserSettings(metaRecord); const penalties = resolvePenalty(general.penalty); + const dedicationLevel = readNumber(metaRecord.dedlevel, 0); + const itemName = (code: string | null): string | null => { + const normalized = normalizeItemCode(code); + return normalized ? (itemNames.get(normalized) ?? sanitizeInternalDisplayCode(normalized)) : null; + }; return { general: { @@ -315,6 +343,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => { picture: general.picture, imageServer: general.imageServer, officerLevel: general.officerLevel, + officerLevelText: resolveOfficerLevelName(general.officerLevel, nation.level), stats: { leadership: general.leadership, strength: general.strength, @@ -331,6 +360,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => { age: general.age, turnTime: general.turnTime.toISOString(), crewTypeId: general.crewTypeId, + crewTypeName: crewTypeNames.get(general.crewTypeId) ?? '-', traits: { personal: resolveTraitDisplayName(general.personalCode, personalityNames), specialDomestic: resolveTraitDisplayName(general.specialCode, domesticNames), @@ -338,7 +368,8 @@ export const getGeneralContext = async (ctx: GameApiContext) => { }, progression: { experienceLevel: readNumber(metaRecord.explevel, 0), - dedicationLevel: readNumber(metaRecord.dedlevel, 0), + dedicationLevel, + dedicationText: resolveDedicationLevelName(dedicationLevel, maxDedicationLevel), statExperience: { leadership: readNumber(metaRecord.leadership_exp, 0), strength: readNumber(metaRecord.strength_exp, 0), @@ -353,6 +384,12 @@ export const getGeneralContext = async (ctx: GameApiContext) => { book: normalizeItemCode(general.bookCode), item: normalizeItemCode(general.itemCode), }, + itemNames: { + horse: itemName(general.horseCode), + weapon: itemName(general.weaponCode), + book: itemName(general.bookCode), + item: itemName(general.itemCode), + }, }, iconChoices: ctx.auth?.user.canUseGeneralPicture === false ? [] : (ctx.auth?.user.icons ?? []), canChangeIcon: general.npcState === 0 && ctx.auth?.user.canUseGeneralPicture !== false, @@ -360,8 +397,23 @@ export const getGeneralContext = async (ctx: GameApiContext) => { typeof metaRecord.generalIconChangedAt === 'string' ? new Date(new Date(metaRecord.generalIconChangedAt).getTime() + 24 * 60 * 60 * 1000).toISOString() : null, - city, - nation, + city: city + ? { + ...city, + levelName: resolveCityLevelName(city.level), + regionName: resolveRegionName(city.region), + nationName: city.nationId > 0 ? (cityNation?.name ?? '-') : '공백지', + } + : null, + nation: { + ...nation, + levelName: resolveNationLevelName(nation.level), + typeName: + nation.id === 0 + ? '해당 없음' + : (nationTypeNames.get(nation.typeCode)?.name ?? sanitizeInternalDisplayCode(nation.typeCode)), + capitalCityName: nation.id === 0 ? null : (capitalCity?.name ?? null), + }, settings, penalties, }; diff --git a/app/game-api/src/router/nation/endpoints/getBattleCenter.ts b/app/game-api/src/router/nation/endpoints/getBattleCenter.ts index c5e3e1f2..5ce0cb83 100644 --- a/app/game-api/src/router/nation/endpoints/getBattleCenter.ts +++ b/app/game-api/src/router/nation/endpoints/getBattleCenter.ts @@ -4,8 +4,15 @@ import { asRecord } from '@sammo-ts/common'; import { LogCategory } from '@sammo-ts/logic'; import { accessAuthedProcedure } from '../../../trpc.js'; +import { + loadCrewTypeDisplayNames, + loadItemDisplayNames, + resolveDedicationLevelName, + resolveOfficerLevelName, + sanitizeInternalDisplayCode, +} from '../../../services/gameDisplayNames.js'; import { getMyGeneral } from '../../shared/general.js'; -import { assertNationAccess, formatDateTime, resolveNationPermission } from '../shared.js'; +import { assertNationAccess, formatDateTime, loadTraitNames, resolveNationPermission } from '../shared.js'; export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => { const me = await getMyGeneral(ctx); @@ -98,6 +105,36 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => { typeof constValues.upgradeLimit === 'number' && Number.isFinite(constValues.upgradeLimit) ? constValues.upgradeLimit : 30; + const maxDedicationLevel = + typeof constValues.maxDedLevel === 'number' && Number.isFinite(constValues.maxDedLevel) + ? Math.max(0, Math.trunc(constValues.maxDedLevel)) + : 30; + const [personalityNames, domesticNames, warNames, crewTypeNames, itemNames] = await Promise.all([ + loadTraitNames( + generalRows.map((general) => general.personalCode), + 'personality' + ), + loadTraitNames( + generalRows.map((general) => general.specialCode), + 'domestic' + ), + loadTraitNames( + generalRows.map((general) => general.special2Code), + 'war' + ), + loadCrewTypeDisplayNames(worldState, ctx.profile.id), + loadItemDisplayNames( + generalRows.flatMap((general) => [ + general.weaponCode, + general.bookCode, + general.horseCode, + general.itemCode, + ]) + ), + ]); + const traitName = (code: string, names: Awaited>): string => + names.get(code)?.name ?? sanitizeInternalDisplayCode(code); + const itemName = (code: string): string => itemNames.get(code) ?? sanitizeInternalDisplayCode(code); const generals = generalRows.map((general) => { const meta = @@ -108,6 +145,11 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => { const value = meta[key]; return typeof value === 'number' && Number.isFinite(value) ? value : 0; }; + const storedDedicationLevel = metaNumber('dedlevel'); + const dedicationLevel = + storedDedicationLevel > 0 + ? storedDedicationLevel + : Math.max(0, Math.min(Math.ceil(Math.sqrt(general.dedication) / 10), maxDedicationLevel)); return { id: general.id, name: general.name, @@ -115,6 +157,7 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => { imageServer: general.imageServer, npcState: general.npcState, officerLevel: general.officerLevel, + officerLevelText: resolveOfficerLevelName(general.officerLevel, nation.level), cityId: general.cityId, turnTime: formatDateTime(general.turnTime), recentWar: formatDateTime(general.recentWarTime), @@ -134,19 +177,28 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => { atmos: general.atmos, age: general.age, crewTypeId: general.crewTypeId, + crewTypeName: crewTypeNames.get(general.crewTypeId) ?? '-', equipment: { weapon: general.weaponCode, book: general.bookCode, horse: general.horseCode, item: general.itemCode, }, + equipmentNames: { + weapon: itemName(general.weaponCode), + book: itemName(general.bookCode), + horse: itemName(general.horseCode), + item: itemName(general.itemCode), + }, traits: { - personal: general.personalCode, - specialDomestic: general.specialCode, - specialWar: general.special2Code, + personal: traitName(general.personalCode, personalityNames), + specialDomestic: traitName(general.specialCode, domesticNames), + specialWar: traitName(general.special2Code, warNames), }, progression: { experienceLevel: metaNumber('explevel'), + dedicationLevel, + dedicationText: resolveDedicationLevelName(dedicationLevel, maxDedicationLevel), statExperience: { leadership: metaNumber('leadership_exp'), strength: metaNumber('strength_exp'), diff --git a/app/game-api/src/router/nation/endpoints/getGeneralList.ts b/app/game-api/src/router/nation/endpoints/getGeneralList.ts index 715765ec..6909611c 100644 --- a/app/game-api/src/router/nation/endpoints/getGeneralList.ts +++ b/app/game-api/src/router/nation/endpoints/getGeneralList.ts @@ -1,6 +1,8 @@ import { TRPCError } from '@trpc/server'; +import { asNumber, asRecord } from '@sammo-ts/common'; import { accessAuthedProcedure } from '../../../trpc.js'; +import { resolveDedicationLevelName, sanitizeInternalDisplayCode } from '../../../services/gameDisplayNames.js'; import { getMyGeneral } from '../../shared/general.js'; import { assertNationAccess, @@ -15,8 +17,8 @@ const experienceLevel = (experience: number): number => 0, Math.min(100, experience < 1000 ? Math.floor(experience / 100) : Math.floor(Math.sqrt(experience / 10))) ); -const dedicationLevel = (dedication: number): number => - Math.max(0, Math.min(10, Math.ceil(Math.sqrt(dedication) / 10))); +const dedicationLevel = (dedication: number, maxLevel: number): number => + Math.max(0, Math.min(maxLevel, Math.ceil(Math.sqrt(dedication) / 10))); export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => { const general = await getMyGeneral(ctx); @@ -85,14 +87,22 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => { const accessByGeneral = new Map(accessRows.map((entry) => [entry.generalId, entry.refreshScoreTotal])); const nationTrait = (await loadTraitNames([nation.typeCode], 'nation')).get(nation.typeCode); const permission = resolveNationPermission(general, nation.meta, true); + const config = asRecord(worldState?.config); + const maxDedicationLevel = Math.max(0, Math.trunc(asNumber(asRecord(config.const).maxDedLevel, 30))); const visibleList = list.map((entry) => { + const entryDedicationLevel = dedicationLevel(entry.dedication, maxDedicationLevel); + const dedicationDisplay = { + dedicationLevel: entryDedicationLevel, + dedicationText: resolveDedicationLevelName(entryDedicationLevel, maxDedicationLevel), + bill: entryDedicationLevel * 200 + 400, + }; const { permission: _targetPermission, ...safeEntry } = entry; if (permission >= 1) { return { ...safeEntry, refreshScoreTotal: accessByGeneral.get(entry.id) ?? 0, experienceLevel: experienceLevel(entry.experience), - dedicationLevel: dedicationLevel(entry.dedication), + ...dedicationDisplay, }; } const { crew: _crew, experience: _experience, dedication: _dedication, ...visible } = safeEntry; @@ -105,7 +115,7 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => { officerCity: 0, officerCityName: null, experienceLevel: experienceLevel(entry.experience), - dedicationLevel: dedicationLevel(entry.dedication), + ...dedicationDisplay, }; }); @@ -118,7 +128,7 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => { typeCode: nation.typeCode, type: { key: nation.typeCode, - name: nationTrait?.name ?? nation.typeCode, + name: nationTrait?.name ?? sanitizeInternalDisplayCode(nation.typeCode), info: nationTrait?.info ?? '', }, capitalCityId: nation.capitalCityId ?? 0, diff --git a/app/game-api/src/router/nation/shared.ts b/app/game-api/src/router/nation/shared.ts index 88eb0c4f..21cd21c5 100644 --- a/app/game-api/src/router/nation/shared.ts +++ b/app/game-api/src/router/nation/shared.ts @@ -28,6 +28,7 @@ import { import type { GameApiContext, InputJsonValue, WorldStateRow } from '../../context.js'; import { purifyNationHtml } from '../../security/nationHtml.js'; +import { sanitizeInternalDisplayCode } from '../../services/gameDisplayNames.js'; import { resolveSecretPermission } from '../shared/secretPermission.js'; export type PermissionKind = 'normal' | 'ambassador' | 'auditor'; @@ -313,10 +314,7 @@ export const loadTraitNames = async (keys: Array, kind: keyof Tra } } if (eventFiltered.length) { - const modules = await loadEventDomesticTraitModules( - eventFiltered, - new EventDomesticTraitLoader() - ); + const modules = await loadEventDomesticTraitModules(eventFiltered, new EventDomesticTraitLoader()); for (const module of modules) { cache.set(module.key, { name: module.name, info: module.info ?? '' }); } @@ -513,21 +511,21 @@ export const mapGeneralList = async ( personality: personalityKey ? { key: personalityKey, - name: personalityMap.get(personalityKey)?.name ?? personalityKey, + name: personalityMap.get(personalityKey)?.name ?? sanitizeInternalDisplayCode(personalityKey), info: personalityMap.get(personalityKey)?.info ?? '', } : null, specialDomestic: domesticKey ? { key: domesticKey, - name: domesticMap.get(domesticKey)?.name ?? domesticKey, + name: domesticMap.get(domesticKey)?.name ?? sanitizeInternalDisplayCode(domesticKey), info: domesticMap.get(domesticKey)?.info ?? '', } : null, specialWar: warKey ? { key: warKey, - name: warMap.get(warKey)?.name ?? warKey, + name: warMap.get(warKey)?.name ?? sanitizeInternalDisplayCode(warKey), info: warMap.get(warKey)?.info ?? '', } : null, diff --git a/app/game-api/src/router/public/index.ts b/app/game-api/src/router/public/index.ts index c503eccd..67c8bef5 100644 --- a/app/game-api/src/router/public/index.ts +++ b/app/game-api/src/router/public/index.ts @@ -8,6 +8,7 @@ import { zWorldStateConfig, zWorldStateMeta } from '../../context.js'; import { loadMapLayout } from '../../maps/mapLayout.js'; import { loadPublicMap } from '../../maps/worldMap.js'; import { accessPages, recordGeneralAccess } from '../../services/generalAccess.js'; +import { sanitizeInternalDisplayCode } from '../../services/gameDisplayNames.js'; import { accessInputProcedure, procedure, router, sessionActivityProcedure } from '../../trpc.js'; import { loadTraitNames } from '../nation/shared.js'; @@ -478,174 +479,170 @@ export const publicRouter = router({ })); }), getNpcList: accessInputProcedure( - z - .object({ - sort: z.number().int().min(1).max(8).catch(1).optional(), - includeAllWithToken: z.boolean().optional(), - }) - .optional() - ) - .query(async ({ ctx, input }) => { - const sort = (input?.sort ?? 1) as NpcListSort; - const includeAllWithToken = input?.includeAllWithToken === true; - if (includeAllWithToken && !ctx.auth) { - throw new TRPCError({ code: 'UNAUTHORIZED' }); - } - const now = new Date(Math.floor(Date.now() / 1000) * 1000); - const poolGeneralIds = includeAllWithToken - ? [] - : ( - await ctx.db.selectPoolEntry.findMany({ - where: { generalId: { not: null } }, - select: { generalId: true }, - }) - ).flatMap(({ generalId }) => (generalId === null ? [] : [generalId])); - const [generals, nations, activeTokens, worldState] = await Promise.all([ - ctx.db.general.findMany({ - ...(includeAllWithToken - ? {} - : { - where: { - OR: [ - { npcState: 1 }, - { npcState: 0, id: { in: poolGeneralIds } }, - ], - }, - }), - select: { - id: true, - name: true, - picture: true, - imageServer: true, - npcState: true, - age: true, - officerLevel: true, - nationId: true, - leadership: true, - strength: true, - intel: true, - experience: true, - dedication: true, - personalCode: true, - specialCode: true, - special2Code: true, - meta: true, - }, - orderBy: { id: 'asc' }, - }), - ctx.db.nation.findMany({ - select: { id: true, name: true, level: true }, - }), - includeAllWithToken - ? ctx.db.npcSelectionToken.findMany({ - where: { validUntil: { gte: now } }, - select: { pickResult: true }, - }) - : [], - includeAllWithToken - ? ctx.db.worldState.findFirst({ - select: { config: true }, - }) - : null, - ]); + z + .object({ + sort: z.number().int().min(1).max(8).catch(1).optional(), + includeAllWithToken: z.boolean().optional(), + }) + .optional() + ).query(async ({ ctx, input }) => { + const sort = (input?.sort ?? 1) as NpcListSort; + const includeAllWithToken = input?.includeAllWithToken === true; + if (includeAllWithToken && !ctx.auth) { + throw new TRPCError({ code: 'UNAUTHORIZED' }); + } + const now = new Date(Math.floor(Date.now() / 1000) * 1000); + const poolGeneralIds = includeAllWithToken + ? [] + : ( + await ctx.db.selectPoolEntry.findMany({ + where: { generalId: { not: null } }, + select: { generalId: true }, + }) + ).flatMap(({ generalId }) => (generalId === null ? [] : [generalId])); + const [generals, nations, activeTokens, worldState] = await Promise.all([ + ctx.db.general.findMany({ + ...(includeAllWithToken + ? {} + : { + where: { + OR: [{ npcState: 1 }, { npcState: 0, id: { in: poolGeneralIds } }], + }, + }), + select: { + id: true, + name: true, + picture: true, + imageServer: true, + npcState: true, + age: true, + officerLevel: true, + nationId: true, + leadership: true, + strength: true, + intel: true, + experience: true, + dedication: true, + personalCode: true, + specialCode: true, + special2Code: true, + meta: true, + }, + orderBy: { id: 'asc' }, + }), + ctx.db.nation.findMany({ + select: { id: true, name: true, level: true }, + }), + includeAllWithToken + ? ctx.db.npcSelectionToken.findMany({ + where: { validUntil: { gte: now } }, + select: { pickResult: true }, + }) + : [], + includeAllWithToken + ? ctx.db.worldState.findFirst({ + select: { config: true }, + }) + : null, + ]); - const personalityKeys = generals.map((general) => normalizeTraitKey(general.personalCode)); - const domesticKeys = generals.map((general) => normalizeTraitKey(general.specialCode)); - const warKeys = generals.map((general) => normalizeTraitKey(general.special2Code)); - const [personalityMap, domesticMap, warMap] = await Promise.all([ - loadTraitNames(personalityKeys, 'personality'), - loadTraitNames(domesticKeys, 'domestic'), - loadTraitNames(warKeys, 'war'), - ]); - const nationMap = new Map(nations.map((nation) => [nation.id, nation])); - const worldConfig = asRecord(worldState?.config); - const worldConstants = asRecord(worldConfig.const); - const maxLevel = Math.max(0, Math.floor(asNumber(worldConstants.maxLevel, 255))); - const maxDedLevel = Math.max(0, Math.floor(asNumber(worldConstants.maxDedLevel, 30))); + const personalityKeys = generals.map((general) => normalizeTraitKey(general.personalCode)); + const domesticKeys = generals.map((general) => normalizeTraitKey(general.specialCode)); + const warKeys = generals.map((general) => normalizeTraitKey(general.special2Code)); + const [personalityMap, domesticMap, warMap] = await Promise.all([ + loadTraitNames(personalityKeys, 'personality'), + loadTraitNames(domesticKeys, 'domestic'), + loadTraitNames(warKeys, 'war'), + ]); + const nationMap = new Map(nations.map((nation) => [nation.id, nation])); + const worldConfig = asRecord(worldState?.config); + const worldConstants = asRecord(worldConfig.const); + const maxLevel = Math.max(0, Math.floor(asNumber(worldConstants.maxLevel, 255))); + const maxDedLevel = Math.max(0, Math.floor(asNumber(worldConstants.maxDedLevel, 30))); - // Legacy a_npcList.php shows select_pool humans first and possessed npc=1 rows. - // Unpossessed npc=2 candidates belong only to the token-aware selection screen. - // selection list instead consumes the raw id-ordered full list before its own comparator. - const sourceRows = includeAllWithToken - ? generals - : [ - ...generals.filter((general) => general.npcState === 0), - ...generals.filter((general) => general.npcState === 1), - ]; - const rows = sourceRows.map((general) => { - const meta = asRecord(general.meta); - const personalityKey = normalizeTraitKey(general.personalCode); - const domesticKey = normalizeTraitKey(general.specialCode); - const warKey = normalizeTraitKey(general.special2Code); - const ownerName = - general.npcState === 1 - ? typeof meta.owner_name === 'string' - ? meta.owner_name - : typeof meta.ownerName === 'string' - ? meta.ownerName - : '' - : ''; - - return { - id: general.id, - name: general.name, - picture: general.picture, - imageServer: general.imageServer, - npcState: general.npcState, - ownerName, - age: general.age, - level: includeAllWithToken - ? resolveExperienceLevel(general.experience, maxLevel) - : readFiniteMetaNumber(meta, 'explevel'), - officerLevel: general.officerLevel, - killturn: readFiniteMetaNumber(meta, 'killturn'), - nationId: general.nationId, - nationName: nationMap.get(general.nationId)?.name ?? '-', - nationLevel: nationMap.get(general.nationId)?.level ?? 0, - personality: personalityKey - ? { - key: personalityKey, - name: personalityMap.get(personalityKey)?.name ?? personalityKey, - info: personalityMap.get(personalityKey)?.info ?? '', - } - : null, - specialDomestic: domesticKey - ? { - key: domesticKey, - name: domesticMap.get(domesticKey)?.name ?? domesticKey, - info: domesticMap.get(domesticKey)?.info ?? '', - } - : null, - specialWar: warKey - ? { - key: warKey, - name: warMap.get(warKey)?.name ?? warKey, - info: warMap.get(warKey)?.info ?? '', - } - : null, - statTotal: general.leadership + general.strength + general.intel, - leadership: general.leadership, - strength: general.strength, - intelligence: general.intel, - experience: general.experience, - experienceText: resolveHonorText(general.experience), - dedication: general.dedication, - dedicationText: resolveDedicationText(general.dedication, maxDedLevel), - }; - }); - const tokenKeepCounts = Object.fromEntries( - activeTokens.flatMap((token) => - Object.entries(asRecord(token.pickResult)).flatMap(([generalId, value]) => { - const keepCount = asNumber(asRecord(value).keepCount, Number.NaN); - return Number.isFinite(keepCount) ? [[generalId, Math.max(0, Math.floor(keepCount))]] : []; - }) - ) - ); + // Legacy a_npcList.php shows select_pool humans first and possessed npc=1 rows. + // Unpossessed npc=2 candidates belong only to the token-aware selection screen. + // selection list instead consumes the raw id-ordered full list before its own comparator. + const sourceRows = includeAllWithToken + ? generals + : [ + ...generals.filter((general) => general.npcState === 0), + ...generals.filter((general) => general.npcState === 1), + ]; + const rows = sourceRows.map((general) => { + const meta = asRecord(general.meta); + const personalityKey = normalizeTraitKey(general.personalCode); + const domesticKey = normalizeTraitKey(general.specialCode); + const warKey = normalizeTraitKey(general.special2Code); + const ownerName = + general.npcState === 1 + ? typeof meta.owner_name === 'string' + ? meta.owner_name + : typeof meta.ownerName === 'string' + ? meta.ownerName + : '' + : ''; return { - sort, - generals: includeAllWithToken ? rows : sortNpcList(rows, sort), - tokenKeepCounts, + id: general.id, + name: general.name, + picture: general.picture, + imageServer: general.imageServer, + npcState: general.npcState, + ownerName, + age: general.age, + level: includeAllWithToken + ? resolveExperienceLevel(general.experience, maxLevel) + : readFiniteMetaNumber(meta, 'explevel'), + officerLevel: general.officerLevel, + killturn: readFiniteMetaNumber(meta, 'killturn'), + nationId: general.nationId, + nationName: nationMap.get(general.nationId)?.name ?? '-', + nationLevel: nationMap.get(general.nationId)?.level ?? 0, + personality: personalityKey + ? { + key: personalityKey, + name: personalityMap.get(personalityKey)?.name ?? sanitizeInternalDisplayCode(personalityKey), + info: personalityMap.get(personalityKey)?.info ?? '', + } + : null, + specialDomestic: domesticKey + ? { + key: domesticKey, + name: domesticMap.get(domesticKey)?.name ?? sanitizeInternalDisplayCode(domesticKey), + info: domesticMap.get(domesticKey)?.info ?? '', + } + : null, + specialWar: warKey + ? { + key: warKey, + name: warMap.get(warKey)?.name ?? sanitizeInternalDisplayCode(warKey), + info: warMap.get(warKey)?.info ?? '', + } + : null, + statTotal: general.leadership + general.strength + general.intel, + leadership: general.leadership, + strength: general.strength, + intelligence: general.intel, + experience: general.experience, + experienceText: resolveHonorText(general.experience), + dedication: general.dedication, + dedicationText: resolveDedicationText(general.dedication, maxDedLevel), }; - }), + }); + const tokenKeepCounts = Object.fromEntries( + activeTokens.flatMap((token) => + Object.entries(asRecord(token.pickResult)).flatMap(([generalId, value]) => { + const keepCount = asNumber(asRecord(value).keepCount, Number.NaN); + return Number.isFinite(keepCount) ? [[generalId, Math.max(0, Math.floor(keepCount))]] : []; + }) + ) + ); + + return { + sort, + generals: includeAllWithToken ? rows : sortNpcList(rows, sort), + tokenKeepCounts, + }; + }), }); diff --git a/app/game-api/src/router/world/directory.ts b/app/game-api/src/router/world/directory.ts index 47b646e4..eb5b901b 100644 --- a/app/game-api/src/router/world/directory.ts +++ b/app/game-api/src/router/world/directory.ts @@ -2,6 +2,7 @@ import { asRecord } from '@sammo-ts/common'; import { z } from 'zod'; import { accessAuthedInputProcedure, authedProcedure } from '../../trpc.js'; +import { sanitizeInternalDisplayCode } from '../../services/gameDisplayNames.js'; import { loadTraitNames } from '../nation/shared.js'; import { getMyGeneral } from '../shared/general.js'; import { resolveSecretPermission } from '../shared/secretPermission.js'; @@ -172,7 +173,7 @@ export const getNationDirectory = authedProcedure.query(async ({ ctx }) => { level: nation.level, type: { key: nation.typeCode, - name: nationTypeNames.get(nation.typeCode)?.name ?? nation.typeCode, + name: nationTypeNames.get(nation.typeCode)?.name ?? sanitizeInternalDisplayCode(nation.typeCode), }, power: readMetaNumber(nation.meta, 'power'), capitalCityId: nation.capitalCityId ?? 0, diff --git a/app/game-api/src/services/gameDisplayNames.ts b/app/game-api/src/services/gameDisplayNames.ts new file mode 100644 index 00000000..29d8e900 --- /dev/null +++ b/app/game-api/src/services/gameDisplayNames.ts @@ -0,0 +1,138 @@ +import { asRecord } from '@sammo-ts/common'; +import { isItemKey, ItemLoader } from '@sammo-ts/logic'; +import { loadUnitSetDefinitionByName } from '@sammo-ts/game-engine/scenario/unitSetLoader.js'; + +import type { WorldStateRow } from '../context.js'; + +const NATION_LEVEL_NAMES: Record = { + 0: '방랑군', + 1: '호족', + 2: '군벌', + 3: '주자사', + 4: '주목', + 5: '공', + 6: '왕', + 7: '황제', +}; + +const CITY_LEVEL_NAMES: Record = { + 1: '수', + 2: '진', + 3: '관', + 4: '이', + 5: '소', + 6: '중', + 7: '대', + 8: '특', +}; + +const REGION_NAMES: Record = { + 1: '하북', + 2: '중원', + 3: '서북', + 4: '서촉', + 5: '남중', + 6: '초', + 7: '오월', + 8: '동이', +}; + +const OFFICER_LEVEL_NAMES: Record = { + 12: '군주', + 11: '참모', + 10: '제1장군', + 9: '제1모사', + 8: '제2장군', + 7: '제2모사', + 6: '제3장군', + 5: '제3모사', + 4: '태수', + 3: '군사', + 2: '종사', + 1: '일반', + 0: '재야', +}; + +const OFFICER_LEVEL_NAMES_BY_NATION_LEVEL: Record> = { + 7: { 12: '황제', 11: '승상', 10: '표기장군', 9: '사공', 8: '거기장군', 7: '태위', 6: '위장군', 5: '사도' }, + 6: { 12: '왕', 11: '광록훈', 10: '좌장군', 9: '상서령', 8: '우장군', 7: '중서령', 6: '전장군', 5: '비서령' }, + 5: { 12: '공', 11: '광록대부', 10: '안국장군', 9: '집금오', 8: '파로장군', 7: '소부' }, + 4: { 12: '주목', 11: '태사령', 10: '아문장군', 9: '낭중', 8: '호군', 7: '종사중랑' }, + 3: { 12: '주자사', 11: '주부', 10: '편장군', 9: '간의대부' }, + 2: { 12: '군벌', 11: '참모', 10: '비장군', 9: '부참모' }, + 1: { 12: '영주', 11: '참모' }, + 0: { 12: '두목', 11: '부두목' }, +}; + +export const sanitizeInternalDisplayCode = (value: string | null | undefined): string => { + if (!value || value === 'None') { + return '-'; + } + if (/^\d+$/u.test(value)) { + return '-'; + } + return value.replace(/^che_(?:event_)?/u, ''); +}; + +export const resolveNationLevelName = (level: number): string => NATION_LEVEL_NAMES[level] ?? '-'; + +export const resolveCityLevelName = (level: number): string => CITY_LEVEL_NAMES[level] ?? '-'; + +export const resolveRegionName = (region: number): string => REGION_NAMES[region] ?? '-'; + +export const resolveOfficerLevelName = (officerLevel: number, nationLevel?: number): string => { + if (officerLevel < 5) { + return OFFICER_LEVEL_NAMES[officerLevel] ?? '-'; + } + if (nationLevel === undefined) { + return OFFICER_LEVEL_NAMES[officerLevel] ?? '-'; + } + return OFFICER_LEVEL_NAMES_BY_NATION_LEVEL[nationLevel]?.[officerLevel] ?? '-'; +}; + +export const resolveDedicationLevelName = (dedicationLevel: number, maxDedicationLevel: number): string => { + if (dedicationLevel <= 0) { + return '무품관'; + } + return `${Math.max(1, maxDedicationLevel - dedicationLevel + 1)}품관`; +}; + +const resolveUnitSetName = (world: Pick | null, fallback: string): string => { + const config = asRecord(world?.config); + const environment = asRecord(config.environment ?? config.map); + return typeof environment.unitSet === 'string' && environment.unitSet.trim() ? environment.unitSet : fallback; +}; + +const crewTypeNameCache = new Map>>(); + +export const loadCrewTypeDisplayNames = ( + world: Pick | null, + fallback: string +): Promise> => { + const unitSetName = resolveUnitSetName(world, fallback); + const cached = crewTypeNameCache.get(unitSetName); + if (cached) { + return cached; + } + const pending = loadUnitSetDefinitionByName(unitSetName) + .then((definition) => new Map((definition.crewTypes ?? []).map((crewType) => [crewType.id, crewType.name]))) + .catch(() => new Map()); + crewTypeNameCache.set(unitSetName, pending); + return pending; +}; + +const itemLoader = new ItemLoader(); + +export const loadItemDisplayNames = async (values: Array): Promise> => { + const keys = Array.from(new Set(values.filter((value): value is string => Boolean(value) && value !== 'None'))); + const entries = await Promise.all( + keys.map(async (key) => { + if (!isItemKey(key)) { + return [key, sanitizeInternalDisplayCode(key)] as const; + } + const item = await itemLoader.load(key).catch(() => null); + return [key, item?.name ?? sanitizeInternalDisplayCode(key)] as const; + }) + ); + return new Map(entries); +}; diff --git a/app/game-api/test/archiveRouter.test.ts b/app/game-api/test/archiveRouter.test.ts index a6fad454..2451600a 100644 --- a/app/game-api/test/archiveRouter.test.ts +++ b/app/game-api/test/archiveRouter.test.ts @@ -156,7 +156,8 @@ describe('archive.myPastPlays', () => { leadership: 80, strength: 70, officerLevel: 8, - personal: '3', + officerLevelText: '제2장군', + personal: '-', historyCount: 2, }), expect.objectContaining({ @@ -166,9 +167,10 @@ describe('archive.myPastPlays', () => { strength: 71, intel: 61, officerLevel: 7, - personal: 'che_의리', - special: 'che_상재', - special2: 'che_신산', + officerLevelText: '제2모사', + personal: '의리', + special: '상재', + special2: '신산', historyCount: 1, }), ], diff --git a/app/game-api/test/gameDisplayNames.test.ts b/app/game-api/test/gameDisplayNames.test.ts new file mode 100644 index 00000000..62a576e6 --- /dev/null +++ b/app/game-api/test/gameDisplayNames.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from 'vitest'; + +import { + resolveCityLevelName, + resolveDedicationLevelName, + resolveNationLevelName, + resolveOfficerLevelName, + resolveRegionName, + sanitizeInternalDisplayCode, +} from '../src/services/gameDisplayNames.js'; + +describe('Ref GUI display names', () => { + it('maps nation, city, region, office, and dedication levels to Ref labels', () => { + expect(resolveNationLevelName(3)).toBe('주자사'); + expect(resolveCityLevelName(8)).toBe('특'); + expect(resolveRegionName(2)).toBe('중원'); + expect(resolveOfficerLevelName(9, 3)).toBe('간의대부'); + expect(resolveOfficerLevelName(5, 3)).toBe('-'); + expect(resolveOfficerLevelName(5)).toBe('제3모사'); + expect(resolveDedicationLevelName(2, 30)).toBe('29품관'); + expect(resolveDedicationLevelName(0, 30)).toBe('무품관'); + }); + + it('never exposes internal prefixes or numeric fallback codes', () => { + expect(sanitizeInternalDisplayCode('che_event_의병')).toBe('의병'); + expect(sanitizeInternalDisplayCode('che_법가')).toBe('법가'); + expect(sanitizeInternalDisplayCode('3')).toBe('-'); + expect(resolveNationLevelName(99)).toBe('-'); + expect(resolveCityLevelName(99)).toBe('-'); + expect(resolveRegionName(99)).toBe('-'); + }); +}); diff --git a/app/game-api/test/inGameMenuPermissions.test.ts b/app/game-api/test/inGameMenuPermissions.test.ts index 77c9dcd6..e634681b 100644 --- a/app/game-api/test/inGameMenuPermissions.test.ts +++ b/app/game-api/test/inGameMenuPermissions.test.ts @@ -248,6 +248,55 @@ describe('in-game my information ownership', () => { ); }); + it('returns Ref display names instead of numeric levels and internal codes for the main GUI', async () => { + const fixture = createContext({ + me: buildGeneral({ + officerLevel: 9, + crewTypeId: 1100, + horseCode: 'che_명마_03_노새', + meta: { explevel: 4, dedlevel: 2 }, + }), + city: { + id: 1, + name: '업', + level: 8, + nationId: 1, + population: 1_000, + populationMax: 2_000, + agriculture: 100, + agricultureMax: 200, + commerce: 100, + commerceMax: 200, + security: 100, + securityMax: 200, + trust: 70, + trade: 100, + defence: 100, + defenceMax: 200, + wall: 100, + wallMax: 200, + region: 2, + supplyState: 1, + frontState: 0, + }, + }); + + await expect(appRouter.createCaller(fixture.context).general.me()).resolves.toMatchObject({ + general: { + officerLevelText: '간의대부', + crewTypeName: '보병', + progression: { experienceLevel: 4, dedicationLevel: 2, dedicationText: '29품관' }, + itemNames: { horse: '노새(+3)' }, + }, + city: { levelName: '특', regionName: '중원', nationName: '위' }, + nation: { + levelName: '주자사', + typeName: '법가', + capitalCityName: '업', + }, + }); + }); + it('returns the Ref-style neutral nation frame and trait display names on the main read model', async () => { const fixture = createContext({ me: buildGeneral({ @@ -542,8 +591,14 @@ describe('battle-center general and user permissions', () => { id: 7, picture: 'default.jpg', imageServer: 0, + officerLevelText: '일반', + crewTypeName: '-', + equipmentNames: { weapon: '-', book: '-', horse: '-', item: '-' }, + traits: { personal: '-', specialDomestic: '-', specialWar: '-' }, progression: { experienceLevel: 0, + dedicationLevel: 1, + dedicationText: '30품관', statExperience: { leadership: 0, strength: 0, intelligence: 0 }, statUpgradeLimit: 20, dex: [0, 0, 0, 0, 0], diff --git a/app/game-api/test/nationGeneralSecretRouter.test.ts b/app/game-api/test/nationGeneralSecretRouter.test.ts index c1fce969..088733e1 100644 --- a/app/game-api/test/nationGeneralSecretRouter.test.ts +++ b/app/game-api/test/nationGeneralSecretRouter.test.ts @@ -117,6 +117,9 @@ describe('nation general and secret office permissions', () => { cityName: null, troopName: null, refreshScoreTotal: 10, + dedicationLevel: 1, + dedicationText: '30품관', + bill: 600, }); expect(result.generals[0]).not.toHaveProperty('crew'); await expect(caller.nation.getSecretGeneralList()).rejects.toMatchObject({ code: 'FORBIDDEN' }); diff --git a/app/game-engine/src/turn/selectPoolService.ts b/app/game-engine/src/turn/selectPoolService.ts index 55931dd9..cb18675b 100644 --- a/app/game-engine/src/turn/selectPoolService.ts +++ b/app/game-engine/src/turn/selectPoolService.ts @@ -6,10 +6,12 @@ import { EventDomesticTraitLoader, isEventDomesticTraitKey, isPersonalityTraitKey, + isWarTraitKey, LogCategory, LogScope, PERSONALITY_TRAIT_KEYS, simpleSerialize, + WarTraitLoader, } from '@sammo-ts/logic'; import type { DatabaseClient, GamePrisma as GamePrismaTypes } from '@sammo-ts/infra'; @@ -78,6 +80,8 @@ export interface SelectPoolCandidateDto { specialDomesticName: string; specialDomesticInfo: string; specialWar: string | null; + specialWarName: string | null; + specialWarInfo: string; ego: string | null; dex: [number, number, number, number, number]; imageServer: 0 | 1; @@ -158,11 +162,16 @@ const candidateWeight = (candidate: SelectPoolCandidateInfo): number => candidate.dex.reduce((sum, value) => sum + value, 0); const eventDomesticTraitLoader = new EventDomesticTraitLoader(); +const warTraitLoader = new WarTraitLoader(); const toCandidateDto = async (candidate: SelectPoolCandidateInfo): Promise => { const trait = isEventDomesticTraitKey(candidate.specialDomestic) ? await eventDomesticTraitLoader.load(candidate.specialDomestic) : null; + const warTrait = + candidate.specialWar && isWarTraitKey(candidate.specialWar) + ? await warTraitLoader.load(candidate.specialWar) + : null; return { uniqueName: candidate.uniqueName, generalName: candidate.generalName, @@ -173,6 +182,8 @@ const toCandidateDto = async (candidate: SelectPoolCandidateInfo): Promise getComputedStyle(el).borderCollapse) ).toBe(borderCollapse); + if (path === 'nation/info') { + await expect(page.locator(selector)).toContainText('작 위호족'); + await expect(page.locator(selector)).not.toContainText('작 위1'); + } } }); +test('국가 정보의 작위는 Ref 국가 등급 이름으로 표시된다', async ({ page }) => { + await install(page); + await page.goto('nation/info'); + + const root = page.locator('.legacy-info-page'); + await expect(root).toContainText('작 위호족'); + await expect(root).not.toContainText('작 위1'); +}); + test('global-info renders the ref nation summary columns beside the map', async ({ page }) => { await install(page); await page.setViewportSize({ width: 1200, height: 900 }); diff --git a/app/game-frontend/e2e/inGameMenus.spec.ts b/app/game-frontend/e2e/inGameMenus.spec.ts index 3db03da2..202496d2 100644 --- a/app/game-frontend/e2e/inGameMenus.spec.ts +++ b/app/game-frontend/e2e/inGameMenus.spec.ts @@ -63,7 +63,9 @@ const myGeneral = (state: FixtureState) => ({ troopId: 0, picture: null, imageServer: 0, - officerLevel: state.permission === 'head' ? 5 : 1, + officerLevel: state.permission === 'head' ? 9 : 1, + officerLevelText: + state.permission === 'head' ? '간의대부' : state.buildNationCandidateEnabled ? '재야' : '일반', stats: { leadership: 70, strength: 60, intelligence: 50 }, gold: 1_000, rice: 2_000, @@ -76,30 +78,74 @@ const myGeneral = (state: FixtureState) => ({ age: 30, turnTime: '2026-01-01 00:10:00', crewTypeId: 1, + crewTypeName: '보병', traits: state.mainTraits ?? { personal: '-', specialDomestic: '-', specialWar: '-' }, progression: { experienceLevel: 1, dedicationLevel: 2, + dedicationText: '29품관', statExperience: { leadership: 7, strength: 8, intelligence: 9 }, statUpgradeLimit: 20, dex: [350, 1_375, 3_500, 7_125, 1_275_975], }, items: { horse: 'che_명마', weapon: null, book: null, item: null }, + itemNames: { horse: '명마', weapon: null, book: null, item: null }, + }, + city: { + id: 1, + name: '업', + level: 8, + levelName: '특', + region: 2, + regionName: '중원', + nationId: 1, + nationName: '위', + population: 1000, + populationMax: 2000, + agriculture: 100, + agricultureMax: 200, + commerce: 100, + commerceMax: 200, + security: 100, + securityMax: 200, + trust: 70, + trade: 100, + defence: 100, + defenceMax: 200, + wall: 100, + wallMax: 200, + supplyState: 1, + frontState: 0, }, - city: { id: 1, name: '업', level: 8, nationId: 1 }, nation: state.buildNationCandidateEnabled ? { id: 0, name: '재야', color: '#000000', level: 0, + levelName: '방랑군', gold: 0, rice: 0, tech: 0, typeCode: 'None', + typeName: '해당 없음', capitalCityId: null, + capitalCityName: null, } - : { id: 1, name: '위', color: '#777777', level: 3 }, + : { + id: 1, + name: '위', + color: '#777777', + level: 3, + levelName: '주자사', + gold: 10_000, + rice: 20_000, + tech: 100, + typeCode: 'che_법가', + typeName: '법가', + capitalCityId: 1, + capitalCityName: '업', + }, settings: { tnmt: 0, defence_train: 80, @@ -116,7 +162,7 @@ const myGeneral = (state: FixtureState) => ({ const battleCenter = (state: FixtureState) => ({ me: { id: 7, - officerLevel: state.permission === 'head' ? 5 : 1, + officerLevel: state.permission === 'head' ? 9 : 1, permissionLevel: state.permission === 'head' ? 2 : 0, }, nation: { id: 1, name: '위', color: '#777777', level: 3 }, @@ -128,7 +174,8 @@ const battleCenter = (state: FixtureState) => ({ id: 7, name: '검증장수', npcState: 0, - officerLevel: state.permission === 'head' ? 5 : 1, + officerLevel: state.permission === 'head' ? 9 : 1, + officerLevelText: state.permission === 'head' ? '간의대부' : '일반', cityId: 1, turnTime: '2026-01-01 00:10:00', recentWar: '2026-01-01 00:00:00', @@ -144,10 +191,14 @@ const battleCenter = (state: FixtureState) => ({ atmos: 90, age: 30, crewTypeId: 1, + crewTypeName: '보병', equipment: { weapon: 'None', book: 'None', horse: 'None', item: 'None' }, - traits: { personal: 'None', specialDomestic: 'None', specialWar: 'None' }, + equipmentNames: { weapon: '-', book: '-', horse: '-', item: '-' }, + traits: { personal: '-', specialDomestic: '-', specialWar: '-' }, progression: { experienceLevel: 1, + dedicationLevel: 2, + dedicationText: '29품관', statExperience: { leadership: 7, strength: 8, intelligence: 9 }, statUpgradeLimit: 20, dex: [350, 1_375, 3_500, 7_125, 1_275_975], @@ -159,6 +210,7 @@ const battleCenter = (state: FixtureState) => ({ name: '다른장수', npcState: 2, officerLevel: 1, + officerLevelText: '일반', cityId: 1, turnTime: '2026-01-01 00:20:00', recentWar: null, @@ -174,10 +226,14 @@ const battleCenter = (state: FixtureState) => ({ atmos: 60, age: 20, crewTypeId: 1, + crewTypeName: '보병', equipment: { weapon: 'None', book: 'None', horse: 'None', item: 'None' }, - traits: { personal: 'None', specialDomestic: 'None', specialWar: 'None' }, + equipmentNames: { weapon: '-', book: '-', horse: '-', item: '-' }, + traits: { personal: '-', specialDomestic: '-', specialWar: '-' }, progression: { experienceLevel: 0, + dedicationLevel: 0, + dedicationText: '무품관', statExperience: { leadership: 0, strength: 0, intelligence: 0 }, statUpgradeLimit: 20, dex: [0, 0, 0, 0, 0], @@ -512,6 +568,35 @@ test('재야 메인은 국가 틀과 성격·특기 표기명을 Chromium에 표 await persistParityArtifact(page, 'main-neutral-trait-display', geometry); }); +test('메인 카드의 국가·수도·관직·계급·병종은 Ref 출력명으로 표시된다', async ({ page }) => { + const state: FixtureState = { + permission: 'head', + myset: 3, + mainTraits: { personal: '안전', specialDomestic: '상재', specialWar: '신산' }, + settingMutations: [], + accessPages: [], + }; + await install(page, state); + await page.setViewportSize({ width: 1000, height: 900 }); + await page.goto(''); + + const nationCard = page.locator('.nation-card'); + await expect(nationCard.locator('.title')).toHaveText('위 (주자사)'); + await expect(nationCard).toContainText('체제법가'); + await expect(nationCard).toContainText('수도업'); + await expect(nationCard).toContainText('국가 등급주자사'); + + const generalCard = page.locator('.general-card'); + await expect(generalCard.locator('.general-title')).toContainText('검증장수 · 간의대부'); + await expect(generalCard).toContainText('병종보병'); + await expect(generalCard).toContainText('계급29품관'); + + const cityCard = page.locator('.city-card'); + await expect(cityCard.locator('.title')).toContainText('【중원 | 특】 업'); + await expect(cityCard.locator('.title')).toContainText('지배 국가 【 위 】'); + await expect(page.locator('.main-page')).not.toContainText('che_'); +}); + test('접속량정보 keeps the legacy public 1016px chart geometry', async ({ page }) => { const state: FixtureState = { permission: 'member', myset: 0, settingMutations: [], accessPages: [] }; await install(page, state); @@ -566,6 +651,10 @@ test('내 정보&설정 keeps the legacy 1000px/500px geometry and saves in plac await install(page, state); await page.setViewportSize({ width: 1000, height: 900 }); await page.goto('my-page'); + await expect(page.locator('.legacy-general-details')).toContainText('계급 29품관'); + await expect(page.locator('.legacy-general-details')).toContainText('병종 보병'); + await expect(page.locator('.item-group')).toContainText('명마'); + await expect(page.locator('#container')).not.toContainText('che_'); await expect(page.locator('.title-row')).toContainText('내 정 보'); await expect(page.locator('#set_my_setting')).toBeVisible(); await expect(page.locator('.general-column [role="progressbar"]')).toHaveCount(14); @@ -1020,6 +1109,10 @@ test('감찰부 keeps the selector interaction and shows the permission error pa await expect(page.locator('.selector-row select').nth(1)).toHaveValue('8'); await page.getByRole('button', { name: '다음 ▶' }).click(); await expect(page.locator('.selector-row select').nth(1)).toHaveValue('7'); + await expect(page.locator('.battle-general-name')).toContainText('검증장수 (간의대부)'); + await expect(page.locator('.battle-general-extra')).toContainText('계급29품관'); + await expect(page.locator('.battle-general-extra')).toContainText('병종보병'); + await expect(page.locator('.battle-general-card')).not.toContainText('che_'); await expect(page.locator('.battle-general-card [role="progressbar"]')).toHaveCount(14); await expect(page.locator('.battle-general-card [aria-label*="1,275,975 (EX+)"]')).toHaveCount(5); expect( diff --git a/app/game-frontend/e2e/pastPlays.spec.ts b/app/game-frontend/e2e/pastPlays.spec.ts index bd6365c3..c696e4be 100644 --- a/app/game-frontend/e2e/pastPlays.spec.ts +++ b/app/game-frontend/e2e/pastPlays.spec.ts @@ -41,6 +41,7 @@ const installArchive = async (page: Page) => { experience: 23000, dedication: 1200, officerLevel: 12, + officerLevelText: '황제', personal: '대담', special: '상재', special2: '신산', @@ -66,6 +67,15 @@ const installArchive = async (page: Page) => { }); }; +test('지난 플레이 관직은 숫자 대신 저장된 Ref 표시명으로 나타난다', async ({ page }) => { + await installArchive(page); + await page.goto('past-plays'); + + const generalRow = page.locator('tbody tr').filter({ hasText: '관우' }); + await expect(generalRow).toContainText('황제'); + await expect(generalRow).not.toContainText('che_'); +}); + test('past plays is available without a current general and preserves desktop interaction geometry', async ({ page, }) => { @@ -78,6 +88,7 @@ test('past plays is available without a current general and preserves desktop in await expect(page.getByRole('heading', { name: '내 지난 플레이 보기' })).toBeVisible(); await expect(page.getByText('천하쟁패 · 51기')).toBeVisible(); await expect(page.locator('.general-name')).toHaveText('관우'); + await expect(page.locator('tbody tr').filter({ hasText: '관우' })).toContainText('황제'); await expect(page.getByRole('link', { name: '이 기수 국가 정보' })).toHaveAttribute('href', gamePath('/dynasty/7')); const historyToggle = page.locator('.history-toggle'); await expect(historyToggle).toHaveText('보기 (2)'); diff --git a/app/game-frontend/src/components/main/CityBasicCard.vue b/app/game-frontend/src/components/main/CityBasicCard.vue index 5f122258..8d81b588 100644 --- a/app/game-frontend/src/components/main/CityBasicCard.vue +++ b/app/game-frontend/src/components/main/CityBasicCard.vue @@ -8,7 +8,10 @@ interface CityInfo { id: number; name: string; level: number; + levelName: string; + regionName: string; nationId: number; + nationName: string; population: number; populationMax: number; agriculture: number; @@ -66,8 +69,8 @@ const metrics = computed(() => {
도시 정보를 불러오지 못했습니다.
- {{ props.city.name }} (Lv {{ props.city.level }}) · 국가 {{ props.city.nationId || '무주' }} · 보급 - {{ props.city.supplyState }} · 전방 {{ props.city.frontState }} + 【{{ props.city.regionName }} | {{ props.city.levelName }}】 {{ props.city.name }} · + {{ props.city.nationId > 0 ? `지배 국가 【 ${props.city.nationName} 】` : '공 백 지' }}
장수 정보를 불러오지 못했습니다.
- {{ props.general.name }} · 관직 {{ props.general.officerLevel }} · {{ props.general.age ?? '-' }}세 · + {{ props.general.name }} · {{ props.general.officerLevelText }} · {{ props.general.age ?? '-' }}세 · 다음 턴 {{ props.general.turnTime?.slice(11, 16) ?? '-' }}
@@ -102,11 +105,11 @@ const experiencePercent = computed(() => >{{ props.general.crew.toLocaleString() }} 훈련{{ props.general.train }} 사기{{ props.general.atmos }} 부상{{ props.general.injury }} 병종{{ props.general.crewTypeId || '-' }} 성격{{ props.general.crewTypeName ?? '-' }} 성격{{ props.general.traits?.personal ?? '-' }} 전투특기{{ props.general.traits?.specialWar ?? '-' }} 내정특기{{ props.general.traits?.specialDomestic ?? '-' }} 계급Lv {{ props.general.progression?.dedicationLevel ?? 0 }} 공헌{{ props.general.progression?.dedicationText ?? '무품관' }} 공헌{{ props.general.dedication.toLocaleString() }}
diff --git a/app/game-frontend/src/components/main/NationBasicCard.vue b/app/game-frontend/src/components/main/NationBasicCard.vue index 6470648f..d94cddcc 100644 --- a/app/game-frontend/src/components/main/NationBasicCard.vue +++ b/app/game-frontend/src/components/main/NationBasicCard.vue @@ -7,11 +7,14 @@ interface NationInfo { name: string; color: string; level: number; + levelName: string; gold: number; rice: number; tech: number; typeCode: string; + typeName: string; capitalCityId: number | null; + capitalCityName: string | null; } const props = defineProps<{ @@ -31,7 +34,7 @@ const props = defineProps<{ class="title" :style="{ backgroundColor: props.nation.color, color: legacyNationTextColor(props.nation.color) }" > - {{ props.nation.name }} + {{ props.nation.name }}
국고{{ props.nation.id === 0 ? '해당 없음' : props.nation.rice.toLocaleString() }} 기술{{ props.nation.id === 0 ? '해당 없음' : props.nation.tech.toLocaleString() }} - 체제{{ props.nation.id === 0 ? '해당 없음' : props.nation.typeCode }} + 체제{{ props.nation.id === 0 ? '해당 없음' : props.nation.typeName }} 수도{{ props.nation.id === 0 ? '해당 없음' : (props.nation.capitalCityId ?? '-') }} - 국가 등급{{ props.nation.id === 0 ? '해당 없음' : props.nation.level }} + >{{ props.nation.id === 0 ? '해당 없음' : (props.nation.capitalCityName ?? '-') }} + 국가 등급{{ props.nation.id === 0 ? '해당 없음' : props.nation.levelName }}
diff --git a/app/game-frontend/src/utils/nationFormat.ts b/app/game-frontend/src/utils/nationFormat.ts index 8fcea664..8d944c79 100644 --- a/app/game-frontend/src/utils/nationFormat.ts +++ b/app/game-frontend/src/utils/nationFormat.ts @@ -14,6 +14,19 @@ export const officerLevelMapDefault: Record = { 0: '재야', }; +export const nationLevelMap: Record = { + 7: '황제', + 6: '왕', + 5: '공', + 4: '주목', + 3: '주자사', + 2: '군벌', + 1: '호족', + 0: '방랑군', +}; + +export const formatNationLevelText = (nationLevel: number): string => nationLevelMap[nationLevel] ?? '-'; + export const officerLevelMapByNationLevel: Record> = { 7: { 12: '황제', @@ -75,15 +88,13 @@ export const officerLevelMapByNationLevel: Record export const formatOfficerLevelText = (officerLevel: number, nationLevel?: number): string => { if (officerLevel < 5) { - return officerLevelMapDefault[officerLevel] ?? '???'; + return officerLevelMapDefault[officerLevel] ?? '-'; } - const nationMap = - nationLevel === undefined - ? officerLevelMapDefault - : (officerLevelMapByNationLevel[nationLevel] ?? officerLevelMapDefault); - - return nationMap[officerLevel] ?? (officerLevelMapDefault[officerLevel] ?? '???'); + if (nationLevel === undefined) { + return officerLevelMapDefault[officerLevel] ?? '-'; + } + return officerLevelMapByNationLevel[nationLevel]?.[officerLevel] ?? '-'; }; export const regionMap: Record = { diff --git a/app/game-frontend/src/views/AuctionView.vue b/app/game-frontend/src/views/AuctionView.vue index bea7221f..dfb03396 100644 --- a/app/game-frontend/src/views/AuctionView.vue +++ b/app/game-frontend/src/views/AuctionView.vue @@ -38,6 +38,8 @@ const resolveErrorMessage = (value: unknown): string => { }; const formatNumber = (value: number | null | undefined): string => (value ?? 0).toLocaleString(); +const displayCode = (value: string | null | undefined): string => + !value || /^\d+$/u.test(value) ? '-' : value.replace(/^che_(?:event_)?/u, ''); const cutDateTime = (value: string | null | undefined, showSecond = false): string => { if (!value) { return '-'; @@ -395,7 +397,7 @@ onMounted(() => {

경매 {{ uniqueDetail.auction.id }}번 상세

경매명
-
{{ uniqueDetail.auction.detail.title ?? uniqueDetail.auction.targetCode }}
+
{{ uniqueDetail.auction.detail.title ?? displayCode(uniqueDetail.auction.targetCode) }}
주최자(익명)
{{ uniqueDetail.auction.hostName }}
종료일시
@@ -442,7 +444,7 @@ onMounted(() => { @keydown.space.prevent="selectUnique(auction)" > {{ auction.id }}{{ auction.detail.title ?? auction.targetCode }} + >{{ auction.detail.title ?? displayCode(auction.targetCode) }} {{ auction.hostName }} {{ cutDateTime(auction.closeAt) }} {{ (auction.detail.remainCloseDateExtensionCnt ?? 0) > 0 ? '남음' : '소진' }} @@ -474,7 +476,7 @@ onMounted(() => { @keydown.space.prevent="selectUnique(auction)" > {{ auction.id }}{{ auction.detail.title ?? auction.targetCode }} + >{{ auction.detail.title ?? displayCode(auction.targetCode) }} {{ auction.hostName }} {{ cutDateTime(auction.closeAt) }} {{ (auction.detail.remainCloseDateExtensionCnt ?? 0) > 0 ? '남음' : '소진' }} diff --git a/app/game-frontend/src/views/BattleCenterView.vue b/app/game-frontend/src/views/BattleCenterView.vue index 5a48d6e6..a71eb31d 100644 --- a/app/game-frontend/src/views/BattleCenterView.vue +++ b/app/game-frontend/src/views/BattleCenterView.vue @@ -269,7 +269,7 @@ onMounted(() => {
- {{ selectedGeneral.name }} (관직 {{ selectedGeneral.officerLevel }}) + {{ selectedGeneral.name }} ({{ selectedGeneral.officerLevelText }})
{
명성{{ selectedGeneral.experience.toLocaleString('ko-KR') }} - 계급{{ selectedGeneral.dedication.toLocaleString('ko-KR') }} + 계급{{ selectedGeneral.progression.dedicationText }} 나이{{ selectedGeneral.age }}세 병종{{ selectedGeneral.crewTypeId }} 승리{{ selectedGeneral.crewTypeName }} 승리{{ selectedGeneral.battleStats.kills }} 패배{{ selectedGeneral.battleStats.deaths }} 사살{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }} diff --git a/app/game-frontend/src/views/MyPageView.vue b/app/game-frontend/src/views/MyPageView.vue index 42cc1df4..2d3be533 100644 --- a/app/game-frontend/src/views/MyPageView.vue +++ b/app/game-frontend/src/views/MyPageView.vue @@ -132,12 +132,40 @@ const noDefencePenaltyWaived = computed(() => { ); }); const noDefenceLabel = computed(() => (noDefencePenaltyWaived.value ? '×' : '× [훈련 -3,사기 -6]')); -const items = computed>(() => [ - { key: 'horse', name: '말', code: data.value?.general.items.horse ?? null }, - { key: 'weapon', name: '무기', code: data.value?.general.items.weapon ?? null }, - { key: 'book', name: '서적', code: data.value?.general.items.book ?? null }, - { key: 'item', name: '도구', code: data.value?.general.items.item ?? null }, -]); +const fallbackDisplayCode = (value: string | null | undefined): string | null => + value && !/^\d+$/u.test(value) ? value.replace(/^che_(?:event_)?/u, '') : null; +const items = computed>( + () => [ + { + key: 'horse', + slotName: '말', + displayName: + data.value?.general.itemNames?.horse ?? fallbackDisplayCode(data.value?.general.items.horse) ?? null, + code: data.value?.general.items.horse ?? null, + }, + { + key: 'weapon', + slotName: '무기', + displayName: + data.value?.general.itemNames?.weapon ?? fallbackDisplayCode(data.value?.general.items.weapon) ?? null, + code: data.value?.general.items.weapon ?? null, + }, + { + key: 'book', + slotName: '서적', + displayName: + data.value?.general.itemNames?.book ?? fallbackDisplayCode(data.value?.general.items.book) ?? null, + code: data.value?.general.items.book ?? null, + }, + { + key: 'item', + slotName: '도구', + displayName: + data.value?.general.itemNames?.item ?? fallbackDisplayCode(data.value?.general.items.item) ?? null, + code: data.value?.general.items.item ?? null, + }, + ] +); const iconChoices = computed(() => data.value?.iconChoices ?? []); const autorunUser = computed(() => asRecord(world.value?.meta.autorun_user)); @@ -302,8 +330,8 @@ const dieOnPrestart = async () => { } }; -const dropItem = (item: { key: ItemSlotKey; name: string; code: string | null }) => - confirmMutation(`${item.code ?? item.name}을(를) 버리시겠습니까?`, () => +const dropItem = (item: { key: ItemSlotKey; slotName: string; displayName: string | null; code: string | null }) => + confirmMutation(`${item.displayName ?? item.slotName}을(를) 버리시겠습니까?`, () => trpc.general.dropItem.mutate({ itemType: item.key }) ); @@ -416,7 +444,7 @@ onMounted(() => { > · 계급 Lv {{ data.general.progression?.dedicationLevel ?? 0 }} ({{ + >{{ data.general.progression?.dedicationText ?? '무품관' }} ({{ data.general.dedication }}) @@ -425,7 +453,7 @@ onMounted(() => {
승률 0% · 승리 0 · 패배 0
살상률 0% · 사살 0 · 피살 0
- 병종 {{ data.general.crewTypeId || '-' }} · 내정특기 + 병종 {{ data.general.crewTypeName ?? '-' }} · 내정특기 {{ data.general.traits?.specialDomestic ?? '-' }} · 부상 {{ data.general.injury }} · 부대 {{ data.general.troopId || '-' }} · 벌점 {{ penalties.length || '-' }}
@@ -595,7 +623,7 @@ onMounted(() => { :disabled="!item.code" @click="dropItem(item)" > - {{ item.code ?? '-' }} + {{ item.displayName ?? '-' }}
diff --git a/app/game-frontend/src/views/NationGeneralsView.vue b/app/game-frontend/src/views/NationGeneralsView.vue index e0e26b0b..733c8741 100644 --- a/app/game-frontend/src/views/NationGeneralsView.vue +++ b/app/game-frontend/src/views/NationGeneralsView.vue @@ -63,7 +63,6 @@ const generals = computed(() => }) ); const special = (general: General) => `${general.specialDomestic?.name ?? '-'} / ${general.specialWar?.name ?? '-'}`; -const rank = (general: General) => (general.dedicationLevel ? `${11 - general.dedicationLevel}품관` : '무품관'); const iconUrl = (general: General) => resolveGeneralIconUrl(general); onMounted(load); @@ -78,7 +77,13 @@ onMounted(load); 세력 장수 - +