///
import { readdirSync, readFileSync } from "node:fs";
export type ComponentPackageJson = {
readonly version?: string;
readonly type: string;
readonly packageManager: string;
readonly bin: Readonly>;
readonly dependencies?: Readonly>;
readonly optionalDependencies?: Readonly>;
readonly scripts?: Readonly>;
readonly files?: readonly string[];
};
export type HookCommand = {
readonly command: string;
};
export type HookEntry = {
readonly matcher?: string;
readonly hooks: readonly HookCommand[];
};
export type HooksJson = {
readonly hooks: Record;
};
export type McpServer = {
readonly command: string;
readonly args: readonly string[];
};
export type McpJson = {
readonly mcpServers: Record;
};
export type PluginJson = {
readonly hooks: string;
};
export function readPackageJson(path: string): ComponentPackageJson {
const parsed = readJsonFile(path);
if (!isComponentPackageJson(parsed)) throw new TypeError(`Invalid package metadata: ${path}`);
return parsed;
}
export function readHooksJson(path: string): HooksJson {
const parsed = readJsonFile(path);
if (!isHooksJson(parsed)) throw new TypeError(`Invalid hooks metadata: ${path}`);
return parsed;
}
export function readMcpJson(path: string): McpJson {
const parsed = readJsonFile(path);
if (!isMcpJson(parsed)) throw new TypeError(`Invalid MCP metadata: ${path}`);
return parsed;
}
export function readPluginJson(path: string): PluginJson {
const parsed = readJsonFile(path);
if (!isPluginJson(parsed)) throw new TypeError(`Invalid plugin metadata: ${path}`);
return parsed;
}
export function readJsonFile(path: string): unknown {
return JSON.parse(readFileSync(path, "utf8"));
}
export function readTextFile(path: string): string {
return readFileSync(path, "utf8");
}
export function listDirectoryEntries(path: string): readonly string[] {
return readdirSync(path);
}
export function requireFiles(packageJson: ComponentPackageJson, path: string): readonly string[] {
if (packageJson.files === undefined) throw new TypeError(`Package metadata missing files: ${path}`);
return packageJson.files;
}
export function requireScripts(packageJson: ComponentPackageJson, path: string): Readonly> {
if (packageJson.scripts === undefined) throw new TypeError(`Package metadata missing scripts: ${path}`);
return packageJson.scripts;
}
export function collectHookCommandsFromValue(value: unknown): readonly string[] {
if (typeof value === "string") return [];
if (Array.isArray(value)) return value.flatMap(collectHookCommandsFromValue);
if (!isRecord(value)) return [];
const ownCommand = typeof value["command"] === "string" ? [value["command"]] : [];
return [...ownCommand, ...Object.values(value).flatMap(collectHookCommandsFromValue)];
}
function isComponentPackageJson(value: unknown): value is ComponentPackageJson {
if (!isRecord(value)) return false;
const dependencies = value["dependencies"];
const optionalDependencies = value["optionalDependencies"];
const scripts = value["scripts"];
const files = value["files"];
return (
value["type"] === "module" &&
value["packageManager"] === "npm@11.12.1" &&
isStringRecord(value["bin"]) &&
(dependencies === undefined || isStringRecord(dependencies)) &&
(optionalDependencies === undefined || isStringRecord(optionalDependencies)) &&
(scripts === undefined || isStringRecord(scripts)) &&
(files === undefined || isStringArray(files))
);
}
function isHooksJson(value: unknown): value is HooksJson {
if (!isRecord(value) || !isRecord(value["hooks"])) return false;
return Object.values(value["hooks"]).every(isHookEntries);
}
function isHookEntries(value: unknown): value is readonly HookEntry[] {
return Array.isArray(value) && value.every(isHookEntry);
}
function isHookEntry(value: unknown): value is HookEntry {
return isRecord(value) && Array.isArray(value["hooks"]) && value["hooks"].every(isHookCommand);
}
function isHookCommand(value: unknown): value is HookCommand {
return isRecord(value) && typeof value["command"] === "string";
}
function isMcpJson(value: unknown): value is McpJson {
if (!isRecord(value) || !isRecord(value["mcpServers"])) return false;
return Object.values(value["mcpServers"]).every(isMcpServer);
}
function isMcpServer(value: unknown): value is McpServer {
return (
isRecord(value) &&
typeof value["command"] === "string" &&
Array.isArray(value["args"]) &&
value["args"].every((item) => typeof item === "string")
);
}
function isPluginJson(value: unknown): value is PluginJson {
return isRecord(value) && typeof value["hooks"] === "string";
}
function isStringArray(value: unknown): value is readonly string[] {
return Array.isArray(value) && value.every((item) => typeof item === "string");
}
function isStringRecord(value: unknown): value is Record {
return isRecord(value) && Object.values(value).every((item) => typeof item === "string");
}
function isRecord(value: unknown): value is Record {
return typeof value === "object" && value !== null && !Array.isArray(value);
}