fix: publish game engine runtime subpaths

This commit is contained in:
2026-08-08 02:33:47 +00:00
parent fd90893cef
commit 05f4443e06
4 changed files with 162 additions and 2 deletions
+88 -2
View File
@@ -5,9 +5,95 @@
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./auction/finalizer.js": {
"types": "./dist/auction/finalizer.d.ts",
"default": "./dist/auction/finalizer.js"
},
"./scenario/mapLoader.js": {
"types": "./dist/scenario/mapLoader.d.ts",
"default": "./dist/scenario/mapLoader.js"
},
"./scenario/scenarioComposition.js": {
"types": "./dist/scenario/scenarioComposition.d.ts",
"default": "./dist/scenario/scenarioComposition.js"
},
"./scenario/scenarioLoader.js": {
"types": "./dist/scenario/scenarioLoader.d.ts",
"default": "./dist/scenario/scenarioLoader.js"
},
"./scenario/scenarioSeeder.js": {
"types": "./dist/scenario/scenarioSeeder.d.ts",
"default": "./dist/scenario/scenarioSeeder.js"
},
"./scenario/unitSetLoader.js": {
"types": "./dist/scenario/unitSetLoader.d.ts",
"default": "./dist/scenario/unitSetLoader.js"
},
"./turn/databaseHooks.js": {
"types": "./dist/turn/databaseHooks.d.ts",
"default": "./dist/turn/databaseHooks.js"
},
"./turn/inMemoryWorld.js": {
"types": "./dist/turn/inMemoryWorld.d.ts",
"default": "./dist/turn/inMemoryWorld.js"
},
"./turn/monthlyDisasterAction.js": {
"types": "./dist/turn/monthlyDisasterAction.d.ts",
"default": "./dist/turn/monthlyDisasterAction.js"
},
"./turn/monthlyEventHandler.js": {
"types": "./dist/turn/monthlyEventHandler.d.ts",
"default": "./dist/turn/monthlyEventHandler.js"
},
"./turn/monthlyNationBettingAction.js": {
"types": "./dist/turn/monthlyNationBettingAction.d.ts",
"default": "./dist/turn/monthlyNationBettingAction.js"
},
"./turn/npcPossessionService.js": {
"types": "./dist/turn/npcPossessionService.d.ts",
"default": "./dist/turn/npcPossessionService.js"
},
"./turn/rankData.js": {
"types": "./dist/turn/rankData.d.ts",
"default": "./dist/turn/rankData.js"
},
"./turn/reservedTurnHandler.js": {
"types": "./dist/turn/reservedTurnHandler.d.ts",
"default": "./dist/turn/reservedTurnHandler.js"
},
"./turn/reservedTurnStore.js": {
"types": "./dist/turn/reservedTurnStore.d.ts",
"default": "./dist/turn/reservedTurnStore.js"
},
"./turn/selectPoolService.js": {
"types": "./dist/turn/selectPoolService.d.ts",
"default": "./dist/turn/selectPoolService.js"
},
"./turn/turnCommandProfile.js": {
"types": "./dist/turn/turnCommandProfile.d.ts",
"default": "./dist/turn/turnCommandProfile.js"
},
"./turn/types.js": {
"types": "./dist/turn/types.d.ts",
"default": "./dist/turn/types.js"
},
"./turn/worldCommandHandler.js": {
"types": "./dist/turn/worldCommandHandler.d.ts",
"default": "./dist/turn/worldCommandHandler.js"
},
"./turn/worldLoader.js": {
"types": "./dist/turn/worldLoader.d.ts",
"default": "./dist/turn/worldLoader.js"
}
},
"scripts": {
"build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-engine",
"dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-engine --watch",
"build": "tsdown -c ./tsdown.config.ts && node ../../tools/check-package-exports.mjs .",
"dev": "tsdown -c ./tsdown.config.ts --watch",
"start": "pnpm run build && GAME_ENGINE_ROLE=turn-daemon node dist/index.js",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
+37
View File
@@ -0,0 +1,37 @@
import { defineConfig } from 'tsdown';
export default defineConfig({
entry: {
index: 'src/index.ts',
'auction/finalizer': 'src/auction/finalizer.ts',
'scenario/mapLoader': 'src/scenario/mapLoader.ts',
'scenario/scenarioComposition': 'src/scenario/scenarioComposition.ts',
'scenario/scenarioLoader': 'src/scenario/scenarioLoader.ts',
'scenario/scenarioSeeder': 'src/scenario/scenarioSeeder.ts',
'scenario/unitSetLoader': 'src/scenario/unitSetLoader.ts',
'turn/databaseHooks': 'src/turn/databaseHooks.ts',
'turn/inMemoryWorld': 'src/turn/inMemoryWorld.ts',
'turn/monthlyDisasterAction': 'src/turn/monthlyDisasterAction.ts',
'turn/monthlyEventHandler': 'src/turn/monthlyEventHandler.ts',
'turn/monthlyNationBettingAction': 'src/turn/monthlyNationBettingAction.ts',
'turn/npcPossessionService': 'src/turn/npcPossessionService.ts',
'turn/rankData': 'src/turn/rankData.ts',
'turn/reservedTurnHandler': 'src/turn/reservedTurnHandler.ts',
'turn/reservedTurnStore': 'src/turn/reservedTurnStore.ts',
'turn/selectPoolService': 'src/turn/selectPoolService.ts',
'turn/turnCommandProfile': 'src/turn/turnCommandProfile.ts',
'turn/types': 'src/turn/types.ts',
'turn/worldCommandHandler': 'src/turn/worldCommandHandler.ts',
'turn/worldLoader': 'src/turn/worldLoader.ts',
},
format: 'es',
outDir: 'dist',
dts: {
build: true,
},
sourcemap: true,
target: 'node22',
platform: 'node',
fixedExtension: false,
hash: false,
});
+6
View File
@@ -40,6 +40,12 @@ Resource를 읽는 `scenarioLoader`, `mapLoader`, `unitSetLoader`,
사용합니다. `@sammo-ts/game-engine` 루트는 daemon process entrypoint이므로
API 제품 소스에서 library처럼 import하지 않습니다.
game-engine의 공개 subpath는 `app/game-engine/package.json``exports`
`app/game-engine/tsdown.config.ts`의 entry에 함께 선언합니다. `pnpm --filter
@sammo-ts/game-engine build`는 각 JS·type export target이 실제 생성됐는지
검사하므로, typecheck에서는 보이지만 production Node에서만 실패하는 subpath
누락을 배포 전에 차단합니다.
Frontend가 tRPC router shape를 참조할 때는 `import type`만 사용하고 backend
package를 `devDependencies`에 둡니다. 브라우저에서 실제 실행하는 공유 값만
`common` 또는 `logic`의 browser-safe export에서 가져옵니다.
+31
View File
@@ -0,0 +1,31 @@
import { access, readFile } from 'node:fs/promises';
import path from 'node:path';
const packageRoot = path.resolve(process.argv[2] ?? '');
if (!process.argv[2]) {
throw new Error('Usage: node tools/check-package-exports.mjs <package-directory>');
}
const manifest = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
const targets = new Set();
for (const descriptor of Object.values(manifest.exports ?? {})) {
if (typeof descriptor === 'string') {
targets.add(descriptor);
continue;
}
if (descriptor && typeof descriptor === 'object') {
for (const target of Object.values(descriptor)) {
if (typeof target === 'string') targets.add(target);
}
}
}
for (const target of targets) {
const absolute = path.resolve(packageRoot, target);
if (absolute !== packageRoot && !absolute.startsWith(`${packageRoot}${path.sep}`)) {
throw new Error(`Package export escapes its package directory: ${target}`);
}
await access(absolute);
}
process.stdout.write(`Validated ${targets.size} package export targets for ${manifest.name}.\n`);