Files
jsc-marketplace/plugins/jsc-mod-companion/hooks/companion/fs.ts
T
rootandClaude Sonnet 5.5 ea17f1cb2d refactor: jsc-mod 改名 jsc-mod-statusline、jsc-companion 改名 jsc-mod-companion,指令拆成獨立指令
- jsc-mod-statusline:/jsc-mod-statusline:enable、compact、disable
- jsc-mod-companion:/jsc-mod-companion:status、switch、disable、setup、todo、quiet
- 同步更新 plugin.json、marketplace.json 與各 README
- 補上 statusline band 測試缺少的 ui.render 底層處理

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-14 05:45:52 +00:00

61 lines
2.0 KiB
TypeScript

// 檔案系統與時鐘皆由外部注入;核心只依賴這兩個介面
export interface Fs {
read(path: string): Promise<string | null>
write(path: string, data: string): Promise<void>
/** 沒有 rename 的環境(hooks 沙箱)會直接覆寫 */
rename?(from: string, to: string): Promise<void>
list(dir: string): Promise<string[]>
}
export type Clock = { now(): number }
// 先寫暫存檔再 rename,當機時不會留下寫到一半的檔案
export async function writeAtomic(fs: Fs, path: string, data: string): Promise<void> {
if (!fs.rename) return fs.write(path, data)
const tmp = `${path}.tmp`
await fs.write(tmp, data)
await fs.rename(tmp, path)
}
export async function readJson<T>(fs: Fs, path: string): Promise<T | null> {
const raw = await fs.read(path)
if (raw === null) return null
try {
return JSON.parse(raw) as T
} catch {
return null
}
}
export const writeJson = (fs: Fs, path: string, value: unknown) => writeAtomic(fs, path, JSON.stringify(value, null, 2))
export function memoryFs(seed: Record<string, string> = {}): Fs & { files: Map<string, string> } {
const files = new Map(Object.entries(seed))
return {
files,
async read(path) {
return files.get(path) ?? null
},
async write(path, data) {
files.set(path, data)
},
async rename(from, to) {
const v = files.get(from)
if (v === undefined) throw new Error(`ENOENT: ${from}`)
files.delete(from)
files.set(to, v)
},
async list(dir) {
const prefix = dir.endsWith('/') ? dir : dir + '/'
const names = new Set<string>()
for (const k of files.keys()) if (k.startsWith(prefix)) names.add(k.slice(prefix.length).split('/')[0]!)
return [...names].filter(n => !n.endsWith('.tmp')).sort()
},
}
}
export function fakeClock(start = Date.parse('2026-10-12T00:00:00Z')) {
let t = start
return { now: () => t, advance: (ms: number) => void (t += ms), set: (ms: number) => void (t = ms) }
}