Files
awesome-copilot/eng/create-skill.test.mjs
T

107 lines
3.2 KiB
JavaScript

import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { test } from "node:test";
import { fileURLToPath } from "node:url";
import { load } from "js-yaml";
const engDir = path.dirname(fileURLToPath(import.meta.url));
async function createSkill(t, description) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "create-skill-"));
const modulesLink = path.join(root, "node_modules");
const scriptDir = path.join(root, "eng");
fs.mkdirSync(scriptDir);
for (const file of ["create-skill.mjs", "constants.mjs"]) {
fs.copyFileSync(path.join(engDir, file), path.join(scriptDir, file));
}
fs.symlinkSync(
path.join(engDir, "..", "node_modules"),
modulesLink,
process.platform === "win32" ? "junction" : "dir"
);
const child = spawn(
process.execPath,
[
path.join(scriptDir, "create-skill.mjs"),
"--name",
"yaml-roundtrip",
"--description",
description,
],
{ cwd: root, stdio: ["pipe", "pipe", "pipe"] }
);
t.after(async () => {
if (child.exitCode === null && child.signalCode === null) {
const closed = new Promise((resolve) => child.once("close", resolve));
child.kill();
await closed;
}
// Unlink the junction before recursively removing only the fixture.
fs.unlinkSync(modulesLink);
fs.rmSync(root, { recursive: true, force: true });
});
let stdout = "";
let stderr = "";
let answeredTitle = false;
let answeredAssets = false;
child.stdout.on("data", (chunk) => {
stdout += chunk;
if (!answeredTitle && stdout.includes("Skill title (default:")) {
answeredTitle = true;
child.stdin.write("\n");
}
if (!answeredAssets && stdout.includes("Would you like to add bundled assets?")) {
answeredAssets = true;
child.stdin.end("n\n");
}
});
child.stderr.on("data", (chunk) => {
stderr += chunk;
});
const exitCode = await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("close", resolve);
});
assert.equal(exitCode, 0, `${stdout}\n${stderr}`);
const content = fs.readFileSync(
path.join(root, "skills", "yaml-roundtrip", "SKILL.md"),
"utf8"
);
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
assert.ok(frontmatter, "generated skill must have YAML frontmatter");
return load(frontmatter[1]);
}
test(
"creates parseable frontmatter for descriptions containing a colon",
{ timeout: 10000 },
async (t) => {
const description = "Review API: protobuf and gRPC contracts";
const metadata = await createSkill(t, description);
assert.equal(metadata.name, "yaml-roundtrip");
assert.equal(metadata.description, description);
}
);
test(
"preserves hashes, apostrophes, backslashes, and multiline descriptions",
{ timeout: 10000 },
async (t) => {
for (const description of [
"Review developer's contracts # keep C:\\schemas\\protobuf intact",
"Review developer's API contracts\nKeep # details and C:\\schemas\\protobuf intact",
]) {
const metadata = await createSkill(t, description);
assert.equal(metadata.description, description);
}
}
);