Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/skillz/match.ts

Raw
import { lstatSync } from "node:fs";
import { hostname } from "node:os";
import { extname, isAbsolute, join, win32 } from "node:path";
import { findExecutable } from "./src/pi-ext-executable.ts";

const platforms: Record<string, string> = {
	lnx: "linux",
	win32: "win32",
	mac: "darwin",
};

const keys = ["os", "host", "path", "which", "env"] as const;
type Field = (typeof keys)[number];
type Mode = "single" | "any" | "all";

export interface ConditionCheck {
	field: Field;
	mode: Mode;
	options: { value: string; matches: boolean }[];
	matches: boolean;
}

export type MatchResult =
	| { matches: boolean; checks: ConditionCheck[]; error?: never }
	| { matches: false; error: string; checks?: never };

function values(value: unknown): { mode: Mode; items: string[] } | undefined {
	let mode: Mode;
	let items: unknown;
	if (typeof value === "string") {
		mode = "single";
		items = [value];
	} else if (Array.isArray(value)) {
		mode = "any";
		items = value;
	} else if (value !== null && typeof value === "object") {
		const entries = Object.entries(value);
		if (entries.length !== 1) return undefined;
		const [operator, choices] = entries[0];
		if (operator !== "any" && operator !== "all") return undefined;
		mode = operator;
		items = choices;
	} else return undefined;
	return Array.isArray(items) &&
		items.length > 0 &&
		items.every((item) => typeof item === "string" && item.length > 0)
		? { mode, items }
		: undefined;
}

function validMarker(marker: string): boolean {
	return !(
		marker.includes("\\") ||
		marker.includes("\0") ||
		isAbsolute(marker) ||
		win32.isAbsolute(marker) ||
		marker
			.split("/")
			.some(
				(part) => !part || part === "." || part === ".." || part.includes(":"),
			)
	);
}

function markerExists(cwd: string, marker: string): boolean {
	let current = cwd;
	try {
		for (const part of marker.split("/")) {
			current = join(current, part);
			if (lstatSync(current).isSymbolicLink()) return false;
		}
		return true;
	} catch {
		return false;
	}
}

function commandExists(command: string, platform: string): boolean {
	const windows = platform === "win32";
	const extensions =
		windows && !extname(command)
			? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
					.split(";")
					.map((ext) => ext.trim())
					.filter(Boolean)
			: [];
	const names = [
		command,
		...extensions.flatMap((ext) => [
			command + ext,
			command + ext.toLowerCase(),
		]),
	];
	return Boolean(findExecutable(names, { platform }));
}

export function matchSkill(
	frontmatter: Record<string, unknown>,
	cwd: string,
	platform = process.platform,
	host = hostname(),
): MatchResult {
	const conditions: { field: Field; mode: Mode; items: string[] }[] = [];
	for (const field of keys) {
		if (!(field in frontmatter)) continue;
		const condition = values(frontmatter[field]);
		if (!condition)
			return { matches: false, error: `invalid ${field} condition` };
		if (field === "os" && condition.items.some((item) => !(item in platforms)))
			return {
				matches: false,
				error: "invalid os condition (expected lnx, win32, or mac)",
			};
		if (field === "path" && condition.items.some((item) => !validMarker(item)))
			return { matches: false, error: "invalid path condition" };
		if (
			field === "which" &&
			condition.items.some(
				(item) => item === "." || item === ".." || /[\\/:\s\0]/.test(item),
			)
		)
			return { matches: false, error: "invalid which condition" };
		if (
			field === "env" &&
			condition.items.some((item) => !/^[A-Za-z_][A-Za-z0-9_]*$/.test(item))
		)
			return { matches: false, error: "invalid env condition" };
		conditions.push({ field, mode: condition.mode, items: condition.items });
	}
	if (conditions.length === 0)
		return {
			matches: false,
			error: "missing os, host, path, which, or env condition",
		};
	const checks: ConditionCheck[] = conditions.map(({ field, mode, items }) => {
		const options = items.map((value) => ({
			value,
			matches:
				field === "os"
					? platforms[value] === platform
					: field === "host"
						? value === host
						: field === "path"
							? markerExists(cwd, value)
							: field === "which"
								? commandExists(value, platform)
								: (process.env[value]?.length ?? 0) > 0,
		}));
		return {
			field,
			mode,
			options,
			matches:
				mode === "all"
					? options.every((option) => option.matches)
					: options.some((option) => option.matches),
		};
	});
	return { matches: checks.every((check) => check.matches), checks };
}