132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
#!/usr/bin/env bun
|
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
import { dirname, join, relative, resolve } from "node:path";
|
|
|
|
const PKG_ROOT = resolve(__dirname, "..");
|
|
const ASSETS_OUT = resolve(PKG_ROOT, "assets/skills");
|
|
const SOURCE_ROOT_DEFAULT = resolve(PKG_ROOT, "../../../../suaveplan-skills/plugins");
|
|
|
|
interface SkillEntry {
|
|
readonly id: string;
|
|
readonly version: string;
|
|
readonly dir: string;
|
|
readonly files: ReadonlyArray<string>;
|
|
}
|
|
|
|
function walk(dir: string, out: string[] = []): string[] {
|
|
let entries: string[];
|
|
try {
|
|
entries = readdirSync(dir);
|
|
} catch {
|
|
return out;
|
|
}
|
|
for (const name of entries) {
|
|
if (name === "node_modules" || name === ".git") continue;
|
|
const p = join(dir, name);
|
|
const st = statSync(p);
|
|
if (st.isDirectory()) {
|
|
walk(p, out);
|
|
} else {
|
|
out.push(p);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function ensureDir(path: string): void {
|
|
if (!existsSync(path)) mkdirSync(path, { recursive: true });
|
|
}
|
|
|
|
function copyFile(from: string, to: string): void {
|
|
ensureDir(dirname(to));
|
|
const bytes = readFileSync(from);
|
|
writeFileSync(to, bytes);
|
|
}
|
|
|
|
function discoverSkills(sourceRoot: string): SkillEntry[] {
|
|
if (!existsSync(sourceRoot)) {
|
|
console.warn(`[build-skills-bundle] no source at ${sourceRoot} — skipping`);
|
|
return [];
|
|
}
|
|
const skills: SkillEntry[] = [];
|
|
const plugins = readdirSync(sourceRoot);
|
|
for (const pluginName of plugins) {
|
|
const pluginPath = join(sourceRoot, pluginName);
|
|
if (!statSync(pluginPath).isDirectory()) continue;
|
|
const skillsDir = join(pluginPath, "skills");
|
|
if (!existsSync(skillsDir)) continue;
|
|
for (const skillName of readdirSync(skillsDir)) {
|
|
const skillSource = join(skillsDir, skillName);
|
|
if (!statSync(skillSource).isDirectory()) continue;
|
|
const skillManifest = join(skillSource, "SKILL.md");
|
|
if (!existsSync(skillManifest)) continue;
|
|
const targetDir = join(ASSETS_OUT, skillName);
|
|
ensureDir(targetDir);
|
|
const allFiles = walk(skillSource);
|
|
const fileList: string[] = [];
|
|
for (const file of allFiles) {
|
|
const rel = relative(skillSource, file);
|
|
copyFile(file, join(targetDir, rel));
|
|
fileList.push(rel.split("\\").join("/"));
|
|
}
|
|
skills.push({
|
|
id: skillName,
|
|
version: "0.1.0",
|
|
dir: skillName,
|
|
files: fileList.sort(),
|
|
});
|
|
}
|
|
}
|
|
return skills.sort((a, b) => a.id.localeCompare(b.id));
|
|
}
|
|
|
|
function discoverExistingSkills(): SkillEntry[] {
|
|
if (!existsSync(ASSETS_OUT)) {
|
|
return [];
|
|
}
|
|
const skills: SkillEntry[] = [];
|
|
for (const skillName of readdirSync(ASSETS_OUT)) {
|
|
const skillPath = join(ASSETS_OUT, skillName);
|
|
if (!statSync(skillPath).isDirectory()) continue;
|
|
const skillManifest = join(skillPath, "SKILL.md");
|
|
if (!existsSync(skillManifest)) continue;
|
|
const allFiles = walk(skillPath);
|
|
const fileList: string[] = [];
|
|
for (const file of allFiles) {
|
|
const rel = relative(skillPath, file);
|
|
fileList.push(rel.split("\\").join("/"));
|
|
}
|
|
skills.push({
|
|
id: skillName,
|
|
version: "0.1.0",
|
|
dir: skillName,
|
|
files: fileList.sort(),
|
|
});
|
|
}
|
|
return skills.sort((a, b) => a.id.localeCompare(b.id));
|
|
}
|
|
|
|
function writeManifest(skills: ReadonlyArray<SkillEntry>): void {
|
|
ensureDir(ASSETS_OUT);
|
|
const manifest = { schemaVersion: 1, skills };
|
|
writeFileSync(
|
|
join(ASSETS_OUT, "manifest.json"),
|
|
`${JSON.stringify(manifest, null, 2)}\n`
|
|
);
|
|
}
|
|
|
|
function main(): void {
|
|
const sourceRoot = process.env["SUAVEPLAN_SKILLS_ROOT"] ?? SOURCE_ROOT_DEFAULT;
|
|
let skills = discoverSkills(sourceRoot);
|
|
if (skills.length === 0) {
|
|
skills = discoverExistingSkills();
|
|
if (skills.length > 0) {
|
|
console.log(`[build-skills-bundle] source missing; regenerated manifest for ${skills.length} existing skills`);
|
|
}
|
|
}
|
|
writeManifest(skills);
|
|
console.log(`[build-skills-bundle] bundled ${skills.length} skills into ${ASSETS_OUT}`);
|
|
}
|
|
|
|
main();
|