feat(util): extract dsh-atomic-write and migrate settings-local writes
writeFileAtomic: exclusive-create random-suffix temp + rename carrying the caller-stated mode; settings-local persistSection now consumes it. The credentials-local store shares it next.
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@deepseek-ai/dsh-atomic-write": "^0.0.1",
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-paths": "^0.0.1",
|
||||
"@deepseek-ai/dsh-settings": "^0.0.1",
|
||||
@@ -38,6 +39,7 @@
|
||||
"yaml": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-atomic-write": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-paths": "workspace:^",
|
||||
"@deepseek-ai/dsh-settings": "workspace:^",
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
import { Context, Service } from 'cordis'
|
||||
import z from 'schemastery'
|
||||
import { watch as chokidarWatch } from 'chokidar'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
||||
import { dirname, extname, join, resolve } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { extname, join, resolve } from 'node:path'
|
||||
import { Document, parseDocument } from 'yaml'
|
||||
import { writeFileAtomic } from '@deepseek-ai/dsh-atomic-write'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
import { Settings, type SettingsNamespace } from '@deepseek-ai/dsh-settings'
|
||||
|
||||
@@ -137,19 +137,8 @@ export class SettingsLocal extends Settings {
|
||||
const output = this.spec.format === 'yaml'
|
||||
? this.renderYaml(ns, section)
|
||||
: this.renderJson(ns, section)
|
||||
await mkdir(dirname(this.spec.filename), { recursive: true })
|
||||
// Exclusive-create (`wx`) a random-suffix sibling: the open refuses to
|
||||
// follow any planted symlink at a guessable temp path, and the fresh inode
|
||||
// carries owner-only permissions that survive the rename — a document that
|
||||
// may hold personal values is never world-readable and never a symlink.
|
||||
const temp = `${this.spec.filename}.${randomBytes(6).toString('hex')}.tmp`
|
||||
try {
|
||||
await writeFile(temp, output, { mode: 0o600, flag: 'wx' })
|
||||
await rename(temp, this.spec.filename)
|
||||
} catch (error) {
|
||||
await rm(temp, { force: true })
|
||||
throw error
|
||||
}
|
||||
// 0600: a document that may hold personal values is never world-readable.
|
||||
await writeFileAtomic(this.spec.filename, output, { mode: 0o600 })
|
||||
this.text = output
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
{
|
||||
"path": "../../../vendor/schemastery"
|
||||
},
|
||||
{
|
||||
"path": "../../util/atomic-write"
|
||||
},
|
||||
{
|
||||
"path": "../../util/paths"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user