127 lines
3.4 KiB
TypeScript
127 lines
3.4 KiB
TypeScript
#!/usr/bin/env bun
|
|
/**
|
|
* Migrate SKILL.md files to move top-level `version` to `metadata.version`
|
|
* per agentskills.io specification.
|
|
*
|
|
* Usage:
|
|
* bun scripts/migrate-skill-version.ts [--dry-run] [path]
|
|
*/
|
|
|
|
import { Glob } from "bun";
|
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
|
|
function relativePath(absolutePath: string): string {
|
|
return absolutePath.replace(process.cwd() + "/", "");
|
|
}
|
|
|
|
function migrateFile(filePath: string, dryRun: boolean): boolean {
|
|
const content = readFileSync(filePath, "utf-8");
|
|
|
|
// Check if file has frontmatter
|
|
if (!content.startsWith("---\n")) {
|
|
return false;
|
|
}
|
|
|
|
const endIndex = content.indexOf("\n---\n", 4);
|
|
if (endIndex === -1) {
|
|
return false;
|
|
}
|
|
|
|
const frontmatter = content.slice(4, endIndex);
|
|
const body = content.slice(endIndex + 5);
|
|
|
|
// Check if there's a top-level version
|
|
const versionMatch = frontmatter.match(/^version:\s*(.+)$/m);
|
|
if (!versionMatch) {
|
|
return false;
|
|
}
|
|
|
|
const version = versionMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
|
|
// Remove top-level version
|
|
let newFrontmatter = frontmatter.replace(/^version:\s*.+\n?/m, "");
|
|
|
|
// Check if metadata exists
|
|
const metadataMatch = newFrontmatter.match(/^metadata:\s*$/m);
|
|
|
|
if (metadataMatch) {
|
|
// Find metadata section and add version to it
|
|
const metadataIndex = newFrontmatter.indexOf("metadata:");
|
|
const afterMetadata = newFrontmatter.slice(metadataIndex + 9);
|
|
|
|
// Find the indentation of metadata items
|
|
const indentMatch = afterMetadata.match(/\n(\s+)\S/);
|
|
const indent = indentMatch ? indentMatch[1] : " ";
|
|
|
|
// Insert version after metadata:
|
|
newFrontmatter =
|
|
newFrontmatter.slice(0, metadataIndex + 9) +
|
|
`\n${indent}version: "${version}"` +
|
|
afterMetadata;
|
|
} else {
|
|
// Add metadata section with version
|
|
newFrontmatter = newFrontmatter.trimEnd() + `\nmetadata:\n version: "${version}"\n`;
|
|
}
|
|
|
|
// Clean up any double newlines in frontmatter
|
|
newFrontmatter = newFrontmatter.replace(/\n{3,}/g, "\n\n").trim();
|
|
|
|
const newContent = `---\n${newFrontmatter}\n---\n${body}`;
|
|
|
|
if (dryRun) {
|
|
console.log(`Would update: ${relativePath(filePath)}`);
|
|
console.log(` version: ${version} -> metadata.version: "${version}"`);
|
|
} else {
|
|
writeFileSync(filePath, newContent);
|
|
console.log(`Updated: ${relativePath(filePath)}`);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const args = process.argv.slice(2);
|
|
const dryRun = args.includes("--dry-run");
|
|
const paths = args.filter((arg) => !arg.startsWith("--"));
|
|
const searchPath = resolve(paths[0] || ".");
|
|
|
|
console.log(
|
|
`\n${dryRun ? "[DRY RUN] " : ""}Migrating SKILL.md version to metadata.version...\n`
|
|
);
|
|
|
|
const glob = new Glob("**/SKILL.md");
|
|
let updated = 0;
|
|
let skipped = 0;
|
|
|
|
for await (const file of glob.scan({
|
|
cwd: searchPath,
|
|
absolute: true,
|
|
onlyFiles: true,
|
|
})) {
|
|
if (
|
|
file.includes("node_modules") ||
|
|
file.includes(".git") ||
|
|
file.includes(".archive") ||
|
|
file.includes("templates/")
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
if (migrateFile(file, dryRun)) {
|
|
updated++;
|
|
} else {
|
|
skipped++;
|
|
}
|
|
}
|
|
|
|
console.log(`\n${dryRun ? "[DRY RUN] " : ""}Done.`);
|
|
console.log(` Updated: ${updated}`);
|
|
console.log(` Skipped: ${skipped} (no top-level version)\n`);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|