refactor: centralize isPlainObject, isRecord, isErrno, isLoopbackHost utilities (#12926)

This commit is contained in:
max
2026-02-09 17:02:55 -08:00
committed by GitHub
parent 70f9edeec7
commit 8d75a496bf
37 changed files with 97 additions and 226 deletions
+2 -9
View File
@@ -1,3 +1,5 @@
import { isPlainObject } from "../utils.js";
type PathNode = Record<string, unknown>;
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
@@ -79,12 +81,3 @@ export function getConfigValueAtPath(root: PathNode, path: string[]): unknown {
}
return cursor;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === "[object Object]"
);
}
+2 -9
View File
@@ -22,6 +22,8 @@
// Pattern for valid uppercase env var names: starts with letter or underscore,
// followed by letters, numbers, or underscores (all uppercase)
import { isPlainObject } from "../utils.js";
const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
export class MissingEnvVarError extends Error {
@@ -34,15 +36,6 @@ export class MissingEnvVarError extends Error {
}
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === "[object Object]"
);
}
function substituteString(value: string, env: NodeJS.ProcessEnv, configPath: string): string {
if (!value.includes("$")) {
return value;
+1 -9
View File
@@ -13,6 +13,7 @@
import JSON5 from "json5";
import fs from "node:fs";
import path from "node:path";
import { isPlainObject } from "../utils.js";
export const INCLUDE_KEY = "$include";
export const MAX_INCLUDE_DEPTH = 10;
@@ -52,15 +53,6 @@ export class CircularIncludeError extends ConfigIncludeError {
// Utilities
// ============================================================================
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === "[object Object]"
);
}
/** Deep merge: arrays concatenate, objects merge recursively, primitives: source wins */
export function deepMerge(target: unknown, source: unknown): unknown {
if (Array.isArray(target) && Array.isArray(source)) {
+2 -2
View File
@@ -10,8 +10,8 @@ export type LegacyConfigMigration = {
apply: (raw: Record<string, unknown>, changes: string[]) => void;
};
export const isRecord = (value: unknown): value is Record<string, unknown> =>
Boolean(value && typeof value === "object" && !Array.isArray(value));
import { isRecord } from "../utils.js";
export { isRecord };
export const getRecord = (value: unknown): Record<string, unknown> | null =>
isRecord(value) ? value : null;
+2 -4
View File
@@ -1,8 +1,6 @@
type PlainObject = Record<string, unknown>;
import { isPlainObject } from "../utils.js";
function isPlainObject(value: unknown): value is PlainObject {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
type PlainObject = Record<string, unknown>;
export function applyMergePatch(base: unknown, patch: unknown): unknown {
if (!isPlainObject(patch)) {
+1 -5
View File
@@ -1,15 +1,11 @@
import type { OpenClawConfig } from "./types.js";
import { resolveUserPath } from "../utils.js";
import { isPlainObject, resolveUserPath } from "../utils.js";
const PATH_VALUE_RE = /^~(?=$|[\\/])/;
const PATH_KEY_RE = /(dir|path|paths|file|root|workspace)$/i;
const PATH_LIST_KEYS = new Set(["paths", "pathPrepend"]);
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function normalizeStringValue(key: string | undefined, value: string): string {
if (!PATH_VALUE_RE.test(value.trim())) {
return value;
+1 -4
View File
@@ -9,6 +9,7 @@ import {
listChatChannels,
normalizeChatChannelId,
} from "../channels/registry.js";
import { isRecord } from "../utils.js";
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
type PluginEnableChange = {
@@ -36,10 +37,6 @@ const PROVIDER_PLUGIN_IDS: Array<{ pluginId: string; providerId: string }> = [
{ pluginId: "minimax-portal-auth", providerId: "minimax-portal" },
];
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
function hasNonEmptyString(value: unknown): boolean {
return typeof value === "string" && value.trim().length > 0;
}
+1 -9
View File
@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "./types.js";
import { isPlainObject } from "../utils.js";
import { parseConfigPath, setConfigValueAtPath, unsetConfigValueAtPath } from "./config-paths.js";
type OverrideTree = Record<string, unknown>;
@@ -19,15 +20,6 @@ function mergeOverrides(base: unknown, override: unknown): unknown {
return next;
}
function isPlainObject(value: unknown): value is Record<string, unknown> {
return (
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
Object.prototype.toString.call(value) === "[object Object]"
);
}
export function getConfigOverrides(): OverrideTree {
return overrides;
}
+1 -4
View File
@@ -9,6 +9,7 @@ import {
} from "../plugins/config-state.js";
import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js";
import { validateJsonSchemaValue } from "../plugins/schema-validator.js";
import { isRecord } from "../utils.js";
import { findDuplicateAgentDirs, formatDuplicateAgentDirError } from "./agent-dirs.js";
import { applyAgentDefaults, applyModelDefaults, applySessionDefaults } from "./defaults.js";
import { findLegacyConfigIssues } from "./legacy.js";
@@ -129,10 +130,6 @@ export function validateConfigObject(
};
}
function isRecord(value: unknown): value is Record<string, unknown> {
return Boolean(value && typeof value === "object" && !Array.isArray(value));
}
export function validateConfigObjectWithPlugins(raw: unknown):
| {
ok: true;