feat(HAI-077): add CI/CD workflows for PRs, releases, and manual testing

- Add CI workflow for pull requests and pushes with lint, test, and build steps
- Add release workflow for tagged versions with automated publishing
- Add manual test-release workflow for on-demand validation
- Add workflow validation tests and remove obsolete build/test files
- Update README with CI/CD documentation and badge references
This commit is contained in:
Dustin Byrne
2026-03-26 00:41:05 -04:00
parent 6643f1f5e0
commit 0b7e8e874a
7 changed files with 339 additions and 1 deletions

View File

@@ -21,6 +21,7 @@
"devDependencies": {
"tsx": "^4.19.0",
"typescript": "^5.7.0",
"vitest": "^3.1.0"
"vitest": "^3.1.0",
"yaml": "^2.8.3"
}
}

View File

@@ -0,0 +1,138 @@
import { describe, it, expect, beforeAll } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { parse } from "yaml";
const workspaceRoot = join(import.meta.dirname!, "..", "..", "..", "..");
function loadWorkflow(name: string): any {
const path = join(workspaceRoot, ".github", "workflows", name);
const content = readFileSync(path, "utf-8");
return { content, parsed: parse(content) };
}
describe("CI workflow (.github/workflows/ci.yml)", () => {
let workflow: any;
let content: string;
beforeAll(() => {
const result = loadWorkflow("ci.yml");
workflow = result.parsed;
content = result.content;
});
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("has push trigger on main", () => {
expect(workflow.on.push.branches).toContain("main");
});
it("has pull_request trigger on main", () => {
expect(workflow.on.pull_request.branches).toContain("main");
});
it("includes pnpm install step", () => {
expect(content).toContain("pnpm install");
});
it("includes pnpm build step", () => {
expect(content).toContain("pnpm build");
});
it("includes pnpm build:exe step", () => {
expect(content).toContain("pnpm build:exe");
});
it("includes pnpm test step", () => {
expect(content).toContain("pnpm test");
});
});
describe("Release workflow (.github/workflows/release.yml)", () => {
let workflow: any;
let content: string;
beforeAll(() => {
const result = loadWorkflow("release.yml");
workflow = result.parsed;
content = result.content;
});
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("has tag-based trigger matching v*", () => {
expect(workflow.on.push.tags).toBeDefined();
const tags = workflow.on.push.tags;
expect(tags.some((t: string) => t.includes("v"))).toBe(true);
});
it("includes softprops/action-gh-release action", () => {
expect(content).toContain("softprops/action-gh-release");
});
it("includes permissions contents write", () => {
expect(workflow.permissions?.contents).toBe("write");
});
it("includes pnpm install step", () => {
expect(content).toContain("pnpm install");
});
it("includes pnpm build step", () => {
expect(content).toContain("pnpm build");
});
it("includes pnpm build:exe step", () => {
expect(content).toContain("pnpm build:exe");
});
it("generates SHA256 checksums", () => {
expect(content).toContain("sha256sum");
});
});
describe("Test Release workflow (.github/workflows/test-release.yml)", () => {
let workflow: any;
let content: string;
beforeAll(() => {
const result = loadWorkflow("test-release.yml");
workflow = result.parsed;
content = result.content;
});
it("is valid YAML", () => {
expect(workflow).toBeDefined();
expect(typeof workflow).toBe("object");
});
it("has workflow_dispatch trigger", () => {
expect(workflow.on).toHaveProperty("workflow_dispatch");
});
it("includes pnpm install step", () => {
expect(content).toContain("pnpm install");
});
it("includes pnpm build step", () => {
expect(content).toContain("pnpm build");
});
it("includes pnpm build:exe step", () => {
expect(content).toContain("pnpm build:exe");
});
it("includes smoke test with --help", () => {
expect(content).toContain("--help");
});
it("uploads artifact", () => {
expect(content).toContain("actions/upload-artifact");
});
});