Add vitest.config.ts with forks pool, v8 coverage, and package aliases.
Run migrate-to-vitest.mjs to replace `from "node:test"` imports with
`from 'vitest'` across 749 test files, converting mock.fn→vi.fn and
mock.timers→vi fake timers where needed.
💘 Generated with Crush
Assisted-by: GLM-5.1 via Crush <crush@charm.land>
128 lines
4.3 KiB
JavaScript
128 lines
4.3 KiB
JavaScript
import { readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const repoRoot = resolve(__dirname, "..");
|
|
|
|
const featuresPath = join(repoRoot, "FEATURES.md");
|
|
const workflowToolsPath = join(repoRoot, "packages", "mcp-server", "src", "workflow-tools.ts");
|
|
const providersPath = join(repoRoot, "packages", "pi-ai", "src", "types.ts");
|
|
const extensionsRoot = join(repoRoot, "src", "resources", "extensions");
|
|
const searchProviderPath = join(repoRoot, "src", "resources", "extensions", "search-the-web", "provider.ts");
|
|
|
|
export const START = "<!-- GENERATED_FEATURE_INVENTORY_START -->";
|
|
export const END = "<!-- GENERATED_FEATURE_INVENTORY_END -->";
|
|
|
|
function uniqueSorted(values) {
|
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
}
|
|
|
|
export function parseWorkflowToolNames() {
|
|
const src = readFileSync(workflowToolsPath, "utf8");
|
|
const matches = [...src.matchAll(/server\.tool\(\s*"([^"]+)"/g)].map((m) => m[1]);
|
|
return uniqueSorted(matches);
|
|
}
|
|
|
|
export function parseKnownProviders() {
|
|
const src = readFileSync(providersPath, "utf8");
|
|
const match = src.match(/export type KnownProvider =([\s\S]*?);/);
|
|
if (!match) throw new Error("Could not find KnownProvider in packages/pi-ai/src/types.ts");
|
|
const providers = [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]);
|
|
return uniqueSorted(providers);
|
|
}
|
|
|
|
export function parseBundledExtensions() {
|
|
const entries = readdirSync(extensionsRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => entry.name)
|
|
.filter((name) => {
|
|
try {
|
|
const manifestPath = join(extensionsRoot, name, "extension-manifest.json");
|
|
readFileSync(manifestPath, "utf8");
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
});
|
|
return uniqueSorted(entries);
|
|
}
|
|
|
|
export function parseSearchProviders() {
|
|
const src = readFileSync(searchProviderPath, "utf8");
|
|
const providers = [
|
|
...src.matchAll(/providers\.push\('([^']+)'\)/g),
|
|
...src.matchAll(/provider\?: '([^']+)'/g),
|
|
...src.matchAll(/\|\s*"([^"]+)"/g),
|
|
]
|
|
.map((m) => m[1])
|
|
.filter((p) => p !== "combosearch" && p !== "minimax" && p !== "auto");
|
|
return uniqueSorted(providers);
|
|
}
|
|
|
|
function formatBullets(values, formatter = (value) => `- \`${value}\``) {
|
|
return values.map((value) => formatter(value)).join("\n");
|
|
}
|
|
|
|
export function buildSection() {
|
|
const workflowTools = parseWorkflowToolNames();
|
|
const extensions = parseBundledExtensions();
|
|
const searchProviders = parseSearchProviders();
|
|
const knownProviders = parseKnownProviders();
|
|
|
|
return [
|
|
"### Workflow Tools",
|
|
"",
|
|
"Generated from `packages/mcp-server/src/workflow-tools.ts`.",
|
|
"",
|
|
formatBullets(workflowTools),
|
|
"",
|
|
"### Bundled Extensions",
|
|
"",
|
|
"Generated from `src/resources/extensions/*/extension-manifest.json`.",
|
|
"",
|
|
formatBullets(
|
|
extensions,
|
|
(value) => `- \`${value}\` — [extension-manifest.json](${relative(repoRoot, join(extensionsRoot, value, "extension-manifest.json"))})`,
|
|
),
|
|
"",
|
|
"### Search Providers",
|
|
"",
|
|
"Generated from the `search-the-web` extension provider declarations.",
|
|
"",
|
|
formatBullets(searchProviders),
|
|
"",
|
|
"### Known Model Providers",
|
|
"",
|
|
"Generated from `packages/pi-ai/src/types.ts` (`KnownProvider`).",
|
|
"",
|
|
formatBullets(knownProviders),
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
export function updateFeaturesContent(features) {
|
|
const startIndex = features.indexOf(START);
|
|
const endIndex = features.indexOf(END);
|
|
|
|
if (startIndex === -1 || endIndex === -1 || endIndex < startIndex) {
|
|
throw new Error("FEATURES.md is missing generated inventory markers");
|
|
}
|
|
|
|
const before = features.slice(0, startIndex + START.length);
|
|
const after = features.slice(endIndex);
|
|
const section = `\n\n${buildSection()}`;
|
|
return `${before}${section}\n${after}`;
|
|
}
|
|
|
|
export function main() {
|
|
const features = readFileSync(featuresPath, "utf8");
|
|
const updated = updateFeaturesContent(features);
|
|
writeFileSync(featuresPath, updated);
|
|
process.stdout.write(`Updated ${relative(repoRoot, featuresPath)}\n`);
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === __filename) {
|
|
main();
|
|
}
|