Skip to content

Commit 3f101a1

Browse files
feat: add gitignore support
1 parent 65494dc commit 3f101a1

File tree

3 files changed

+133
-135
lines changed

3 files changed

+133
-135
lines changed

package-lock.json

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "morphcloud",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "A Typescript SDK for creating, managing, and interacting with Morph Cloud VMs.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -36,6 +36,7 @@
3636
"typescript": "^5.7.2"
3737
},
3838
"dependencies": {
39+
"ignore": "^7.0.0",
3940
"node-ssh": "^13.2.0"
4041
}
4142
}

src/examples/sync-test.ts

Lines changed: 120 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1,158 +1,146 @@
11
import { MorphCloudClient } from "../api";
2-
import * as fs from "fs";
3-
import * as path from "path";
2+
import * as fs from 'fs';
3+
import * as path from 'path';
44

5-
// Initialize the client
6-
const client = new MorphCloudClient({
7-
apiKey: "morph_YmBV85wIXBQqIPrNx6BnDc",
8-
});
9-
10-
(async () => {
11-
try {
12-
// Create sample local directory structure
5+
// Create test directory structure
6+
const createTestEnv = async () => {
137
console.log("Creating sample local directory structure...");
14-
if (fs.existsSync("./foo")) {
15-
console.log("Removing existing ./foo directory...");
16-
fs.rmSync("./foo", { recursive: true, force: true });
17-
}
18-
if (fs.existsSync("./foo2")) {
19-
console.log("Removing existing ./foo2 directory...");
20-
fs.rmSync("./foo2", { recursive: true, force: true });
8+
9+
const testDir = './foo';
10+
console.log("Removing existing ./foo directory...");
11+
if (fs.existsSync(testDir)) {
12+
fs.rmSync(testDir, { recursive: true });
2113
}
22-
14+
2315
console.log("Creating new ./foo directory...");
24-
fs.mkdirSync("./foo");
25-
fs.writeFileSync("./foo/test1.txt", "Hello from test1!");
26-
fs.writeFileSync("./foo/test2.txt", "Hello from test2!");
27-
fs.mkdirSync("./foo/subdir");
28-
fs.writeFileSync("./foo/subdir/test3.txt", "Hello from subdir!");
29-
30-
// Create a minimal snapshot and instance
31-
console.log("\nCreating snapshot and instance...");
16+
fs.mkdirSync(testDir);
17+
fs.mkdirSync(path.join(testDir, 'subdir'), { recursive: true });
18+
19+
// Create test files
20+
fs.writeFileSync(path.join(testDir, 'test1.txt'), 'Test file 1');
21+
fs.writeFileSync(path.join(testDir, 'test2.txt'), 'Test file 2');
22+
fs.writeFileSync(path.join(testDir, 'subdir', 'test3.txt'), 'Test file 3');
23+
24+
// Create .gitignore and files that should be ignored
25+
fs.writeFileSync(path.join(testDir, '.gitignore'),
26+
`# Python artifacts
27+
__pycache__/
28+
*.py[cod]
29+
*$py.class
30+
31+
# Temp files
32+
*.tmp
33+
temp/
34+
35+
# Test patterns
36+
ignored_file.txt
37+
ignored_dir/
38+
*.ignore
39+
`);
40+
41+
// Create files that should be ignored
42+
fs.writeFileSync(path.join(testDir, 'ignored_file.txt'), 'This file should be ignored');
43+
fs.writeFileSync(path.join(testDir, 'test.ignore'), 'This file should be ignored');
44+
fs.mkdirSync(path.join(testDir, 'ignored_dir'));
45+
fs.writeFileSync(path.join(testDir, 'ignored_dir', 'file.txt'), 'This file should be ignored');
46+
};
47+
48+
const main = async () => {
49+
await createTestEnv();
50+
console.log("\nGetting snapshot and creating instance...");
51+
52+
const client = new MorphCloudClient({
53+
apiKey: process.env.MORPH_API_KEY || ''
54+
});
55+
56+
// Use hardcoded snapshot
3257
const snapshot = await client.snapshots.get({
33-
snapshotId: "snapshot_9xy9io0w",
58+
snapshotId: "snapshot_9xy9io0w"
3459
});
3560

3661
const instance = await client.instances.start({
37-
snapshotId: snapshot.id,
62+
snapshotId: snapshot.id
3863
});
3964

40-
// Wait for instance to be ready
4165
console.log("Waiting for instance to be ready...");
4266
await instance.waitUntilReady(60);
4367

44-
// Ensure remote directory exists and is empty
68+
// Create remote directory and check its status
4569
console.log("\nPreparing remote environment...");
4670
const ssh = await instance.ssh();
47-
await ssh.execCommand("rm -rf /tmp/foo");
48-
await ssh.execCommand("mkdir -p /tmp/foo");
49-
50-
// Verify remote directory was created
51-
const checkResult = await ssh.execCommand("ls -la /tmp/foo");
52-
console.log(
53-
"Remote directory status:",
54-
checkResult.stdout || checkResult.stderr,
55-
);
56-
57-
// First sync: local to remote
58-
console.log("\nSyncing ./foo to remote /tmp/foo...");
59-
try {
60-
await instance.sync(
61-
path.resolve("./foo"), // Use absolute path
62-
`${instance.id}:/tmp/foo`,
63-
{ verbose: true },
64-
);
65-
} catch (error) {
66-
console.error("Error during first sync:", error);
67-
throw error;
68-
}
71+
await ssh.execCommand('mkdir -p /tmp/foo');
72+
const { stdout } = await ssh.execCommand('ls -la /tmp/foo');
73+
console.log("Remote directory status:", stdout);
6974

70-
// Verify remote contents
71-
console.log("\nVerifying remote contents...");
72-
const remoteList = await ssh.execCommand(
73-
"find /tmp/foo -type f -exec cat {} \\;",
74-
);
75-
console.log("Remote files content:", remoteList.stdout);
76-
77-
// Second sync: remote back to local
78-
console.log("\nSyncing remote /tmp/foo back to ./foo2...");
7975
try {
80-
await instance.sync(
81-
`${instance.id}:/tmp/foo`,
82-
path.resolve("./foo2"), // Use absolute path
83-
{ verbose: true },
84-
);
85-
} catch (error) {
86-
console.error("Error during second sync:", error);
87-
throw error;
88-
}
89-
90-
// Verify the contents
91-
console.log("\nVerifying local contents...");
92-
const compareDirectories = (
93-
dir1: string,
94-
dir2: string,
95-
relativePath: string = "",
96-
) => {
97-
const files1 = fs.readdirSync(path.join(dir1, relativePath));
98-
const files2 = fs.readdirSync(path.join(dir2, relativePath));
99-
100-
console.log(`Comparing ${relativePath || "/"}`);
101-
console.log(`Files in ${dir1}:`, files1);
102-
console.log(`Files in ${dir2}:`, files2);
103-
104-
if (files1.length !== files2.length) {
105-
throw new Error(
106-
`Directory ${relativePath} has different number of files`,
76+
// First sync without gitignore
77+
console.log("\nSyncing ./foo to remote /tmp/foo WITHOUT gitignore...");
78+
await instance.sync(
79+
path.resolve('./foo'),
80+
`${instance.id}:/tmp/foo`,
81+
{
82+
verbose: true,
83+
delete: false
84+
}
10785
);
108-
}
10986

110-
for (const file of files1) {
111-
const fullPath1 = path.join(dir1, relativePath, file);
112-
const fullPath2 = path.join(dir2, relativePath, file);
113-
114-
const stat1 = fs.statSync(fullPath1);
115-
const stat2 = fs.statSync(fullPath2);
87+
console.log("\nVerifying all files were synced...");
88+
const { stdout: allFiles } = await ssh.execCommand('find /tmp/foo -type f -exec ls -l {} \\;');
89+
console.log("Remote files (should include ignored files):", allFiles);
90+
91+
// Second sync with gitignore
92+
console.log("\nSyncing ./foo to remote /tmp/foo WITH gitignore...");
93+
await instance.sync(
94+
path.resolve('./foo'),
95+
`${instance.id}:/tmp/foo`,
96+
{
97+
verbose: true,
98+
delete: true,
99+
respectGitignore: true
100+
}
101+
);
116102

117-
if (stat1.isDirectory() !== stat2.isDirectory()) {
118-
throw new Error(
119-
`${file} is directory in one location but not in other`,
120-
);
103+
console.log("\nVerifying only non-ignored files were synced...");
104+
const { stdout: nonIgnoredFiles } = await ssh.execCommand('find /tmp/foo -type f -exec ls -l {} \\;');
105+
console.log("Remote files (should NOT include ignored files):", nonIgnoredFiles);
106+
107+
// Verify specific file contents
108+
console.log("\nVerifying file contents...");
109+
const { stdout: contents } = await ssh.execCommand('cat /tmp/foo/subdir/test3.txt');
110+
console.log("test3.txt contents:", contents);
111+
112+
// Verify ignored files are not present
113+
console.log("\nVerifying ignored files are not present...");
114+
const ignoredFiles = [
115+
'/tmp/foo/ignored_file.txt',
116+
'/tmp/foo/test.ignore',
117+
'/tmp/foo/ignored_dir/file.txt'
118+
];
119+
120+
for (const filePath of ignoredFiles) {
121+
const { stdout: fileCheck } = await ssh.execCommand(
122+
`test -f ${filePath} && echo "EXISTS" || echo "NOT FOUND"`
123+
);
124+
console.log(`${filePath}: ${fileCheck.trim()}`);
121125
}
122126

123-
if (stat1.isDirectory()) {
124-
compareDirectories(dir1, dir2, path.join(relativePath, file));
127+
} catch (error) {
128+
if (error instanceof Error) {
129+
console.error("Error during sync:", error);
130+
console.error("Error details:", {
131+
message: error.message,
132+
stack: error.stack,
133+
code: (error as any).code
134+
});
125135
} else {
126-
const content1 = fs.readFileSync(fullPath1, "utf8");
127-
const content2 = fs.readFileSync(fullPath2, "utf8");
128-
console.log(
129-
`Comparing ${file}:`,
130-
content1 === content2 ? "match" : "different",
131-
);
132-
if (content1 !== content2) {
133-
throw new Error(`File contents different for ${file}`);
134-
}
136+
console.error("Unknown error during sync:", error);
135137
}
136-
}
137-
};
138-
139-
compareDirectories("./foo", "./foo2");
140-
console.log("Success! Directory contents match exactly.");
141-
142-
// Cleanup
143-
console.log("\nCleaning up...");
144-
await instance.stop();
145-
// await snapshot.delete();
146-
} catch (error) {
147-
console.error("Error:", error);
148-
// Log instance status if available
149-
if (typeof error === "object" && error !== null) {
150-
console.error("Error details:", {
151-
message: (error as Error).message,
152-
stack: (error as Error).stack,
153-
code: (error as any).code,
154-
});
138+
throw error;
139+
} finally {
140+
// Clean up - just stop the instance
141+
console.log("\nCleaning up...");
142+
await instance.stop();
155143
}
156-
throw error;
157-
}
158-
})();
144+
};
145+
146+
main().catch(console.error);

0 commit comments

Comments
 (0)