just-git 1.1.1 → 1.1.3

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 CHANGED
@@ -4,9 +4,9 @@
4
4
  [![npm](https://img.shields.io/npm/v/just-git)](https://www.npmjs.com/package/just-git)
5
5
  [![bundle size](https://img.shields.io/bundlejs/size/just-git)](https://bundlejs.com/?q=just-git)
6
6
 
7
- Pure TypeScript git implementation. Zero dependencies. 34 commands. Works in Node, Bun, Deno, and the browser. [Tested against real git](TESTING.md) across more than a million randomized operations, comparing repository state and command output at every step.
7
+ Pure TypeScript git implementation. Zero dependencies. 34 commands. Works in Node, Bun, Deno, Cloudflare Workers, and the browser. [Tested against real git](TESTING.md) across more than a million randomized operations.
8
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. Built to work with [just-bash](https://github.com/vercel-labs/just-bash), which provides a filesystem interface and shell that just-git registers into as a custom command, but can be used on its own.
9
+ Two entry points: a **virtual filesystem client** for sandboxed environments (pairs with [just-bash](https://github.com/vercel-labs/just-bash), or use standalone), and an **[embeddable git server](SERVER.md)** that any standard `git` client can clone, fetch, and push to.
10
10
 
11
11
  ## Install
12
12
 
@@ -16,26 +16,67 @@ npm install just-git
16
16
 
17
17
  ## Quick start
18
18
 
19
+ ### Client
20
+
21
+ Provide any `FileSystem` implementation and call `git.exec()`:
22
+
19
23
  ```ts
20
- import { Bash } from "just-bash";
21
24
  import { createGit } from "just-git";
22
25
 
23
- const git = createGit({
24
- identity: { name: "Alice", email: "alice@example.com" },
25
- });
26
+ const git = createGit({ identity: { name: "Alice", email: "alice@example.com" } });
27
+
28
+ await git.exec("git init", { fs, cwd: "/repo" });
29
+ await git.exec("git add .", { fs, cwd: "/repo" });
30
+ await git.exec('git commit -m "initial commit"', { fs, cwd: "/repo" });
31
+ await git.exec("git log --oneline", { fs, cwd: "/repo" });
32
+ ```
33
+
34
+ Tokenization handles single and double quotes. Pass `env` as a plain object when needed (e.g. `GIT_AUTHOR_NAME`).
35
+
36
+ For a full virtual shell with file I/O, pipes, and scripting, pair with [just-bash](https://github.com/vercel-labs/just-bash):
37
+
38
+ ```ts
39
+ import { Bash } from "just-bash";
40
+ import { createGit } from "just-git";
26
41
 
27
42
  const bash = new Bash({
28
43
  cwd: "/repo",
29
- customCommands: [git],
44
+ customCommands: [createGit({ identity: { name: "Alice", email: "alice@example.com" } })],
30
45
  });
31
46
 
32
- await bash.exec("git init");
33
47
  await bash.exec("echo 'hello' > README.md");
34
- await bash.exec("git add .");
35
- await bash.exec('git commit -m "initial commit"');
36
- await bash.exec("git log --oneline");
48
+ await bash.exec("git add . && git commit -m 'initial commit'");
49
+ ```
50
+
51
+ ### Server
52
+
53
+ Stand up a git server with SQLite-backed storage, branch protection, and push hooks:
54
+
55
+ ```ts
56
+ import { createGitServer, SqliteStorage } from "just-git/server";
57
+ import { Database } from "bun:sqlite";
58
+
59
+ const storage = new SqliteStorage(new Database("repos.sqlite"));
60
+
61
+ const server = createGitServer({
62
+ resolveRepo: (path) => storage.repo(path),
63
+ hooks: {
64
+ preReceive: ({ updates }) => {
65
+ if (updates.some((u) => u.ref === "refs/heads/main" && !u.isFF))
66
+ return { reject: true, message: "no force-push to main" };
67
+ },
68
+ postReceive: ({ repoPath, updates }) => {
69
+ console.log(`${repoPath}: ${updates.length} ref(s) updated`);
70
+ },
71
+ },
72
+ });
73
+
74
+ Bun.serve({ fetch: server.fetch });
75
+ // git clone http://localhost:3000/my-repo ← works with real git
37
76
  ```
38
77
 
78
+ Uses web-standard `Request`/`Response` — works with Bun, Hono, Cloudflare Workers, or any fetch-compatible runtime. See [SERVER.md](SERVER.md) for the full API.
79
+
39
80
  ## Options
40
81
 
41
82
  `createGit(options?)` accepts:
@@ -205,43 +246,6 @@ Concurrent pushes to the same remote are automatically serialized — if two age
205
246
 
206
247
  See [`examples/multi-agent.ts`](examples/multi-agent.ts) for a full working example with a coordinator agent that merges feature branches.
207
248
 
208
- ## Server
209
-
210
- `just-git/server` is an embeddable Git Smart HTTP server. Any standard git client can clone, fetch, and push. Uses web-standard `Request`/`Response` — works with Bun, Hono, Cloudflare Workers, or any fetch-compatible runtime.
211
-
212
- ```ts
213
- import { createGitServer, SqliteStorage } from "just-git/server";
214
- import { getChangedFiles } from "just-git/repo";
215
- import { Database } from "bun:sqlite";
216
-
217
- const storage = new SqliteStorage(new Database("repos.sqlite"));
218
-
219
- const server = createGitServer({
220
- resolveRepo: async (repoPath) => storage.repo(repoPath),
221
- hooks: {
222
- preReceive: async ({ updates }) => {
223
- for (const u of updates) {
224
- if (u.ref === "refs/heads/main" && !u.isFF && !u.isCreate) {
225
- return { reject: true, message: "no force-push to main" };
226
- }
227
- }
228
- },
229
- postReceive: async ({ repo, updates }) => {
230
- for (const u of updates) {
231
- const files = await getChangedFiles(repo, u.oldHash, u.newHash);
232
- console.log(`${u.ref}: ${files.length} files changed`);
233
- }
234
- },
235
- },
236
- });
237
-
238
- Bun.serve({ fetch: server.fetch });
239
- ```
240
-
241
- `SqliteStorage` persists repos in SQLite without a filesystem — works with `bun:sqlite`, `better-sqlite3`, or any compatible driver. Repos backed by `SqliteStorage` work with both the server (external HTTP) and `resolveRemote` (in-process), with CAS-protected ref updates ensuring correctness regardless of write path.
242
-
243
- See [SERVER.md](SERVER.md) for the full API: hooks, `createStandardHooks`, `SqliteStorage`, configuration, and deployment patterns. See [`examples/sqlite-server.ts`](examples/sqlite-server.ts) for a runnable SQLite server and [`examples/server.ts`](examples/server.ts) for a VFS-backed server with virtual client. See [`src/platform/`](src/platform/) for a reference implementation that builds GitHub-like functionality (repos, pull requests, merge strategies) on top of these primitives.
244
-
245
249
  ## Command coverage
246
250
 
247
251
  See [CLI.md](CLI.md) for full usage details.
@@ -305,27 +309,6 @@ Targets high fidelity to real git (2.53.0). Validated with an [oracle testing fr
305
309
 
306
310
  When backed by a real filesystem (e.g. just-bash `ReadWriteFs`), interoperable with real git on the same repo. Try `bun sandbox "git init"` to explore interactively.
307
311
 
308
- ## Without just-bash
309
-
310
- `git.execute()` takes an args array and a `CommandContext`. Provide any `FileSystem` implementation:
311
-
312
- ```ts
313
- import { createGit } from "just-git";
314
-
315
- const git = createGit({ identity: { name: "Bot", email: "bot@example.com" } });
316
-
317
- const result = await git.execute(["init"], {
318
- fs: myFileSystem, // any FileSystem implementation
319
- cwd: "/repo",
320
- env: new Map(),
321
- stdin: "",
322
- });
323
-
324
- console.log(result.exitCode); // 0
325
- ```
326
-
327
- The `FileSystem` interface requires: `readFile`, `readFileBuffer`, `writeFile`, `exists`, `stat`, `mkdir`, `readdir`, `rm`. Optional: `lstat`, `readlink`, `symlink`.
328
-
329
312
  ## Examples
330
313
 
331
314
  Runnable examples in [`examples/`](examples/):
package/dist/index.d.ts CHANGED
@@ -63,14 +63,39 @@ interface GitExtensions {
63
63
  objectStore?: ObjectStore;
64
64
  refStore?: RefStore;
65
65
  }
66
+ /** Simplified context for {@link Git.exec}. */
67
+ interface ExecContext {
68
+ fs: FileSystem;
69
+ cwd: string;
70
+ env?: Record<string, string>;
71
+ stdin?: string;
72
+ }
66
73
  declare class Git {
67
74
  readonly name = "git";
68
75
  private blocked;
69
76
  private hooks;
70
77
  private inner;
71
78
  constructor(options?: GitOptions);
79
+ /**
80
+ * Run a git command from a string.
81
+ *
82
+ * Tokenizes the input with basic shell quoting (single/double quotes).
83
+ * Strips a leading `git ` prefix if present. Does not support shell
84
+ * features like pipes, redirections, variable expansion, or `&&`.
85
+ *
86
+ * ```ts
87
+ * await git.exec('commit -m "initial commit"', { fs, cwd: "/repo" });
88
+ * ```
89
+ */
90
+ exec: (command: string, ctx: ExecContext) => Promise<ExecResult>;
72
91
  execute: (args: string[], ctx: CommandContext) => Promise<ExecResult>;
73
92
  }
93
+ /**
94
+ * Tokenize a command string with basic shell quoting.
95
+ * Supports single quotes, double quotes (with backslash escapes),
96
+ * and whitespace splitting. Strips a leading "git" token if present.
97
+ */
98
+ declare function tokenizeCommand(input: string): string[];
74
99
  declare function createGit(options?: GitOptions): Git;
75
100
 
76
101
  /**
@@ -81,4 +106,4 @@ declare function createGit(options?: GitOptions): Git;
81
106
  */
82
107
  declare function findRepo(fs: FileSystem, startPath: string): Promise<GitContext | null>;
83
108
 
84
- export { type CommandContext, type CommandExecOptions, CredentialProvider, ExecResult, FetchFunction, FileSystem, Git, type GitCommandName, GitContext, type GitExtensions, GitHooks, type GitOptions, IdentityOverride, NetworkPolicy, ObjectStore, RefStore, RemoteResolver, createGit, findRepo };
109
+ export { type CommandContext, type CommandExecOptions, CredentialProvider, type ExecContext, ExecResult, FetchFunction, FileSystem, Git, type GitCommandName, GitContext, type GitExtensions, GitHooks, type GitOptions, IdentityOverride, NetworkPolicy, ObjectStore, RefStore, RemoteResolver, createGit, findRepo, tokenizeCommand };