refactor(core): use @snowluma/common/coerce in config normalizers

Phase 2. notifications/config and webui/ui-config dropped their verbatim-
duplicated isObject/boolOr/clampInt (+ ui-config clampNum) in favor of the
shared @snowluma/common/coerce. Pure delete-and-import: the shared
primitives are byte-identical to the originals (adversarially verified),
so behavior is unchanged.

Kept local: strOr (notifications-only), oneOf/isFiniteNum/hexOr
(ui-specific). notifications-config suite green (21), core+common
typecheck clean.
This commit is contained in:
DrSmoothl
2026-06-25 00:05:46 +08:00
parent 1ce7385d1e
commit fbc5a3c981
2 changed files with 7 additions and 33 deletions
+4 -15
View File
@@ -7,6 +7,7 @@
//
// Channels are GLOBAL (defined once here); each UIN opts into a subset via
// `OneBotConfig.notifications.channelIds` (see packages/onebot/src/config.ts).
import { boolOr, clampInt, isObject } from '@snowluma/common/coerce';
import { createLogger } from '@snowluma/common/logger';
import fs from 'fs';
import path from 'path';
@@ -92,21 +93,9 @@ export function renderTemplate(template: string, vars: Record<string, string>):
});
}
// ─── Normalization helpers (replicated from ui-config conventions) ──────────
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function boolOr(value: unknown, fallback: boolean): boolean {
return typeof value === 'boolean' ? value : fallback;
}
function clampInt(value: unknown, min: number, max: number, fallback: number): number {
const n = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
if (!Number.isFinite(n)) return fallback;
return Math.trunc(Math.min(max, Math.max(min, n)));
}
// ─── Normalization helpers ──────────────────────────────────────────────────
// isObject / boolOr / clampInt come from @snowluma/common/coerce. strOr is
// notifications-only (string truncation to maxLen), so it stays here.
function strOr(value: unknown, fallback: string, maxLen: number): string {
return typeof value === 'string' ? value.slice(0, maxLen) : fallback;
+3 -18
View File
@@ -1,3 +1,4 @@
import { boolOr, clampInt, clampNum, isObject } from '@snowluma/common/coerce';
import { createLogger } from '@snowluma/common/logger';
import fs from 'fs';
import path from 'path';
@@ -308,10 +309,8 @@ function defaultPages(): UiPages {
}
// ─── Normalization helpers ─────────────────────────────────────────────────
function isObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
// isObject / boolOr / clampNum / clampInt come from @snowluma/common/coerce.
// oneOf (enum), isFiniteNum, and hexOr (CSS color) are ui-specific and stay.
function oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
return typeof value === 'string' && (allowed as readonly string[]).includes(value)
@@ -319,25 +318,11 @@ function oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback
: fallback;
}
function clampNum(value: unknown, min: number, max: number, fallback: number): number {
const n = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
if (!Number.isFinite(n)) return fallback;
return Math.min(max, Math.max(min, n));
}
function clampInt(value: unknown, min: number, max: number, fallback: number): number {
return Math.trunc(clampNum(value, min, max, fallback));
}
function isFiniteNum(value: unknown): boolean {
if (typeof value === 'number') return Number.isFinite(value);
return typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value));
}
function boolOr(value: unknown, fallback: boolean): boolean {
return typeof value === 'boolean' ? value : fallback;
}
const HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
function hexOr(value: unknown, fallback: string): string {
return typeof value === 'string' && HEX_RE.test(value.trim()) ? value.trim() : fallback;