fs.ts
1976 bytes
1import { constants } from "node:fs";
2import fs from "node:fs/promises";
3import { dirname, join } from "node:path";
4
5export interface FileSystem {
6 readFile(path: string): Promise<string>;
7 writeFileAtomic(path: string, content: string): Promise<void>;
8 access(path: string, mode?: number): Promise<boolean>;
9 readdir(path: string): Promise<Array<{ name: string; isDirectory: boolean; isFile: boolean; isSymbolicLink: boolean }>>;
10 stat(path: string): Promise<{ isDirectory: boolean; isFile: boolean; mode: number }>;
11}
12
13export class NodeFileSystem implements FileSystem {
14 async readFile(path: string): Promise<string> {
15 return fs.readFile(path, "utf8");
16 }
17
18 async writeFileAtomic(path: string, content: string): Promise<void> {
19 const dir = dirname(path);
20 const tmp = join(dir, `.pi-skill-toggle-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}.tmp`);
21 let mode: number | undefined;
22 try {
23 mode = (await fs.stat(path)).mode;
24 } catch {
25 mode = undefined;
26 }
27
28 await fs.writeFile(tmp, content, "utf8");
29 if (mode !== undefined) {
30 await fs.chmod(tmp, mode);
31 }
32 await fs.rename(tmp, path);
33 }
34
35 async access(path: string, mode = constants.F_OK): Promise<boolean> {
36 try {
37 await fs.access(path, mode);
38 return true;
39 } catch {
40 return false;
41 }
42 }
43
44 async readdir(path: string): Promise<Array<{ name: string; isDirectory: boolean; isFile: boolean; isSymbolicLink: boolean }>> {
45 const entries = await fs.readdir(path, { withFileTypes: true });
46 return entries.map((entry) => ({
47 name: entry.name,
48 isDirectory: entry.isDirectory(),
49 isFile: entry.isFile(),
50 isSymbolicLink: entry.isSymbolicLink(),
51 }));
52 }
53
54 async stat(path: string): Promise<{ isDirectory: boolean; isFile: boolean; mode: number }> {
55 const stats = await fs.stat(path);
56 return { isDirectory: stats.isDirectory(), isFile: stats.isFile(), mode: stats.mode };
57 }
58}