Skip to content

Commit 00840d1

Browse files
authored
Merge pull request #17 from appwrite/fix-filesystem
fix: filesystem
2 parents c077192 + f8d73bc commit 00840d1

File tree

5 files changed

+70
-41
lines changed

5 files changed

+70
-41
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@appwrite.io/synapse",
3-
"version": "0.4.5",
3+
"version": "0.4.6",
44
"description": "Operating system gateway for remote serverless environments",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/services/filesystem.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,15 @@ export class Filesystem {
3434
constructor(synapse: Synapse, workDir?: string) {
3535
this.synapse = synapse;
3636
this.synapse.setFilesystem(this);
37-
this.workDir = workDir ?? process.cwd();
37+
38+
if (workDir) {
39+
if (!fsSync.existsSync(workDir)) {
40+
fsSync.mkdirSync(workDir, { recursive: true });
41+
}
42+
this.workDir = workDir;
43+
} else {
44+
this.workDir = process.cwd();
45+
}
3846
}
3947

4048
private log(message: string): void {

src/services/git.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ export class Git {
1919
*/
2020
constructor(synapse: Synapse, workDir?: string) {
2121
this.synapse = synapse;
22-
this.workDir = workDir ?? process.cwd();
22+
23+
if (workDir) {
24+
if (!fs.existsSync(workDir)) {
25+
fs.mkdirSync(workDir, { recursive: true });
26+
}
27+
this.workDir = workDir;
28+
} else {
29+
this.workDir = process.cwd();
30+
}
2331
}
2432

2533
private isErrnoException(error: unknown): error is NodeJS.ErrnoException {

src/services/terminal.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type TerminalOptions = {
77
shell: string;
88
cols?: number;
99
rows?: number;
10-
workDir?: string;
10+
workDir: string;
1111
};
1212

1313
export class Terminal {
@@ -35,6 +35,10 @@ export class Terminal {
3535
this.synapse = synapse;
3636
this.synapse.registerTerminal(this);
3737

38+
if (!fs.existsSync(terminalOptions.workDir)) {
39+
fs.mkdirSync(terminalOptions.workDir, { recursive: true });
40+
}
41+
3842
try {
3943
this.term = pty.spawn(terminalOptions.shell, [], {
4044
name: "xterm-color",

tests/services/filesystem.test.ts

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import * as fsSync from "fs";
2-
import * as fs from "fs/promises";
1+
import * as fs from "fs";
2+
import * as fsp from "fs/promises";
33
import ignore from "ignore";
4+
import * as path from "path";
45
import { Filesystem } from "../../src/services/filesystem";
56
import { Synapse } from "../../src/synapse";
67

@@ -19,29 +20,39 @@ jest.mock("ignore");
1920
describe("Filesystem", () => {
2021
let filesystem: Filesystem;
2122
let mockSynapse: jest.Mocked<Synapse>;
23+
const tempDir = path.join(process.cwd(), "tmp", "test");
2224

2325
beforeEach(() => {
26+
if (!fs.existsSync(tempDir)) {
27+
fs.mkdirSync(tempDir, { recursive: true });
28+
}
2429
mockSynapse = jest.mocked({
2530
logger: jest.fn(),
26-
workDir: "/test",
31+
workDir: tempDir,
2732
setFilesystem: jest.fn(),
2833
} as unknown as Synapse);
2934

30-
filesystem = new Filesystem(mockSynapse, "/test");
35+
filesystem = new Filesystem(mockSynapse, tempDir);
3136
jest.clearAllMocks();
3237
});
3338

39+
afterAll(() => {
40+
if (fs.existsSync(tempDir)) {
41+
fs.rmSync(tempDir, { recursive: true, force: true });
42+
}
43+
});
44+
3445
describe("createFile", () => {
3546
it("should successfully create a file", async () => {
36-
const filePath = "/file.txt";
47+
const filePath = path.join(tempDir, "file.txt");
3748
const content = "test content";
3849

3950
// Mock access to indicate file does NOT exist initially
4051
const accessError = new Error("ENOENT") as NodeJS.ErrnoException;
4152
accessError.code = "ENOENT";
42-
(fs.access as jest.Mock).mockRejectedValue(accessError);
43-
(fs.mkdir as jest.Mock).mockResolvedValue(undefined); // Assuming createFolder is called
44-
(fs.writeFile as jest.Mock).mockResolvedValue(undefined);
53+
(fsp.access as jest.Mock).mockRejectedValue(accessError);
54+
(fsp.mkdir as jest.Mock).mockResolvedValue(undefined); // Assuming createFolder is called
55+
(fsp.writeFile as jest.Mock).mockResolvedValue(undefined);
4556

4657
const result = await filesystem.createFile(filePath, content);
4758
expect(result).toEqual({
@@ -51,30 +62,30 @@ describe("Filesystem", () => {
5162
});
5263

5364
it("should return error if file already exists", async () => {
54-
const filePath = "/existing.txt";
65+
const filePath = path.join(tempDir, "existing.txt");
5566
const content = "test content";
5667

5768
// Mock access to indicate file DOES exist
58-
(fs.access as jest.Mock).mockResolvedValue(undefined);
69+
(fsp.access as jest.Mock).mockResolvedValue(undefined);
5970

6071
const result = await filesystem.createFile(filePath, content);
61-
expect(fs.writeFile).not.toHaveBeenCalled(); // Should not attempt to write
72+
expect(fsp.writeFile).not.toHaveBeenCalled(); // Should not attempt to write
6273
expect(result).toEqual({
6374
success: false,
6475
error: `File already exists at path: ${filePath}`,
6576
});
6677
});
6778

6879
it("should handle file creation errors during write", async () => {
69-
const filePath = "/test/error.txt";
80+
const filePath = path.join(tempDir, "error.txt");
7081
const content = "test content";
7182

7283
// Mock access to indicate file does NOT exist initially
7384
const accessError = new Error("ENOENT") as NodeJS.ErrnoException;
7485
accessError.code = "ENOENT";
75-
(fs.access as jest.Mock).mockRejectedValue(accessError);
76-
(fs.mkdir as jest.Mock).mockResolvedValue(undefined); // Mock directory creation
77-
(fs.writeFile as jest.Mock).mockRejectedValue(
86+
(fsp.access as jest.Mock).mockRejectedValue(accessError);
87+
(fsp.mkdir as jest.Mock).mockResolvedValue(undefined); // Mock directory creation
88+
(fsp.writeFile as jest.Mock).mockRejectedValue(
7889
new Error("Failed to write file"),
7990
);
8091

@@ -88,10 +99,10 @@ describe("Filesystem", () => {
8899

89100
describe("getFile", () => {
90101
it("should successfully read file content", async () => {
91-
const filePath = "/file.txt";
102+
const filePath = path.join(tempDir, "file.txt");
92103
const content = "test content";
93104

94-
(fs.readFile as jest.Mock).mockResolvedValue(content);
105+
(fsp.readFile as jest.Mock).mockResolvedValue(content);
95106

96107
const result = await filesystem.getFile(filePath);
97108
expect(result).toEqual({
@@ -101,9 +112,11 @@ describe("Filesystem", () => {
101112
});
102113

103114
it("should handle file reading errors", async () => {
104-
const filePath = "/test/nonexistent.txt";
115+
const filePath = path.join(tempDir, "nonexistent.txt");
105116

106-
(fs.readFile as jest.Mock).mockRejectedValue(new Error("File not found"));
117+
(fsp.readFile as jest.Mock).mockRejectedValue(
118+
new Error("File not found"),
119+
);
107120

108121
const result = await filesystem.getFile(filePath);
109122
expect(result).toEqual({
@@ -120,11 +133,9 @@ describe("Filesystem", () => {
120133
};
121134

122135
// Mock the filesystem functions
123-
(fsSync.watch as jest.Mock).mockReturnValue(mockWatcher);
124-
(fsSync.existsSync as jest.Mock).mockReturnValue(true);
125-
(fsSync.readFileSync as jest.Mock).mockReturnValue(
126-
"*.env\nnode_modules/",
127-
);
136+
(fs.watch as jest.Mock).mockReturnValue(mockWatcher);
137+
(fs.existsSync as jest.Mock).mockReturnValue(true);
138+
(fs.readFileSync as jest.Mock).mockReturnValue("*.env\nnode_modules/");
128139

129140
// Mock ignore implementation
130141
const mockIgnore = {
@@ -137,18 +148,16 @@ describe("Filesystem", () => {
137148
let watchCallback:
138149
| ((eventType: string, filename: string) => void)
139150
| null = null;
140-
(fsSync.watch as jest.Mock).mockImplementation(
141-
(path, options, callback) => {
142-
watchCallback = callback;
143-
return mockWatcher;
144-
},
145-
);
151+
(fs.watch as jest.Mock).mockImplementation((path, options, callback) => {
152+
watchCallback = callback;
153+
return mockWatcher;
154+
});
146155

147156
// Mock fs.lstat and fs.readFile for the file change event
148-
(fs.lstat as jest.Mock).mockResolvedValue({
157+
(fsp.lstat as unknown as jest.Mock).mockResolvedValue({
149158
isFile: jest.fn().mockReturnValue(true),
150159
});
151-
(fs.readFile as jest.Mock).mockResolvedValue("file content");
160+
(fsp.readFile as unknown as jest.Mock).mockResolvedValue("file content");
152161

153162
// Set up the callback spy
154163
const onChangeMock = jest.fn();
@@ -157,8 +166,8 @@ describe("Filesystem", () => {
157166
filesystem.watchWorkDir(onChangeMock);
158167

159168
// Verify watch was called with correct path
160-
expect(fsSync.watch).toHaveBeenCalledWith(
161-
mockSynapse.workDir,
169+
expect(fs.watch).toHaveBeenCalledWith(
170+
tempDir,
162171
{ recursive: true },
163172
expect.any(Function),
164173
);
@@ -196,12 +205,12 @@ describe("Filesystem", () => {
196205

197206
describe("appendFile", () => {
198207
it("should append content to a file", async () => {
199-
const filePath = "/file.txt";
208+
const filePath = path.join(tempDir, "file.txt");
200209
const content = "appended content";
201-
(fs.appendFile as jest.Mock).mockResolvedValue(undefined);
210+
(fsp.appendFile as unknown as jest.Mock).mockResolvedValue(undefined);
202211

203212
const result = await filesystem.appendFile(filePath, content);
204-
expect(fs.appendFile).toHaveBeenCalledWith(
213+
expect(fsp.appendFile).toHaveBeenCalledWith(
205214
expect.stringContaining(filePath),
206215
content,
207216
);

0 commit comments

Comments
 (0)