repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
scripts/readme/config.ts
Raw/** Static extraction of an extension's public README surface. */
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { parseSync } from "oxc-parser";
import { type EsNode, literalString, walkEs } from "../lib/esast.ts";
export interface SettingRow {
key: string;
env?: string;
flag?: string;
shape?: string;
fields?: SettingField[];
default: string;
}
export interface SettingField {
path: string;
shape: string;
optional: boolean;
}
export interface FlagRow {
name: string;
type: string;
description: string;
}
export interface NamedRow {
name: string;
description: string;
}
export interface Surface {
settings: SettingRow[];
flags: FlagRow[];
commands: NamedRow[];
tools: NamedRow[];
toolDiscovery: boolean;
diagnostics: string[];
}
export const CONFIG_START = "<!-- config:start -->";
export const CONFIG_END = "<!-- config:end -->";
const EXCLUDED_DIRECTORIES = new Set([
"node_modules",
"dist",
"assets",
"demo",
"skills",
"__tests__",
"__e2e__",
"__bench__",
"__benches__",
"__fuzz__",
"__serial__",
"__fixtures__",
]);
type Value = EsNode | undefined;
type Bindings = Map<string, EsNode>;
type TypeBindings = Map<string, EsNode>;
type Source = { file: string; text: string; program: EsNode; known: Bindings };
const MAX_RESOLVE_DEPTH = 20;
const TYPE_WRAPPERS = new Set([
"TSAsExpression",
"TSSatisfiesExpression",
"TSNonNullExpression",
"TypeAssertion",
"ParenthesizedExpression",
]);
function sourceFiles(dir: string): string[] {
const files: string[] = [];
const walk = (current: string) => {
for (const entry of readdirSync(current, { withFileTypes: true })) {
const path = join(current, entry.name);
if (entry.isDirectory()) {
if (!EXCLUDED_DIRECTORIES.has(entry.name)) walk(path);
} else if (
entry.name.endsWith(".ts") &&
!/(?:\.(?:test|spec|bench)|^pi-ext-(?:debug|settings))\.ts$/u.test(
entry.name,
)
)
files.push(path);
}
};
walk(dir);
return files.sort();
}
function property(node: EsNode | undefined, name: string): Value {
for (const item of (node?.["properties"] as EsNode[] | undefined) ?? []) {
if (item.type !== "Property") continue;
if (nameOf(item["key"] as EsNode | undefined) === name)
return item["value"] as EsNode;
}
return undefined;
}
function resolvedProperty(
node: Value,
name: string,
known: Bindings,
seen = new Set<EsNode>(),
depth = 0,
): Value {
if (depth >= MAX_RESOLVE_DEPTH) return undefined;
node = resolve(node, known);
if (node?.type === "CallExpression" && callName(node) === "defineTool")
return resolvedProperty(
(node["arguments"] as EsNode[] | undefined)?.[0],
name,
known,
seen,
depth + 1,
);
if (node?.type !== "ObjectExpression" || seen.has(node)) return undefined;
seen.add(node);
const properties = (node["properties"] as EsNode[] | undefined) ?? [];
for (const item of properties)
if (item.type === "Property" && nameOf(item["key"] as EsNode) === name)
return item["value"] as EsNode;
for (const item of properties)
if (item.type === "SpreadElement") {
const value = resolvedProperty(
item["argument"] as EsNode,
name,
known,
seen,
depth + 1,
);
if (value) return value;
}
return undefined;
}
function nameOf(node: Value): string | undefined {
if (node?.type === "Identifier") return node["name"] as string;
return literalString(node);
}
function bindings(program: EsNode): Bindings {
const result: Bindings = new Map();
walkEs(program, (node) => {
if (node.type === "FunctionDeclaration") {
const id = node["id"] as EsNode | undefined;
if (id?.type === "Identifier") result.set(id["name"] as string, node);
return;
}
if (node.type !== "VariableDeclarator") return;
const id = node["id"] as EsNode;
const init = node["init"] as EsNode | undefined;
if (id?.type === "Identifier" && init)
result.set(id["name"] as string, init);
if (id?.type === "ObjectPattern" && init?.type === "ObjectExpression")
for (const item of (id["properties"] as EsNode[] | undefined) ?? []) {
const local = item["value"] as EsNode;
const remote = nameOf(item["key"] as EsNode);
if (local?.type === "Identifier" && remote)
result.set(local["name"] as string, property(init, remote) as EsNode);
}
});
return result;
}
function typeBindings(sources: Source[]): TypeBindings {
const result: TypeBindings = new Map();
for (const { program } of sources)
walkEs(program, (node) => {
if (
node.type !== "TSInterfaceDeclaration" &&
node.type !== "TSTypeAliasDeclaration"
)
return;
const id = node["id"] as EsNode | undefined;
if (id?.type === "Identifier") result.set(id["name"] as string, node);
});
return result;
}
function typeArguments(node: EsNode): EsNode[] {
const parameters = (node["typeArguments"] ?? node["typeParameters"]) as
| EsNode
| undefined;
return (parameters?.["params"] as EsNode[] | undefined) ?? [];
}
function settingType(node: EsNode): EsNode | undefined {
if (node.type !== "VariableDeclarator") return undefined;
const id = node["id"] as EsNode;
const annotation = id["typeAnnotation"] as EsNode | undefined;
const type = annotation?.["typeAnnotation"] as EsNode | undefined;
if (
type?.type !== "TSTypeReference" ||
nameOf(type["typeName"] as EsNode) !== "SettingDeclaration"
)
return undefined;
return typeArguments(type)[0];
}
function settingShape(node: EsNode, known: TypeBindings): string | undefined {
const value = settingType(node);
if (!value) return undefined;
const name =
value.type === "TSTypeReference"
? nameOf(value["typeName"] as EsNode)
: undefined;
const definition = name ? known.get(name) : undefined;
if (definition && isParsedWrapper(definition))
return `parser-defined input (parsed as ${name})`;
return formatType(value, known);
}
function settingFields(
node: Value,
known: TypeBindings,
path: string,
depth = 0,
): SettingField[] {
if (!node || depth >= MAX_RESOLVE_DEPTH) return [];
if (node.type === "TSTypeReference") {
const name = nameOf(node["typeName"] as EsNode);
if (name === "Array")
return settingFields(
typeArguments(node)[0],
known,
`${path}[]`,
depth + 1,
);
return settingFields(
name ? known.get(name) : undefined,
known,
path,
depth + 1,
);
}
if (
node.type === "TSTypeAliasDeclaration" ||
node.type === "TSTypeAnnotation"
)
return settingFields(
node["typeAnnotation"] as EsNode,
known,
path,
depth + 1,
);
if (node.type === "TSArrayType")
return settingFields(
node["elementType"] as EsNode,
known,
`${path}[]`,
depth + 1,
);
if (isParsedWrapper(node)) {
const members = (node["body"] as EsNode)["body"] as EsNode[];
const config = members.find(
(member) => nameOf(member["key"] as EsNode) === "config",
);
return settingFields(
config?.["typeAnnotation"] as EsNode | undefined,
known,
path,
depth + 1,
);
}
const members =
node.type === "TSInterfaceDeclaration"
? ((node["body"] as EsNode)["body"] as EsNode[])
: node.type === "TSTypeLiteral"
? (node["members"] as EsNode[])
: [];
return members.flatMap((member): SettingField[] => {
if (member.type !== "TSPropertySignature") return [];
const key = nameOf(member["key"] as EsNode);
const type = member["typeAnnotation"] as EsNode | undefined;
if (!key || !type) return [];
const fieldPath = `${path}.${key}`;
const children = settingFields(type, known, fieldPath, depth + 1);
return [
{
path: fieldPath,
shape: children.length
? "object / array (fields below)"
: formatType(type, known),
optional: Boolean(member["optional"]),
},
...children,
];
});
}
function isParsedWrapper(node: EsNode): boolean {
if (node.type !== "TSInterfaceDeclaration") return false;
const members =
((node["body"] as EsNode | undefined)?.["body"] as EsNode[] | undefined) ??
[];
const names = new Set(
members
.filter((member) => member.type === "TSPropertySignature")
.map((member) => nameOf(member["key"] as EsNode)),
);
return names.has("config") && names.has("errors");
}
function formatType(
node: EsNode,
known: TypeBindings,
seen = new Set<string>(),
depth = 0,
): string {
if (depth >= MAX_RESOLVE_DEPTH) return "…";
if (node.type === "TSTypeAnnotation")
return formatType(node["typeAnnotation"] as EsNode, known, seen, depth + 1);
if (node.type === "TSTypeAliasDeclaration")
return formatType(node["typeAnnotation"] as EsNode, known, seen, depth + 1);
if (node.type === "TSStringKeyword") return "string";
if (node.type === "TSNumberKeyword") return "number";
if (node.type === "TSBooleanKeyword") return "boolean";
if (node.type === "TSNullKeyword") return "null";
if (node.type === "TSUndefinedKeyword") return "undefined";
if (node.type === "TSArrayType")
return `${formatType(node["elementType"] as EsNode, known, seen, depth + 1)}[]`;
if (node.type === "TSUnionType")
return ((node["types"] as EsNode[] | undefined) ?? [])
.map((item) => formatType(item, known, seen, depth + 1))
.join(" | ");
if (node.type === "TSLiteralType") {
const literal = node["literal"] as EsNode;
const value = literalString(literal);
return value === undefined
? String(literal["value"] ?? "literal")
: JSON.stringify(value);
}
if (node.type === "TSTypeLiteral")
return formatMembers(
node["members"] as EsNode[] | undefined,
known,
seen,
depth,
);
if (node.type === "TSInterfaceDeclaration")
return formatMembers(
((node["body"] as EsNode | undefined)?.["body"] as
| EsNode[]
| undefined) ?? [],
known,
seen,
depth,
);
if (node.type === "TSTypeReference") {
const name = nameOf(node["typeName"] as EsNode);
const args = typeArguments(node);
if (name === "Array" && args[0])
return `${formatType(args[0], known, seen, depth + 1)}[]`;
if (name === "Record" && args.length === 2) {
const [key, value] = args;
if (key && value)
return `Record<${formatType(key, known, seen, depth + 1)}, ${formatType(value, known, seen, depth + 1)}>`;
}
const declaration = name ? known.get(name) : undefined;
if (!name || !declaration || seen.has(name)) return name ?? "unknown";
return formatType(declaration, known, new Set([...seen, name]), depth + 1);
}
return "unknown";
}
function formatMembers(
members: EsNode[] | undefined,
known: TypeBindings,
seen: Set<string>,
depth: number,
): string {
const fields = (members ?? []).flatMap((member) => {
if (member.type !== "TSPropertySignature") return [];
const key = nameOf(member["key"] as EsNode);
const type = member["typeAnnotation"] as EsNode | undefined;
if (!key || !type) return [];
return `${key}${member["optional"] ? "?" : ""}: ${formatType(type, known, seen, depth + 1)}`;
});
return `{ ${fields.join("; ")} }`;
}
function substitute(node: EsNode, values: Map<string, EsNode>): EsNode {
if (node.type === "Identifier" && values.has(node["name"] as string))
return structuredClone(values.get(node["name"] as string)) as EsNode;
const copy = structuredClone(node) as EsNode;
for (const [key, value] of Object.entries(copy)) {
if (key === "type" || (copy.type === "Property" && key === "key")) continue;
if (Array.isArray(value))
copy[key] = value.map((item) =>
item && typeof item === "object" && "type" in item
? substitute(item as EsNode, values)
: item,
);
else if (value && typeof value === "object" && "type" in value)
copy[key] = substitute(value as EsNode, values);
}
return copy;
}
function resolve(
node: Value,
known: Bindings,
seen = new Set<string>(),
depth = 0,
): Value {
if (!node || depth >= MAX_RESOLVE_DEPTH) return node;
if (TYPE_WRAPPERS.has(node.type))
return resolve(node["expression"] as EsNode, known, seen, depth + 1);
if (node.type === "Identifier") {
const name = node["name"] as string;
if (seen.has(name)) return undefined;
const value = known.get(name);
if (!value) return node;
return resolve(value, known, new Set([...seen, name]), depth + 1);
}
if (
node.type !== "CallExpression" ||
(node["callee"] as EsNode)?.type !== "Identifier"
)
return node;
const factory = resolve(
known.get((node["callee"] as EsNode)["name"] as string),
known,
seen,
depth + 1,
);
if (
factory?.type !== "FunctionDeclaration" &&
factory?.type !== "FunctionExpression" &&
factory?.type !== "ArrowFunctionExpression"
)
return node;
const params = (factory["params"] as EsNode[] | undefined) ?? [];
const args = (node["arguments"] as EsNode[] | undefined) ?? [];
const values = new Map<string, EsNode>();
for (const [index, param] of params.entries()) {
if (param.type !== "Identifier" || !args[index]) return node;
values.set(param["name"] as string, args[index] as EsNode);
}
const body = factory["body"] as EsNode;
const returned =
body?.type === "BlockStatement"
? ((body["body"] as EsNode[] | undefined)?.find(
(item) => item.type === "ReturnStatement",
)?.["argument"] as EsNode | undefined)
: body;
return returned
? resolve(substitute(returned, values), known, seen, depth + 1)
: node;
}
function stringValue(
node: Value,
known: Bindings,
depth = 0,
): string | undefined {
if (depth >= MAX_RESOLVE_DEPTH) return undefined;
node = resolve(node, known);
const literal = literalString(node);
if (literal !== undefined) return literal;
if (node?.type === "BinaryExpression" && node["operator"] === "+") {
const left = stringValue(node["left"] as EsNode, known, depth + 1);
const right = stringValue(node["right"] as EsNode, known, depth + 1);
return left === undefined || right === undefined ? undefined : left + right;
}
if (node?.type === "TemplateLiteral") {
const quasis = (node["quasis"] as EsNode[] | undefined) ?? [];
const expressions = (node["expressions"] as EsNode[] | undefined) ?? [];
let value = "";
for (const [index, quasi] of quasis.entries()) {
const cooked = (quasi["value"] as { cooked?: string } | undefined)
?.cooked;
if (cooked === undefined) return undefined;
value += cooked;
if (expressions[index]) {
const expression = stringValue(expressions[index], known, depth + 1);
if (expression === undefined) return undefined;
value += expression;
}
}
return value;
}
return undefined;
}
function sourceText(text: string, node: EsNode): string {
return text
.slice(node["start"] as number, node["end"] as number)
.replaceAll(/\s+/g, " ")
.trim();
}
function describeDefault(text: string, node: Value, known: Bindings): string {
node = resolve(node, known);
if (!node) return "";
if (node.type === "Literal") return `\`${JSON.stringify(node["value"])}\``;
const literal = literalString(node);
if (literal !== undefined) return `\`${JSON.stringify(literal)}\``;
const raw = sourceText(text, node);
return `\`${raw.length > 60 ? `${raw.slice(0, 57)}…` : raw}\``;
}
function callName(node: EsNode): string | undefined {
const callee = node["callee"] as EsNode;
if (callee?.type === "Identifier") return callee["name"] as string;
if (callee?.type === "MemberExpression" && !callee["computed"])
return nameOf(callee["property"] as EsNode);
return undefined;
}
function addNamed(
rows: NamedRow[],
seen: Set<string>,
kind: string,
name: string,
description: string,
): void {
if (seen.has(`${kind}:${name}`)) return;
seen.add(`${kind}:${name}`);
rows.push({ name, description });
}
function conciseDescription(description: string): string {
const firstLine = description.trim().split(/\r?\n/u, 1)[0] ?? "";
const firstSentence = firstLine.match(/^.*?[.!?](?:\s|$)/u)?.[0] ?? firstLine;
return firstSentence.length > 180
? `${firstSentence.slice(0, 177).trimEnd()}…`
: firstSentence;
}
function addTool(
surface: Surface,
seen: Set<string>,
definition: Value,
known: Bindings,
where: string,
toolFactories: Set<string>,
): void {
definition = resolve(definition, known);
if (
definition?.type === "CallExpression" &&
callName(definition) === "defineTool"
)
definition = (definition["arguments"] as EsNode[] | undefined)?.[0];
if (definition?.type !== "ObjectExpression") {
const callee =
definition?.type === "CallExpression"
? (definition["callee"] as EsNode | undefined)
: undefined;
if (
callee?.type !== "Identifier" ||
!toolFactories.has(callee["name"] as string)
)
surface.diagnostics.push(
`${where}: tool definition cannot be resolved statically`,
);
return;
}
const nameNode = resolvedProperty(definition, "name", known);
const name = stringValue(nameNode, known);
if (!name) {
const dynamicName = resolve(nameNode, known);
if (dynamicName?.type === "MemberExpression") {
surface.toolDiscovery = true;
return;
}
surface.diagnostics.push(
`${where}: tool name cannot be resolved statically`,
);
return;
}
addNamed(
surface.tools,
seen,
"tool",
name,
conciseDescription(
stringValue(resolvedProperty(definition, "description", known), known) ??
"",
),
);
}
function settingRow(
surface: Surface,
seen: Set<string>,
text: string,
declaration: Value,
known: Bindings,
types: TypeBindings,
): void {
const type = declaration ? settingType(declaration) : undefined;
const shape = declaration ? settingShape(declaration, types) : undefined;
if (declaration?.type === "VariableDeclarator")
declaration = declaration["init"] as EsNode | undefined;
declaration = resolve(declaration, known);
if (declaration?.type !== "ObjectExpression") return;
const key = stringValue(resolvedProperty(declaration, "key", known), known);
const parse = resolvedProperty(declaration, "parse", known);
const defaultValue = resolvedProperty(declaration, "default", known);
if (!key || !parse) return;
const env = stringValue(resolvedProperty(declaration, "env", known), known);
if (seen.has(`setting:${key}`)) {
const existing = surface.settings.find((row) => row.key === key);
if (existing && env && !existing.env?.includes(env))
existing.env = existing.env ? `${existing.env} | ${env}` : env;
return;
}
seen.add(`setting:${key}`);
const flag = resolve(resolvedProperty(declaration, "flag", known), known);
const flagName =
flag?.type === "ObjectExpression"
? stringValue(property(flag, "name"), known)
: undefined;
const fields = type ? settingFields(type, types, key) : [];
surface.settings.push({
key,
...(env ? { env } : {}),
...(flagName ? { flag: `--${flagName}` } : {}),
...(shape ? { shape } : {}),
...(fields.length ? { fields } : {}),
default: defaultValue
? describeDefault(text, defaultValue, known)
: "omitted",
});
}
function toolFactoryNames(sources: Source[]): Set<string> {
const names = new Set<string>();
for (const { known } of sources)
for (const [name, value] of known) {
let containsDefinition = false;
walkEs(value, (node) => {
if (node.type === "CallExpression" && callName(node) === "defineTool")
containsDefinition = true;
});
if (containsDefinition) names.add(name);
}
return names;
}
/** Collect one extension's public surface without executing extension code. */
export function extractSurface(dir: string): Surface {
const surface: Surface = {
settings: [],
flags: [],
commands: [],
tools: [],
toolDiscovery: false,
diagnostics: [],
};
const sources: Source[] = [];
for (const file of sourceFiles(dir)) {
const text = readFileSync(file, "utf8");
try {
const parsed = parseSync(file, text);
if (parsed.errors.length) throw new Error(parsed.errors[0]?.message);
const program = parsed.program as unknown as EsNode;
sources.push({ file, text, program, known: bindings(program) });
} catch (error) {
surface.diagnostics.push(
`${relative(dir, file)}: parse failed: ${String(error)}`,
);
}
}
const seen = new Set<string>();
const types = typeBindings(sources);
const toolFactories = toolFactoryNames(sources);
for (const { file, text, program, known } of sources) {
walkEs(program, (node) => {
settingRow(surface, seen, text, node, known, types);
if (node.type !== "CallExpression") return;
const method = callName(node);
const args = (node["arguments"] as EsNode[] | undefined) ?? [];
if (method === "registerCommand") {
const name = stringValue(args[0], known);
if (!name) {
surface.diagnostics.push(
`${relative(dir, file)}: command name cannot be resolved statically`,
);
return;
}
const options = resolve(args[1], known);
addNamed(
surface.commands,
seen,
"command",
`/${name}`,
options?.type === "ObjectExpression"
? (stringValue(property(options, "description"), known) ?? "")
: "",
);
} else if (method === "registerFlag") {
const name = stringValue(args[0], known);
if (!name) {
surface.diagnostics.push(
`${relative(dir, file)}: flag name cannot be resolved statically`,
);
return;
}
if (seen.has(`flag:${name}`)) return;
seen.add(`flag:${name}`);
const options = resolve(args[1], known);
surface.flags.push({
name: `--${name}`,
type:
options?.type === "ObjectExpression"
? (stringValue(property(options, "type"), known) ?? "boolean")
: "boolean",
description:
options?.type === "ObjectExpression"
? (stringValue(property(options, "description"), known) ?? "")
: "",
});
} else if (method === "registerTool" || method === "defineTool") {
const definition = resolve(args[0], known);
if (
method === "registerTool" &&
definition?.type === "MemberExpression"
)
return;
addTool(
surface,
seen,
args[0],
known,
relative(dir, file),
toolFactories,
);
} else if (
method === "resolveSetting" ||
method === "registerSettingFlag"
) {
settingRow(surface, seen, text, args.at(-1), known, types);
}
});
}
for (const rows of [
surface.settings,
surface.flags,
surface.commands,
surface.tools,
])
rows.sort((a, b) =>
("key" in a ? a.key : a.name).localeCompare("key" in b ? b.key : b.name),
);
surface.diagnostics = [...new Set(surface.diagnostics)].sort();
return surface;
}
const cell = (value: string | undefined) =>
(value ?? "").replaceAll("|", "\\|").replaceAll("\n", " ").trim() || " ";
/** Render generated Markdown between README config markers. */
export function renderSurface(surface: Surface): string {
const lines = [CONFIG_START];
if (surface.commands.length) {
lines.push("### Commands", "", "| Command | Purpose |", "| --- | --- |");
for (const row of surface.commands)
lines.push(`| \`${row.name}\` | ${cell(row.description)} |`);
lines.push("");
}
if (surface.settings.length) {
const hasEnv = surface.settings.some((row) => row.env);
const hasFlag = surface.settings.some((row) => row.flag);
lines.push(
"### Setting names",
"",
`| Setting${hasEnv ? " | Environment" : ""}${hasFlag ? " | CLI flag" : ""} |`,
`| ---${hasEnv ? " | ---" : ""}${hasFlag ? " | ---" : ""} |`,
);
for (const row of surface.settings)
lines.push(
`| \`${row.key}\`${hasEnv ? ` | ${row.env ? `\`${cell(row.env)}\`` : " "}` : ""}${hasFlag ? ` | ${row.flag ? `\`${cell(row.flag)}\`` : " "}` : ""} |`,
);
lines.push("");
const fields = surface.settings.flatMap((row) => row.fields ?? []);
if (fields.length) {
lines.push(
"#### Object fields",
"",
"Paths below describe fields inside their parent settings object, not literal top-level keys.",
"`[]` denotes an array element; see setup above for examples and defaults.",
"",
"| Field |",
"| --- |",
);
for (const field of fields) lines.push(`| \`${cell(field.path)}\` |`);
lines.push("");
}
}
if (surface.flags.length) {
lines.push(
"### CLI flags",
"",
"| Flag | Type | Purpose |",
"| --- | --- | --- |",
);
for (const row of surface.flags)
lines.push(
`| \`${row.name}\` | ${cell(row.type)} | ${cell(row.description)} |`,
);
lines.push("");
}
if (surface.diagnostics.length) {
lines.push(
"### Extraction diagnostics",
"",
"Some source registrations cannot be resolved statically.",
"",
"```text",
...surface.diagnostics,
"```",
);
}
while (lines.at(-1) === "") lines.pop();
lines.push(CONFIG_END);
return lines.join("\n");
}
export function injectSurface(
readme: string,
block: string,
): string | undefined {
const start = readme.indexOf(CONFIG_START);
const end = readme.indexOf(CONFIG_END);
if (start < 0 || end < start) return undefined;
return `${readme.slice(0, start)}${block}${readme.slice(end + CONFIG_END.length)}`;
}
export function extensionDirs(root: string): string[] {
const base = join(root, "extensions");
return readdirSync(base)
.filter(
(name) =>
statSync(join(base, name)).isDirectory() &&
existsSync(join(base, name, "package.json")),
)
.sort();
}