- 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>
61 lines
2.0 KiB
TypeScript
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) }
|
|
}
|