Files
openclaw/src/shared/requirements.ts
2026-02-18 17:48:02 +00:00

222 lines
5.9 KiB
TypeScript

export type Requirements = {
bins: string[];
anyBins: string[];
env: string[];
config: string[];
os: string[];
};
export type RequirementConfigCheck = {
path: string;
satisfied: boolean;
};
export type RequirementsMetadata = {
requires?: Partial<Pick<Requirements, "bins" | "anyBins" | "env" | "config">>;
os?: string[];
};
export type RequirementRemote = {
hasBin?: (bin: string) => boolean;
hasAnyBin?: (bins: string[]) => boolean;
platforms?: string[];
};
type RequirementsEvaluationContext = {
always: boolean;
hasLocalBin: (bin: string) => boolean;
localPlatform: string;
isEnvSatisfied: (envName: string) => boolean;
isConfigSatisfied: (pathStr: string) => boolean;
};
type RequirementsEvaluationRemoteContext = {
hasRemoteBin?: (bin: string) => boolean;
hasRemoteAnyBin?: (bins: string[]) => boolean;
remotePlatforms?: string[];
};
export function resolveMissingBins(params: {
required: string[];
hasLocalBin: (bin: string) => boolean;
hasRemoteBin?: (bin: string) => boolean;
}): string[] {
const remote = params.hasRemoteBin;
return params.required.filter((bin) => {
if (params.hasLocalBin(bin)) {
return false;
}
if (remote?.(bin)) {
return false;
}
return true;
});
}
export function resolveMissingAnyBins(params: {
required: string[];
hasLocalBin: (bin: string) => boolean;
hasRemoteAnyBin?: (bins: string[]) => boolean;
}): string[] {
if (params.required.length === 0) {
return [];
}
if (params.required.some((bin) => params.hasLocalBin(bin))) {
return [];
}
if (params.hasRemoteAnyBin?.(params.required)) {
return [];
}
return params.required;
}
export function resolveMissingOs(params: {
required: string[];
localPlatform: string;
remotePlatforms?: string[];
}): string[] {
if (params.required.length === 0) {
return [];
}
if (params.required.includes(params.localPlatform)) {
return [];
}
if (params.remotePlatforms?.some((platform) => params.required.includes(platform))) {
return [];
}
return params.required;
}
export function resolveMissingEnv(params: {
required: string[];
isSatisfied: (envName: string) => boolean;
}): string[] {
const missing: string[] = [];
for (const envName of params.required) {
if (params.isSatisfied(envName)) {
continue;
}
missing.push(envName);
}
return missing;
}
export function buildConfigChecks(params: {
required: string[];
isSatisfied: (pathStr: string) => boolean;
}): RequirementConfigCheck[] {
return params.required.map((pathStr) => {
const satisfied = params.isSatisfied(pathStr);
return { path: pathStr, satisfied };
});
}
export function evaluateRequirements(
params: RequirementsEvaluationContext &
RequirementsEvaluationRemoteContext & {
required: Requirements;
},
): { missing: Requirements; eligible: boolean; configChecks: RequirementConfigCheck[] } {
const missingBins = resolveMissingBins({
required: params.required.bins,
hasLocalBin: params.hasLocalBin,
hasRemoteBin: params.hasRemoteBin,
});
const missingAnyBins = resolveMissingAnyBins({
required: params.required.anyBins,
hasLocalBin: params.hasLocalBin,
hasRemoteAnyBin: params.hasRemoteAnyBin,
});
const missingOs = resolveMissingOs({
required: params.required.os,
localPlatform: params.localPlatform,
remotePlatforms: params.remotePlatforms,
});
const missingEnv = resolveMissingEnv({
required: params.required.env,
isSatisfied: params.isEnvSatisfied,
});
const configChecks = buildConfigChecks({
required: params.required.config,
isSatisfied: params.isConfigSatisfied,
});
const missingConfig = configChecks.filter((check) => !check.satisfied).map((check) => check.path);
const missing = params.always
? { bins: [], anyBins: [], env: [], config: [], os: [] }
: {
bins: missingBins,
anyBins: missingAnyBins,
env: missingEnv,
config: missingConfig,
os: missingOs,
};
const eligible =
params.always ||
(missing.bins.length === 0 &&
missing.anyBins.length === 0 &&
missing.env.length === 0 &&
missing.config.length === 0 &&
missing.os.length === 0);
return { missing, eligible, configChecks };
}
export function evaluateRequirementsFromMetadata(
params: RequirementsEvaluationContext &
RequirementsEvaluationRemoteContext & {
metadata?: RequirementsMetadata;
},
): {
required: Requirements;
missing: Requirements;
eligible: boolean;
configChecks: RequirementConfigCheck[];
} {
const required: Requirements = {
bins: params.metadata?.requires?.bins ?? [],
anyBins: params.metadata?.requires?.anyBins ?? [],
env: params.metadata?.requires?.env ?? [],
config: params.metadata?.requires?.config ?? [],
os: params.metadata?.os ?? [],
};
const result = evaluateRequirements({
always: params.always,
required,
hasLocalBin: params.hasLocalBin,
hasRemoteBin: params.hasRemoteBin,
hasRemoteAnyBin: params.hasRemoteAnyBin,
localPlatform: params.localPlatform,
remotePlatforms: params.remotePlatforms,
isEnvSatisfied: params.isEnvSatisfied,
isConfigSatisfied: params.isConfigSatisfied,
});
return { required, ...result };
}
export function evaluateRequirementsFromMetadataWithRemote(
params: RequirementsEvaluationContext & {
metadata?: RequirementsMetadata;
remote?: RequirementRemote;
},
): {
required: Requirements;
missing: Requirements;
eligible: boolean;
configChecks: RequirementConfigCheck[];
} {
return evaluateRequirementsFromMetadata({
always: params.always,
metadata: params.metadata,
hasLocalBin: params.hasLocalBin,
hasRemoteBin: params.remote?.hasBin,
hasRemoteAnyBin: params.remote?.hasAnyBin,
localPlatform: params.localPlatform,
remotePlatforms: params.remote?.platforms,
isEnvSatisfied: params.isEnvSatisfied,
isConfigSatisfied: params.isConfigSatisfied,
});
}