GitRoot
Craft your forge, Build your project, Grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: MIT
4
5import {
6 dircookie,
7 errno,
8 fd,
9 fd_readdir,
10 fdflags,
11 filestat,
12 lookupflags,
13 oflags,
14 path_filestat_get,
15 path_open,
16 rights,
17} from "@assemblyscript/wasi-shim/assembly/bindings/wasi_snapshot_preview1";
18import { Descriptor } from "as-wasi/assembly";
19import {
20 _copyFile,
21 _deleteFile,
22 _moveFile,
23 _replaceContent,
24 _writeContent,
25} from "./imports";
26import { errorOrNull } from "./wasm";
27
28export const FsBaseWorktree = "worktree";
29export const FsBaseWebcontent = "webcontent";
30export const FsBaseCache = "cache";
31
32export class FS {
33 private fs: FileSystem = new FileSystem(3);
34 private base: string;
35
36 constructor(base: string) {
37 this.base = base;
38 }
39
40 readDir(path: string): string[] | null {
41 const dirs = this.fs.readdir(this.base + "/" + path);
42 if (dirs == null) {
43 return [];
44 }
45 return dirs;
46 }
47
48 readAll(path: string): string | null {
49 const fd = this.fs.open(this.base + "/" + path);
50 if (fd == null) {
51 return null;
52 }
53 const data = fd.readString();
54 if (data == null) {
55 return null;
56 }
57 fd.close();
58 return data;
59 }
60
61 readLine(path: string): File | null {
62 const fd = this.fs.open(this.base + "/" + path);
63 if (fd == null) {
64 return null;
65 }
66 return new File(fd);
67 }
68
69 exists(path: string): bool {
70 return this.fs.exists(this.base + "/" + path);
71 }
72
73 writeContent(path: string, content: string): Error | null {
74 return errorOrNull(_writeContent(this.base, path, content));
75 }
76
77 replaceContent(
78 path: string,
79 oldContent: string,
80 content: string,
81 ): Error | null {
82 return errorOrNull(_replaceContent(this.base, path, oldContent, content));
83 }
84
85 copyFile(fromPath: string, toPath: string): Error | null {
86 return errorOrNull(_copyFile(this.base, fromPath, this.base, toPath));
87 }
88
89 copyFileToFs(fromPath: string, toFs: FS, toPath: string): Error | null {
90 return errorOrNull(_copyFile(this.base, fromPath, toFs.base, toPath));
91 }
92
93 deleteFile(filename: string): Error | null {
94 return errorOrNull(_deleteFile(this.base, filename));
95 }
96
97 moveFile(fromPath: string, toPath: string): Error | null {
98 return errorOrNull(_moveFile(this.base, fromPath, this.base, toPath));
99 }
100
101 moveFileToFs(fromPath: string, toFs: FS, toPath: string): Error | null {
102 return errorOrNull(_moveFile(this.base, fromPath, toFs.base, toPath));
103 }
104}
105
106class File {
107 constructor(private fd: Descriptor) {}
108
109 readLine(): string | null {
110 return this.fd.readLine();
111 }
112
113 close(): void {
114 this.fd.close();
115 }
116}
117
118/**
119 * ######################################
120 * COPIED FROM AS-WASI
121 * ######################################
122 */
123
124/**
125 * A class to access a filesystem
126 */
127class FileSystem {
128 constructor(private dirfd: u32) {}
129
130 /**
131 * Open a path
132 * @path path
133 * @flags r, r+, w, wx, w+ or xw+
134 * @returns a descriptor
135 */
136 open(path: string, flags: string = "r"): Descriptor | null {
137 let dirfd = this.dirfdForPath(path);
138 let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW;
139 let fd_oflags: u16 = 0;
140 let fd_rights: u64 = 0;
141 if (flags == "r") {
142 fd_rights =
143 rights.FD_READ |
144 rights.FD_SEEK |
145 rights.FD_TELL |
146 rights.FD_FILESTAT_GET |
147 rights.FD_READDIR;
148 } else if (flags == "r+") {
149 fd_rights =
150 rights.FD_WRITE |
151 rights.FD_READ |
152 rights.FD_SEEK |
153 rights.FD_TELL |
154 rights.FD_FILESTAT_GET |
155 rights.PATH_CREATE_FILE;
156 } else if (flags == "w") {
157 fd_oflags = oflags.CREAT | oflags.TRUNC;
158 fd_rights =
159 rights.FD_WRITE |
160 rights.FD_SEEK |
161 rights.FD_TELL |
162 rights.FD_FILESTAT_GET |
163 rights.PATH_CREATE_FILE;
164 } else if (flags == "wx") {
165 fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL;
166 fd_rights =
167 rights.FD_WRITE |
168 rights.FD_SEEK |
169 rights.FD_TELL |
170 rights.FD_FILESTAT_GET |
171 rights.PATH_CREATE_FILE;
172 } else if (flags == "w+") {
173 fd_oflags = oflags.CREAT | oflags.TRUNC;
174 fd_rights =
175 rights.FD_WRITE |
176 rights.FD_READ |
177 rights.FD_SEEK |
178 rights.FD_TELL |
179 rights.FD_FILESTAT_GET |
180 rights.PATH_CREATE_FILE;
181 } else if (flags == "xw+") {
182 fd_oflags = oflags.CREAT | oflags.TRUNC | oflags.EXCL;
183 fd_rights =
184 rights.FD_WRITE |
185 rights.FD_READ |
186 rights.FD_SEEK |
187 rights.FD_TELL |
188 rights.FD_FILESTAT_GET |
189 rights.PATH_CREATE_FILE;
190 } else {
191 return null;
192 }
193 let fd_rights_inherited = fd_rights;
194 let fd_flags: fdflags = 0;
195 let path_utf8_buf = String.UTF8.encode(path);
196 let path_utf8_len: usize = path_utf8_buf.byteLength;
197 let path_utf8 = changetype<usize>(path_utf8_buf);
198 let fd_buf = memory.data(8);
199 let res = path_open(
200 dirfd as fd,
201 fd_lookup_flags,
202 path_utf8,
203 path_utf8_len,
204 fd_oflags,
205 fd_rights,
206 fd_rights_inherited,
207 fd_flags,
208 fd_buf,
209 );
210 if (res !== errno.SUCCESS) {
211 return null;
212 }
213 let fd = load<u32>(fd_buf);
214 return new Descriptor(fd);
215 }
216
217 /**
218 * Check if a file exists at a given path
219 * @path path
220 * @returns `true` on success, `false` on failure
221 */
222 exists(path: string): bool {
223 let dirfd = this.dirfdForPath(path);
224 let path_utf8_buf = String.UTF8.encode(path);
225 let path_utf8_len: usize = path_utf8_buf.byteLength;
226 let path_utf8 = changetype<usize>(path_utf8_buf);
227 let fd_lookup_flags = lookupflags.SYMLINK_FOLLOW;
228 let st_buf = changetype<usize>(new ArrayBuffer(64));
229 let res = path_filestat_get(
230 dirfd,
231 fd_lookup_flags,
232 path_utf8,
233 path_utf8_len,
234 changetype<filestat>(st_buf),
235 );
236 return res === errno.SUCCESS;
237 }
238
239 /**
240 * Get the content of a directory
241 * @param path the directory path
242 * @returns An array of file names
243 */
244 readdir(path: string): Array<string> | null {
245 let fd = this.open(path, "r");
246 if (fd === null) {
247 return null;
248 }
249 let out = new Array<string>();
250 let buf_size = 4096;
251 // @ts-ignore
252 let buf = heap.alloc(buf_size);
253 // @ts-ignore
254 let buf_used_p = memory.data(8);
255 let buf_used = 0;
256 for (;;) {
257 if (
258 fd_readdir(fd.rawfd, buf, buf_size, 0 as dircookie, buf_used_p) !==
259 errno.SUCCESS
260 ) {
261 fd.close();
262 }
263 buf_used = load<u32>(buf_used_p);
264 if (buf_used < buf_size) {
265 break;
266 }
267 buf_size <<= 1;
268 // @ts-ignore
269 buf = heap.realloc(buf, buf_size);
270 }
271 let offset = 0;
272 while (offset < buf_used) {
273 offset += 16;
274 let name_len = load<u32>(buf + offset);
275 offset += 8;
276 if (offset + name_len > buf_used) {
277 return null;
278 }
279 let name = String.UTF8.decodeUnsafe(buf + offset, name_len);
280 out.push(name);
281 offset += name_len;
282 }
283 // @ts-ignore
284 heap.free(buf);
285 fd.close();
286
287 return out;
288 }
289
290 dirfdForPath(path: string): fd {
291 return this.dirfd;
292 }
293}