feat: 重构配置布局,server.listen/storage/logging + probes.execution 分组
- 新增 server.listen (host/port)、server.storage (dataDir/retention)、 server.logging 分组 - 新增 probes.execution (maxConcurrentChecks) 分组,替代顶层 runtime - 旧配置入口 (runtime/logging/server.host/server.port/server.dataDir) 启动期拒绝 - 更新 types.ts、builder.ts、config-loader.ts 适配新路径 - 更新 probe-config.schema.json、probes.example.yaml、README.md、 DEVELOPMENT.md - 补充 config-loader 和 variables 测试覆盖新路径和旧入口拒绝 - 同步 5 个 delta specs 到主规范 (probe-config, config-variables, data-retention, probe-engine, runtime-logging) - 归档 openspec change reorganize-config-layout
This commit is contained in:
@@ -4,13 +4,14 @@ import { dirname, resolve } from "node:path";
|
||||
import type { ConfigValidationIssue } from "./schema/issues";
|
||||
import type {
|
||||
DefaultsConfig,
|
||||
EngineRuntimeConfig,
|
||||
ExecutionConfig,
|
||||
LoggingConfig,
|
||||
LogLevel,
|
||||
RawTargetConfig,
|
||||
ResolvedLoggingConfig,
|
||||
ResolvedTargetBase,
|
||||
RotationFrequency,
|
||||
ServerStorageConfig,
|
||||
} from "./types";
|
||||
|
||||
import { checkerRegistry } from "./runner";
|
||||
@@ -87,20 +88,23 @@ export async function loadConfig(configPath: string): Promise<ResolvedConfig> {
|
||||
|
||||
const configDir = dirname(resolve(configPath));
|
||||
const server = validated.server ?? {};
|
||||
const runtime = validated.runtime ?? {};
|
||||
const listen = server.listen ?? {};
|
||||
const storage = server.storage ?? {};
|
||||
const defaults = validated.defaults ?? {};
|
||||
|
||||
const host = server.host ?? DEFAULT_HOST;
|
||||
const port = server.port ?? DEFAULT_PORT;
|
||||
const dataDir = resolve(configDir, server.dataDir ?? DEFAULT_DATA_DIR);
|
||||
const host = listen.host ?? DEFAULT_HOST;
|
||||
const port = listen.port ?? DEFAULT_PORT;
|
||||
const dataDir = resolve(configDir, storage.dataDir ?? DEFAULT_DATA_DIR);
|
||||
|
||||
const maxConcurrentChecks = resolveMaxConcurrentChecks(runtime);
|
||||
const retentionMs = resolveRetention(runtime);
|
||||
const probes = validated.probes ?? {};
|
||||
const execution = probes.execution ?? {};
|
||||
const maxConcurrentChecks = resolveMaxConcurrentChecks(execution);
|
||||
const retentionMs = resolveRetention(storage);
|
||||
|
||||
const logging = resolveLogging(validated.logging ?? {}, dataDir, configDir);
|
||||
const logging = resolveLogging(server.logging ?? {}, dataDir, configDir);
|
||||
|
||||
const allRuntimeIssues = [...allIssues];
|
||||
validateLoggingConfig(validated.logging, allRuntimeIssues);
|
||||
validateLoggingConfig(server.logging, allRuntimeIssues);
|
||||
if (allRuntimeIssues.length > 0) {
|
||||
throwConfigIssues(dedupeIssues(allRuntimeIssues));
|
||||
}
|
||||
@@ -172,19 +176,19 @@ function resolveLogLevel(level: unknown, fallback: LogLevel): LogLevel {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function resolveMaxConcurrentChecks(runtime: EngineRuntimeConfig): number {
|
||||
if (runtime.maxConcurrentChecks === undefined) return DEFAULT_MAX_CONCURRENT_CHECKS;
|
||||
function resolveMaxConcurrentChecks(execution: ExecutionConfig): number {
|
||||
if (execution.maxConcurrentChecks === undefined) return DEFAULT_MAX_CONCURRENT_CHECKS;
|
||||
if (
|
||||
!isNumber(runtime.maxConcurrentChecks) ||
|
||||
!Number.isInteger(runtime.maxConcurrentChecks) ||
|
||||
runtime.maxConcurrentChecks <= 0
|
||||
!isNumber(execution.maxConcurrentChecks) ||
|
||||
!Number.isInteger(execution.maxConcurrentChecks) ||
|
||||
execution.maxConcurrentChecks <= 0
|
||||
)
|
||||
return DEFAULT_MAX_CONCURRENT_CHECKS;
|
||||
return runtime.maxConcurrentChecks;
|
||||
return execution.maxConcurrentChecks;
|
||||
}
|
||||
|
||||
function resolveRetention(runtime: EngineRuntimeConfig): number {
|
||||
return parseDuration(runtime.retention ?? DEFAULT_RETENTION);
|
||||
function resolveRetention(storage: ServerStorageConfig): number {
|
||||
return parseDuration(storage.retention ?? DEFAULT_RETENTION);
|
||||
}
|
||||
|
||||
function resolveTarget(
|
||||
@@ -277,8 +281,8 @@ function validateConfig(config: RawProbeConfig): ConfigValidationIssue[] {
|
||||
validateDurationValue(config.defaults?.interval, "defaults.interval", issues);
|
||||
validateDurationValue(config.defaults?.timeout, "defaults.timeout", issues);
|
||||
validateDurationValue(
|
||||
isString(config.runtime?.retention) ? config.runtime.retention : undefined,
|
||||
"runtime.retention",
|
||||
isString(config.server?.storage?.retention) ? config.server.storage.retention : undefined,
|
||||
"server.storage.retention",
|
||||
issues,
|
||||
);
|
||||
for (let i = 0; i < config.targets.length; i++) {
|
||||
@@ -328,7 +332,11 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
|
||||
if (logging.level !== undefined && !VALID_LOG_LEVELS.includes(logging.level)) {
|
||||
issues.push(
|
||||
issue("invalid-value", "logging.level", `日志等级非法: "${logging.level}",支持: ${VALID_LOG_LEVELS.join(", ")}`),
|
||||
issue(
|
||||
"invalid-value",
|
||||
"server.logging.level",
|
||||
`日志等级非法: "${logging.level}",支持: ${VALID_LOG_LEVELS.join(", ")}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,7 +344,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
issues.push(
|
||||
issue(
|
||||
"invalid-value",
|
||||
"logging.console.level",
|
||||
"server.logging.console.level",
|
||||
`日志等级非法: "${logging.console.level}",支持: ${VALID_LOG_LEVELS.join(", ")}`,
|
||||
),
|
||||
);
|
||||
@@ -346,7 +354,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
issues.push(
|
||||
issue(
|
||||
"invalid-value",
|
||||
"logging.file.level",
|
||||
"server.logging.file.level",
|
||||
`日志等级非法: "${logging.file.level}",支持: ${VALID_LOG_LEVELS.join(", ")}`,
|
||||
),
|
||||
);
|
||||
@@ -354,7 +362,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
|
||||
if (logging.file?.path !== undefined) {
|
||||
if (!isString(logging.file.path) || logging.file.path.trim() === "") {
|
||||
issues.push(issue("invalid-value", "logging.file.path", "日志路径不能为空字符串或空白字符串"));
|
||||
issues.push(issue("invalid-value", "server.logging.file.path", "日志路径不能为空字符串或空白字符串"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,11 +371,15 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
try {
|
||||
const bytes = parseSize(rotation.size);
|
||||
if (bytes <= 0) {
|
||||
issues.push(issue("invalid-value", "logging.file.rotation.size", "滚动大小必须为正整数字节数"));
|
||||
issues.push(issue("invalid-value", "server.logging.file.rotation.size", "滚动大小必须为正整数字节数"));
|
||||
}
|
||||
} catch (error) {
|
||||
issues.push(
|
||||
issue("invalid-value", "logging.file.rotation.size", error instanceof Error ? error.message : "size 格式非法"),
|
||||
issue(
|
||||
"invalid-value",
|
||||
"server.logging.file.rotation.size",
|
||||
error instanceof Error ? error.message : "size 格式非法",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -376,7 +388,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
issues.push(
|
||||
issue(
|
||||
"invalid-value",
|
||||
"logging.file.rotation.frequency",
|
||||
"server.logging.file.rotation.frequency",
|
||||
`滚动频率非法: "${rotation.frequency}",支持: ${VALID_ROTATION_FREQUENCIES.join(", ")}`,
|
||||
),
|
||||
);
|
||||
@@ -384,7 +396,7 @@ function validateLoggingConfig(logging: LoggingConfig | undefined, issues: Confi
|
||||
|
||||
if (rotation?.maxFiles !== undefined) {
|
||||
if (!isNumber(rotation.maxFiles) || !Number.isInteger(rotation.maxFiles) || rotation.maxFiles <= 0) {
|
||||
issues.push(issue("invalid-value", "logging.file.rotation.maxFiles", "maxFiles 必须为正整数"));
|
||||
issues.push(issue("invalid-value", "server.logging.file.rotation.maxFiles", "maxFiles 必须为正整数"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user