chore: release script in repo + AGENTS.md dev/release docs
This commit is contained in:
1 parent
8cb193fa6a
commit
dc3d54fa07
3 files changed
+157
No files matched your search
@@ -32,3 +32,6 @@ release/
|
||||
# stray local test artifacts
|
||||
.min-test.bin
|
||||
.min2-test.bin
|
||||
|
||||
# release notes draft (consumed by scripts/release.cjs)
|
||||
RELEASE_NOTES.md
|
||||
@@ -0,0 +1,37 @@
|
||||
# OpenTerminal 开发备忘
|
||||
|
||||
Electron + electron-vite + React 终端工具(本地终端 / SSH / SFTP)。
|
||||
|
||||
## 常用命令
|
||||
|
||||
- 开发:`npm run dev`(主进程改动不热重建,需重启)
|
||||
- 类型检查:`npx tsc --noEmit -p tsconfig.web.json`
|
||||
- 打包:`npm run dist`,产物在 `release/`(msi + exe + latest.yml + blockmap)
|
||||
- 国内网络需镜像:`ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/ ELECTRON_BUILDER_BINARIES_MIRROR=https://npmmirror.com/mirrors/electron-builder-binaries/ npm run dist`
|
||||
|
||||
## 仓库与远程
|
||||
|
||||
- `origin` = Gitea `https://git.codingplan.site/admin/OpenTerminal.git`(主仓库 + 更新通道)
|
||||
- `github` = `https://github.com/billowliu2/OpenTerminal.git`(镜像 + 备用更新源)
|
||||
- ⚠️ Gitea 的 upload-pack 有故障,`git fetch origin` 会报 `bad pack header`;查询远端状态用 API 或 `git ls-remote`。推送正常。
|
||||
- 2026-09:远端 main 旧历史(M3–M8,至 v0.8.0 d3a628f)被 force-push 覆盖为当前历史,旧提交仍由 tag v0.6.0 / v0.7.0 / v0.8.0 保留。
|
||||
|
||||
## 凭据
|
||||
|
||||
- `.env`(已 gitignore):`GIT_TOKEN`(Gitea)、`GH_TOKEN`(GitHub)等。git 推送通过 credential helper 使用该令牌。
|
||||
|
||||
## 发布新版本
|
||||
|
||||
1. `package.json` 版本号 +1,写 `RELEASE_NOTES.md`(仓库根,已 gitignore)
|
||||
2. `npm run dist` 构建
|
||||
3. `node scripts/release.cjs <版本号>`,例如 `node scripts/release.cjs 1.0.2`
|
||||
- 依次完成:Gitea release(含 msi/exe 资产)→ Gitea 更新通道(`api/packages/admin/generic/openterminal-update/stable`,更新 latest.yml/blockmap/exe)→ GitHub release
|
||||
- 可用 `--skip-github` / `--skip-gitea` 跳过某步
|
||||
- 访问 GitHub API 不通时,设 `HTTPS_PROXY=http://127.0.0.1:7897` 再走代理
|
||||
4. 验证更新通道:`curl https://git.codingplan.site/api/packages/admin/generic/openterminal-update/stable/latest.yml` 应返回新版本号
|
||||
5. `git tag v<版本号>` 并推送两个远程
|
||||
|
||||
## 主题机制
|
||||
|
||||
- 终端主题由 xterm 主题派生 UI 配色:`src/renderer/src/theme/chrome.ts` 的 `applyChromeTheme` 写入 `--chrome-bg/-bg-deep/-border/-hover` CSS 变量,antd token 在 `main.tsx` ThemedConfigProvider 派生
|
||||
- 标签强调色:`settings.tabAccentColor`(默认 `#3fb950`),经 `--tab-accent` CSS 变量生效
|
||||
@@ -0,0 +1,117 @@
|
||||
// OpenTerminal release publisher
|
||||
// Usage: node scripts/release.cjs <version> [--skip-github] [--skip-gitea]
|
||||
// node scripts/release.cjs 1.0.2
|
||||
// Reads release notes from RELEASE_NOTES.md (repo root, gitignored).
|
||||
// Credentials from .env (repo root, gitignored): GIT_TOKEN, GH_TOKEN.
|
||||
// If HTTPS_PROXY / HTTP_PROXY (env or .env) is set, traffic goes through it
|
||||
// (needed for api.github.com / uploads.github.com in some networks).
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const ROOT = path.join(__dirname, '..')
|
||||
const V = process.argv[2]
|
||||
if (!V || !/^\d+\.\d+\.\d+$/.test(V)) {
|
||||
console.error('usage: node scripts/release.cjs <x.y.z> [--skip-github] [--skip-gitea]')
|
||||
process.exit(1)
|
||||
}
|
||||
const SKIP_GH = process.argv.includes('--skip-github')
|
||||
const SKIP_GITEA = process.argv.includes('--skip-gitea')
|
||||
|
||||
const env = Object.fromEntries(
|
||||
fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')
|
||||
.filter((l) => l.includes('=') && !l.startsWith('#'))
|
||||
.map((l) => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()] })
|
||||
)
|
||||
const notes = fs.readFileSync(path.join(ROOT, 'RELEASE_NOTES.md'), 'utf8')
|
||||
const R = path.join(ROOT, 'release')
|
||||
const files = [
|
||||
path.join(R, `OpenTerminal-${V}-setup.msi`),
|
||||
path.join(R, `OpenTerminal-${V}-setup.exe`)
|
||||
]
|
||||
for (const f of files) {
|
||||
if (!fs.existsSync(f)) { console.error('missing build artifact:', f); process.exit(1) }
|
||||
}
|
||||
|
||||
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || env.HTTPS_PROXY || env.PROXY
|
||||
if (proxy) {
|
||||
const { ProxyAgent, setGlobalDispatcher } = require('undici')
|
||||
setGlobalDispatcher(new ProxyAgent(proxy))
|
||||
console.log('using proxy:', proxy)
|
||||
}
|
||||
|
||||
async function upload(url, file, token, authScheme) {
|
||||
const name = path.basename(file)
|
||||
const stat = fs.statSync(file)
|
||||
const resp = await fetch(`${url}${url.includes('?') ? '&' : '?'}name=${encodeURIComponent(name)}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `${authScheme} ${token}`,
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Content-Length': String(stat.size)
|
||||
},
|
||||
body: fs.createReadStream(file),
|
||||
duplex: 'half'
|
||||
})
|
||||
console.log(` upload ${name}: HTTP ${resp.status}`)
|
||||
if (!resp.ok) console.log(' ', (await resp.text()).slice(0, 300))
|
||||
}
|
||||
|
||||
async function gitea() {
|
||||
console.log('=== Gitea release ===')
|
||||
const base = 'https://git.codingplan.site/api/v1/repos/admin/OpenTerminal'
|
||||
const resp = await fetch(`${base}/releases`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `token ${env.GIT_TOKEN}`, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tag_name: `v${V}`, name: `OpenTerminal v${V}`, body: notes, draft: false, prerelease: false })
|
||||
})
|
||||
if (!resp.ok) { console.log('create failed:', resp.status, (await resp.text()).slice(0, 300)); return }
|
||||
const rel = await resp.json()
|
||||
console.log('release created, id =', rel.id)
|
||||
for (const f of files) await upload(`${base}/releases/${rel.id}/assets`, f, env.GIT_TOKEN, 'token')
|
||||
}
|
||||
|
||||
async function giteaChannel() {
|
||||
console.log('=== Gitea update channel ===')
|
||||
const base = 'https://git.codingplan.site/api/packages/admin/generic/openterminal-update/stable'
|
||||
const del = await fetch(base, { method: 'DELETE', headers: { Authorization: `token ${env.GIT_TOKEN}` } })
|
||||
console.log(' delete old stable:', del.status)
|
||||
for (const f of [path.join(R, 'latest.yml'), path.join(R, `OpenTerminal-${V}-setup.exe.blockmap`), path.join(R, `OpenTerminal-${V}-setup.exe`)]) {
|
||||
const name = path.basename(f)
|
||||
const stat = fs.statSync(f)
|
||||
const resp = await fetch(`${base}/${encodeURIComponent(name)}`, {
|
||||
method: 'PUT',
|
||||
headers: { Authorization: `token ${env.GIT_TOKEN}`, 'Content-Type': 'application/octet-stream', 'Content-Length': String(stat.size) },
|
||||
body: fs.createReadStream(f),
|
||||
duplex: 'half'
|
||||
})
|
||||
console.log(` put ${name}: HTTP ${resp.status}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function github() {
|
||||
console.log('=== GitHub release ===')
|
||||
const resp = await fetch('https://api.github.com/repos/billowliu2/OpenTerminal/releases', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.GH_TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'OpenTerminal',
|
||||
Accept: 'application/vnd.github+json'
|
||||
},
|
||||
body: JSON.stringify({ tag_name: `v${V}`, name: `OpenTerminal v${V}`, body: notes, draft: false, prerelease: false })
|
||||
})
|
||||
if (!resp.ok) { console.log('create failed:', resp.status, (await resp.text()).slice(0, 300)); return }
|
||||
const rel = await resp.json()
|
||||
console.log('release created, id =', rel.id)
|
||||
const up = `https://uploads.github.com/repos/billowliu2/OpenTerminal/releases/${rel.id}/assets`
|
||||
for (const f of [...files, path.join(R, `OpenTerminal-${V}-setup.exe.blockmap`), path.join(R, 'latest.yml')]) {
|
||||
await upload(up, f, env.GH_TOKEN, 'Bearer')
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!SKIP_GITEA) { await gitea(); await giteaChannel() }
|
||||
if (!SKIP_GH) await github()
|
||||
console.log('done')
|
||||
}
|
||||
main().catch((e) => { console.error(e); process.exit(1) })
|
||||
Reference in new issue
Block a user