Skip to content

Commit 04edd27

Browse files
committed
ci(release): keep test helpers out of package dist
Keep runtime dist in the published package while moving release helper CLIs outside the TypeScript build and rejecting test-only artifacts before publication.
1 parent 9a5a926 commit 04edd27

8 files changed

Lines changed: 62 additions & 10 deletions

File tree

.github/workflows/release.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@ jobs:
3838
- name: Lint and format check
3939
run: bun run check
4040

41-
- name: Unit tests
42-
run: bun run test
41+
- name: Unit tests with coverage
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
run: bun run test:coverage
4345

4446
- name: Release sanity checks
4547
run: bun run release:check

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@
2323
}
2424
},
2525
"files": {
26-
"includes": ["src/**/*", "*.json", "*.md", "!.mcp.json"]
26+
"includes": ["src/**/*", "scripts/**/*", "*.json", "*.md", "!.mcp.json"]
2727
}
2828
}

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@
3131
"check": "biome check .",
3232
"check:fix": "biome check --write .",
3333
"test": "bun test src/",
34-
"test:coverage": "bun src/coverage-threshold.ts",
35-
"release:check": "bun src/release-sanity.ts",
36-
"prepublishOnly": "bun run release:check && bun run build && bun run check && bun run test",
34+
"test:coverage": "bun scripts/coverage-threshold.ts",
35+
"release:check": "bun scripts/release-sanity.ts",
36+
"prepublishOnly": "bun run release:check && bun run build && bun run check && bun run test:coverage",
3737
"setup-hooks": "git config core.hooksPath .githooks"
3838
},
3939
"keywords": [
Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { readFileSync } from "node:fs";
1+
import { existsSync, readdirSync, readFileSync } from "node:fs";
2+
import { join } from "node:path";
23

34
interface PackageJson {
45
version?: unknown;
@@ -9,6 +10,7 @@ interface ReleaseSanityInput {
910
packageJson: PackageJson;
1011
changelog: string;
1112
githubRef?: string;
13+
distFiles?: string[];
1214
}
1315

1416
export function checkReleaseSanity(input: ReleaseSanityInput): string[] {
@@ -39,9 +41,24 @@ export function checkReleaseSanity(input: ReleaseSanityInput): string[] {
3941
}
4042
}
4143

44+
for (const file of input.distFiles ?? []) {
45+
if (isForbiddenDistArtifact(file)) {
46+
errors.push(`dist must not include test-only artifact ${file}.`);
47+
}
48+
}
49+
4250
return errors;
4351
}
4452

53+
function isForbiddenDistArtifact(file: string): boolean {
54+
return (
55+
file.endsWith(".test.js") ||
56+
file === "server/test-harness.js" ||
57+
file === "coverage-threshold.js" ||
58+
file === "release-sanity.js"
59+
);
60+
}
61+
4562
function escapeRegExp(value: string): string {
4663
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4764
}
@@ -55,6 +72,7 @@ function runReleaseSanityCli(): void {
5572
packageJson: readPackageJson(),
5673
changelog: readFileSync("CHANGELOG.md", "utf8"),
5774
githubRef: process.env.GITHUB_REF,
75+
distFiles: listDistFiles(),
5876
});
5977

6078
if (errors.length > 0) {
@@ -67,6 +85,25 @@ function runReleaseSanityCli(): void {
6785
console.log("Release sanity OK");
6886
}
6987

88+
function listDistFiles(): string[] {
89+
if (!existsSync("dist")) return [];
90+
91+
const files: string[] = [];
92+
const walk = (dir: string, prefix = ""): void => {
93+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
94+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
95+
if (entry.isDirectory()) {
96+
walk(join(dir, entry.name), relative);
97+
} else {
98+
files.push(relative);
99+
}
100+
}
101+
};
102+
103+
walk("dist");
104+
return files;
105+
}
106+
70107
if (import.meta.main) {
71108
runReleaseSanityCli();
72109
}

src/coverage-threshold.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { describe, expect, test } from "bun:test";
22

3-
import { assertCoverageThreshold, parseAllFilesLineCoverage } from "./coverage-threshold.js";
3+
import {
4+
assertCoverageThreshold,
5+
parseAllFilesLineCoverage,
6+
} from "../scripts/coverage-threshold.js";
47

58
describe("parseAllFilesLineCoverage", () => {
69
test("extracts line coverage from Bun's All files summary row", () => {

src/release-sanity.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, test } from "bun:test";
22

3-
import { checkReleaseSanity } from "./release-sanity.js";
3+
import { checkReleaseSanity } from "../scripts/release-sanity.js";
44

55
const BASE_PACKAGE = {
66
name: "@rethunk/github-mcp",
@@ -40,4 +40,14 @@ describe("checkReleaseSanity", () => {
4040
}),
4141
).toEqual(['package.json "files" must include "dist".']);
4242
});
43+
44+
test("rejects test-only files in built dist artifacts", () => {
45+
expect(
46+
checkReleaseSanity({
47+
packageJson: BASE_PACKAGE,
48+
changelog: "## [1.2.3] — 2026-04-26\n",
49+
distFiles: ["server.js", "server/test-harness.js"],
50+
}),
51+
).toEqual(["dist must not include test-only artifact server/test-harness.js."]);
52+
});
4353
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@
1717
"types": ["node"]
1818
},
1919
"include": ["src/**/*"],
20-
"exclude": ["node_modules", "dist", "**/*.test.ts"]
20+
"exclude": ["node_modules", "dist", "**/*.test.ts", "src/server/test-harness.ts"]
2121
}

0 commit comments

Comments
 (0)