删除 Gitea Actions、Tag/Main 自动流水线和旧 Runner 配置,取消 Git 操作与发布授权的绑定。\n\n新增本地镜像发布、固定生产部署助手、digest manifest、迁移安全检查、simulation 冒烟及显式回滚流程。\n\n验证:pnpm lint、pnpm test、pnpm build、Go 全量测试、ShellCheck、Compose 配置、人工发布测试和 linux/amd64 完整栈冒烟。
43 lines
1.4 KiB
JavaScript
Executable File
43 lines
1.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import { execFileSync, spawnSync } from 'node:child_process'
|
|
|
|
function fail(message) {
|
|
console.error(`release_preflight=FAIL ${message}`)
|
|
process.exit(1)
|
|
}
|
|
|
|
function git(args) {
|
|
try {
|
|
return execFileSync('git', args, {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim()
|
|
} catch (error) {
|
|
const stderr = error.stderr?.toString().trim()
|
|
fail(stderr ? `git_error=${JSON.stringify(stderr)}` : 'git command failed')
|
|
}
|
|
}
|
|
|
|
const root = git(['rev-parse', '--show-toplevel'])
|
|
process.chdir(root)
|
|
|
|
for (const args of [['diff', '--quiet'], ['diff', '--cached', '--quiet']]) {
|
|
if (spawnSync('git', args, { stdio: 'ignore' }).status !== 0) {
|
|
fail('the worktree must be clean')
|
|
}
|
|
}
|
|
if (git(['ls-files', '--others', '--exclude-standard'])) {
|
|
fail('the worktree must not contain untracked files')
|
|
}
|
|
|
|
const sourceSha = git(['rev-parse', '--verify', 'HEAD^{commit}'])
|
|
if (!/^[0-9a-f]{40}$/.test(sourceSha)) fail('HEAD is not a full Git commit')
|
|
const mainSha = git(['rev-parse', '--verify', 'refs/remotes/origin/main^{commit}'])
|
|
if (!/^[0-9a-f]{40}$/.test(mainSha)) fail('origin/main is unavailable')
|
|
if (spawnSync('git', ['merge-base', '--is-ancestor', sourceSha, mainSha], { stdio: 'ignore' }).status !== 0) {
|
|
fail('HEAD is not part of origin/main history')
|
|
}
|
|
|
|
console.log(JSON.stringify({ sourceSha, originMainSha: mainSha }))
|