返回 CodeWhale
artifacts.js
根目录 / npm / codewhale / scripts / artifacts.js
1 const path = require("path");
2 const os = require("os");
3
4 const CHECKSUM_MANIFEST = "codewhale-artifacts-sha256.txt";
5 const BUNDLE_CHECKSUM_MANIFEST = "codewhale-bundles-sha256.txt";
6 const WINDOWS_INSTALLER_ASSET = "CodeWhaleSetup.exe";
7
8 // Compatibility bridge introduced in v0.9.5: already-shipped v0.9.4 clients
9 // require these names before
10 // they will advertise or install a newer release. They contain byte-identical
11 // copies of the consolidated `codewhale` runtime; current installers do not
12 // expose them as a third command.
13 const LEGACY_TUI_BRIDGE_ASSET_NAMES = [
14 "codewhale-tui-linux-x64",
15 "codewhale-tui-linux-arm64",
16 "codewhale-tui-android-arm64",
17 "codewhale-tui-macos-x64",
18 "codewhale-tui-macos-arm64",
19 "codewhale-tui-windows-x64.exe",
20 "codewhale-tui-windows-arm64.exe",
21 ];
22
23 const CNB_BINARY_ASSET_NAMES = [
24 "codewhale-linux-x64",
25 "codew-linux-x64",
26 "codewhale-tui-linux-x64",
27 ];
28 const CNB_RELEASE_ASSET_NAMES = [
29 ...CNB_BINARY_ASSET_NAMES,
30 CHECKSUM_MANIFEST,
31 ];
32
33 const BUNDLE_ASSET_NAMES = [
34 "codewhale-linux-x64.tar.gz",
35 "codewhale-linux-arm64.tar.gz",
36 "codewhale-android-arm64.tar.gz",
37 "codewhale-macos-x64.tar.gz",
38 "codewhale-macos-arm64.tar.gz",
39 "codewhale-windows-x64.zip",
40 "codewhale-windows-x64-portable.zip",
41 "codewhale-windows-arm64.zip",
42 "codewhale-windows-arm64-portable.zip",
43 ];
44
45 const ASSET_MATRIX = {
46 linux: {
47 x64: ["codewhale-linux-x64", "codew-linux-x64"],
48 arm64: ["codewhale-linux-arm64", "codew-linux-arm64"],
49 },
50 android: {
51 arm64: ["codewhale-android-arm64", "codew-android-arm64"],
52 },
53 darwin: {
54 x64: ["codewhale-macos-x64", "codew-macos-x64"],
55 arm64: ["codewhale-macos-arm64", "codew-macos-arm64"],
56 },
57 win32: {
58 x64: ["codewhale-windows-x64.exe", "codew-windows-x64.exe", "codewhale.bat"],
59 arm64: ["codewhale-windows-arm64.exe", "codew-windows-arm64.exe"],
60 },
61 };
62
63 // HarmonyPC (openharmony) is an x86_64 Linux-compatible environment; map it to
64 // the linux binary family so npm install succeeds without a separate build target.
65 const PLATFORM_ALIASES = {
66 openharmony: "linux",
67 };
68
69 function detectBinaryNames() {
70 const rawPlatform = os.platform();
71 const platform = PLATFORM_ALIASES[rawPlatform] || rawPlatform;
72 const arch = os.arch();
73 const defaults = ASSET_MATRIX[platform];
74 if (!defaults) {
75 const supported = Object.keys(ASSET_MATRIX).map(p => `'${p}'`).join(', ');
76 throw new Error(
77 `Unsupported platform: ${rawPlatform}. Supported platforms: ${supported}.\n\n` +
78 unsupportedBuildHint(),
79 );
80 }
81 const pair = defaults[arch];
82 if (!pair) {
83 const supported = Object.keys(defaults).map(a => `'${a}'`).join(', ');
84 const hint = platform === "linux" && arch === "riscv64" ? unsupportedRiscvHint() : unsupportedBuildHint();
85 throw new Error(
86 `Unsupported architecture: ${arch} on platform ${platform}. ` +
87 `Supported architectures: ${supported}.\n\n` +
88 hint,
89 );
90 }
91 return {
92 platform,
93 arch,
94 codewhale: pair[0],
95 codew: pair[1],
96 };
97 }
98
99 function unsupportedBuildHint() {
100 return [
101 "No prebuilt binary is available for this platform/architecture combo.",
102 "You can still run codewhale by building from source with Cargo (single binary):",
103 "",
104 " # Requires Rust 1.88+ (https://rustup.rs)",
105 " cargo install codewhale-cli --locked # provides `codewhale`",
106 "",
107 "Or build from a checkout:",
108 "",
109 " git clone https://github.com/Hmbown/CodeWhale.git",
110 " cd CodeWhale",
111 " cargo install --path crates/cli --locked # single binary",
112 "",
113 "See https://github.com/Hmbown/CodeWhale/blob/main/docs/INSTALL.md",
114 "for cross-compilation, mirror, Linux ARM64, FreeBSD, and winget specifics.",
115 ].join("\n");
116 }
117
118 function unsupportedRiscvHint() {
119 return [
120 "Linux riscv64 prebuilt binaries are temporarily unavailable.",
121 "CodeWhale currently depends on rquickjs-sys, which does not ship",
122 "riscv64gc-unknown-linux-gnu bindings in the locked dependency set.",
123 "",
124 "Track the release notes and docs/INSTALL.md for the next RISC-V support update.",
125 ].join("\n");
126 }
127
128 function executableName(base, platform) {
129 return platform === "win32" ? `${base}.exe` : base;
130 }
131
132 function ensureTrailingSlash(baseUrl) {
133 const trimmed = String(baseUrl).trim();
134 return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
135 }
136
137 function githubReleaseBaseUrl(version, repo = "Hmbown/CodeWhale") {
138 return `https://github.com/${repo}/releases/download/v${version}/`;
139 }
140
141 function cnbReleaseBaseUrl(version) {
142 return `https://cnb.cool/codewhale.net/codewhale/-/releases/download/v${version}/`;
143 }
144
145 function releaseAssetUrlFromBase(baseName, baseUrl) {
146 return new URL(baseName, ensureTrailingSlash(baseUrl)).toString();
147 }
148
149 function explicitReleaseBase(env = process.env) {
150 const candidates = [
151 env.CODEWHALE_RELEASE_BASE_URL,
152 env.DEEPSEEK_TUI_RELEASE_BASE_URL,
153 env.DEEPSEEK_RELEASE_BASE_URL,
154 ];
155 for (const candidate of candidates) {
156 const override = String(candidate || "").trim();
157 if (override) {
158 return ensureTrailingSlash(override);
159 }
160 }
161 return "";
162 }
163
164 function hasExplicitReleaseBase(env = process.env) {
165 return Boolean(explicitReleaseBase(env));
166 }
167
168 function isCnbSupportedTarget(
169 rawPlatform = os.platform(),
170 arch = os.arch(),
171 ) {
172 const platform = PLATFORM_ALIASES[rawPlatform] || rawPlatform;
173 return platform === "linux" && arch === "x64";
174 }
175
176 function releaseBaseUrl(version, repo = "Hmbown/CodeWhale") {
177 // CODEWHALE_RELEASE_BASE_URL is the canonical override.
178 // DEEPSEEK_TUI_RELEASE_BASE_URL / DEEPSEEK_RELEASE_BASE_URL are legacy aliases.
179 const override = explicitReleaseBase();
180 if (override) {
181 return override;
182 }
183 // When CODEWHALE_USE_CNB_MIRROR is set, use the CNB (China-friendly)
184 // mirror that already builds and publishes binary release assets.
185 if (usesCnbMirror()) {
186 assertCnbMirrorSupportedPlatform();
187 return cnbReleaseBaseUrl(version);
188 }
189 return githubReleaseBaseUrl(version, repo);
190 }
191
192 function usesCnbMirror(env = process.env) {
193 return !hasExplicitReleaseBase(env) && env.CODEWHALE_USE_CNB_MIRROR === "1";
194 }
195
196 function shouldRaceFirstPartyMirrors(
197 env = process.env,
198 rawPlatform = os.platform(),
199 arch = os.arch(),
200 ) {
201 return (
202 isCnbSupportedTarget(rawPlatform, arch) &&
203 !hasExplicitReleaseBase(env) &&
204 env.CODEWHALE_USE_CNB_MIRROR !== "1"
205 );
206 }
207
208 function firstPartyReleaseSources(version, repo = "Hmbown/CodeWhale") {
209 return [
210 {
211 id: "github",
212 label: "GitHub Releases",
213 baseUrl: githubReleaseBaseUrl(version, repo),
214 },
215 {
216 id: "cnb",
217 label: "CNB first-party mirror",
218 baseUrl: cnbReleaseBaseUrl(version),
219 },
220 ];
221 }
222
223 function assertCnbMirrorSupportedPlatform(
224 rawPlatform = os.platform(),
225 arch = os.arch(),
226 ) {
227 if (isCnbSupportedTarget(rawPlatform, arch)) {
228 return;
229 }
230 throw new Error(
231 "CODEWHALE_USE_CNB_MIRROR=1 currently supports only Linux x64 " +
232 `(including OpenHarmony x64); detected ${rawPlatform} ${arch}. ` +
233 "Use the GitHub Release or set CODEWHALE_RELEASE_BASE_URL to a " +
234 "complete mirror for this platform.",
235 );
236 }
237
238 function releaseAssetUrl(baseName, version, repo = "Hmbown/CodeWhale") {
239 return releaseAssetUrlFromBase(baseName, releaseBaseUrl(version, repo));
240 }
241
242 function checksumManifestUrl(version, repo = "Hmbown/CodeWhale") {
243 return releaseAssetUrl(CHECKSUM_MANIFEST, version, repo);
244 }
245
246 function releaseBinaryDirectory() {
247 return path.join(__dirname, "..", "bin", "downloads");
248 }
249
250 function allAssetNames() {
251 const names = [];
252 for (const platformAssets of Object.values(ASSET_MATRIX)) {
253 for (const assets of Object.values(platformAssets)) {
254 names.push(...assets);
255 }
256 }
257 return Array.from(new Set(names));
258 }
259
260 function allReleaseAssetNames() {
261 return [
262 ...allAssetNames(),
263 ...LEGACY_TUI_BRIDGE_ASSET_NAMES,
264 ...BUNDLE_ASSET_NAMES,
265 WINDOWS_INSTALLER_ASSET,
266 BUNDLE_CHECKSUM_MANIFEST,
267 CHECKSUM_MANIFEST,
268 ];
269 }
270
271 function checksummedReleaseAssetNames() {
272 return allReleaseAssetNames().filter((name) => name !== CHECKSUM_MANIFEST);
273 }
274
275 module.exports = {
276 allAssetNames,
277 allReleaseAssetNames,
278 assertCnbMirrorSupportedPlatform,
279 BUNDLE_ASSET_NAMES,
280 BUNDLE_CHECKSUM_MANIFEST,
281 CHECKSUM_MANIFEST,
282 checksummedReleaseAssetNames,
283 CNB_BINARY_ASSET_NAMES,
284 CNB_RELEASE_ASSET_NAMES,
285 checksumManifestUrl,
286 cnbReleaseBaseUrl,
287 detectBinaryNames,
288 executableName,
289 explicitReleaseBase,
290 firstPartyReleaseSources,
291 githubReleaseBaseUrl,
292 hasExplicitReleaseBase,
293 isCnbSupportedTarget,
294 LEGACY_TUI_BRIDGE_ASSET_NAMES,
295 releaseAssetUrl,
296 releaseAssetUrlFromBase,
297 releaseBaseUrl,
298 releaseBinaryDirectory,
299 shouldRaceFirstPartyMirrors,
300 usesCnbMirror,
301 WINDOWS_INSTALLER_ASSET,
302 };
303
303 lines JAVASCRIPT