Merge master and repair CI for bounded diff basis
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
/**
|
||||
* Cordis-free local filesystem mechanics. This provider layer returns validated UTF-8 text,
|
||||
* streams large files, and rejects binary data; line windows belong to `dsh-tool-fs`. Writes
|
||||
* stage an exclusive owner-only file in a private sibling directory and atomically rename it.
|
||||
* stage an exclusive owner-only file in a private sibling directory and atomically publish it.
|
||||
* @module @deepseek-ai/dsh-fs-local/fsio
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { chmod, lstat, mkdir, open, readFile, realpath, readdir, rename, rm, stat } from 'node:fs/promises'
|
||||
import { chmod, link, lstat, mkdir, open, readFile, realpath, readdir, rename, rm, stat } from 'node:fs/promises'
|
||||
import type { BigIntStats, Dirent, Stats } from 'node:fs'
|
||||
import { basename, dirname, join, resolve } from 'node:path'
|
||||
import { TextDecoder } from 'node:util'
|
||||
@@ -22,6 +22,10 @@ function isENOENT(error: unknown): boolean {
|
||||
return error instanceof Error && 'code' in error && error.code === 'ENOENT'
|
||||
}
|
||||
|
||||
function isEEXIST(error: unknown): boolean {
|
||||
return error instanceof Error && 'code' in error && error.code === 'EEXIST'
|
||||
}
|
||||
|
||||
/**
|
||||
* A path component that is expected to be a directory is a regular file (e.g.
|
||||
* resolving `afile/child.txt` when `afile` is a file). Like `ENOENT`, the target
|
||||
@@ -72,7 +76,7 @@ function versionOf(info: BigIntStats): FsVersion {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test seam: lets specs pin the atomic-write temp names (to prove exclusive-open behavior without
|
||||
* Test hook: lets specs pin the atomic-write temp names (to prove exclusive-open behavior without
|
||||
* a name race), override native boundaries, and observe the staged temp file before publication.
|
||||
*/
|
||||
export interface FsIoInternals {
|
||||
@@ -86,7 +90,13 @@ export interface FsIoInternals {
|
||||
copyFileDacl?: (source: string, destination: string) => Promise<void>
|
||||
/** Override the Win32 security-preserving replacement boundary. */
|
||||
replaceFile?: (replaced: string, replacement: string) => Promise<void>
|
||||
/** Test hook after the temp file is written/synced but before final chmod+rename. */
|
||||
/** Override the hard-link no-replace publication boundary. */
|
||||
linkFile?: (existingPath: string, newPath: string) => Promise<void>
|
||||
/** Override target inspection after guarded publication fails. */
|
||||
inspectPublicationTarget?: (path: string) => Promise<BigIntStats>
|
||||
/** Override staging-directory removal for commit-point failure coverage. */
|
||||
removeStagingDir?: (stagingDir: string) => Promise<void>
|
||||
/** Test hook after the temp file is written/synced but before final chmod+publication. */
|
||||
inspectTemp?: (paths: { stagingDir: string; tempPath: string }) => void | Promise<void>
|
||||
}
|
||||
|
||||
@@ -408,9 +418,13 @@ export async function* streamWholeText(target: LocalTarget, signal?: AbortSignal
|
||||
|
||||
// --- Writing ---
|
||||
|
||||
async function removeStagingDirOrThrow(stagingDir: string, originalError: unknown): Promise<never> {
|
||||
async function removeStagingDirOrThrow(
|
||||
stagingDir: string,
|
||||
originalError: unknown,
|
||||
removeStagingDir: (path: string) => Promise<void>,
|
||||
): Promise<never> {
|
||||
try {
|
||||
await rm(stagingDir, { recursive: true, force: true })
|
||||
await removeStagingDir(stagingDir)
|
||||
} catch (cleanupError: unknown) {
|
||||
/* v8 ignore next 1 -- cleanup failure here needs a second filesystem fault after the primary write failure. */
|
||||
throw new FsError(`write failed (${errorMessage(originalError)}) and temp cleanup failed (${errorMessage(cleanupError)})`, 'FS_NOT_FOUND', { cause: originalError })
|
||||
@@ -418,6 +432,43 @@ async function removeStagingDirOrThrow(stagingDir: string, originalError: unknow
|
||||
throw originalError
|
||||
}
|
||||
|
||||
async function throwGuardedCreateFailure(
|
||||
error: unknown,
|
||||
absolutePath: string,
|
||||
displayPath: string,
|
||||
inspectPublicationTarget: (path: string) => Promise<BigIntStats>,
|
||||
): Promise<never> {
|
||||
let existing: BigIntStats | undefined
|
||||
try {
|
||||
existing = await inspectPublicationTarget(absolutePath)
|
||||
} catch (metadataError: unknown) {
|
||||
if (!isENOENT(metadataError) && !isENOTDIR(metadataError)) {
|
||||
throw new FsError(`cannot write "${displayPath}": ${errorMessage(metadataError)}`, 'FS_IO_ERROR', { cause: metadataError })
|
||||
}
|
||||
}
|
||||
|
||||
// Link errno values vary by platform and filesystem. Inspect the target entry
|
||||
// after failure so a collision is not confused with missing hard-link support.
|
||||
if (existing !== undefined) {
|
||||
if (!existing.isFile()) {
|
||||
throw new FsError(`cannot write "${displayPath}": not a regular file`, 'FS_NOT_REGULAR_FILE', { cause: error })
|
||||
}
|
||||
throw new FsError(
|
||||
`cannot overwrite existing "${displayPath}" without reading it first`,
|
||||
'FS_NOT_OBSERVED',
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
if (isEEXIST(error)) {
|
||||
throw new FsError(
|
||||
`cannot overwrite existing "${displayPath}" without reading it first`,
|
||||
'FS_NOT_OBSERVED',
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
throw new FsError(`cannot write "${displayPath}": ${errorMessage(error)}`, 'FS_IO_ERROR', { cause: error })
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically replace a file through a private, synced staging file in the same directory.
|
||||
* POSIX protects the staging directory and file with `0o700` and `0o600`. A new Windows file
|
||||
@@ -427,8 +478,11 @@ async function removeStagingDirOrThrow(stagingDir: string, originalError: unknow
|
||||
* @param content - the full UTF-8 text to write.
|
||||
* @param mode - existing destination's POSIX mode to preserve, or `undefined` for a new file;
|
||||
* inert as a mode on Windows but identifies replacement security semantics.
|
||||
* @param signal - cancellation checked before the final rename.
|
||||
* @param internals - test seam for pinning temp names and observing the staged file.
|
||||
* @param signal - cancellation checked before final publication.
|
||||
* @param internals - Test hook for pinning temp names and observing the staged file.
|
||||
* @param createIfAbsent - when provided, publish with a hard-link no-replace
|
||||
* primitive; a concurrent creator's file is preserved and this write is
|
||||
* rejected with `FS_NOT_OBSERVED` using the supplied display path.
|
||||
*/
|
||||
export async function writeFileAtomic(
|
||||
absolutePath: string,
|
||||
@@ -436,6 +490,7 @@ export async function writeFileAtomic(
|
||||
mode: number | undefined,
|
||||
signal: AbortSignal | undefined,
|
||||
internals: FsIoInternals = {},
|
||||
createIfAbsent?: { displayPath: string },
|
||||
): Promise<void> {
|
||||
throwIfAborted(signal, 'write')
|
||||
const directory = dirname(absolutePath)
|
||||
@@ -449,6 +504,11 @@ export async function writeFileAtomic(
|
||||
const platform = internals.platform ?? process.platform
|
||||
const copyFileDacl = internals.copyFileDacl ?? copyFileDaclWin32
|
||||
const replaceFile = internals.replaceFile ?? replaceFileWin32
|
||||
const linkFile = internals.linkFile ?? link
|
||||
const inspectPublicationTarget = internals.inspectPublicationTarget
|
||||
?? (path => lstat(path, { bigint: true }))
|
||||
const removeStagingDir = internals.removeStagingDir
|
||||
?? (path => rm(path, { recursive: true, force: true }))
|
||||
let handle: Awaited<ReturnType<typeof open>> | undefined
|
||||
let stagingCreated = false
|
||||
try {
|
||||
@@ -469,7 +529,13 @@ export async function writeFileAtomic(
|
||||
handle = undefined
|
||||
|
||||
throwIfAborted(signal, 'write')
|
||||
if (platform === 'win32' && mode !== undefined) {
|
||||
if (createIfAbsent !== undefined) {
|
||||
try {
|
||||
await linkFile(tempPath, absolutePath)
|
||||
} catch (error: unknown) {
|
||||
await throwGuardedCreateFailure(error, absolutePath, createIfAbsent.displayPath, inspectPublicationTarget)
|
||||
}
|
||||
} else if (platform === 'win32' && mode !== undefined) {
|
||||
try {
|
||||
await replaceFile(absolutePath, tempPath)
|
||||
} catch (error: unknown) {
|
||||
@@ -481,7 +547,11 @@ export async function writeFileAtomic(
|
||||
} else {
|
||||
await rename(tempPath, absolutePath)
|
||||
}
|
||||
await rm(stagingDir, { recursive: true, force: true })
|
||||
try {
|
||||
await removeStagingDir(stagingDir)
|
||||
} catch (_committedStagingCleanupFailure) {
|
||||
// The target is committed; owner-only staging residue cannot turn that write into a failure.
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
/* v8 ignore next -- abort-mid-write needs a writeFile/signal race; the non-abort (rename/open) side is tested. */
|
||||
let failure: unknown = isAbortError(error) ? new FsError('write aborted', 'FS_ABORTED') : error
|
||||
@@ -494,7 +564,7 @@ export async function writeFileAtomic(
|
||||
}
|
||||
}
|
||||
if (!stagingCreated) throw failure
|
||||
return removeStagingDirOrThrow(stagingDir, failure)
|
||||
return removeStagingDirOrThrow(stagingDir, failure, removeStagingDir)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ export class LocalFileSystem extends FileSystem {
|
||||
|
||||
/** Validated config (schemastery applied the defaults before construction). */
|
||||
readonly config: ResolvedConfig
|
||||
/** Test seam forwarded to fsio for atomic-publication boundaries. */
|
||||
/** Test hook forwarded to fsio for atomic-publication boundaries. */
|
||||
internals: FsIoInternals = {}
|
||||
/** Per-targetKey tail promise: serializes mutating ops so the read→guard→write
|
||||
* window can't interleave, making concurrent writes/edits deterministically
|
||||
@@ -192,7 +192,14 @@ export class LocalFileSystem extends FileSystem {
|
||||
const before = diffable
|
||||
? await readTextForDiff(target.targetKey, this.config.diffBasisMaxBytes, signal)
|
||||
: null
|
||||
await writeFileAtomic(target.targetKey, content, existing?.mode, signal, this.internals)
|
||||
await writeFileAtomic(
|
||||
target.targetKey,
|
||||
content,
|
||||
existing?.mode,
|
||||
signal,
|
||||
this.internals,
|
||||
expected?.kind === 'createIfAbsent' ? { displayPath: target.displayPath } : undefined,
|
||||
)
|
||||
const after = await probe(target.targetKey)
|
||||
return {
|
||||
operation: existing ? 'update' : 'create',
|
||||
|
||||
Reference in New Issue
Block a user