mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-26 10:45:03 +00:00
71f7c9b1dc
* fix(eng): list nested skill assets instead of directory names * test(eng): clean up temp dirs and cover nested SKILL.md assets * fix(eng): do not follow symlinks when listing bundled assets * fix(eng): keep file symlinks in bundled asset lists
136 lines
4.0 KiB
JavaScript
136 lines
4.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { after, test } from "node:test";
|
|
import { parseSkillMetadata } from "./yaml-parser.mjs";
|
|
|
|
const tempDirs = [];
|
|
|
|
after(() => {
|
|
for (const dir of tempDirs) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function createSkill(files) {
|
|
const skillPath = fs.mkdtempSync(path.join(os.tmpdir(), "skill-"));
|
|
tempDirs.push(skillPath);
|
|
for (const [relativePath, contents] of Object.entries(files)) {
|
|
const target = path.join(skillPath, relativePath);
|
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
fs.writeFileSync(target, contents);
|
|
}
|
|
return skillPath;
|
|
}
|
|
|
|
const SKILL_MD = `---
|
|
name: sample-skill
|
|
description: A sample skill used to exercise bundled asset discovery.
|
|
---
|
|
|
|
# Sample skill
|
|
`;
|
|
|
|
test("lists bundled assets from the spec-defined subdirectories", () => {
|
|
const skillPath = createSkill({
|
|
"SKILL.md": SKILL_MD,
|
|
"references/guide.md": "guide",
|
|
"scripts/run.py": "run",
|
|
"assets/logo.png": "logo",
|
|
});
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
assert.deepEqual(metadata.assets, [
|
|
"assets/logo.png",
|
|
"references/guide.md",
|
|
"scripts/run.py",
|
|
]);
|
|
});
|
|
|
|
test("recurses into nested and non-spec subdirectories instead of listing the directory", () => {
|
|
const skillPath = createSkill({
|
|
"SKILL.md": SKILL_MD,
|
|
"templates/report.md": "report",
|
|
"references/skeletons/threat-model.md": "skeleton",
|
|
"nested/deep/tool.sh": "tool",
|
|
});
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
assert.deepEqual(metadata.assets, [
|
|
"nested/deep/tool.sh",
|
|
"references/skeletons/threat-model.md",
|
|
"templates/report.md",
|
|
]);
|
|
for (const asset of metadata.assets) {
|
|
assert.ok(
|
|
fs.statSync(path.join(skillPath, asset)).isFile(),
|
|
`expected "${asset}" to be a file, not a directory`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("excludes only the root SKILL.md, keeping nested ones as bundled assets", () => {
|
|
const skillPath = createSkill({
|
|
"SKILL.md": SKILL_MD,
|
|
"scaling-qps/SKILL.md": SKILL_MD,
|
|
"scaling-data-volume/tenant-scaling/SKILL.md": SKILL_MD,
|
|
});
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
assert.deepEqual(metadata.assets, [
|
|
"scaling-data-volume/tenant-scaling/SKILL.md",
|
|
"scaling-qps/SKILL.md",
|
|
]);
|
|
assert.ok(
|
|
!metadata.assets.includes("SKILL.md"),
|
|
"the skill's own SKILL.md must not be listed as a bundled asset"
|
|
);
|
|
});
|
|
|
|
test("skips directory symlinks instead of escaping the skill folder or cycling", () => {
|
|
const outside = createSkill({ "secret.md": "not part of the skill" });
|
|
const skillPath = createSkill({
|
|
"SKILL.md": SKILL_MD,
|
|
"references/guide.md": "guide",
|
|
});
|
|
|
|
// A link that leaves the skill root, and one that points back at it.
|
|
fs.symlinkSync(outside, path.join(skillPath, "templates"));
|
|
fs.symlinkSync(skillPath, path.join(skillPath, "references", "loop"));
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
assert.deepEqual(metadata.assets, ["references/guide.md"]);
|
|
for (const asset of metadata.assets) {
|
|
assert.ok(
|
|
!asset.startsWith("templates/") && !asset.includes("loop/"),
|
|
`asset "${asset}" was resolved through a symlink`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("keeps file symlinks, which materialization dereferences into the bundle", () => {
|
|
const outside = createSkill({ "shared.md": "shared content" });
|
|
const skillPath = createSkill({ "SKILL.md": SKILL_MD });
|
|
fs.symlinkSync(path.join(outside, "shared.md"), path.join(skillPath, "shared.md"));
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
// copyDirRecursive in materialize-plugins.mjs copies this via copyFileSync,
|
|
// so the file ships with the plugin and must appear in the asset list.
|
|
assert.deepEqual(metadata.assets, ["shared.md"]);
|
|
});
|
|
|
|
test("skips broken symlinks", () => {
|
|
const skillPath = createSkill({ "SKILL.md": SKILL_MD });
|
|
fs.symlinkSync(path.join(skillPath, "does-not-exist.md"), path.join(skillPath, "dangling.md"));
|
|
|
|
const metadata = parseSkillMetadata(skillPath);
|
|
|
|
assert.deepEqual(metadata.assets, []);
|
|
});
|