✨ feat(vscode): 移交时显示步骤,会话包超限先停
This commit is contained in:
1 parent
9220bad599
commit
162134dcc0
6 files changed
+412
-32
No files matched your search
@@ -1,8 +1,37 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import { execFile, spawn } from 'node:child_process'
|
||||
import { promises as fsp, statSync } from 'node:fs'
|
||||
import * as path from 'node:path'
|
||||
|
||||
function tar(args: string[]): Promise<void> {
|
||||
/** Gitea 工单附件上限。压缩后的 tgz 超过这个值,服务器会拒收。 */
|
||||
export const HANDOFF_ATTACHMENT_MAX_BYTES = 100 * 1024 * 1024
|
||||
|
||||
export function formatByteSize(bytes: number): string {
|
||||
const mb = bytes / (1024 * 1024)
|
||||
if (mb >= 10)
|
||||
return `${Math.round(mb)}MB`
|
||||
if (mb >= 1)
|
||||
return `${mb.toFixed(1)}MB`
|
||||
const kb = bytes / 1024
|
||||
if (kb >= 10)
|
||||
return `${Math.round(kb)}KB`
|
||||
if (kb >= 1)
|
||||
return `${kb.toFixed(1)}KB`
|
||||
return `${bytes}B`
|
||||
}
|
||||
|
||||
export class HandoffArchiveTooLargeError extends Error {
|
||||
readonly packedBytes: number
|
||||
readonly limitBytes: number
|
||||
|
||||
constructor(packedBytes: number, limitBytes: number) {
|
||||
super(`压缩后超过 Gitea 附件上限 ${formatByteSize(limitBytes)}`)
|
||||
this.name = 'HandoffArchiveTooLargeError'
|
||||
this.packedBytes = packedBytes
|
||||
this.limitBytes = limitBytes
|
||||
}
|
||||
}
|
||||
|
||||
function tarUnpack(args: string[]): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile('tar', args, { timeout: 120_000 }, (err, _stdout, stderr) => {
|
||||
if (err) {
|
||||
@@ -14,12 +43,74 @@ function tar(args: string[]): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
export async function packHandoff(stagingDir: string, outFile: string): Promise<void> {
|
||||
export async function packHandoff(
|
||||
stagingDir: string,
|
||||
outFile: string,
|
||||
opts?: { maxBytes?: number, onProgress?: (packedBytes: number) => void },
|
||||
): Promise<void> {
|
||||
const maxBytes = opts?.maxBytes ?? HANDOFF_ATTACHMENT_MAX_BYTES
|
||||
await fsp.mkdir(path.dirname(outFile), { recursive: true })
|
||||
await tar(['-czf', outFile, '-C', stagingDir, '.'])
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const child = spawn('tar', ['-czf', outFile, '-C', stagingDir, '.'], { stdio: ['ignore', 'ignore', 'pipe'] })
|
||||
const decode = new TextDecoder()
|
||||
let stderr = ''
|
||||
let settled = false
|
||||
let poll: ReturnType<typeof setInterval> | undefined
|
||||
child.stderr?.on('data', (chunk: Uint8Array) => {
|
||||
stderr += decode.decode(chunk, { stream: true })
|
||||
})
|
||||
const finish = (err?: Error): void => {
|
||||
if (settled)
|
||||
return
|
||||
settled = true
|
||||
if (poll)
|
||||
clearInterval(poll)
|
||||
if (err)
|
||||
reject(err)
|
||||
else
|
||||
resolve()
|
||||
}
|
||||
const checkSize = (): void => {
|
||||
let size = 0
|
||||
try {
|
||||
size = statSync(outFile).size
|
||||
}
|
||||
catch {
|
||||
return
|
||||
}
|
||||
opts?.onProgress?.(size)
|
||||
// 产出一超过上限就杀掉。等 tar 自己打完再失败,大会话会卡很久。
|
||||
if (size > maxBytes) {
|
||||
child.kill('SIGKILL')
|
||||
finish(new HandoffArchiveTooLargeError(size, maxBytes))
|
||||
}
|
||||
}
|
||||
poll = setInterval(checkSize, 200)
|
||||
child.on('error', err => finish(err))
|
||||
child.on('close', (code) => {
|
||||
if (settled)
|
||||
return
|
||||
let size = 0
|
||||
try {
|
||||
size = statSync(outFile).size
|
||||
}
|
||||
catch {
|
||||
// 包还没写出来
|
||||
}
|
||||
opts?.onProgress?.(size)
|
||||
if (size > maxBytes) {
|
||||
finish(new HandoffArchiveTooLargeError(size, maxBytes))
|
||||
return
|
||||
}
|
||||
if (code === 0)
|
||||
finish()
|
||||
else
|
||||
finish(new Error(`tar -czf 失败: ${stderr.trim() || `exit ${code}`}`))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function unpackHandoff(archive: string, dstDir: string): Promise<void> {
|
||||
await fsp.mkdir(dstDir, { recursive: true })
|
||||
await tar(['-xzf', archive, '-C', dstDir])
|
||||
await tarUnpack(['-xzf', archive, '-C', dstDir])
|
||||
}
|
||||
Reference in new issue
Block a user