Files
codex-market-app/scripts/publish-codex-installer.js

127 lines
4.4 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// publish-codex-installer.js —— 发布 Codex 安装器资源到自建 gitea。
//
// 用法:
// node scripts/publish-codex-installer.js /Users/mac/Downloads/Codex-Windows-x64.msix
// node scripts/publish-codex-installer.js /Users/mac/Downloads/ChatGPT.dmg
// token 可设置 GIT_TOKEN,或写入 ~/.tokendun-git-token。
"use strict";
const fs = require("fs");
const os = require("os");
const path = require("path");
const crypto = require("crypto");
const { execFileSync, spawnSync } = require("child_process");
const OWNER = "mhgmh";
const REPO = "codex-market-app";
const TAG = "codex-installers";
const MSIX_NAME = "Codex-Windows-x64.msix";
const DMG_NAME = "ChatGPT.dmg";
const ALLOWED_NAMES = new Set([MSIX_NAME, DMG_NAME]);
const API = `https://up.tokendun.cc/api/v1/repos/${OWNER}/${REPO}`;
const DL = `https://update.tokendun.cc/${OWNER}/${REPO}/releases/download/${TAG}`;
const installerPath = path.resolve(process.argv[2] || "/Users/mac/Downloads/Codex-Windows-x64.msix");
const installerName = path.basename(installerPath);
if (!ALLOWED_NAMES.has(installerName)) {
console.error(`安装包文件名必须是 ${Array.from(ALLOWED_NAMES).join(" 或 ")},当前是 ${installerName}`);
process.exit(1);
}
if (!fs.existsSync(installerPath)) {
console.error(`安装包不存在:${installerPath}`);
process.exit(1);
}
let token = process.env.GIT_TOKEN || "";
if (!token) {
try { token = fs.readFileSync(path.join(os.homedir(), ".tokendun-git-token"), "utf8").trim(); } catch {}
}
if (!token) {
console.error("缺少 gitea token:设置 GIT_TOKEN 或写入 ~/.tokendun-git-token");
process.exit(1);
}
function hashFile(file) {
return new Promise((resolve, reject) => {
const h = crypto.createHash("sha256");
fs.createReadStream(file)
.on("data", (chunk) => h.update(chunk))
.on("error", reject)
.on("end", () => resolve(h.digest("hex")));
});
}
function api(method, urlPath, body) {
const args = ["-s", "-X", method, "-H", `Authorization: token ${token}`, `${API}${urlPath}`];
if (body !== undefined) {
args.push("-H", "Content-Type: application/json", "-d", JSON.stringify(body));
}
return execFileSync("curl", args, { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
}
function apiJson(method, urlPath, body) {
const out = api(method, urlPath, body);
try { return JSON.parse(out); } catch { return null; }
}
function upload(rel, file, name) {
const result = spawnSync("curl", [
"--progress-bar",
"-o", "/dev/null",
"-w", "%{http_code}",
"-X", "POST",
"-H", `Authorization: token ${token}`,
"-F", `attachment=@${file};type=application/octet-stream`,
`${API}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`,
], { encoding: "utf8", stdio: ["ignore", "pipe", "inherit"], maxBuffer: 64 * 1024 * 1024 });
if (result.error) throw result.error;
const code = String(result.stdout || "").trim();
console.log(`上传 ${name} → HTTP ${code}`);
if (code !== "201") {
console.error(`上传失败:${name}`);
process.exit(1);
}
}
(async () => {
const stat = fs.statSync(installerPath);
console.log(`准备发布 ${installerName}:${(stat.size / 1048576).toFixed(2)}MB`);
const sha256 = await hashFile(installerPath);
const shaPath = path.join(os.tmpdir(), `${installerName}.sha256`);
fs.writeFileSync(shaPath, `${sha256} ${installerName}\n`);
console.log(`sha256=${sha256}`);
let rel = apiJson("GET", `/releases/tags/${encodeURIComponent(TAG)}`);
if (!rel || !rel.id) {
console.log(`创建 release ${TAG} …`);
rel = apiJson("POST", "/releases", {
tag_name: TAG,
target_commitish: "main",
name: "Codex 安装器资源",
body: "TD工具箱内置安装器使用的远程安装包资源。",
draft: false,
prerelease: false,
});
}
if (!rel || !rel.id) {
console.error(`无法创建/获取 release ${TAG}`);
process.exit(1);
}
const existing = new Map((rel.assets || []).map((a) => [a.name, a.id]));
for (const name of [installerName, `${installerName}.sha256`]) {
if (existing.has(name)) {
api("DELETE", `/releases/${rel.id}/assets/${existing.get(name)}`);
console.log(`删除旧 asset: ${name}`);
}
}
upload(rel, installerPath, installerName);
upload(rel, shaPath, `${installerName}.sha256`);
console.log("\n发布完成:");
console.log(` ${DL}/${installerName}`);
console.log(` ${DL}/${installerName}.sha256`);
})();