|
| 1 | +import { readFileSync, readdirSync, writeFileSync } from "node:fs"; |
| 2 | +import { join } from "node:path"; |
| 3 | +import { pathToFileURL } from "node:url"; |
| 4 | + |
| 5 | +export const DOWNLOADS_START = "<!-- codex-zh-downloads:start -->"; |
| 6 | +export const DOWNLOADS_END = "<!-- codex-zh-downloads:end -->"; |
| 7 | + |
| 8 | +function parseArgs(argv) { |
| 9 | + const args = {}; |
| 10 | + for (let index = 0; index < argv.length; index += 1) { |
| 11 | + const name = argv[index]; |
| 12 | + if (!name.startsWith("--")) { |
| 13 | + throw new Error(`Unexpected argument: ${name}`); |
| 14 | + } |
| 15 | + const key = name.slice(2); |
| 16 | + const value = argv[index + 1]; |
| 17 | + if (!value || value.startsWith("--")) { |
| 18 | + throw new Error(`Missing value for --${key}`); |
| 19 | + } |
| 20 | + args[key] = value; |
| 21 | + index += 1; |
| 22 | + } |
| 23 | + return args; |
| 24 | +} |
| 25 | + |
| 26 | +function requireValue(value, name) { |
| 27 | + if (!value) { |
| 28 | + throw new Error(`Missing required ${name}.`); |
| 29 | + } |
| 30 | + return value; |
| 31 | +} |
| 32 | + |
| 33 | +function normalizeVersion(versionOrTag) { |
| 34 | + return requireValue(versionOrTag, "version").replace(/^v/u, ""); |
| 35 | +} |
| 36 | + |
| 37 | +export function buildAssetDownloadUrl({ repo, tag, assetName }) { |
| 38 | + requireValue(repo, "repo"); |
| 39 | + requireValue(tag, "tag"); |
| 40 | + requireValue(assetName, "assetName"); |
| 41 | + return `https://github.com/${repo}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(assetName)}`; |
| 42 | +} |
| 43 | + |
| 44 | +export function findReleaseAssets(outputDir) { |
| 45 | + const files = readdirSync(outputDir, { withFileTypes: true }) |
| 46 | + .filter((entry) => entry.isFile()) |
| 47 | + .map((entry) => entry.name) |
| 48 | + .sort(); |
| 49 | + const installerName = files.find((name) => name.endsWith(".exe")); |
| 50 | + if (!installerName) { |
| 51 | + throw new Error(`Could not find a Windows installer exe in ${outputDir}.`); |
| 52 | + } |
| 53 | + |
| 54 | + const expectedShaName = `${installerName}.sha256`; |
| 55 | + const sha256Name = files.includes(expectedShaName) |
| 56 | + ? expectedShaName |
| 57 | + : files.find((name) => name.endsWith(".sha256")); |
| 58 | + if (!sha256Name) { |
| 59 | + throw new Error(`Could not find a sha256 file in ${outputDir}.`); |
| 60 | + } |
| 61 | + |
| 62 | + const sha256Text = readFileSync(join(outputDir, sha256Name), "utf8"); |
| 63 | + const sha256 = sha256Text.match(/\b[a-f0-9]{64}\b/iu)?.[0].toLowerCase(); |
| 64 | + if (!sha256) { |
| 65 | + throw new Error(`Could not find a SHA-256 hash in ${join(outputDir, sha256Name)}.`); |
| 66 | + } |
| 67 | + |
| 68 | + return { installerName, sha256Name, sha256 }; |
| 69 | +} |
| 70 | + |
| 71 | +export function generateDownloadBlock({ repo, tag, version, installerName, sha256Name, sha256 }) { |
| 72 | + const cleanVersion = normalizeVersion(version); |
| 73 | + const installerUrl = buildAssetDownloadUrl({ repo, tag, assetName: installerName }); |
| 74 | + const sha256Url = buildAssetDownloadUrl({ repo, tag, assetName: sha256Name }); |
| 75 | + |
| 76 | + return `${DOWNLOADS_START} |
| 77 | +当前最新版:v${cleanVersion} |
| 78 | +
|
| 79 | +| 你的系统 | 下载哪个版本 | |
| 80 | +| --- | --- | |
| 81 | +| Windows 10 / Windows 11(64 位) | [下载 Codex-ZH ${cleanVersion} Windows x64 安装包](${installerUrl}) | |
| 82 | +| macOS | 暂不提供 Codex-ZH 安装包,不要下载 Windows 版 | |
| 83 | +| Linux | 暂不提供 Codex-ZH 安装包,不要下载 Windows 版 | |
| 84 | +
|
| 85 | +普通用户只需要下载上面的 \`.exe\` 文件。不要下载 GitHub 页面里的 \`Source code\`,那是源码,不是安装包。 |
| 86 | +
|
| 87 | +校验文件:[\`${sha256Name}\`](${sha256Url}) |
| 88 | +SHA256:\`${sha256}\` |
| 89 | +${DOWNLOADS_END}`; |
| 90 | +} |
| 91 | + |
| 92 | +export function updateDownloadsSection(readme, block) { |
| 93 | + const markedSection = new RegExp(`${DOWNLOADS_START}[\\s\\S]*?${DOWNLOADS_END}`, "u"); |
| 94 | + if (markedSection.test(readme)) { |
| 95 | + return readme.replace(markedSection, block); |
| 96 | + } |
| 97 | + |
| 98 | + const headingMatch = readme.match(/^## 下载\s*$/mu); |
| 99 | + if (!headingMatch || headingMatch.index === undefined) { |
| 100 | + throw new Error("Could not find README section: ## 下载"); |
| 101 | + } |
| 102 | + |
| 103 | + const sectionStart = headingMatch.index + headingMatch[0].length; |
| 104 | + const nextHeadingMatch = readme.slice(sectionStart).match(/\n## /u); |
| 105 | + if (!nextHeadingMatch || nextHeadingMatch.index === undefined) { |
| 106 | + throw new Error("Could not find the section after README downloads."); |
| 107 | + } |
| 108 | + |
| 109 | + const sectionEnd = sectionStart + nextHeadingMatch.index; |
| 110 | + return `${readme.slice(0, sectionStart)}\n\n${block}\n${readme.slice(sectionEnd)}`; |
| 111 | +} |
| 112 | + |
| 113 | +export function updateReadmeDownloads({ |
| 114 | + readmePath = "README.md", |
| 115 | + outputDir, |
| 116 | + repo, |
| 117 | + tag, |
| 118 | + version, |
| 119 | +}) { |
| 120 | + const assets = findReleaseAssets(requireValue(outputDir, "outputDir")); |
| 121 | + const block = generateDownloadBlock({ |
| 122 | + repo: requireValue(repo, "repo"), |
| 123 | + tag: requireValue(tag, "tag"), |
| 124 | + version: requireValue(version, "version"), |
| 125 | + ...assets, |
| 126 | + }); |
| 127 | + const original = readFileSync(readmePath, "utf8"); |
| 128 | + const updated = updateDownloadsSection(original, block); |
| 129 | + if (updated !== original) { |
| 130 | + writeFileSync(readmePath, updated, "utf8"); |
| 131 | + return true; |
| 132 | + } |
| 133 | + return false; |
| 134 | +} |
| 135 | + |
| 136 | +if (import.meta.url === pathToFileURL(process.argv[1]).href) { |
| 137 | + const args = parseArgs(process.argv.slice(2)); |
| 138 | + const changed = updateReadmeDownloads({ |
| 139 | + readmePath: args.readme ?? "README.md", |
| 140 | + outputDir: args["release-output-dir"] ?? process.env.RELEASE_OUTPUT_DIR, |
| 141 | + repo: args.repo ?? process.env.GITHUB_REPOSITORY, |
| 142 | + tag: args.tag ?? process.env.RELEASE_TAG ?? process.env.GITHUB_REF_NAME, |
| 143 | + version: args.version ?? process.env.CODEX_ZH_VERSION ?? process.env.RELEASE_TAG ?? process.env.GITHUB_REF_NAME, |
| 144 | + }); |
| 145 | + console.log(changed ? "README downloads updated." : "README downloads already up to date."); |
| 146 | +} |
0 commit comments