mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
test: add sanity test for skills installation
Add a sanity test that verifies the skills CLI installation works: - Runs `npx skills add . -a claude-code -y` using current directory - Checks command doesn't fail (exit code 0) - Verifies .claude/skills directory is created - Dynamically discovers skills in the repo and verifies they're installed - Checks SKILL.md exists in each installed skill The test runs on every push to main and pull requests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
@@ -52,3 +52,19 @@ jobs:
|
|||||||
git diff skills/*/AGENTS.md
|
git diff skills/*/AGENTS.md
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
sanity-test:
|
||||||
|
name: Sanity Test (skills add)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Run sanity tests
|
||||||
|
run: npm run test
|
||||||
|
|||||||
1925
package-lock.json
generated
1925
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -12,9 +12,12 @@
|
|||||||
"lint": "biome lint --write .",
|
"lint": "biome lint --write .",
|
||||||
"lint:check": "biome lint .",
|
"lint:check": "biome lint .",
|
||||||
"check": "biome check --write .",
|
"check": "biome check --write .",
|
||||||
"ci:check": "biome ci ."
|
"ci:check": "biome ci .",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:sanity": "vitest run test/sanity.test.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "2.3.11"
|
"@biomejs/biome": "2.3.11",
|
||||||
|
"vitest": "^3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
test/sanity.test.ts
Normal file
106
test/sanity.test.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { execSync } from "node:child_process";
|
||||||
|
import { existsSync, readdirSync, rmSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const SKILLS_DIR = join(__dirname, "..", "skills");
|
||||||
|
const CLAUDE_SKILLS_DIR = join(__dirname, "..", ".claude", "skills");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically discover all skill names from the skills/ directory
|
||||||
|
*/
|
||||||
|
function discoverSkillNames(): string[] {
|
||||||
|
if (!existsSync(SKILLS_DIR)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return readdirSync(SKILLS_DIR, { withFileTypes: true })
|
||||||
|
.filter((entry) => entry.isDirectory())
|
||||||
|
.filter((entry) => existsSync(join(SKILLS_DIR, entry.name, "SKILL.md")))
|
||||||
|
.map((entry) => entry.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("skills add sanity check", () => {
|
||||||
|
let commandOutput: string;
|
||||||
|
let commandExitCode: number;
|
||||||
|
const skillNames = discoverSkillNames();
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// Clean up any existing .claude/skills directory
|
||||||
|
if (existsSync(CLAUDE_SKILLS_DIR)) {
|
||||||
|
rmSync(CLAUDE_SKILLS_DIR, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the skills add command using current directory (.) as source
|
||||||
|
// This tests the current branch's skills
|
||||||
|
try {
|
||||||
|
commandOutput = execSync("npx skills add . -a claude-code -y", {
|
||||||
|
cwd: join(__dirname, ".."),
|
||||||
|
encoding: "utf-8",
|
||||||
|
stdio: ["pipe", "pipe", "pipe"],
|
||||||
|
timeout: 120000, // 2 minute timeout
|
||||||
|
});
|
||||||
|
commandExitCode = 0;
|
||||||
|
} catch (error) {
|
||||||
|
const execError = error as {
|
||||||
|
stdout?: string;
|
||||||
|
stderr?: string;
|
||||||
|
status?: number;
|
||||||
|
};
|
||||||
|
commandOutput = `${execError.stdout || ""}\n${execError.stderr || ""}`;
|
||||||
|
commandExitCode = execError.status ?? 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
// Clean up .claude/skills directory after tests
|
||||||
|
if (existsSync(CLAUDE_SKILLS_DIR)) {
|
||||||
|
rmSync(CLAUDE_SKILLS_DIR, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have discovered skills in the repository", () => {
|
||||||
|
expect(skillNames.length).toBeGreaterThan(0);
|
||||||
|
console.log(
|
||||||
|
`Discovered ${skillNames.length} skills: ${skillNames.join(", ")}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not contain 'Error' in command output", () => {
|
||||||
|
// Check for error patterns in output (case-insensitive for common error messages)
|
||||||
|
const hasError =
|
||||||
|
/\bError\b/i.test(commandOutput) && !/✓/.test(commandOutput);
|
||||||
|
|
||||||
|
if (hasError) {
|
||||||
|
console.log("Command output:", commandOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow output with errors if the command still succeeded
|
||||||
|
// Some tools output "Error" in informational messages
|
||||||
|
expect(commandExitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should create .claude/skills directory", () => {
|
||||||
|
expect(existsSync(CLAUDE_SKILLS_DIR)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should install all skills from the repository", () => {
|
||||||
|
for (const skillName of skillNames) {
|
||||||
|
const skillPath = join(CLAUDE_SKILLS_DIR, skillName);
|
||||||
|
expect(
|
||||||
|
existsSync(skillPath),
|
||||||
|
`Expected skill "${skillName}" to be installed at ${skillPath}`,
|
||||||
|
).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have SKILL.md in each installed skill", () => {
|
||||||
|
for (const skillName of skillNames) {
|
||||||
|
const skillMdPath = join(CLAUDE_SKILLS_DIR, skillName, "SKILL.md");
|
||||||
|
expect(
|
||||||
|
existsSync(skillMdPath),
|
||||||
|
`Expected SKILL.md to exist at ${skillMdPath}`,
|
||||||
|
).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
8
vitest.config.ts
Normal file
8
vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
testTimeout: 180000, // 3 minute timeout for sanity tests
|
||||||
|
hookTimeout: 180000,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user