feat: eslint 적용 및 관련 코드 일괄 수정

This commit is contained in:
2026-01-05 15:46:47 +00:00
parent cb312f02b3
commit c965b1120f
387 changed files with 39808 additions and 38800 deletions
+14 -42
View File
@@ -18,11 +18,7 @@ const asRecord = (value: unknown): Record<string, unknown> => {
return value as Record<string, unknown>;
};
const resolveNumber = (
record: Record<string, unknown>,
keys: string[],
fallback: number
): number => {
const resolveNumber = (record: Record<string, unknown>, keys: string[], fallback: number): number => {
for (const key of keys) {
const value = record[key];
if (typeof value === 'number' && Number.isFinite(value)) {
@@ -32,10 +28,7 @@ const resolveNumber = (
return fallback;
};
const resolveUnitSetName = (
worldState: WorldStateRow,
fallback: string
): string => {
const resolveUnitSetName = (worldState: WorldStateRow, fallback: string): string => {
const config = asRecord(worldState.config);
const environment = asRecord(config.environment ?? config.map);
const unitSet = environment.unitSet;
@@ -76,9 +69,12 @@ const resolveCastleCrewTypeId = (unitSet: {
return crewTypes[0]?.id ?? 0;
};
const resolveCastleArmType = (unitSet: {
crewTypes?: Array<{ id: number; armType: number }>;
}, castleCrewTypeId: number): number => {
const resolveCastleArmType = (
unitSet: {
crewTypes?: Array<{ id: number; armType: number }>;
},
castleCrewTypeId: number
): number => {
const crewTypes = unitSet.crewTypes ?? [];
return crewTypes.find((crewType) => crewType.id === castleCrewTypeId)?.armType ?? 0;
};
@@ -93,39 +89,15 @@ export const buildBattleSimJobPayload = async (
const configRecord = asRecord(worldState.config);
const constValues = asRecord(configRecord.const ?? configRecord.consts);
const castleCrewTypeId = resolveNumber(
constValues,
['castleCrewTypeId'],
resolveCastleCrewTypeId(unitSet)
);
const castleCrewTypeId = resolveNumber(constValues, ['castleCrewTypeId'], resolveCastleCrewTypeId(unitSet));
const castleArmType = resolveCastleArmType(unitSet, castleCrewTypeId);
const config: WarEngineConfig = {
armPerPhase: resolveNumber(
constValues,
['armPerPhase', 'armperphase'],
DEFAULT_WAR_CONFIG.armPerPhase
),
maxTrainByCommand: resolveNumber(
constValues,
['maxTrainByCommand'],
DEFAULT_WAR_CONFIG.maxTrainByCommand
),
maxAtmosByCommand: resolveNumber(
constValues,
['maxAtmosByCommand'],
DEFAULT_WAR_CONFIG.maxAtmosByCommand
),
maxTrainByWar: resolveNumber(
constValues,
['maxTrainByWar'],
DEFAULT_WAR_CONFIG.maxTrainByWar
),
maxAtmosByWar: resolveNumber(
constValues,
['maxAtmosByWar'],
DEFAULT_WAR_CONFIG.maxAtmosByWar
),
armPerPhase: resolveNumber(constValues, ['armPerPhase', 'armperphase'], DEFAULT_WAR_CONFIG.armPerPhase),
maxTrainByCommand: resolveNumber(constValues, ['maxTrainByCommand'], DEFAULT_WAR_CONFIG.maxTrainByCommand),
maxAtmosByCommand: resolveNumber(constValues, ['maxAtmosByCommand'], DEFAULT_WAR_CONFIG.maxAtmosByCommand),
maxTrainByWar: resolveNumber(constValues, ['maxTrainByWar'], DEFAULT_WAR_CONFIG.maxTrainByWar),
maxAtmosByWar: resolveNumber(constValues, ['maxAtmosByWar'], DEFAULT_WAR_CONFIG.maxAtmosByWar),
castleCrewTypeId,
armTypes: {
footman: 1,
+1 -3
View File
@@ -4,9 +4,7 @@ export interface BattleSimQueueKeys {
notifyKeyPrefix: string;
}
export const buildBattleSimQueueKeys = (
profileName: string
): BattleSimQueueKeys => ({
export const buildBattleSimQueueKeys = (profileName: string): BattleSimQueueKeys => ({
queueKey: `sammo:${profileName}:battle-sim:queue`,
resultKeyPrefix: `sammo:${profileName}:battle-sim:result:`,
notifyKeyPrefix: `sammo:${profileName}:battle-sim:notify:`,
+8 -24
View File
@@ -20,11 +20,7 @@ import {
type WarActionModule,
} from '@sammo-ts/logic';
import {
type BattleSimJobPayload,
type BattleSimLogBuckets,
type BattleSimResultPayload,
} from './types.js';
import { type BattleSimJobPayload, type BattleSimLogBuckets, type BattleSimResultPayload } from './types.js';
import { convertLog } from './logFormatter.js';
const DEFAULT_GENERAL_AGE = 20;
@@ -33,8 +29,7 @@ const itemWarModules: WarActionModule[] = createItemActionModules(
createItemModuleRegistry(await loadItemModules([...ITEM_KEYS]))
).war;
const normalizeItemCode = (value: string | null): string | null =>
value === 'None' ? null : value;
const normalizeItemCode = (value: string | null): string | null => (value === 'None' ? null : value);
const mapNationPayload = (payload: BattleSimJobPayload['attackerNation']): Nation => ({
id: payload.nation,
@@ -81,9 +76,7 @@ const mapCityPayload = (payload: BattleSimJobPayload['attackerCity']): City => (
},
});
const mapGeneralPayload = (
payload: BattleSimJobPayload['attackerGeneral']
): General => ({
const mapGeneralPayload = (payload: BattleSimJobPayload['attackerGeneral']): General => ({
id: payload.no,
name: payload.name,
nationId: payload.nation,
@@ -121,9 +114,7 @@ const mapGeneralPayload = (
flags: {},
counters: {},
modifiers: {},
meta: payload.inheritBuff
? { inheritBuff: JSON.stringify(payload.inheritBuff) }
: {},
meta: payload.inheritBuff ? { inheritBuff: JSON.stringify(payload.inheritBuff) } : {},
},
meta: {
explevel: payload.explevel,
@@ -226,9 +217,7 @@ const resolveCityRiceConsumption = (options: {
year: number;
startYear: number;
}): number => {
const cityReport = options.battle.reports.find(
(report) => report.type === 'city'
);
const cityReport = options.battle.reports.find((report) => report.type === 'city');
if (!cityReport) {
return 0;
}
@@ -236,9 +225,7 @@ const resolveCityRiceConsumption = (options: {
return 0;
}
const crewType = options.unitSet.crewTypes?.find(
(item) => item.id === options.castleCrewTypeId
);
const crewType = options.unitSet.crewTypes?.find((item) => item.id === options.castleCrewTypeId);
const riceCoef = crewType?.rice ?? 1;
const tech = Number(options.defenderNation.meta.tech ?? 0);
const trainAtmos = resolveCityTrainAtmos(options.year, options.startYear);
@@ -346,9 +333,7 @@ export const processBattleSimJob = (payload: BattleSimJobPayload): BattleSimResu
});
lastBattle = outcome;
const attackerReport = outcome.reports.find(
(report) => report.type === 'general' && report.isAttacker
);
const attackerReport = outcome.reports.find((report) => report.type === 'general' && report.isAttacker);
const killed = attackerReport?.killed ?? 0;
const dead = attackerReport?.dead ?? 0;
@@ -386,8 +371,7 @@ export const processBattleSimJob = (payload: BattleSimJobPayload): BattleSimResu
const attackerActivated = outcome.metrics?.attackerActivatedSkills ?? {};
for (const [skillName, value] of Object.entries(attackerActivated)) {
attackerSkills[skillName] =
(attackerSkills[skillName] ?? 0) + value * weight;
attackerSkills[skillName] = (attackerSkills[skillName] ?? 0) + value * weight;
}
const defenderActivated = outcome.metrics?.defenderActivatedSkills ?? [];
+2 -6
View File
@@ -3,10 +3,7 @@ import crypto from 'node:crypto';
import type { BattleSimJobPayload, BattleSimResultPayload, BattleSimTransportResponse } from './types.js';
import type { BattleSimQueueKeys } from './keys.js';
type RedisBlPopResult =
| { key: string; element: string }
| [string, string]
| null;
type RedisBlPopResult = { key: string; element: string } | [string, string] | null;
interface RedisClientLike {
rPush(key: string, value: string): Promise<number>;
@@ -22,8 +19,7 @@ export interface RedisBattleSimTransportOptions {
resultTtlSeconds: number;
}
const toTimeoutSeconds = (timeoutMs: number): number =>
Math.max(1, Math.ceil(timeoutMs / 1000));
const toTimeoutSeconds = (timeoutMs: number): number => Math.max(1, Math.ceil(timeoutMs / 1000));
const parseBlPopValue = (result: RedisBlPopResult): string | null => {
if (!result) {
+4 -18
View File
@@ -6,15 +6,7 @@ import { parseUnitSetDefinition, type UnitSetDefinition } from '@sammo-ts/logic'
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const DEFAULT_UNIT_SET_ROOT = path.resolve(
__dirname,
'..',
'..',
'..',
'game-engine',
'resources',
'unitset'
);
const DEFAULT_UNIT_SET_ROOT = path.resolve(__dirname, '..', '..', '..', 'game-engine', 'resources', 'unitset');
export interface UnitSetLoaderOptions {
unitSetRoot?: string;
@@ -26,20 +18,14 @@ const readJsonFile = async (filePath: string): Promise<unknown> => {
return JSON.parse(raw) as unknown;
};
const resolveUnitSetRoot = (options?: UnitSetLoaderOptions): string =>
options?.unitSetRoot ?? DEFAULT_UNIT_SET_ROOT;
const resolveUnitSetRoot = (options?: UnitSetLoaderOptions): string => options?.unitSetRoot ?? DEFAULT_UNIT_SET_ROOT;
export const resolveUnitSetDefinitionPath = (
unitSetName: string,
options?: UnitSetLoaderOptions
): string => {
export const resolveUnitSetDefinitionPath = (unitSetName: string, options?: UnitSetLoaderOptions): string => {
const prefix = options?.filePrefix ?? 'unitset_';
return path.resolve(resolveUnitSetRoot(options), `${prefix}${unitSetName}.json`);
};
export const loadUnitSetDefinition = async (
unitSetPath: string
): Promise<UnitSetDefinition> => {
export const loadUnitSetDefinition = async (unitSetPath: string): Promise<UnitSetDefinition> => {
const raw = await readJsonFile(unitSetPath);
return parseUnitSetDefinition(raw);
};
+2 -6
View File
@@ -6,10 +6,7 @@ import { processBattleSimJob } from './processor.js';
import { RedisBattleSimTransport } from './redisTransport.js';
import type { BattleSimJob } from './types.js';
type RedisBlPopResult =
| { key: string; element: string }
| [string, string]
| null;
type RedisBlPopResult = { key: string; element: string } | [string, string] | null;
const parseBlPopValue = (result: RedisBlPopResult): string | null => {
if (!result) {
@@ -57,8 +54,7 @@ export const runBattleSimWorker = async (): Promise<void> => {
const result = processBattleSimJob(job.payload);
await transport.pushResult(job.jobId, result);
} catch (error) {
const reason =
error instanceof Error ? error.message : '전투 시뮬레이션 오류';
const reason = error instanceof Error ? error.message : '전투 시뮬레이션 오류';
await transport.pushResult(job.jobId, {
result: false,
reason,
+3 -13
View File
@@ -23,9 +23,7 @@ const parseNumber = (value: string | undefined, fallback: number, label: string)
return parsed;
};
export const resolveGameApiConfigFromEnv = (
env: NodeJS.ProcessEnv = process.env
): GameApiConfig => {
export const resolveGameApiConfigFromEnv = (env: NodeJS.ProcessEnv = process.env): GameApiConfig => {
const profile = env.PROFILE ?? env.SERVER_PROFILE ?? 'hwe';
const scenario = env.SCENARIO ?? 'default';
const profileName = `${profile}:${scenario}`;
@@ -42,21 +40,13 @@ export const resolveGameApiConfigFromEnv = (
profile,
scenario,
profileName,
daemonRequestTimeoutMs: parseNumber(
env.DAEMON_REQUEST_TIMEOUT_MS,
5000,
'DAEMON_REQUEST_TIMEOUT_MS'
),
daemonRequestTimeoutMs: parseNumber(env.DAEMON_REQUEST_TIMEOUT_MS, 5000, 'DAEMON_REQUEST_TIMEOUT_MS'),
battleSimRequestTimeoutMs: parseNumber(
env.BATTLE_SIM_REQUEST_TIMEOUT_MS,
8000,
'BATTLE_SIM_REQUEST_TIMEOUT_MS'
),
battleSimResultTtlSeconds: parseNumber(
env.BATTLE_SIM_RESULT_TTL_SECONDS,
60,
'BATTLE_SIM_RESULT_TTL_SECONDS'
),
battleSimResultTtlSeconds: parseNumber(env.BATTLE_SIM_RESULT_TTL_SECONDS, 60, 'BATTLE_SIM_RESULT_TTL_SECONDS'),
gameTokenSecret: secret,
flushChannel: `${gatewayPrefix}:flush`,
};
+7 -7
View File
@@ -26,13 +26,13 @@ export const zWorldStateMeta = z.object({
});
export type WorldStateMeta = z.infer<typeof zWorldStateMeta>;
export type WorldStateRow = GamePrisma.WorldStateGetPayload<{}>;
export type GeneralRow = GamePrisma.GeneralGetPayload<{}>;
export type GeneralTurnRow = GamePrisma.GeneralTurnGetPayload<{}>;
export type NationTurnRow = GamePrisma.NationTurnGetPayload<{}>;
export type CityRow = GamePrisma.CityGetPayload<{}>;
export type NationRow = GamePrisma.NationGetPayload<{}>;
export type TroopRow = GamePrisma.TroopGetPayload<{}>;
export type WorldStateRow = GamePrisma.WorldStateGetPayload<Record<string, never>>;
export type GeneralRow = GamePrisma.GeneralGetPayload<Record<string, never>>;
export type GeneralTurnRow = GamePrisma.GeneralTurnGetPayload<Record<string, never>>;
export type NationTurnRow = GamePrisma.NationTurnGetPayload<Record<string, never>>;
export type CityRow = GamePrisma.CityGetPayload<Record<string, never>>;
export type NationRow = GamePrisma.NationGetPayload<Record<string, never>>;
export type TroopRow = GamePrisma.TroopGetPayload<Record<string, never>>;
export type JsonValue = GamePrisma.JsonValue;
export type InputJsonValue = GamePrisma.InputJsonValue;
+2 -8
View File
@@ -27,10 +27,7 @@ export class InMemoryTurnDaemonTransport implements TurnDaemonTransport {
// 테스트용: 메모리 큐에 명령을 저장하고 requestId를 반환한다.
async sendCommand(command: TurnDaemonCommand): Promise<string> {
const requestId =
command.type === 'getStatus' && command.requestId
? command.requestId
: randomUUID();
const requestId = command.type === 'getStatus' && command.requestId ? command.requestId : randomUUID();
this.commands.push({
requestId,
sentAt: new Date().toISOString(),
@@ -39,10 +36,7 @@ export class InMemoryTurnDaemonTransport implements TurnDaemonTransport {
return requestId;
}
async requestCommand(
command: TurnDaemonCommand,
_timeoutMs?: number
): Promise<TurnDaemonCommandResult | null> {
async requestCommand(command: TurnDaemonCommand, _timeoutMs?: number): Promise<TurnDaemonCommandResult | null> {
const requestId = await this.sendCommand(command);
return this.results.get(requestId) ?? null;
}
+4 -16
View File
@@ -17,10 +17,7 @@ interface RedisTurnDaemonTransportOptions {
interface RedisClientLike {
xAdd(stream: string, id: string, message: Record<string, string>): Promise<string>;
xRead(
streams: { key: string; id: string },
options?: { BLOCK?: number; COUNT?: number }
): Promise<unknown>;
xRead(streams: { key: string; id: string }, options?: { BLOCK?: number; COUNT?: number }): Promise<unknown>;
}
type RedisStreamReadResponse = Array<{
@@ -29,10 +26,7 @@ type RedisStreamReadResponse = Array<{
}>;
const buildCommandEnvelope = (command: TurnDaemonCommand): TurnDaemonCommandEnvelope => {
const requestId =
command.type === 'getStatus' && command.requestId
? command.requestId
: randomUUID();
const requestId = command.type === 'getStatus' && command.requestId ? command.requestId : randomUUID();
return {
requestId,
sentAt: new Date().toISOString(),
@@ -79,10 +73,7 @@ export class RedisTurnDaemonTransport implements TurnDaemonTransport {
return envelope.requestId;
}
async requestCommand(
command: TurnDaemonCommand,
timeoutMs?: number
): Promise<TurnDaemonCommandResult | null> {
async requestCommand(command: TurnDaemonCommand, timeoutMs?: number): Promise<TurnDaemonCommandResult | null> {
const requestId = await this.sendCommand(command);
const deadline = Date.now() + (timeoutMs ?? this.requestTimeoutMs);
@@ -110,10 +101,7 @@ export class RedisTurnDaemonTransport implements TurnDaemonTransport {
if (!envelope) {
continue;
}
if (
envelope.event.type === 'commandResult' &&
envelope.requestId === requestId
) {
if (envelope.event.type === 'commandResult' && envelope.requestId === requestId) {
return envelope.event.result;
}
}
+2 -9
View File
@@ -1,14 +1,7 @@
import type {
TurnDaemonCommand,
TurnDaemonCommandResult,
TurnDaemonStatus,
} from './types.js';
import type { TurnDaemonCommand, TurnDaemonCommandResult, TurnDaemonStatus } from './types.js';
export interface TurnDaemonTransport {
sendCommand(command: TurnDaemonCommand): Promise<string>;
requestCommand(
command: TurnDaemonCommand,
timeoutMs?: number
): Promise<TurnDaemonCommandResult | null>;
requestCommand(command: TurnDaemonCommand, timeoutMs?: number): Promise<TurnDaemonCommandResult | null>;
requestStatus(timeoutMs?: number): Promise<TurnDaemonStatus | null>;
}
+1 -2
View File
@@ -31,8 +31,7 @@ const isMain = (): boolean => {
if (isMain()) {
const role = process.env.GAME_API_ROLE ?? 'server';
const run =
role === 'battle-sim-worker' ? runBattleSimWorker : runGameApiServer;
const run = role === 'battle-sim-worker' ? runBattleSimWorker : runGameApiServer;
run().catch((error) => {
console.error('[game-api] failed to start', error);
process.exitCode = 1;
+4 -17
View File
@@ -47,8 +47,7 @@ const BASE_MAP_TTL_SECONDS = 30;
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === 'object' && !Array.isArray(value);
const asRecord = (value: unknown): Record<string, unknown> =>
isRecord(value) ? value : {};
const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const resolveStartYear = (worldState: WorldStateRow): number => {
const meta = asRecord(worldState.meta);
@@ -98,10 +97,7 @@ const resolveSpyList = (meta: Record<string, unknown>): Record<number, number> =
const buildBaseMapCacheKey = (ctx: GameApiContext): string =>
`sammo:map:base:${ctx.profile.id}:${ctx.profile.scenario}`;
const loadBaseMap = async (
ctx: GameApiContext,
useCache: boolean
): Promise<BaseMapResult | null> => {
const loadBaseMap = async (ctx: GameApiContext, useCache: boolean): Promise<BaseMapResult | null> => {
const cacheKey = buildBaseMapCacheKey(ctx);
if (useCache) {
const cached = await ctx.redis.get(cacheKey);
@@ -143,14 +139,7 @@ const loadBaseMap = async (
const meta = asRecord(row.meta);
const state = readState(meta);
const supplyFlag = row.supplyState > 0 ? 1 : 0;
return [
row.id,
row.level,
state,
row.nationId,
row.region,
supplyFlag,
];
return [row.id, row.level, state, row.nationId, row.region, supplyFlag];
});
const nationList: MapNationCompact[] = nationRows.map((row) => [
@@ -225,9 +214,7 @@ export const loadWorldMap = async (
FROM general
WHERE nation_id = ${myNation}
`;
shownByGeneralList = generalCities
.map((row) => row.cityId)
.filter((id) => Number.isFinite(id));
shownByGeneralList = generalCities.map((row) => row.cityId).filter((id) => Number.isFinite(id));
}
return {
+1 -4
View File
@@ -50,10 +50,7 @@ const toMessageView = (row: MessageRow): MessageView => {
};
};
export const insertMessage = async (
db: DatabaseClient,
draft: MessageRecordDraft
): Promise<number> => {
export const insertMessage = async (db: DatabaseClient, draft: MessageRecordDraft): Promise<number> => {
const rows = await db.$queryRaw<Array<{ id: number }>>`
INSERT INTO message (mailbox, type, src, dest, time, valid_until, message)
VALUES (
+2 -9
View File
@@ -21,10 +21,7 @@ export const resolveNationInfo = async (
return { name: nation.name, color: nation.color };
};
export const buildTargetFromGeneral = async (
db: DatabaseClient,
general: GeneralRow
): Promise<MessageTarget> => {
export const buildTargetFromGeneral = async (db: DatabaseClient, general: GeneralRow): Promise<MessageTarget> => {
const nation = await resolveNationInfo(db, general.nationId);
return {
generalId: general.id,
@@ -36,11 +33,7 @@ export const buildTargetFromGeneral = async (
};
};
export const buildNationTarget = (
nationId: number,
nationName: string,
color: string
): MessageTarget => ({
export const buildNationTarget = (nationId: number, nationName: string, color: string): MessageTarget => ({
generalId: 0,
generalName: '',
nationId,
+123 -154
View File
@@ -48,9 +48,9 @@ const zTurnRunBudget = z.object({
catchUpCap: z.number().int().positive(),
});
const buildShiftAmountSchema = (maxTurns: number) =>
z.number()
z
.number()
.int()
.min(-(maxTurns - 1))
.max(maxTurns - 1)
@@ -114,7 +114,7 @@ export const appRouter = router({
if (ctx.auth?.user.id) {
const general = await ctx.db.general.findFirst({
where: { userId: ctx.auth.user.id },
select: { name: true, picture: true }
select: { name: true, picture: true },
});
if (general) {
myGeneral = {
@@ -143,33 +143,25 @@ export const appRouter = router({
}),
}),
battle: router({
simulate: procedure
.input(zBattleSimRequest)
.mutation(async ({ ctx, input }) => {
const worldState = await ctx.db.worldState.findFirst();
if (!worldState) {
throw new TRPCError({
code: 'PRECONDITION_FAILED',
message: 'World state is not initialized.',
});
}
simulate: procedure.input(zBattleSimRequest).mutation(async ({ ctx, input }) => {
const worldState = await ctx.db.worldState.findFirst();
if (!worldState) {
throw new TRPCError({
code: 'PRECONDITION_FAILED',
message: 'World state is not initialized.',
});
}
const payload = await buildBattleSimJobPayload(
worldState,
input,
ctx.profile.id
);
return ctx.battleSim.simulate(payload);
}),
getSimulation: procedure
.input(zBattleSimJobId)
.query(async ({ ctx, input }) => {
const result = await ctx.battleSim.getSimulationResult(input.jobId);
if (!result) {
return { status: 'queued', jobId: input.jobId };
}
return { status: 'completed', jobId: input.jobId, payload: result };
}),
const payload = await buildBattleSimJobPayload(worldState, input, ctx.profile.id);
return ctx.battleSim.simulate(payload);
}),
getSimulation: procedure.input(zBattleSimJobId).query(async ({ ctx, input }) => {
const result = await ctx.battleSim.getSimulationResult(input.jobId);
if (!result) {
return { status: 'queued', jobId: input.jobId };
}
return { status: 'completed', jobId: input.jobId, payload: result };
}),
}),
world: router({
getState: procedure.query(async ({ ctx }) => {
@@ -254,7 +246,8 @@ export const appRouter = router({
.input(
z.object({
generalId: z.number().int().positive(),
turnIndex: z.number()
turnIndex: z
.number()
.int()
.min(0)
.max(MAX_GENERAL_TURNS - 1),
@@ -300,18 +293,15 @@ export const appRouter = router({
});
}
const turns = await shiftGeneralTurns(
ctx.db,
input.generalId,
input.amount
);
const turns = await shiftGeneralTurns(ctx.db, input.generalId, input.amount);
return { ok: true, turns };
}),
setNation: authedProcedure
.input(
z.object({
generalId: z.number().int().positive(),
turnIndex: z.number()
turnIndex: z
.number()
.int()
.min(0)
.max(MAX_NATION_TURNS - 1),
@@ -382,12 +372,7 @@ export const appRouter = router({
});
}
const turns = await shiftNationTurns(
ctx.db,
general.nationId,
general.officerLevel,
input.amount
);
const turns = await shiftNationTurns(ctx.db, general.nationId, general.officerLevel, input.amount);
return { ok: true, turns };
}),
}),
@@ -420,37 +405,36 @@ export const appRouter = router({
diplomacy: MESSAGE_MAILBOX_NATIONAL_BASE + nationId,
} satisfies Record<MessageType, number>;
const [privateMessages, publicMessages, nationalMessages, diplomacyMessages] =
await Promise.all([
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.private,
msgType: 'private',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.public,
msgType: 'public',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.national,
msgType: 'national',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.diplomacy,
msgType: 'diplomacy',
limit: 15,
fromSeq: sequence,
}),
]);
const [privateMessages, publicMessages, nationalMessages, diplomacyMessages] = await Promise.all([
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.private,
msgType: 'private',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.public,
msgType: 'public',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.national,
msgType: 'national',
limit: 15,
fromSeq: sequence,
}),
fetchMessagesFromMailbox({
db: ctx.db,
mailbox: mailboxes.diplomacy,
msgType: 'diplomacy',
limit: 15,
fromSeq: sequence,
}),
]);
const messageBuckets: Record<MessageType, MessageView[]> = {
private: privateMessages,
@@ -481,20 +465,11 @@ export const appRouter = router({
if (lastType === 'private' && messageBuckets.private.length > 0) {
messageBuckets.private.pop();
} else if (
lastType === 'public' &&
messageBuckets.public.length > 0
) {
} else if (lastType === 'public' && messageBuckets.public.length > 0) {
messageBuckets.public.pop();
} else if (
lastType === 'national' &&
messageBuckets.national.length > 0
) {
} else if (lastType === 'national' && messageBuckets.national.length > 0) {
messageBuckets.national.pop();
} else if (
lastType === 'diplomacy' &&
messageBuckets.diplomacy.length > 0
) {
} else if (lastType === 'diplomacy' && messageBuckets.diplomacy.length > 0) {
messageBuckets.diplomacy.pop();
}
@@ -591,27 +566,16 @@ export const appRouter = router({
if (input.mailbox === MESSAGE_MAILBOX_PUBLIC) {
msgType = 'public';
} else if (input.mailbox >= MESSAGE_MAILBOX_NATIONAL_BASE) {
const destNationId =
input.mailbox - MESSAGE_MAILBOX_NATIONAL_BASE;
const destNationId = input.mailbox - MESSAGE_MAILBOX_NATIONAL_BASE;
if (destNationId <= 0) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Invalid nation mailbox.',
});
}
const nationInfo = await resolveNationInfo(
ctx.db,
destNationId
);
dest = buildNationTarget(
destNationId,
nationInfo.name,
nationInfo.color
);
msgType =
destNationId === general.nationId
? 'national'
: 'diplomacy';
const nationInfo = await resolveNationInfo(ctx.db, destNationId);
dest = buildNationTarget(destNationId, nationInfo.name, nationInfo.color);
msgType = destNationId === general.nationId ? 'national' : 'diplomacy';
} else if (input.mailbox > 0) {
const destGeneral = await ctx.db.general.findUnique({
where: { id: input.mailbox },
@@ -643,8 +607,7 @@ export const appRouter = router({
const result = await sendMessage(
{
insertMessage: (draft: MessageRecordDraft) =>
insertMessage(ctx.db, draft),
insertMessage: (draft: MessageRecordDraft) => insertMessage(ctx.db, draft),
},
draft
);
@@ -777,47 +740,45 @@ export const appRouter = router({
}
return { ok: true };
}),
setMySetting: authedProcedure
.input(zGeneralSettings)
.mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
type: 'setMySetting',
generalId: general.id,
settings: input,
});
if (!result || result.type !== 'setMySetting') {
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Unexpected response' });
}
if (!result.ok) {
throw new TRPCError({ code: 'BAD_REQUEST', message: result.reason });
}
return { ok: true };
}),
dropItem: authedProcedure
.input(z.object({ itemType: z.string() }))
.mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
type: 'dropItem',
generalId: general.id,
itemType: input.itemType,
});
if (!result || result.type !== 'dropItem') {
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Unexpected response' });
}
if (!result.ok) {
throw new TRPCError({ code: 'BAD_REQUEST', message: result.reason });
}
return { ok: true };
}),
setMySetting: authedProcedure.input(zGeneralSettings).mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
type: 'setMySetting',
generalId: general.id,
settings: input,
});
if (!result || result.type !== 'setMySetting') {
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Unexpected response' });
}
if (!result.ok) {
throw new TRPCError({ code: 'BAD_REQUEST', message: result.reason });
}
return { ok: true };
}),
dropItem: authedProcedure.input(z.object({ itemType: z.string() })).mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
type: 'dropItem',
generalId: general.id,
itemType: input.itemType,
});
if (!result || result.type !== 'dropItem') {
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Unexpected response' });
}
if (!result.ok) {
throw new TRPCError({ code: 'BAD_REQUEST', message: result.reason });
}
return { ok: true };
}),
}),
nation: router({
changePermission: authedProcedure
.input(z.object({
isAmbassador: z.boolean(),
targetGeneralIds: z.array(z.number().int().positive()),
}))
.input(
z.object({
isAmbassador: z.boolean(),
targetGeneralIds: z.array(z.number().int().positive()),
})
)
.mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
@@ -852,11 +813,13 @@ export const appRouter = router({
return { ok: true };
}),
appoint: authedProcedure
.input(z.object({
destGeneralId: z.number().int().nonnegative(),
destCityId: z.number().int().nonnegative(),
officerLevel: z.number().int().nonnegative(),
}))
.input(
z.object({
destGeneralId: z.number().int().nonnegative(),
destCityId: z.number().int().nonnegative(),
officerLevel: z.number().int().nonnegative(),
})
)
.mutation(async ({ ctx, input }) => {
const general = await getMyGeneral(ctx);
const result = await ctx.turnDaemon.requestCommand({
@@ -895,9 +858,11 @@ export const appRouter = router({
}),
pause: procedure
.input(
z.object({
reason: z.string().min(1).optional(),
}).optional()
z
.object({
reason: z.string().min(1).optional(),
})
.optional()
)
.mutation(async ({ ctx, input }) => {
const requestId = await ctx.turnDaemon.sendCommand({
@@ -908,9 +873,11 @@ export const appRouter = router({
}),
resume: procedure
.input(
z.object({
reason: z.string().min(1).optional(),
}).optional()
z
.object({
reason: z.string().min(1).optional(),
})
.optional()
)
.mutation(async ({ ctx, input }) => {
const requestId = await ctx.turnDaemon.sendCommand({
@@ -921,9 +888,11 @@ export const appRouter = router({
}),
status: procedure
.input(
z.object({
timeoutMs: z.number().int().positive().optional(),
}).optional()
z
.object({
timeoutMs: z.number().int().positive().optional(),
})
.optional()
)
.query(async ({ ctx, input }) => {
return ctx.turnDaemon.requestStatus(input?.timeoutMs);
+3 -10
View File
@@ -9,7 +9,7 @@ import {
} from '@sammo-ts/infra';
import { resolveGameApiConfigFromEnv } from './config.js';
import { createGameApiContext, type DatabaseClient } from './context.js';
import { createGameApiContext, type DatabaseClient as _DatabaseClient } from './context.js';
import { buildTurnDaemonStreamKeys } from './daemon/streamKeys.js';
import { RedisTurnDaemonTransport } from './daemon/redisTransport.js';
import { InMemoryFlushStore, RedisGatewayFlushSubscriber } from './auth/flushStore.js';
@@ -35,9 +35,7 @@ const extractBearerToken = (value: string | string[] | undefined): string | null
export const createGameApiServer = async () => {
const config = resolveGameApiConfigFromEnv();
const postgres = createGamePostgresConnector(
resolvePostgresConfigFromEnv({ schema: config.profile })
);
const postgres = createGamePostgresConnector(resolvePostgresConfigFromEnv({ schema: config.profile }));
const redis = createRedisConnector(resolveRedisConfigFromEnv());
await postgres.connect();
@@ -55,11 +53,7 @@ export const createGameApiServer = async () => {
const flushStore = new InMemoryFlushStore();
const flushSubscriberClient = redis.client.duplicate();
await flushSubscriberClient.connect();
const flushSubscriber = new RedisGatewayFlushSubscriber(
flushSubscriberClient,
config.flushChannel,
flushStore
);
const flushSubscriber = new RedisGatewayFlushSubscriber(flushSubscriberClient, config.flushChannel, flushStore);
await flushSubscriber.start();
const tokenVerifier = createGameTokenVerifier({
secret: config.gameTokenSecret,
@@ -104,7 +98,6 @@ export const createGameApiServer = async () => {
profile: config.profileName,
}));
app.addHook('onClose', async () => {
await flushSubscriber.stop();
await flushSubscriberClient.quit();
+42 -160
View File
@@ -12,18 +12,9 @@ import type {
TurnCommandEnv,
TriggerValue,
} from '@sammo-ts/logic';
import {
evaluateConstraints,
loadGeneralTurnCommandSpecs,
loadNationTurnCommandSpecs,
} from '@sammo-ts/logic';
import { evaluateConstraints, loadGeneralTurnCommandSpecs, loadNationTurnCommandSpecs } from '@sammo-ts/logic';
import type {
CityRow,
GeneralRow,
NationRow,
WorldStateRow,
} from '../context.js';
import type { CityRow, GeneralRow, NationRow, WorldStateRow } from '../context.js';
import { loadTurnCommandProfile } from './turnCommandProfile.js';
type AvailabilityStatus = 'available' | 'blocked' | 'needsInput' | 'unknown';
@@ -85,8 +76,7 @@ const DEFAULT_CREW_TYPE_ID = 1100;
const isRecord = (value: unknown): value is Record<string, unknown> =>
value !== null && typeof value === 'object' && !Array.isArray(value);
const asRecord = (value: unknown): Record<string, unknown> =>
isRecord(value) ? value : {};
const asRecord = (value: unknown): Record<string, unknown> => (isRecord(value) ? value : {});
const asTriggerRecord = (value: unknown): Record<string, TriggerValue> =>
isRecord(value) ? (value as Record<string, TriggerValue>) : {};
@@ -143,11 +133,7 @@ class MemoryStateView implements StateView {
}
}
const resolveNumber = (
source: Record<string, unknown>,
keys: string[],
fallback: number
): number => {
const resolveNumber = (source: Record<string, unknown>, keys: string[], fallback: number): number => {
for (const key of keys) {
const value = source[key];
if (typeof value === 'number' && Number.isFinite(value)) {
@@ -157,10 +143,7 @@ const resolveNumber = (
return fallback;
};
const resolveOptionalString = (
source: Record<string, unknown>,
keys: string[]
): string | null => {
const resolveOptionalString = (source: Record<string, unknown>, keys: string[]): string | null => {
for (const key of keys) {
const value = source[key];
if (typeof value === 'string') {
@@ -175,106 +158,36 @@ const buildCommandEnv = (worldState: WorldStateRow): CommandEnv => {
const constValues = asRecord(config.const);
return {
develCost: resolveNumber(
constValues,
['develCost', 'develcost', 'develrate'],
0
),
develCost: resolveNumber(constValues, ['develCost', 'develcost', 'develrate'], 0),
trainDelta: resolveNumber(constValues, ['trainDelta'], 0),
atmosDelta: resolveNumber(constValues, ['atmosDelta'], 0),
maxTrainByCommand: resolveNumber(constValues, ['maxTrainByCommand'], 0),
maxAtmosByCommand: resolveNumber(
constValues,
['maxAtmosByCommand'],
0
),
sabotageDefaultProb: resolveNumber(
constValues,
['sabotageDefaultProb'],
0
),
sabotageProbCoefByStat: resolveNumber(
constValues,
['sabotageProbCoefByStat'],
0
),
sabotageDefenceCoefByGeneralCount: resolveNumber(
constValues,
['sabotageDefenceCoefByGeneralCount'],
0
),
sabotageDamageMin: resolveNumber(
constValues,
['sabotageDamageMin'],
0
),
sabotageDamageMax: resolveNumber(
constValues,
['sabotageDamageMax'],
0
),
openingPartYear: resolveNumber(
constValues,
['openingPartYear'],
0
),
maxGeneral: resolveNumber(
constValues,
['defaultMaxGeneral', 'maxGeneral'],
0
),
defaultNpcGold: resolveNumber(
constValues,
['defaultNpcGold', 'defaultGold'],
DEFAULT_GENERAL_GOLD
),
defaultNpcRice: resolveNumber(
constValues,
['defaultNpcRice', 'defaultRice'],
DEFAULT_GENERAL_RICE
),
defaultCrewTypeId: resolveNumber(
constValues,
['defaultCrewTypeId'],
DEFAULT_CREW_TYPE_ID
),
defaultSpecialDomestic: resolveOptionalString(
constValues,
['defaultSpecialDomestic']
),
defaultSpecialWar: resolveOptionalString(
constValues,
['defaultSpecialWar']
),
initialNationGenLimit: resolveNumber(
constValues,
['initialNationGenLimit'],
0
),
maxAtmosByCommand: resolveNumber(constValues, ['maxAtmosByCommand'], 0),
sabotageDefaultProb: resolveNumber(constValues, ['sabotageDefaultProb'], 0),
sabotageProbCoefByStat: resolveNumber(constValues, ['sabotageProbCoefByStat'], 0),
sabotageDefenceCoefByGeneralCount: resolveNumber(constValues, ['sabotageDefenceCoefByGeneralCount'], 0),
sabotageDamageMin: resolveNumber(constValues, ['sabotageDamageMin'], 0),
sabotageDamageMax: resolveNumber(constValues, ['sabotageDamageMax'], 0),
openingPartYear: resolveNumber(constValues, ['openingPartYear'], 0),
maxGeneral: resolveNumber(constValues, ['defaultMaxGeneral', 'maxGeneral'], 0),
defaultNpcGold: resolveNumber(constValues, ['defaultNpcGold', 'defaultGold'], DEFAULT_GENERAL_GOLD),
defaultNpcRice: resolveNumber(constValues, ['defaultNpcRice', 'defaultRice'], DEFAULT_GENERAL_RICE),
defaultCrewTypeId: resolveNumber(constValues, ['defaultCrewTypeId'], DEFAULT_CREW_TYPE_ID),
defaultSpecialDomestic: resolveOptionalString(constValues, ['defaultSpecialDomestic']),
defaultSpecialWar: resolveOptionalString(constValues, ['defaultSpecialWar']),
initialNationGenLimit: resolveNumber(constValues, ['initialNationGenLimit'], 0),
maxTechLevel: resolveNumber(constValues, ['maxTechLevel'], 0),
baseGold: resolveNumber(constValues, ['baseGold', 'basegold'], 0),
baseRice: resolveNumber(constValues, ['baseRice', 'baserice'], 0),
maxResourceActionAmount: resolveNumber(
constValues,
['maxResourceActionAmount'],
0
),
maxResourceActionAmount: resolveNumber(constValues, ['maxResourceActionAmount'], 0),
};
};
const buildConstraintEnv = (
worldState: WorldStateRow
): Record<string, unknown> => {
const buildConstraintEnv = (worldState: WorldStateRow): Record<string, unknown> => {
const meta = asRecord(worldState.meta);
const scenarioMeta = asRecord(meta.scenarioMeta);
const startYear =
typeof scenarioMeta.startYear === 'number'
? scenarioMeta.startYear
: undefined;
const relYear =
typeof startYear === 'number'
? worldState.currentYear - startYear
: undefined;
const startYear = typeof scenarioMeta.startYear === 'number' ? scenarioMeta.startYear : undefined;
const relYear = typeof startYear === 'number' ? worldState.currentYear - startYear : undefined;
return {
currentYear: worldState.currentYear,
@@ -331,10 +244,7 @@ const mapGeneralRow = (row: GeneralRow): General => ({
const mapCityRow = (row: CityRow): City => {
const meta = asTriggerRecord(row.meta);
const state =
typeof meta.state === 'number' && Number.isFinite(meta.state)
? Math.floor(meta.state)
: 0;
const state = typeof meta.state === 'number' && Number.isFinite(meta.state) ? Math.floor(meta.state) : 0;
return {
id: row.id,
name: row.name,
@@ -420,11 +330,7 @@ const evaluateAvailability = (
}
const missingKinds = new Set(result.missing.map((req) => req.kind));
const inputOnlyMissing =
missingKinds.size === 0
? reqArg
: Array.from(missingKinds).every((kind) =>
INPUT_REQUIREMENT_KINDS.has(kind)
);
missingKinds.size === 0 ? reqArg : Array.from(missingKinds).every((kind) => INPUT_REQUIREMENT_KINDS.has(kind));
if (inputOnlyMissing) {
return {
possible: true,
@@ -450,20 +356,12 @@ const evaluateDefinition = (
return evaluateAvailability(constraints, ctx, view, reqArg);
};
const pickAvailability = (
lhs: AvailabilityCore,
rhs: AvailabilityCore
): AvailabilityCore =>
AVAILABILITY_PRIORITY[lhs.status] >= AVAILABILITY_PRIORITY[rhs.status]
? lhs
: rhs;
const pickAvailability = (lhs: AvailabilityCore, rhs: AvailabilityCore): AvailabilityCore =>
AVAILABILITY_PRIORITY[lhs.status] >= AVAILABILITY_PRIORITY[rhs.status] ? lhs : rhs;
type TurnCommandSpec = GeneralTurnCommandSpec | NationTurnCommandSpec;
const buildEntries = (
env: CommandEnv,
specs: TurnCommandSpec[]
): CommandEntry[] => {
const buildEntries = (env: CommandEnv, specs: TurnCommandSpec[]): CommandEntry[] => {
const entries: CommandEntry[] = [];
for (const spec of specs) {
@@ -477,20 +375,16 @@ const buildEntries = (
if (spec.key === 'che_포상') {
entry.evaluate = (ctx, view) => {
const gold = evaluateDefinition(
definition,
ctx,
view,
true,
{ isGold: true, amount: 1, destGeneralId: 0 }
);
const rice = evaluateDefinition(
definition,
ctx,
view,
true,
{ isGold: false, amount: 1, destGeneralId: 0 }
);
const gold = evaluateDefinition(definition, ctx, view, true, {
isGold: true,
amount: 1,
destGeneralId: 0,
});
const rice = evaluateDefinition(definition, ctx, view, true, {
isGold: false,
amount: 1,
destGeneralId: 0,
});
return pickAvailability(gold, rice);
};
}
@@ -501,23 +395,13 @@ const buildEntries = (
return entries;
};
const buildGroups = (
entries: CommandEntry[],
ctx: ConstraintContext,
view: StateView
): TurnCommandGroup[] => {
const buildGroups = (entries: CommandEntry[], ctx: ConstraintContext, view: StateView): TurnCommandGroup[] => {
const groups = new Map<string, TurnCommandAvailability[]>();
for (const entry of entries) {
const availability = entry.evaluate
? entry.evaluate(ctx, view)
: evaluateDefinition(
entry.definition,
ctx,
view,
entry.reqArg,
entry.args
);
: evaluateDefinition(entry.definition, ctx, view, entry.reqArg, entry.args);
const value: TurnCommandAvailability = {
key: entry.definition.key,
name: entry.definition.name,
@@ -550,9 +434,7 @@ export const buildTurnCommandTable = async (options: {
const general = mapGeneralRow(options.general);
const city = options.city ? mapCityRow(options.city) : null;
const nation = options.nation ? mapNationRow(options.nation) : null;
const generalList = options.nationGenerals
? options.nationGenerals.map(mapGeneralRow)
: null;
const generalList = options.nationGenerals ? options.nationGenerals.map(mapGeneralRow) : null;
const view = buildStateView(general, city, nation, generalList);
const ctx: ConstraintContext = {
+6 -22
View File
@@ -1,9 +1,4 @@
import type {
DatabaseClient,
GeneralTurnRow,
NationTurnRow,
InputJsonValue,
} from '../context.js';
import type { DatabaseClient, GeneralTurnRow, NationTurnRow, InputJsonValue } from '../context.js';
export const DEFAULT_TURN_ACTION = '휴식';
export const MAX_GENERAL_TURNS = 30;
@@ -26,21 +21,16 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
const normalizeAction = (action: string | null | undefined): string =>
action && action.length > 0 ? action : DEFAULT_TURN_ACTION;
const normalizeArgs = (args: unknown): InputJsonValue =>
isRecord(args) ? (args as InputJsonValue) : {};
const normalizeArgs = (args: unknown): InputJsonValue => (isRecord(args) ? (args as InputJsonValue) : {});
const createDefaultEntry = (): ReservedTurnEntry => ({
action: DEFAULT_TURN_ACTION,
args: {},
});
const buildDefaultTurns = (length: number): ReservedTurnEntry[] =>
Array.from({ length }, () => createDefaultEntry());
const buildDefaultTurns = (length: number): ReservedTurnEntry[] => Array.from({ length }, () => createDefaultEntry());
const applyShift = (
turns: ReservedTurnEntry[],
amount: number
): ReservedTurnEntry[] => {
const applyShift = (turns: ReservedTurnEntry[], amount: number): ReservedTurnEntry[] => {
if (amount === 0) {
return turns.slice();
}
@@ -55,10 +45,7 @@ const applyShift = (
return sliced.concat(padding);
};
const buildTurnListFromRows = (
rows: Array<GeneralTurnRow | NationTurnRow>,
maxTurns: number
): ReservedTurnEntry[] => {
const buildTurnListFromRows = (rows: Array<GeneralTurnRow | NationTurnRow>, maxTurns: number): ReservedTurnEntry[] => {
const result = buildDefaultTurns(maxTurns);
for (const row of rows) {
if (row.turnIdx < 0 || row.turnIdx >= maxTurns) {
@@ -113,10 +100,7 @@ const persistNationTurns = async (
});
};
export const loadGeneralTurns = async (
db: DatabaseClient,
generalId: number
): Promise<ReservedTurnEntry[]> => {
export const loadGeneralTurns = async (db: DatabaseClient, generalId: number): Promise<ReservedTurnEntry[]> => {
const rows = await db.generalTurn.findMany({
where: { generalId },
orderBy: [{ turnIdx: 'asc' }],
+4 -16
View File
@@ -2,21 +2,12 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
DEFAULT_TURN_COMMAND_PROFILE,
parseTurnCommandProfile,
type TurnCommandProfile,
} from '@sammo-ts/logic';
import { DEFAULT_TURN_COMMAND_PROFILE, parseTurnCommandProfile, type TurnCommandProfile } from '@sammo-ts/logic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
const DEFAULT_PROFILE_PATH = path.resolve(
REPO_ROOT,
'resources',
'turn-commands',
'default.json'
);
const DEFAULT_PROFILE_PATH = path.resolve(REPO_ROOT, 'resources', 'turn-commands', 'default.json');
export interface TurnCommandProfileOptions {
filePath?: string;
@@ -27,11 +18,8 @@ const readCommandProfile = async (filePath: string): Promise<TurnCommandProfile>
return parseTurnCommandProfile(JSON.parse(raw) as unknown);
};
export const loadTurnCommandProfile = async (
options?: TurnCommandProfileOptions
): Promise<TurnCommandProfile> => {
const filePath =
options?.filePath ?? process.env.TURN_COMMANDS_PATH ?? DEFAULT_PROFILE_PATH;
export const loadTurnCommandProfile = async (options?: TurnCommandProfileOptions): Promise<TurnCommandProfile> => {
const filePath = options?.filePath ?? process.env.TURN_COMMANDS_PATH ?? DEFAULT_PROFILE_PATH;
try {
return await readCommandProfile(filePath);
} catch (error) {