|
1 | | -import { fs as zenfs } from '@zenfs/core'; |
2 | | -import * as path from 'path'; |
3 | | - |
4 | | -export interface FsStat { |
5 | | - isFile: boolean; |
6 | | - isDirectory: boolean; |
7 | | - isSymbolicLink: boolean; |
8 | | - mode: number; |
9 | | - size: number; |
10 | | - mtime: Date; |
11 | | -} |
12 | | - |
13 | | -export interface MkdirOptions { |
14 | | - recursive?: boolean; |
15 | | -} |
16 | | - |
17 | | -export interface RmOptions { |
18 | | - recursive?: boolean; |
19 | | - force?: boolean; |
20 | | -} |
21 | | - |
22 | | -export interface CpOptions { |
23 | | - recursive?: boolean; |
24 | | -} |
25 | | - |
26 | | -export interface ReadFileOptions { |
27 | | - encoding?: string | null; |
28 | | -} |
29 | | - |
30 | | -export interface WriteFileOptions { |
31 | | - encoding?: string; |
32 | | -} |
33 | | - |
34 | | -export type FileContent = string | Uint8Array; |
| 1 | +import { fs } from '@zenfs/core'; |
| 2 | +import { posix } from 'path'; |
| 3 | + |
| 4 | +type StatLike = { isFile(): boolean; isDirectory(): boolean; isSymbolicLink(): boolean; mode: number | bigint; size: number | bigint; mtime: Date }; |
| 5 | +const toStat = (s: StatLike) => ({ |
| 6 | + isFile: s.isFile(), |
| 7 | + isDirectory: s.isDirectory(), |
| 8 | + isSymbolicLink: s.isSymbolicLink(), |
| 9 | + mode: Number(s.mode), |
| 10 | + size: Number(s.size), |
| 11 | + mtime: s.mtime, |
| 12 | +}); |
35 | 13 |
|
36 | 14 | export class ZenFsAdapter { |
37 | | - async readFile(filePath: string, options?: ReadFileOptions | string): Promise<string> { |
38 | | - const encoding = typeof options === 'string' ? options : options?.encoding ?? 'utf-8'; |
39 | | - return zenfs.readFileSync(filePath, encoding as BufferEncoding); |
40 | | - } |
41 | | - |
42 | | - async readFileBuffer(filePath: string): Promise<Uint8Array> { |
43 | | - const buffer = zenfs.readFileSync(filePath); |
44 | | - if (typeof buffer === 'string') { |
45 | | - return new TextEncoder().encode(buffer); |
46 | | - } |
47 | | - return new Uint8Array(buffer); |
48 | | - } |
49 | | - |
50 | | - async writeFile(filePath: string, content: FileContent, options?: WriteFileOptions | string): Promise<void> { |
51 | | - this.ensureParentDirs(filePath); |
52 | | - zenfs.writeFileSync(filePath, content); |
53 | | - } |
54 | | - |
55 | | - async appendFile(filePath: string, content: FileContent, options?: WriteFileOptions | string): Promise<void> { |
56 | | - this.ensureParentDirs(filePath); |
57 | | - zenfs.appendFileSync(filePath, content); |
58 | | - } |
59 | | - |
60 | | - async exists(filePath: string): Promise<boolean> { |
61 | | - return zenfs.existsSync(filePath); |
62 | | - } |
63 | | - |
64 | | - async stat(filePath: string): Promise<FsStat> { |
65 | | - const stats = zenfs.statSync(filePath); |
66 | | - return { |
67 | | - isFile: stats.isFile(), |
68 | | - isDirectory: stats.isDirectory(), |
69 | | - isSymbolicLink: stats.isSymbolicLink(), |
70 | | - mode: stats.mode, |
71 | | - size: stats.size, |
72 | | - mtime: stats.mtime, |
73 | | - }; |
74 | | - } |
75 | | - |
76 | | - async lstat(filePath: string): Promise<FsStat> { |
77 | | - const stats = zenfs.lstatSync(filePath); |
78 | | - return { |
79 | | - isFile: stats.isFile(), |
80 | | - isDirectory: stats.isDirectory(), |
81 | | - isSymbolicLink: stats.isSymbolicLink(), |
82 | | - mode: stats.mode, |
83 | | - size: stats.size, |
84 | | - mtime: stats.mtime, |
85 | | - }; |
86 | | - } |
87 | | - |
88 | | - async mkdir(dirPath: string, options?: MkdirOptions): Promise<void> { |
89 | | - zenfs.mkdirSync(dirPath, { recursive: options?.recursive }); |
90 | | - } |
91 | | - |
92 | | - async readdir(dirPath: string): Promise<string[]> { |
93 | | - return zenfs.readdirSync(dirPath) as string[]; |
94 | | - } |
95 | | - |
96 | | - async rm(filePath: string, options?: RmOptions): Promise<void> { |
97 | | - try { |
98 | | - zenfs.rmSync(filePath, { recursive: options?.recursive, force: options?.force }); |
99 | | - } catch (e) { |
100 | | - if (!options?.force) throw e; |
101 | | - } |
102 | | - } |
103 | | - |
104 | | - async cp(src: string, dest: string, options?: CpOptions): Promise<void> { |
105 | | - const srcStat = zenfs.statSync(src); |
106 | | - if (srcStat.isDirectory()) { |
107 | | - if (!options?.recursive) { |
108 | | - throw new Error(`EISDIR: is a directory, cp '${src}'`); |
109 | | - } |
110 | | - zenfs.mkdirSync(dest, { recursive: true }); |
111 | | - const entries = zenfs.readdirSync(src) as string[]; |
112 | | - for (const entry of entries) { |
113 | | - await this.cp(path.posix.join(src, entry), path.posix.join(dest, entry), options); |
114 | | - } |
115 | | - } else { |
116 | | - this.ensureParentDirs(dest); |
117 | | - zenfs.copyFileSync(src, dest); |
118 | | - } |
119 | | - } |
120 | | - |
121 | | - async mv(src: string, dest: string): Promise<void> { |
122 | | - zenfs.renameSync(src, dest); |
123 | | - } |
124 | | - |
125 | | - resolvePath(base: string, filePath: string): string { |
126 | | - if (filePath.startsWith('/')) { |
127 | | - return path.posix.normalize(filePath); |
128 | | - } |
129 | | - return path.posix.normalize(path.posix.join(base, filePath)); |
130 | | - } |
| 15 | + readFile = (p: string) => fs.promises.readFile(p, 'utf-8'); |
| 16 | + readFileBuffer = async (p: string) => new Uint8Array(await fs.promises.readFile(p)); |
| 17 | + writeFile = (p: string, c: string | Uint8Array) => fs.promises.writeFile(p, c); |
| 18 | + appendFile = (p: string, c: string | Uint8Array) => fs.promises.appendFile(p, c); |
| 19 | + exists = (p: string) => fs.promises.exists(p); |
| 20 | + mkdir = async (p: string, o?: { recursive?: boolean }) => { await fs.promises.mkdir(p, o); }; |
| 21 | + readdir = (p: string) => fs.promises.readdir(p) as Promise<string[]>; |
| 22 | + rm = (p: string, o?: { recursive?: boolean; force?: boolean }) => fs.promises.rm(p, o); |
| 23 | + cp = (s: string, d: string, o?: { recursive?: boolean }) => fs.promises.cp(s, d, o); |
| 24 | + mv = async (s: string, d: string) => { await fs.promises.rename(s, d); }; |
| 25 | + chmod = (p: string, m: number) => fs.promises.chmod(p, m); |
| 26 | + symlink = (t: string, l: string) => fs.promises.symlink(t, l); |
| 27 | + link = (e: string, n: string) => fs.promises.link(e, n); |
| 28 | + readlink = (p: string) => fs.promises.readlink(p); |
| 29 | + stat = async (p: string) => toStat(await fs.promises.stat(p)); |
| 30 | + lstat = async (p: string) => toStat(await fs.promises.lstat(p)); |
| 31 | + resolvePath = (base: string, p: string) => posix.normalize(p.startsWith('/') ? p : posix.join(base, p)); |
131 | 32 |
|
132 | 33 | getAllPaths(): string[] { |
133 | | - const paths: string[] = []; |
134 | | - this.walkDir('/', paths); |
135 | | - return paths; |
136 | | - } |
137 | | - |
138 | | - private walkDir(dir: string, paths: string[]): void { |
139 | | - paths.push(dir); |
140 | | - try { |
141 | | - const entries = zenfs.readdirSync(dir) as string[]; |
142 | | - for (const entry of entries) { |
143 | | - const fullPath = dir === '/' ? `/${entry}` : `${dir}/${entry}`; |
144 | | - try { |
145 | | - const stats = zenfs.lstatSync(fullPath); |
146 | | - if (stats.isDirectory()) { |
147 | | - this.walkDir(fullPath, paths); |
148 | | - } else { |
149 | | - paths.push(fullPath); |
150 | | - } |
151 | | - } catch { |
152 | | - paths.push(fullPath); |
| 34 | + const walk = (dir: string): string[] => { |
| 35 | + const paths = [dir]; |
| 36 | + try { |
| 37 | + for (const e of fs.readdirSync(dir)) { |
| 38 | + const full = dir === '/' ? `/${e}` : `${dir}/${e}`; |
| 39 | + paths.push(...(fs.lstatSync(full).isDirectory() ? walk(full) : [full])); |
153 | 40 | } |
154 | | - } |
155 | | - } catch { |
156 | | - // Directory not readable, skip |
157 | | - } |
158 | | - } |
159 | | - |
160 | | - async chmod(filePath: string, mode: number): Promise<void> { |
161 | | - zenfs.chmodSync(filePath, mode); |
162 | | - } |
163 | | - |
164 | | - async symlink(target: string, linkPath: string): Promise<void> { |
165 | | - this.ensureParentDirs(linkPath); |
166 | | - zenfs.symlinkSync(target, linkPath); |
167 | | - } |
168 | | - |
169 | | - async link(existingPath: string, newPath: string): Promise<void> { |
170 | | - this.ensureParentDirs(newPath); |
171 | | - zenfs.linkSync(existingPath, newPath); |
172 | | - } |
173 | | - |
174 | | - async readlink(linkPath: string): Promise<string> { |
175 | | - const result = zenfs.readlinkSync(linkPath); |
176 | | - return typeof result === 'string' ? result : result.toString('utf-8'); |
177 | | - } |
178 | | - |
179 | | - private ensureParentDirs(filePath: string): void { |
180 | | - const dir = path.posix.dirname(filePath); |
181 | | - if (dir && dir !== '/' && !zenfs.existsSync(dir)) { |
182 | | - zenfs.mkdirSync(dir, { recursive: true }); |
183 | | - } |
| 41 | + } catch { /* skip unreadable */ } |
| 42 | + return paths; |
| 43 | + }; |
| 44 | + return walk('/'); |
184 | 45 | } |
185 | 46 | } |
0 commit comments