49 lines
2.5 KiB
TypeScript
49 lines
2.5 KiB
TypeScript
import { promises as fs } from 'node:fs'
|
|
import { dirname, isAbsolute, relative, resolve, sep, join } from 'node:path'
|
|
import { randomUUID } from 'node:crypto'
|
|
import type { RevertChange } from '@shared/revert'
|
|
import { getRevertChanges } from '@shared/revert'
|
|
import type { ChatMessage } from '@shared/types'
|
|
|
|
async function replaceFile(path: string, content: string | null): Promise<void> {
|
|
if (content === null) { await fs.unlink(path); return }
|
|
const temporary = join(dirname(path), `.ycode-restore-${randomUUID()}.tmp`)
|
|
try { await fs.writeFile(temporary, content, 'utf8'); await fs.rename(temporary, path) }
|
|
finally { await fs.unlink(temporary).catch(() => undefined) }
|
|
}
|
|
/** Validate every current file before applying any change; roll back if a write fails. */
|
|
export async function restoreWorkspaceFiles(workspace: string, changes: RevertChange[]): Promise<() => Promise<void>> {
|
|
const root = await fs.realpath(workspace)
|
|
const validated: RevertChange[] = []
|
|
for (const change of changes) {
|
|
const path = await fs.realpath(resolve(change.path))
|
|
const rel = relative(root, path)
|
|
if (!rel || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) throw new Error('恢复文件不在当前工作区内')
|
|
if (await fs.readFile(path, 'utf8') !== change.after) throw new Error(`文件已被外部修改,未执行恢复:${rel}`)
|
|
validated.push({ ...change, path })
|
|
}
|
|
const applied: RevertChange[] = []
|
|
const undo = async (): Promise<void> => {
|
|
for (const change of [...applied].reverse()) await replaceFile(change.path, change.after)
|
|
}
|
|
try {
|
|
for (const change of validated) {
|
|
if (await fs.readFile(change.path, 'utf8') !== change.after) throw new Error('文件在恢复前已变化')
|
|
await replaceFile(change.path, change.before)
|
|
applied.push(change)
|
|
}
|
|
} catch (error) { await undo(); throw error }
|
|
return undo
|
|
}
|
|
|
|
/**
|
|
* 编辑提交前的产物回退(原版 userMessage.edit.hint:产物回退至此消息前版本)。
|
|
* 返回撤销函数;该消息之后没有可回退的写入时返回 undefined。
|
|
* 记录不完整时由 getRevertChanges 抛错,调用方应中止提交而不是静默保留文件修改。
|
|
*/
|
|
export async function rollbackEditedArtifacts(workspace: string, messages: ChatMessage[], userMessageId: string): Promise<(() => Promise<void>) | undefined> {
|
|
const changes = getRevertChanges(messages, userMessageId)
|
|
if (!changes.length) return undefined
|
|
return restoreWorkspaceFiles(workspace, changes)
|
|
}
|