|
1 | | -import { mkdir, rename, unlink, writeFile, chmod } from "node:fs/promises"; |
| 1 | +import { mkdir, rename, unlink, writeFile, chmod, readFile } from "node:fs/promises"; |
2 | 2 | import { dirname, join } from "node:path"; |
3 | 3 | import { homedir } from "node:os"; |
4 | 4 | import { createHash } from "node:crypto"; |
5 | 5 | import { |
6 | 6 | binaryAssetFileName, |
| 7 | + binaryInnerFileName, |
7 | 8 | channelManifestUrl, |
8 | 9 | detectBinaryPlatform, |
| 10 | + extractZipEntryToFile, |
9 | 11 | getConfigDir, |
10 | 12 | releaseAssetUrl, |
11 | 13 | writeInstallMethodSync, |
12 | 14 | } from "bailian-cli-core"; |
13 | 15 |
|
14 | 16 | export interface ChannelManifest { |
15 | 17 | version: string; |
16 | | - assets?: Record<string, { file?: string; sha256?: string; url?: string }>; |
| 18 | + assets?: Record<string, { file?: string; sha256?: string; url?: string; inner?: string }>; |
17 | 19 | } |
18 | 20 |
|
19 | 21 | export async function fetchBinaryChannelVersion( |
@@ -81,40 +83,46 @@ function sha256(buffer: Buffer): string { |
81 | 83 |
|
82 | 84 | /** |
83 | 85 | * Download and install a newer standalone binary in place of the current install. |
| 86 | + * Assets are per-platform `.zip` files; checksum applies to the zip. |
84 | 87 | * Returns the installed version string. |
85 | 88 | */ |
86 | 89 | export async function performBinaryUpdate(targetVersion: string): Promise<string> { |
87 | 90 | const { os, arch, fileSuffix } = detectBinaryPlatform(); |
| 91 | + const exe = fileSuffix === ".exe"; |
88 | 92 | const manifest = await fetchBinaryChannelManifest("latest"); |
89 | 93 | const assetKey = `${os}-${arch}`; |
90 | 94 | const assetMeta = manifest?.assets?.[assetKey]; |
91 | | - const fileName = |
92 | | - assetMeta?.file ?? binaryAssetFileName(targetVersion, os, arch, fileSuffix === ".exe"); |
| 95 | + const zipName = assetMeta?.file ?? binaryAssetFileName(targetVersion, os, arch, exe); |
| 96 | + const innerName = assetMeta?.inner ?? binaryInnerFileName(targetVersion, os, arch, exe); |
93 | 97 | const expectedSha = assetMeta?.sha256; |
94 | | - const url = assetMeta?.url ?? releaseAssetUrl(targetVersion, fileName); |
| 98 | + const url = assetMeta?.url ?? releaseAssetUrl(targetVersion, zipName); |
95 | 99 |
|
96 | | - const tmpPath = join(shareRoot(), ".tmp", fileName); |
97 | | - const buffer = await downloadToFile(url, tmpPath); |
| 100 | + const tmpZip = join(shareRoot(), ".tmp", zipName); |
| 101 | + const buffer = await downloadToFile(url, tmpZip); |
98 | 102 | const actualSha = sha256(buffer); |
99 | 103 | if (expectedSha && expectedSha !== actualSha) { |
100 | | - await unlink(tmpPath).catch(() => {}); |
101 | | - throw new Error(`Checksum mismatch for ${fileName}`); |
| 104 | + await unlink(tmpZip).catch(() => {}); |
| 105 | + throw new Error(`Checksum mismatch for ${zipName}`); |
102 | 106 | } |
103 | 107 |
|
104 | 108 | const versionDir = join(shareRoot(), "versions", targetVersion); |
105 | 109 | await mkdir(versionDir, { recursive: true }); |
106 | 110 | const binaryName = process.platform === "win32" ? "bl.exe" : "bl"; |
107 | 111 | const finalPath = join(versionDir, binaryName); |
108 | | - await rename(tmpPath, finalPath); |
| 112 | + const tmpBinary = join(shareRoot(), ".tmp", binaryName); |
| 113 | + await extractZipEntryToFile(tmpZip, tmpBinary, innerName); |
| 114 | + await unlink(tmpZip).catch(() => {}); |
| 115 | + await rename(tmpBinary, finalPath); |
109 | 116 | if (process.platform !== "win32") { |
110 | 117 | await chmod(finalPath, 0o755); |
111 | 118 | } |
112 | 119 |
|
113 | 120 | const binDir = binRoot(); |
114 | 121 | await mkdir(binDir, { recursive: true }); |
115 | 122 | if (process.platform === "win32") { |
116 | | - await writeFile(join(binDir, "bl.exe"), buffer); |
117 | | - await writeFile(join(binDir, "bailian.exe"), buffer); |
| 123 | + const installed = await readFile(finalPath); |
| 124 | + await writeFile(join(binDir, "bl.exe"), installed); |
| 125 | + await writeFile(join(binDir, "bailian.exe"), installed); |
118 | 126 | } else { |
119 | 127 | const { symlink } = await import("node:fs/promises"); |
120 | 128 | for (const name of ["bl", "bailian"] as const) { |
|
0 commit comments