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>; 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, }); }