Files
one-pipe-system/scripts/deploy-frontend.mjs

153 lines
4.6 KiB
JavaScript
Raw 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.
import { execFile } from 'node:child_process'
import { mkdtemp, rm } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { promisify } from 'node:util'
const execFileAsync = promisify(execFile)
export const REGISTRY = process.env.FRONTEND_REGISTRY ?? 'registry.boss160.cn'
export const IMAGE_NAME = process.env.FRONTEND_IMAGE ?? `${REGISTRY}/junhong/cmp-admin-web`
export const NODE_OPTIONS = process.env.FRONTEND_NODE_OPTIONS ?? '--max-old-space-size=1536'
const ZERO_SHA = '0'.repeat(40)
export function parseArguments(args) {
if (args.length === 1 && args[0] === '--pre-push') {
return { mode: 'pre-push' }
}
if (args[0] === '--branch' && args.length === 2 && args[1]) {
return { mode: 'branch', branch: args[1] }
}
if (args.length === 0) {
throw new Error('缺少参数:Hook 使用 --pre-push,手动构建使用 --branch <分支>')
}
throw new Error(`未知参数:${args.join(' ')}`)
}
export function parsePrePushLines(input) {
return input
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
const fields = line.split(/\s+/)
if (fields.length !== 4) {
throw new Error(`无法解析 pre-push 数据:${line}`)
}
const [localRef, localSha, remoteRef, remoteSha] = fields
return { localRef, localSha, remoteRef, remoteSha }
})
}
export function resolveImageTag(sha) {
if (!/^[0-9a-f]{40}$/i.test(sha)) {
throw new Error(`不是完整 commit SHA:${sha}`)
}
return sha.toLowerCase()
}
function branchFromRef(ref) {
if (!ref.startsWith('refs/heads/')) {
throw new Error(`只允许推送分支,收到:${ref}`)
}
const branch = ref.slice('refs/heads/'.length)
if (!/^[A-Za-z0-9._/-]+$/.test(branch) || branch.includes('..') || branch.startsWith('-')) {
throw new Error(`非法分支名:${branch}`)
}
return branch
}
async function run(command, args, options = {}) {
const display = [command, ...args].join(' ')
console.log(`$ ${display}`)
return execFileAsync(command, args, {
cwd: options.cwd,
env: options.env,
maxBuffer: 10 * 1024 * 1024
})
}
async function readStdin() {
const chunks = []
for await (const chunk of process.stdin) chunks.push(chunk)
return Buffer.concat(chunks).toString('utf8')
}
async function resolveBranchCommit(branch) {
branchFromRef(`refs/heads/${branch}`)
await run('git', [
'fetch',
'--quiet',
'origin',
`refs/heads/${branch}:refs/remotes/origin/${branch}`
])
const { stdout } = await run('git', ['rev-parse', `refs/remotes/origin/${branch}`])
return { branch, sha: resolveImageTag(stdout.trim()) }
}
async function buildAndPush({ branch, sha, repoRoot }) {
const imageTag = resolveImageTag(sha)
const tempDir = await mkdtemp(join(tmpdir(), 'one-pipe-frontend-'))
try {
await run('git', ['worktree', 'add', '--detach', tempDir, imageTag], { cwd: repoRoot })
await run(
'docker',
[
'buildx',
'build',
'--platform',
'linux/amd64',
'--build-arg',
`NODE_OPTIONS=${NODE_OPTIONS}`,
'--tag',
`${IMAGE_NAME}:${imageTag}`,
'--push',
tempDir
],
{ cwd: repoRoot }
)
console.log(`镜像已推送:${IMAGE_NAME}:${imageTag}`)
console.log(`分支:${branch},提交:${imageTag}`)
} finally {
await run('git', ['worktree', 'remove', '--force', tempDir], { cwd: repoRoot }).catch(() => {})
await rm(tempDir, { recursive: true, force: true })
}
}
async function repoRoot() {
const { stdout } = await run('git', ['rev-parse', '--show-toplevel'])
return resolve(stdout.trim())
}
async function main() {
const options = parseArguments(process.argv.slice(2))
const root = await repoRoot()
if (options.mode === 'pre-push') {
const refs = parsePrePushLines(await readStdin())
for (const ref of refs) {
if (ref.remoteSha === ZERO_SHA || ref.localSha === ZERO_SHA) continue
const branch = branchFromRef(ref.remoteRef)
await buildAndPush({ branch, sha: ref.localSha, repoRoot: root })
}
return
}
const target = await resolveBranchCommit(options.branch)
await buildAndPush({ ...target, repoRoot: root })
console.log('手动构建完成。要触发 Gitea 自动部署,请将该分支对应提交推送到远程。')
}
if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
main().catch((error) => {
console.error(`前端镜像部署失败:${error.message}`)
process.exitCode = 1
})
}