fix: publish game engine runtime subpaths
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -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에서 가져옵니다.
|
||||
|
||||
@@ -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`);
|
||||
Reference in New Issue
Block a user