返回 CodeWhale
release-assets.test.js
根目录 / npm / codewhale / test / release-assets.test.js
1 const assert = require("node:assert/strict");
2 const { execFileSync } = require("node:child_process");
3 const fs = require("node:fs");
4 const os = require("node:os");
5 const path = require("node:path");
6 const test = require("node:test");
7
8 const pkg = require("../package.json");
9 const {
10 allReleaseAssetNames,
11 BUNDLE_ASSET_NAMES,
12 BUNDLE_CHECKSUM_MANIFEST,
13 CHECKSUM_MANIFEST,
14 checksummedReleaseAssetNames,
15 } = require("../scripts/artifacts");
16 const {
17 assertChecksumManifestIncludes,
18 assertPackageVersionMatchesBinaryVersion,
19 assertReleaseAssetsFresh,
20 parseChecksumManifest,
21 } = require("../scripts/verify-release-assets");
22
23 test("parseChecksumManifest accepts GNU and BSD filename forms", () => {
24 const manifest = parseChecksumManifest(
25 [
26 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa codewhale-linux-x64",
27 "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb *codewhale-windows-x64.exe",
28 ].join("\n"),
29 );
30
31 assert.equal(manifest.get("codewhale-linux-x64"), "a".repeat(64));
32 assert.equal(manifest.get("codewhale-windows-x64.exe"), "b".repeat(64));
33 });
34
35 test("parseChecksumManifest rejects malformed checksum rows", () => {
36 assert.throws(
37 () => parseChecksumManifest("not-a-sha codewhale-linux-x64"),
38 /Invalid checksum manifest line/,
39 );
40 });
41
42 test("assertReleaseAssetsFresh rejects missing release assets", () => {
43 assert.throws(
44 () =>
45 assertReleaseAssetsFresh(
46 { assets: [{ name: "codewhale-linux-x64", state: "uploaded", updated_at: "2026-06-26T00:10:00Z" }] },
47 ["codewhale-linux-x64", "codewhale-artifacts-sha256.txt"],
48 { database_id: 123, created_at: "2026-06-26T00:00:00Z" },
49 ),
50 /missing required release asset/,
51 );
52 });
53
54 test("assertChecksumManifestIncludes rejects missing bundle manifest and archive rows", () => {
55 const manifest = parseChecksumManifest(
56 `${"a".repeat(64)} codewhale-linux-x64.tar.gz`,
57 );
58
59 assert.throws(
60 () =>
61 assertChecksumManifestIncludes(
62 manifest,
63 ["codewhale-linux-x64.tar.gz", "codewhale-bundles-sha256.txt"],
64 "Canonical checksum manifest",
65 ),
66 /Canonical checksum manifest is missing codewhale-bundles-sha256\.txt/,
67 );
68 });
69
70 test("bundle checksum rows use public archive basenames", () => {
71 const manifest = parseChecksumManifest(
72 `${"a".repeat(64)} bundles/codewhale-linux-x64.tar.gz`,
73 );
74
75 assert.throws(
76 () =>
77 assertChecksumManifestIncludes(
78 manifest,
79 ["codewhale-linux-x64.tar.gz"],
80 "Bundle checksum manifest",
81 ),
82 /Bundle checksum manifest is missing codewhale-linux-x64\.tar\.gz/,
83 );
84 });
85
86 test("assertReleaseAssetsFresh rejects assets older than the release workflow run", () => {
87 assert.throws(
88 () =>
89 assertReleaseAssetsFresh(
90 { assets: [{ name: "codewhale-linux-x64", state: "uploaded", updated_at: "2026-06-25T23:59:59Z" }] },
91 ["codewhale-linux-x64"],
92 { database_id: 123, created_at: "2026-06-26T00:00:00Z" },
93 ),
94 /asset set is stale/,
95 );
96 });
97
98 test("assertReleaseAssetsFresh rejects non-uploaded assets", () => {
99 assert.throws(
100 () =>
101 assertReleaseAssetsFresh(
102 { assets: [{ name: "codewhale-linux-x64", state: "new", updated_at: "2026-06-26T00:10:00Z" }] },
103 ["codewhale-linux-x64"],
104 { database_id: 123, created_at: "2026-06-26T00:00:00Z" },
105 ),
106 /asset set is stale/,
107 );
108 });
109
110 test("assertReleaseAssetsFresh accepts assets updated by the release workflow run", () => {
111 assert.doesNotThrow(() =>
112 assertReleaseAssetsFresh(
113 { assets: [{ name: "codewhale-linux-x64", state: "uploaded", updated_at: "2026-06-26T00:10:00Z" }] },
114 ["codewhale-linux-x64"],
115 { database_id: 123, created_at: "2026-06-26T00:00:00Z" },
116 ),
117 );
118 });
119
120 test("assertPackageVersionMatchesBinaryVersion allows packaging-only releases only with an explicit override", () => {
121 assert.doesNotThrow(() => assertPackageVersionMatchesBinaryVersion(pkg.version));
122 assert.throws(
123 () => assertPackageVersionMatchesBinaryVersion("0.0.0-packaging-test"),
124 /does not match codewhaleBinaryVersion/,
125 );
126
127 const previous = process.env.CODEWHALE_ALLOW_NPM_BINARY_MISMATCH;
128 process.env.CODEWHALE_ALLOW_NPM_BINARY_MISMATCH = "1";
129 try {
130 assert.doesNotThrow(() => assertPackageVersionMatchesBinaryVersion("0.0.0-packaging-test"));
131 } finally {
132 if (previous === undefined) {
133 delete process.env.CODEWHALE_ALLOW_NPM_BINARY_MISMATCH;
134 } else {
135 process.env.CODEWHALE_ALLOW_NPM_BINARY_MISMATCH = previous;
136 }
137 }
138 });
139
140 test("npm publication requires the checkout guard and canonical release-asset gate", () => {
141 assert.equal(
142 pkg.scripts.prepublishOnly,
143 "bash ../../scripts/release/require-release-tag-checkout.sh && " +
144 "bash ../../scripts/release/verify-release-assets.sh",
145 );
146 });
147
148 test("full local release fixture satisfies the public asset inventory", () => {
149 const repoRoot = path.resolve(__dirname, "..", "..", "..");
150 const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), "codewhale-assets-"));
151 const buildDir = path.join(fixtureRoot, "build");
152 const outputDir = path.join(fixtureRoot, "assets");
153 const executableSuffix = process.platform === "win32" ? ".exe" : "";
154
155 try {
156 fs.mkdirSync(buildDir, { recursive: true });
157 for (const binary of ["codewhale", "codew", "codewhale-tui"]) {
158 fs.writeFileSync(
159 path.join(buildDir, `${binary}${executableSuffix}`),
160 `fixture:${binary}\n`,
161 );
162 }
163
164 execFileSync(
165 process.execPath,
166 [
167 path.join(repoRoot, "scripts", "release", "prepare-local-release-assets.js"),
168 outputDir,
169 buildDir,
170 ],
171 {
172 env: { ...process.env, DEEPSEEK_TUI_PREPARE_ALL_ASSETS: "1" },
173 stdio: "pipe",
174 },
175 );
176
177 for (const assetName of allReleaseAssetNames()) {
178 assert.equal(
179 fs.existsSync(path.join(outputDir, assetName)),
180 true,
181 `missing fixture asset ${assetName}`,
182 );
183 }
184
185 const canonicalChecksums = parseChecksumManifest(
186 fs.readFileSync(path.join(outputDir, CHECKSUM_MANIFEST), "utf8"),
187 );
188 assert.doesNotThrow(() =>
189 assertChecksumManifestIncludes(
190 canonicalChecksums,
191 checksummedReleaseAssetNames(),
192 "Canonical checksum manifest",
193 ),
194 );
195
196 const bundleChecksums = parseChecksumManifest(
197 fs.readFileSync(path.join(outputDir, BUNDLE_CHECKSUM_MANIFEST), "utf8"),
198 );
199 assert.doesNotThrow(() =>
200 assertChecksumManifestIncludes(
201 bundleChecksums,
202 BUNDLE_ASSET_NAMES,
203 "Bundle checksum manifest",
204 ),
205 );
206 } finally {
207 fs.rmSync(fixtureRoot, { recursive: true, force: true });
208 }
209 });
210
210 lines JAVASCRIPT