refactor: statusbar、companion、herdr 外掛改名加上 jsc- 前綴
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
f10b168c28
commit
58e2b2d26a
@@ -0,0 +1,60 @@
|
||||
// 檔案系統與時鐘皆由外部注入;核心只依賴這兩個介面
|
||||
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) }
|
||||
}
|
||||
Reference in New Issue
Block a user