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