@superhq/shuru 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/sandbox.ts +81 -0
- package/src/types.ts +33 -0
package/README.md
CHANGED
|
@@ -34,6 +34,16 @@ await sb.watch("/workspace", (event) => {
|
|
|
34
34
|
await sb.writeFile("/tmp/app.ts", "console.log('hi')");
|
|
35
35
|
const data = await sb.readFile("/tmp/app.ts"); // Uint8Array
|
|
36
36
|
|
|
37
|
+
// Filesystem operations
|
|
38
|
+
await sb.mkdir("/workspace/src");
|
|
39
|
+
const entries = await sb.readDir("/workspace"); // { name, type, size }[]
|
|
40
|
+
const info = await sb.stat("/tmp/app.ts"); // { size, mode, mtime, isDir, ... }
|
|
41
|
+
await sb.copy("/tmp/app.ts", "/tmp/backup.ts");
|
|
42
|
+
await sb.rename("/tmp/backup.ts", "/tmp/old.ts");
|
|
43
|
+
await sb.chmod("/tmp/app.ts", 0o755);
|
|
44
|
+
await sb.remove("/tmp/old.ts");
|
|
45
|
+
if (await sb.exists("/tmp/app.ts")) { /* ... */ }
|
|
46
|
+
|
|
37
47
|
// Checkpoint — save disk state and stop
|
|
38
48
|
await sb.checkpoint("my-env");
|
|
39
49
|
```
|
|
@@ -133,6 +143,50 @@ Read a file from the VM. Returns raw bytes. Use `new TextDecoder().decode(data)`
|
|
|
133
143
|
|
|
134
144
|
Write a file to the VM. Accepts raw bytes or a string.
|
|
135
145
|
|
|
146
|
+
### `sandbox.mkdir(path, opts?): Promise<void>`
|
|
147
|
+
|
|
148
|
+
Create a directory. Creates parent directories by default.
|
|
149
|
+
|
|
150
|
+
| Option | Type | Default | Description |
|
|
151
|
+
|--------|------|---------|-------------|
|
|
152
|
+
| `recursive` | `boolean` | `true` | Create parent directories |
|
|
153
|
+
|
|
154
|
+
### `sandbox.readDir(path): Promise<DirEntry[]>`
|
|
155
|
+
|
|
156
|
+
List the contents of a directory. Each entry has `name`, `type` (`"file"`, `"dir"`, or `"symlink"`), and `size` in bytes.
|
|
157
|
+
|
|
158
|
+
### `sandbox.stat(path): Promise<StatResult>`
|
|
159
|
+
|
|
160
|
+
Get file metadata. Returns `{ size, mode, mtime, isDir, isFile, isSymlink }`. `mtime` is seconds since the Unix epoch. `mode` includes the file type bits (e.g. `0o100644` for a regular file with 644 permissions).
|
|
161
|
+
|
|
162
|
+
### `sandbox.remove(path, opts?): Promise<void>`
|
|
163
|
+
|
|
164
|
+
Delete a file or empty directory. To remove a non-empty directory, pass `{ recursive: true }`.
|
|
165
|
+
|
|
166
|
+
| Option | Type | Default | Description |
|
|
167
|
+
|--------|------|---------|-------------|
|
|
168
|
+
| `recursive` | `boolean` | `false` | Remove directories and their contents |
|
|
169
|
+
|
|
170
|
+
### `sandbox.rename(oldPath, newPath): Promise<void>`
|
|
171
|
+
|
|
172
|
+
Move or rename a file or directory within the guest filesystem. Atomic on the same filesystem.
|
|
173
|
+
|
|
174
|
+
### `sandbox.copy(src, dst, opts?): Promise<void>`
|
|
175
|
+
|
|
176
|
+
Copy a file. To copy a directory tree, pass `{ recursive: true }`.
|
|
177
|
+
|
|
178
|
+
| Option | Type | Default | Description |
|
|
179
|
+
|--------|------|---------|-------------|
|
|
180
|
+
| `recursive` | `boolean` | `false` | Copy directories recursively |
|
|
181
|
+
|
|
182
|
+
### `sandbox.chmod(path, mode): Promise<void>`
|
|
183
|
+
|
|
184
|
+
Change file permissions. `mode` is a numeric permission value (e.g. `0o755`).
|
|
185
|
+
|
|
186
|
+
### `sandbox.exists(path): Promise<boolean>`
|
|
187
|
+
|
|
188
|
+
Check if a path exists. Returns `true` if it does, `false` otherwise.
|
|
189
|
+
|
|
136
190
|
### `sandbox.checkpoint(name): Promise<void>`
|
|
137
191
|
|
|
138
192
|
Save the VM's disk state and stop the VM. To continue working, call `Sandbox.start({ from: name })`.
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
export { SandboxProcess } from "./process-handle";
|
|
2
2
|
export { Sandbox } from "./sandbox";
|
|
3
3
|
export type {
|
|
4
|
+
CopyOptions,
|
|
5
|
+
DirEntry,
|
|
4
6
|
ExecOptions,
|
|
5
7
|
ExecResult,
|
|
6
8
|
FileChangeEvent,
|
|
9
|
+
MkdirOptions,
|
|
7
10
|
NetworkConfig,
|
|
11
|
+
RemoveOptions,
|
|
8
12
|
SecretConfig,
|
|
9
13
|
SpawnOptions,
|
|
10
14
|
StartOptions,
|
|
15
|
+
StatResult,
|
|
11
16
|
WatchOptions,
|
|
12
17
|
} from "./types";
|
package/src/sandbox.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { ShuruProcess } from "./process";
|
|
2
2
|
import { SandboxProcess } from "./process-handle";
|
|
3
3
|
import type {
|
|
4
|
+
CopyOptions,
|
|
5
|
+
DirEntry,
|
|
4
6
|
ExecOptions,
|
|
5
7
|
ExecResult,
|
|
6
8
|
FileChangeEvent,
|
|
9
|
+
MkdirOptions,
|
|
10
|
+
RemoveOptions,
|
|
7
11
|
SpawnOptions,
|
|
8
12
|
StartOptions,
|
|
13
|
+
StatResult,
|
|
9
14
|
WatchOptions,
|
|
10
15
|
} from "./types";
|
|
11
16
|
|
|
@@ -16,6 +21,13 @@ const Method = {
|
|
|
16
21
|
WRITE_FILE: "write_file",
|
|
17
22
|
CHECKPOINT: "checkpoint",
|
|
18
23
|
WATCH: "watch",
|
|
24
|
+
MKDIR: "mkdir",
|
|
25
|
+
READ_DIR: "read_dir",
|
|
26
|
+
STAT: "stat",
|
|
27
|
+
REMOVE: "remove",
|
|
28
|
+
RENAME: "rename",
|
|
29
|
+
COPY: "copy",
|
|
30
|
+
CHMOD: "chmod",
|
|
19
31
|
} as const;
|
|
20
32
|
|
|
21
33
|
export class Sandbox {
|
|
@@ -97,6 +109,74 @@ export class Sandbox {
|
|
|
97
109
|
await this.proc.send(Method.WRITE_FILE, { path, content: b64 });
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
async mkdir(path: string, opts?: MkdirOptions): Promise<void> {
|
|
113
|
+
await this.proc.send(Method.MKDIR, {
|
|
114
|
+
path,
|
|
115
|
+
recursive: opts?.recursive ?? true,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async readDir(path: string): Promise<DirEntry[]> {
|
|
120
|
+
const resp = await this.proc.send(Method.READ_DIR, { path });
|
|
121
|
+
const r = resp.result as { entries: DirEntry[] };
|
|
122
|
+
return r.entries;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async stat(path: string): Promise<StatResult> {
|
|
126
|
+
const resp = await this.proc.send(Method.STAT, { path });
|
|
127
|
+
const r = resp.result as {
|
|
128
|
+
size: number;
|
|
129
|
+
mode: number;
|
|
130
|
+
mtime: number;
|
|
131
|
+
is_dir: boolean;
|
|
132
|
+
is_file: boolean;
|
|
133
|
+
is_symlink: boolean;
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
size: r.size,
|
|
137
|
+
mode: r.mode,
|
|
138
|
+
mtime: r.mtime,
|
|
139
|
+
isDir: r.is_dir,
|
|
140
|
+
isFile: r.is_file,
|
|
141
|
+
isSymlink: r.is_symlink,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async remove(path: string, opts?: RemoveOptions): Promise<void> {
|
|
146
|
+
await this.proc.send(Method.REMOVE, {
|
|
147
|
+
path,
|
|
148
|
+
recursive: opts?.recursive ?? false,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async rename(oldPath: string, newPath: string): Promise<void> {
|
|
153
|
+
await this.proc.send(Method.RENAME, {
|
|
154
|
+
old_path: oldPath,
|
|
155
|
+
new_path: newPath,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async copy(src: string, dst: string, opts?: CopyOptions): Promise<void> {
|
|
160
|
+
await this.proc.send(Method.COPY, {
|
|
161
|
+
src,
|
|
162
|
+
dst,
|
|
163
|
+
recursive: opts?.recursive ?? false,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async chmod(path: string, mode: number): Promise<void> {
|
|
168
|
+
await this.proc.send(Method.CHMOD, { path, mode });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async exists(path: string): Promise<boolean> {
|
|
172
|
+
try {
|
|
173
|
+
await this.stat(path);
|
|
174
|
+
return true;
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
100
180
|
async checkpoint(name: string): Promise<void> {
|
|
101
181
|
await this.proc.send(Method.CHECKPOINT, { name });
|
|
102
182
|
this.stopped = true;
|
|
@@ -119,6 +199,7 @@ export function buildArgs(bin: string, opts: StartOptions): string[] {
|
|
|
119
199
|
if (opts.memory) args.push("--memory", String(opts.memory));
|
|
120
200
|
if (opts.diskSize) args.push("--disk-size", String(opts.diskSize));
|
|
121
201
|
if (opts.allowNet) args.push("--allow-net");
|
|
202
|
+
if (opts.allowHostWrites) args.push("--allow-host-writes");
|
|
122
203
|
|
|
123
204
|
if (opts.secrets) {
|
|
124
205
|
for (const [name, secret] of Object.entries(opts.secrets)) {
|
package/src/types.ts
CHANGED
|
@@ -16,7 +16,10 @@ export interface StartOptions {
|
|
|
16
16
|
memory?: number;
|
|
17
17
|
diskSize?: number;
|
|
18
18
|
allowNet?: boolean;
|
|
19
|
+
/** Allow :rw mounts to write to host filesystem. Default: false. */
|
|
20
|
+
allowHostWrites?: boolean;
|
|
19
21
|
ports?: string[];
|
|
22
|
+
/** Directory mounts. Key is host path, value is guest path or guest path with mode suffix (e.g. "/workspace" or "/workspace:rw"). */
|
|
20
23
|
mounts?: Record<string, string>;
|
|
21
24
|
secrets?: Record<string, SecretConfig>;
|
|
22
25
|
network?: NetworkConfig;
|
|
@@ -50,6 +53,36 @@ export interface FileChangeEvent {
|
|
|
50
53
|
event: "create" | "modify" | "delete" | "rename";
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
// --- Filesystem types ---
|
|
57
|
+
|
|
58
|
+
export interface DirEntry {
|
|
59
|
+
name: string;
|
|
60
|
+
type: "file" | "dir" | "symlink";
|
|
61
|
+
size: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface StatResult {
|
|
65
|
+
size: number;
|
|
66
|
+
mode: number;
|
|
67
|
+
/** Seconds since Unix epoch */
|
|
68
|
+
mtime: number;
|
|
69
|
+
isDir: boolean;
|
|
70
|
+
isFile: boolean;
|
|
71
|
+
isSymlink: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface MkdirOptions {
|
|
75
|
+
recursive?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface RemoveOptions {
|
|
79
|
+
recursive?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CopyOptions {
|
|
83
|
+
recursive?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
53
86
|
// --- JSON-RPC 2.0 wire types (internal) ---
|
|
54
87
|
|
|
55
88
|
export interface JsonRpcResult {
|