Commit b7dfc180 authored by nanahira's avatar nanahira

update config format

parent 7dcde5ac
Pipeline #43217 passed with stages
in 1 minute and 44 seconds
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
- 目录内引用:同级文件使用 `'./xxx'`,子目录文件使用 `'../xxx'` - 目录内引用:同级文件使用 `'./xxx'`,子目录文件使用 `'../xxx'`
- 目录外引用:必须通过 index.ts 引用,如 `'../room'` `'../client'`(指向 index.ts) - 目录外引用:必须通过 index.ts 引用,如 `'../room'` `'../client'`(指向 index.ts)
- 禁止直接引用具体文件:不要使用 `'../room/room'` `'../client/client'` 这样的路径 - 禁止直接引用具体文件:不要使用 `'../room/room'` `'../client/client'` 这样的路径
- 如果正在写的算法代码与 this 和业务无关,那么不要放在类方法里面,而是在 utility 目录新开一个 ts 文件放进去
## 模块结构 ## 模块结构
......
HOST: "::" host: "::"
PORT: "7911" port: 7911
REDIS_URL: "" redisUrl: ""
LOG_LEVEL: info logLevel: info
WS_PORT: "0" wsPort: 0
SSL_PATH: "" sslPath: ""
SSL_CERT: "" sslCert: ""
SSL_KEY: "" sslKey: ""
TRUSTED_PROXIES: 127.0.0.0/8,::1/128 trustedProxies:
NO_CONNECT_COUNT_LIMIT: "" - 127.0.0.0/8
YGOPRO_VERSION: "" - ::1/128
ALT_VERSIONS: "" noConnectCountLimit: ""
USE_PROXY: "" ygoproVersion: "0x1362"
YGOPRO_PATH: ./ygopro altVersions:
EXTRA_SCRIPT_PATH: "" - 2330
DECK_MAIN_MIN: "40" - 2331
DECK_MAIN_MAX: "60" useProxy: ""
DECK_EXTRA_MAX: "15" ygoproPath: ./ygopro
DECK_SIDE_MAX: "15" extraScriptPath: ""
DECK_MAX_COPIES: "3" deckMainMin: 40
OCGCORE_DEBUG_LOG: "" deckMainMax: 60
OCGCORE_WASM_PATH: "" deckExtraMax: 15
WELCOME: "" deckSideMax: 15
ENABLE_RECONNECT: "1" deckMaxCopies: 3
RECONNECT_TIMEOUT: "180000" ocgcoreDebugLog: 0
HOSTINFO_LFLIST: "0" ocgcoreWasmPath: ""
HOSTINFO_RULE: "0" welcome: ""
HOSTINFO_MODE: "0" enableReconnect: 1
HOSTINFO_DUEL_RULE: "5" reconnectTimeout: 180000
HOSTINFO_NO_CHECK_DECK: "0" hostinfoLflist: 0
HOSTINFO_NO_SHUFFLE_DECK: "0" hostinfoRule: 0
HOSTINFO_START_LP: "8000" hostinfoMode: 0
HOSTINFO_START_HAND: "5" hostinfoDuelRule: 5
HOSTINFO_DRAW_COUNT: "1" hostinfoNoCheckDeck: 0
HOSTINFO_TIME_LIMIT: "240" hostinfoNoShuffleDeck: 0
hostinfoStartLp: 8000
hostinfoStartHand: 5
hostinfoDrawCount: 1
hostinfoTimeLimit: 240
...@@ -2,6 +2,7 @@ import yaml from 'yaml'; ...@@ -2,6 +2,7 @@ import yaml from 'yaml';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { DefaultHostinfo } from './room/default-hostinfo'; import { DefaultHostinfo } from './room/default-hostinfo';
import { Prettify } from 'nfkit'; import { Prettify } from 'nfkit';
import { normalizeConfigByDefaultKeys } from './utility/normalize-config-by-default-keys';
export type HostinfoOptions = { export type HostinfoOptions = {
[K in keyof typeof DefaultHostinfo as `HOSTINFO_${Uppercase<K>}`]: string; [K in keyof typeof DefaultHostinfo as `HOSTINFO_${Uppercase<K>}`]: string;
...@@ -30,9 +31,9 @@ export const defaultConfig = { ...@@ -30,9 +31,9 @@ export const defaultConfig = {
// Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true. // Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true.
NO_CONNECT_COUNT_LIMIT: '', NO_CONNECT_COUNT_LIMIT: '',
// Restrict accepted YGOPro version. Format: version string; empty means no restriction. // Restrict accepted YGOPro version. Format: version string; empty means no restriction.
YGOPRO_VERSION: '', YGOPRO_VERSION: '0x1362',
// Additional accepted versions. Format: comma-separated version strings. // Additional accepted versions. Format: comma-separated version strings.
ALT_VERSIONS: '', ALT_VERSIONS: '2330,2331',
// Proxy URL for outbound HTTP(S) requests. // Proxy URL for outbound HTTP(S) requests.
// Format: proxy URL string (e.g. http://127.0.0.1:7890). Empty means no proxy. // Format: proxy URL string (e.g. http://127.0.0.1:7890). Empty means no proxy.
USE_PROXY: '', USE_PROXY: '',
...@@ -52,7 +53,7 @@ export const defaultConfig = { ...@@ -52,7 +53,7 @@ export const defaultConfig = {
DECK_MAX_COPIES: '3', DECK_MAX_COPIES: '3',
// Enable ocgcore debug logs. // Enable ocgcore debug logs.
// Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true. // Boolean parse rule (default false): ''/'0'/'false'/'null' => false, otherwise true.
OCGCORE_DEBUG_LOG: '', OCGCORE_DEBUG_LOG: '0',
// OCGCore wasm file path. Format: filesystem path string. Empty means use default wasm loading. // OCGCore wasm file path. Format: filesystem path string. Empty means use default wasm loading.
OCGCORE_WASM_PATH: '', OCGCORE_WASM_PATH: '',
// Welcome message sent when players join. Format: plain string. // Welcome message sent when players join. Format: plain string.
...@@ -77,16 +78,25 @@ export const defaultConfig = { ...@@ -77,16 +78,25 @@ export const defaultConfig = {
export type Config = Prettify<typeof defaultConfig & HostinfoOptions>; export type Config = Prettify<typeof defaultConfig & HostinfoOptions>;
export function loadConfig(): Config { export function loadConfig(): Config {
let readConfig: Partial<Config> = {}; let readConfig: Record<string, unknown> = {};
try { try {
const configText = fs.readFileSync('./config.yaml', 'utf-8'); const configText = fs.readFileSync('./config.yaml', 'utf-8');
readConfig = yaml.parse(configText); const parsed = yaml.parse(configText);
if (parsed && typeof parsed === 'object') {
readConfig = parsed;
}
} catch (e) { } catch (e) {
console.error(`Failed to read config: ${e.toString()}`); console.error(`Failed to read config: ${e.toString()}`);
} }
const normalizedConfig = normalizeConfigByDefaultKeys(
readConfig,
defaultConfig,
);
return { return {
...defaultConfig, ...defaultConfig,
...readConfig, ...normalizedConfig,
...process.env, ...process.env,
}; };
} }
...@@ -2,8 +2,29 @@ import * as fs from 'fs'; ...@@ -2,8 +2,29 @@ import * as fs from 'fs';
import yaml from 'yaml'; import yaml from 'yaml';
import { defaultConfig } from '../config'; import { defaultConfig } from '../config';
function toCamelCaseKey(key: string): string {
const lower = key.toLowerCase();
return lower.replace(/_([a-z0-9])/g, (_, ch: string) => ch.toUpperCase());
}
function toTypedValue(value: string): string | number {
if (/^\d+$/.test(value)) {
return Number.parseInt(value, 10);
}
return value;
}
async function main(): Promise<void> { async function main(): Promise<void> {
const output = yaml.stringify(defaultConfig); const exampleConfig = Object.fromEntries(
Object.entries(defaultConfig).map(([key, value]) => {
if (value.includes(',')) {
const items = value.split(',').map((item) => toTypedValue(item));
return [toCamelCaseKey(key), items];
}
return [toCamelCaseKey(key), toTypedValue(value)];
}),
);
const output = yaml.stringify(exampleConfig);
await fs.promises.writeFile('./config.example.yaml', output, 'utf-8'); await fs.promises.writeFile('./config.example.yaml', output, 'utf-8');
console.log('Generated config.example.yaml'); console.log('Generated config.example.yaml');
} }
......
import { normalizeConfigValue } from './normalize-config-value';
function toCamelCaseKey(key: string): string {
const lower = key.toLowerCase();
return lower.replace(/_([a-z0-9])/g, (_, ch: string) => ch.toUpperCase());
}
export function normalizeConfigByDefaultKeys<T extends Record<string, string>>(
readConfig: Record<string, unknown>,
defaultConfig: T,
): Partial<T> {
const normalizedConfig: Partial<T> = {};
for (const key of Object.keys(defaultConfig) as Array<keyof T>) {
const rawKey = key as string;
const camelKey = toCamelCaseKey(rawKey);
const value =
readConfig[camelKey] !== undefined
? readConfig[camelKey]
: readConfig[rawKey];
const normalized = normalizeConfigValue(value);
if (normalized !== undefined) {
normalizedConfig[key] = normalized as T[typeof key];
}
}
return normalizedConfig;
}
function normalizeArrayItem(value: unknown): string {
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return value.toString();
}
if (typeof value === 'boolean') {
return value ? '1' : '0';
}
return String(value);
}
export function normalizeConfigValue(value: unknown): string | undefined {
if (value === undefined) {
return undefined;
}
if (typeof value === 'string') {
return value;
}
if (typeof value === 'number') {
return value.toString();
}
if (typeof value === 'boolean') {
return value ? '1' : '0';
}
if (Array.isArray(value)) {
return value.map((item) => normalizeArrayItem(item)).join(',');
}
return String(value);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment