just-git 0.1.9 → 0.1.11

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.
Files changed (3) hide show
  1. package/README.md +99 -124
  2. package/dist/index.js +377 -367
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  [![CI](https://github.com/blindmansion/just-git/actions/workflows/ci.yml/badge.svg)](https://github.com/blindmansion/just-git/actions/workflows/ci.yml)
4
4
  [![npm](https://img.shields.io/npm/v/just-git)](https://www.npmjs.com/package/just-git)
5
+ [![bundle size](https://img.shields.io/bundlejs/size/just-git)](https://bundlejs.com/?q=just-git)
5
6
 
6
- Git implementation for virtual bash environments (particularly [just-bash](https://github.com/vercel-labs/just-bash)). Pure TypeScript, zero dependencies. 34 commands implemented. Works in Node, Bun, Deno, and the browser. ~100 kB gzipped.
7
+ Pure TypeScript git implementation. Zero dependencies. 34 commands. Works in Node, Bun, Deno, and the browser.
8
+
9
+ Designed for sandboxed environments where shelling out to real git isn't possible or desirable. Targets faithful reproduction of real git's behavior and output. Operates on an abstract `FileSystem` interface — plug in an in-memory VFS, a real filesystem, or anything else. Pairs with [just-bash](https://github.com/vercel-labs/just-bash), which provides an in-memory filesystem and shell that just-git registers into as a custom command.
7
10
 
8
11
  ## Install
9
12
 
@@ -37,28 +40,26 @@ await bash.exec("git log --oneline");
37
40
 
38
41
  `createGit(options?)` accepts:
39
42
 
40
- | Option | Description |
41
- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
- | `identity` | Author/committer override. With `locked: true`, always wins over env vars and git config. Without `locked`, acts as a fallback. |
43
- | `credentials` | `(url) => HttpAuth \| null` callback for Smart HTTP transport auth. |
44
- | `disabled` | `GitCommandName[]` of subcommands to block (e.g. `["push", "rebase"]`). |
45
- | `network` | `{ allowed?: string[], fetch?: FetchFunction }` to restrict HTTP access and/or provide a custom `fetch` implementation. Set to `false` to block all network access. |
46
- | `resolveRemote` | `(url) => GitContext \| null` callback for cross-VFS remote resolution. See [Multi-agent collaboration](#multi-agent-collaboration). |
43
+ | Option | Description |
44
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
45
+ | `identity` | Author/committer override. With `locked: true`, always wins over env vars and git config. Without `locked`, acts as a fallback. |
46
+ | `credentials` | `(url) => HttpAuth \| null` callback for Smart HTTP transport auth. |
47
+ | `disabled` | `GitCommandName[]` of subcommands to block (e.g. `["push", "rebase"]`). |
48
+ | `network` | `{ allowed?: string[], fetch?: FetchFunction }` to restrict HTTP access and/or provide a custom `fetch`. `allowed` accepts hostnames (`"github.com"`) or URL prefixes (`"https://github.com/myorg/"`). Set to `false` to block all network access. |
49
+ | `resolveRemote` | `(url) => GitContext \| null` callback for cross-VFS remote resolution. See [Multi-agent collaboration](#multi-agent-collaboration). |
47
50
 
48
51
  ```ts
49
52
  const git = createGit({
50
53
  identity: { name: "Agent Bot", email: "bot@company.com", locked: true },
51
54
  credentials: async (url) => ({ type: "bearer", token: "ghp_..." }),
52
55
  disabled: ["rebase"],
53
- network: { allowed: ["github.com"], fetch: customFetch },
56
+ network: false, // no HTTP access
54
57
  });
55
58
  ```
56
59
 
57
60
  ## Middleware
58
61
 
59
- Middleware wraps every `git <subcommand>` invocation. Each middleware receives a `CommandEvent` and a `next()` function. Call `next()` to proceed, or return an `ExecResult` to short-circuit. Middlewares compose in registration order (first registered = outermost).
60
-
61
- The `CommandEvent` provides the execution context: `{ command, rawArgs, fs, cwd, env, stdin }`, plus optional `exec` and `signal` when available.
62
+ Middleware wraps every `git <subcommand>` invocation. Each middleware receives a `CommandEvent` and a `next()` function. Call `next()` to proceed, or return an `ExecResult` to short-circuit. Middlewares compose in registration order (first registered = outermost). `git.use()` returns an unsubscribe function.
62
63
 
63
64
  ```ts
64
65
  // Audit log — record every command the agent runs
@@ -91,15 +92,11 @@ git.use(async (event, next) => {
91
92
  });
92
93
  ```
93
94
 
94
- `git.use()` returns an unsubscribe function to remove the middleware dynamically.
95
-
96
95
  ## Hooks
97
96
 
98
97
  Hooks fire at specific points inside command execution (after middleware, inside operation logic). Register with `git.on(event, handler)`, which returns an unsubscribe function.
99
98
 
100
- ### Pre-hooks
101
-
102
- Pre-hooks can abort the operation by returning `{ abort: true, message? }`.
99
+ Pre-hooks can abort the operation by returning `{ abort: true, message? }`. Post-hooks are observational — return value is ignored.
103
100
 
104
101
  ```ts
105
102
  // Block secrets from being committed
@@ -116,67 +113,16 @@ git.on("commit-msg", (event) => {
116
113
  return { abort: true, message: "Commit message must follow conventional commits format" };
117
114
  }
118
115
  });
119
- ```
120
-
121
- | Hook | Payload |
122
- | ------------------ | --------------------------------------------------------------- |
123
- | `pre-commit` | `{ index, treeHash }` |
124
- | `commit-msg` | `{ message }` (mutable) |
125
- | `merge-msg` | `{ message, treeHash, headHash, theirsHash }` (mutable message) |
126
- | `pre-merge-commit` | `{ mergeMessage, treeHash, headHash, theirsHash }` |
127
- | `pre-checkout` | `{ target, mode }` |
128
- | `pre-push` | `{ remote, url, refs[] }` |
129
- | `pre-fetch` | `{ remote, url, refspecs, prune, tags }` |
130
- | `pre-clone` | `{ repository, targetPath, bare, branch }` |
131
- | `pre-pull` | `{ remote, branch }` |
132
- | `pre-rebase` | `{ upstream, branch }` |
133
- | `pre-reset` | `{ mode, target }` |
134
- | `pre-clean` | `{ dryRun, force, removeDirs, removeIgnored, onlyIgnored }` |
135
- | `pre-rm` | `{ paths, cached, recursive, force }` |
136
- | `pre-cherry-pick` | `{ mode, commit }` |
137
- | `pre-revert` | `{ mode, commit }` |
138
- | `pre-stash` | `{ action, ref }` |
139
-
140
- ### Post-hooks
141
-
142
- Post-hooks are observational -- return value is ignored. Handlers are awaited in registration order.
143
116
 
144
- ```ts
145
117
  // Feed agent activity to your UI or orchestration layer
146
118
  git.on("post-commit", (event) => {
147
119
  onAgentCommit({ hash: event.hash, branch: event.branch, message: event.message });
148
120
  });
149
-
150
- git.on("post-push", (event) => {
151
- onAgentPush({ remote: event.remote, refs: event.refs });
152
- });
153
121
  ```
154
122
 
155
- | Hook | Payload |
156
- | ------------------ | ------------------------------------------------ |
157
- | `post-commit` | `{ hash, message, branch, parents, author }` |
158
- | `post-merge` | `{ headHash, theirsHash, strategy, commitHash }` |
159
- | `post-checkout` | `{ prevHead, newHead, isBranchCheckout }` |
160
- | `post-push` | same payload as `pre-push` |
161
- | `post-fetch` | `{ remote, url, refsUpdated }` |
162
- | `post-clone` | `{ repository, targetPath, bare, branch }` |
163
- | `post-pull` | `{ remote, branch, strategy, commitHash }` |
164
- | `post-reset` | `{ mode, targetHash }` |
165
- | `post-clean` | `{ removed, dryRun }` |
166
- | `post-rm` | `{ removedPaths, cached }` |
167
- | `post-cherry-pick` | `{ mode, commitHash, hadConflicts }` |
168
- | `post-revert` | `{ mode, commitHash, hadConflicts }` |
169
- | `post-stash` | `{ action, ok }` |
170
-
171
- ### Low-level events
172
-
173
- Fire-and-forget events emitted on every object/ref write. Handler errors are caught and forwarded to `hooks.onError` (no-op by default).
174
-
175
- | Event | Payload |
176
- | -------------- | --------------------------- |
177
- | `ref:update` | `{ ref, oldHash, newHash }` |
178
- | `ref:delete` | `{ ref, oldHash }` |
179
- | `object:write` | `{ type, hash }` |
123
+ Available pre-hooks: `pre-commit`, `commit-msg`, `merge-msg`, `pre-merge-commit`, `pre-checkout`, `pre-push`, `pre-fetch`, `pre-clone`, `pre-pull`, `pre-rebase`, `pre-reset`, `pre-clean`, `pre-rm`, `pre-cherry-pick`, `pre-revert`, `pre-stash`. Available post-hooks: `post-commit`, `post-merge`, `post-checkout`, `post-push`, `post-fetch`, `post-clone`, `post-pull`, `post-reset`, `post-clean`, `post-rm`, `post-cherry-pick`, `post-revert`, `post-stash`. Low-level events: `ref:update`, `ref:delete`, `object:write`.
124
+
125
+ See [HOOKS.md](HOOKS.md) for full payload types and the `CommandEvent` shape.
180
126
 
181
127
  ## Multi-agent collaboration
182
128
 
@@ -200,22 +146,33 @@ await setupBash.exec("echo 'hello' > README.md");
200
146
  await setupBash.exec("git add . && git commit -m 'initial'");
201
147
 
202
148
  const originCtx = await findGitDir(originFs, "/repo");
149
+ const resolve = (url: string) => (url === "/origin" ? originCtx : null);
203
150
 
204
151
  // Each agent gets its own filesystem + resolveRemote pointing to origin
205
- function createAgent(name: string, email: string) {
206
- const agentFs = new InMemoryFs();
207
- const git = createGit({
208
- identity: { name, email, locked: true },
209
- resolveRemote: (url) => (url === "/origin" ? originCtx : null),
210
- });
211
- return new Bash({ fs: agentFs, cwd: "/repo", customCommands: [git] });
212
- }
152
+ const alice = new Bash({
153
+ fs: new InMemoryFs(),
154
+ cwd: "/repo",
155
+ customCommands: [
156
+ createGit({
157
+ identity: { name: "Alice", email: "alice@example.com", locked: true },
158
+ resolveRemote: resolve,
159
+ }),
160
+ ],
161
+ });
213
162
 
214
- const alice = createAgent("Alice", "alice@example.com");
215
- const bob = createAgent("Bob", "bob@example.com");
163
+ const bob = new Bash({
164
+ fs: new InMemoryFs(),
165
+ cwd: "/repo",
166
+ customCommands: [
167
+ createGit({
168
+ identity: { name: "Bob", email: "bob@example.com", locked: true },
169
+ resolveRemote: resolve,
170
+ }),
171
+ ],
172
+ });
216
173
 
217
- await alice.exec("git clone /origin /repo", { cwd: "/" });
218
- await bob.exec("git clone /origin /repo", { cwd: "/" });
174
+ await alice.exec("git clone /origin /repo");
175
+ await bob.exec("git clone /origin /repo");
219
176
 
220
177
  // Alice and Bob work independently, push to origin, fetch each other's changes
221
178
  ```
@@ -228,46 +185,47 @@ See [`examples/multi-agent.ts`](examples/multi-agent.ts) for a full working exam
228
185
 
229
186
  See [CLI.md](CLI.md) for full usage details.
230
187
 
231
- | Command | Flags / options |
232
- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
233
- | `init [<dir>]` | `--bare`, `--initial-branch` |
234
- | `clone <repo> [<dir>]` | `--bare`, `-b <branch>` |
235
- | `blame <file>` | `-L <start>,<end>`, `-l`/`--long`, `-e`/`--show-email`, `-s`/`--suppress`, `-p`/`--porcelain`, `--line-porcelain` |
236
- | `add <paths>` | `.`, `--all`/`-A`, `--update`/`-u`, `--force`/`-f`, `-n`/`--dry-run`, glob pathspecs |
237
- | `rm <paths>` | `--cached`, `-r`, `-f`, `-n`/`--dry-run`, glob pathspecs |
238
- | `mv <src> <dst>` | `-f`, `-n`/`--dry-run`, `-k` |
239
- | `commit` | `-m`, `-F <file>` / `-F -`, `--allow-empty`, `--amend`, `--no-edit`, `-a` |
240
- | `status` | `-s`/`--short`, `--porcelain`, `-b`/`--branch` |
241
- | `log` | `--oneline`, `-n`, `--all`, `--reverse`, `--decorate`, `--format`/`--pretty`, `A..B`, `A...B`, `-- <path>`, `--author=`, `--grep=`, `--since`/`--after`, `--until`/`--before` |
242
- | `show [<object>]` | Commits (with diff), annotated tags, trees, blobs |
243
- | `diff` | `--cached`/`--staged`, `<commit>`, `<commit> <commit>`, `A..B`, `A...B`, `-- <path>`, `--stat`, `--shortstat`, `--numstat`, `--name-only`, `--name-status` |
244
- | `branch` | `-d`, `-D`, `-m`, `-M`, `-r`, `-a`/`--all`, `-v`/`-vv`, `-u`/`--set-upstream-to` |
245
- | `tag [<name>] [<commit>]` | `-a -m` (annotated), `-d`, `-l <pattern>`, `-f` |
246
- | `switch` | `-c`/`-C` (create/force-create), `--detach`/`-d`, `--orphan`, `-` (previous branch), `--guess`/`--no-guess` |
247
- | `restore` | `-s`/`--source`, `-S`/`--staged`, `-W`/`--worktree`, `-S -W` (both), `--ours`/`--theirs`, pathspec globs |
248
- | `checkout` | `-b`, `-B`, `--orphan`, detached HEAD, `-- <paths>`, `--ours`/`--theirs`, pathspec globs |
249
- | `reset [<commit>]` | `-- <paths>`, `--soft`, `--mixed`, `--hard`, pathspec globs |
250
- | `merge <branch>` | `--no-ff`, `--ff-only`, `--squash`, `-m`, `--abort`, `--continue`, conflict markers |
251
- | `revert <commit>` | `--abort`, `--continue`, `-n`/`--no-commit`, `--no-edit`, `-m`/`--mainline` |
252
- | `cherry-pick <commit>` | `--abort`, `--continue`, `--skip`, `-x`, `-m`/`--mainline`, `-n`/`--no-commit`, preserves original author |
253
- | `rebase <upstream>` | `--onto <newbase>`, `--abort`, `--continue`, `--skip` |
254
- | `stash` | `push`, `pop`, `apply`, `list`, `drop`, `show`, `clear`, `-m`, `-u`/`--include-untracked`, `stash@{N}` |
255
- | `remote` | `add`, `remove`/`rm`, `rename`, `set-url`, `get-url`, `-v` |
256
- | `config` | `get`, `set`, `unset`, `list`, `--list`/`-l`, `--unset` |
257
- | `fetch [<remote>] [<refspec>...]` | `--all`, `--tags`, `--prune`/`-p` |
258
- | `push [<remote>] [<refspec>...]` | `--force`/`-f`, `-u`/`--set-upstream`, `--all`, `--tags`, `--delete`/`-d` |
259
- | `pull [<remote>] [<branch>]` | `--ff-only`, `--no-ff`, `--rebase`/`-r`, `--no-rebase` |
260
- | `bisect` | `start`, `bad`/`good`/`new`/`old`, `skip`, `reset`, `log`, `replay`, `run`, `terms`, `visualize`/`view`, `--term-new`/`--term-old`, `--no-checkout`, `--first-parent` |
261
- | `clean` | `-f`, `-n`/`--dry-run`, `-d`, `-x`, `-X`, `-e`/`--exclude` |
262
- | `reflog` | `show [<ref>]`, `exists`, `-n`/`--max-count` |
263
- | `gc` | `--aggressive` |
264
- | `repack` | `-a`/`--all`, `-d`/`--delete` |
265
- | `rev-parse` | `--verify`, `--short`, `--abbrev-ref`, `--symbolic-full-name`, `--show-toplevel`, `--git-dir`, `--is-inside-work-tree`, `--is-bare-repository`, `--show-prefix`, `--show-cdup` |
266
- | `ls-files` | `-c`/`--cached`, `-m`/`--modified`, `-d`/`--deleted`, `-o`/`--others`, `-u`/`--unmerged`, `-s`/`--stage`, `--exclude-standard`, `-z`, `-t` |
188
+ | Command | Flags / options |
189
+ | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
190
+ | `init [<dir>]` | `--bare`, `--initial-branch` |
191
+ | `clone <repo> [<dir>]` | `--bare`, `-b <branch>` |
192
+ | `blame <file>` | `-L <start>,<end>`, `-l`/`--long`, `-e`/`--show-email`, `-s`/`--suppress`, `-p`/`--porcelain`, `--line-porcelain` |
193
+ | `add <paths>` | `.`, `--all`/`-A`, `--update`/`-u`, `--force`/`-f`, `-n`/`--dry-run`, glob pathspecs |
194
+ | `rm <paths>` | `--cached`, `-r`, `-f`, `-n`/`--dry-run`, glob pathspecs |
195
+ | `mv <src> <dst>` | `-f`, `-n`/`--dry-run`, `-k` |
196
+ | `commit` | `-m`, `-F <file>` / `-F -`, `--allow-empty`, `--amend`, `--no-edit`, `-a` |
197
+ | `status` | `-s`/`--short`, `--porcelain`, `-b`/`--branch` |
198
+ | `log` | `--oneline`, `-n`, `--all`, `--reverse`, `--decorate`, `--format`/`--pretty`, `-p`/`--patch`, `--stat`, `--name-status`, `--name-only`, `--shortstat`, `--numstat`, `A..B`, `A...B`, `-- <path>`, `--author=`, `--grep=`, `--since`/`--after`, `--until`/`--before` |
199
+ | `show [<object>]` | Commits (with diff), annotated tags, trees, blobs |
200
+ | `diff` | `--cached`/`--staged`, `<commit>`, `<commit> <commit>`, `A..B`, `A...B`, `-- <path>`, `--stat`, `--shortstat`, `--numstat`, `--name-only`, `--name-status` |
201
+ | `branch` | `-d`, `-D`, `-m`, `-M`, `-r`, `-a`/`--all`, `-v`/`-vv`, `-u`/`--set-upstream-to` |
202
+ | `tag [<name>] [<commit>]` | `-a -m` (annotated), `-d`, `-l <pattern>`, `-f` |
203
+ | `switch` | `-c`/`-C` (create/force-create), `--detach`/`-d`, `--orphan`, `-` (previous branch), `--guess`/`--no-guess` |
204
+ | `restore` | `-s`/`--source`, `-S`/`--staged`, `-W`/`--worktree`, `-S -W` (both), `--ours`/`--theirs`, pathspec globs |
205
+ | `checkout` | `-b`, `-B`, `--orphan`, detached HEAD, `-- <paths>`, `--ours`/`--theirs`, pathspec globs |
206
+ | `reset [<commit>]` | `-- <paths>`, `--soft`, `--mixed`, `--hard`, pathspec globs |
207
+ | `merge <branch>` | `--no-ff`, `--ff-only`, `--squash`, `-m`, `--abort`, `--continue`, conflict markers |
208
+ | `revert <commit>` | `--abort`, `--continue`, `-n`/`--no-commit`, `--no-edit`, `-m`/`--mainline` |
209
+ | `cherry-pick <commit>` | `--abort`, `--continue`, `--skip`, `-x`, `-m`/`--mainline`, `-n`/`--no-commit`, preserves original author |
210
+ | `rebase <upstream>` | `--onto <newbase>`, `--abort`, `--continue`, `--skip` |
211
+ | `stash` | `push`, `pop`, `apply`, `list`, `drop`, `show`, `clear`, `-m`, `-u`/`--include-untracked`, `stash@{N}` |
212
+ | `remote` | `add`, `remove`/`rm`, `rename`, `set-url`, `get-url`, `-v` |
213
+ | `config` | `get`, `set`, `unset`, `list`, `--list`/`-l`, `--unset` |
214
+ | `fetch [<remote>] [<refspec>...]` | `--all`, `--tags`, `--prune`/`-p` |
215
+ | `push [<remote>] [<refspec>...]` | `--force`/`-f`, `-u`/`--set-upstream`, `--all`, `--tags`, `--delete`/`-d` |
216
+ | `pull [<remote>] [<branch>]` | `--ff-only`, `--no-ff`, `--rebase`/`-r`, `--no-rebase` |
217
+ | `bisect` | `start`, `bad`/`good`/`new`/`old`, `skip`, `reset`, `log`, `replay`, `run`, `terms`, `visualize`/`view`, `--term-new`/`--term-old`, `--no-checkout`, `--first-parent` |
218
+ | `clean` | `-f`, `-n`/`--dry-run`, `-d`, `-x`, `-X`, `-e`/`--exclude` |
219
+ | `reflog` | `show [<ref>]`, `exists`, `-n`/`--max-count` |
220
+ | `gc` | `--aggressive` |
221
+ | `repack` | `-a`/`--all`, `-d`/`--delete` |
222
+ | `rev-parse` | `--verify`, `--short`, `--abbrev-ref`, `--symbolic-full-name`, `--show-toplevel`, `--git-dir`, `--is-inside-work-tree`, `--is-bare-repository`, `--show-prefix`, `--show-cdup` |
223
+ | `ls-files` | `-c`/`--cached`, `-m`/`--modified`, `-d`/`--deleted`, `-o`/`--others`, `-u`/`--unmerged`, `-s`/`--stage`, `--exclude-standard`, `-z`, `-t` |
267
224
 
268
225
  ### Transport
269
226
 
270
227
  - **Local paths** -- direct filesystem transfer between repositories.
228
+ - **Cross-VFS** -- clone, fetch, and push between isolated in-memory filesystems via `resolveRemote`. See [Multi-agent collaboration](#multi-agent-collaboration).
271
229
  - **Smart HTTP** -- clone, fetch, and push against real Git servers (e.g. GitHub) via Git Smart HTTP protocol. Auth via `credentials` option or `GIT_HTTP_BEARER_TOKEN` / `GIT_HTTP_USER` + `GIT_HTTP_PASSWORD` env vars.
272
230
 
273
231
  ### Internals
@@ -280,12 +238,29 @@ See [CLI.md](CLI.md) for full usage details.
280
238
  - Packfiles with zlib compression for storage and transport
281
239
  - Pathspec globs across `add`, `rm`, `diff`, `reset`, `checkout`, `restore`, `log`
282
240
 
283
- ## Goals and testing
241
+ ## Testing
242
+
243
+ Targets high fidelity to real git (2.53.0). Tested with an [oracle framework](test/oracle/README.md) that generates hundreds of randomized git workflows totaling hundreds of thousands of operations, runs them against real git, then replays each step against just-git and compares repository state and command output. State comparison covers HEAD, refs, index, worktree, active operation state, and stash. Output comparison covers exit codes, stdout, and stderr.
244
+
245
+ When backed by a real filesystem (e.g. just-bash `ReadWriteFs`), interoperable with real git on the same repo, though less extensively tested than behavioral correctness.
246
+
247
+ ## Without just-bash
284
248
 
285
- High fidelity to real git (2.53.0) state and output. Tested using real git as an [oracle](test/oracle/README.md) hundreds of randomized traces totaling hundreds of thousands of git operations, each verified step-by-step against real git's state and output.
249
+ `git.execute()` takes an args array and a `CommandContext`. Provide any `FileSystem` implementation:
286
250
 
287
- When backed by a real filesystem (e.g. `just-bash` `ReadWriteFs`), interoperable with real git on the same repo, though less extensively tested than behavioral correctness.
251
+ ```ts
252
+ import { createGit } from "just-git";
288
253
 
289
- ## Disclaimer
254
+ const git = createGit({ identity: { name: "Bot", email: "bot@example.com" } });
255
+
256
+ const result = await git.execute(["init"], {
257
+ fs: myFileSystem, // any FileSystem implementation
258
+ cwd: "/repo",
259
+ env: new Map(),
260
+ stdin: "",
261
+ });
262
+
263
+ console.log(result.exitCode); // 0
264
+ ```
290
265
 
291
- This project is not affiliated with [just-bash](https://github.com/vercel-labs/just-bash) or Vercel.
266
+ The `FileSystem` interface requires: `readFile`, `readFileBuffer`, `writeFile`, `exists`, `stat`, `mkdir`, `readdir`, `rm`. Optional: `lstat`, `readlink`, `symlink`.