@mizchi/bit 0.43.2

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 (8) hide show
  1. package/README.md +341 -0
  2. package/bin/bit.mjs +11 -0
  3. package/bit.cjs +30712 -0
  4. package/bit.js +8 -0
  5. package/lib.d.ts +604 -0
  6. package/lib.js +1261 -0
  7. package/lib.raw.js +58436 -0
  8. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,341 @@
1
+ # @mizchi/bit
2
+
3
+ Experimental Git implementation in MoonBit.
4
+
5
+ This package exposes:
6
+
7
+ - `bit` CLI
8
+ - a tree-shakable JS library API at `@mizchi/bit`
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @mizchi/bit
14
+ ```
15
+
16
+ ## CLI
17
+
18
+ ```bash
19
+ npx bit init
20
+ npx bit status
21
+ npx bit add hello.txt
22
+ npx bit commit -m "initial commit"
23
+ ```
24
+
25
+ Or use the installed binary:
26
+
27
+ ```bash
28
+ bit init
29
+ bit status
30
+ ```
31
+
32
+ ## JS Library
33
+
34
+ The library API is designed for bundlers.
35
+ It does not rely on `registerRuntime()`.
36
+ Use named imports so unused operations can be tree-shaken.
37
+ Every operation accepts your backend as the first argument.
38
+
39
+ ```ts
40
+ import {
41
+ add,
42
+ type BitBackend,
43
+ branchList,
44
+ buildCommitPayload,
45
+ commit,
46
+ commitAmend,
47
+ commitSignedChecked,
48
+ commitWithSigner,
49
+ createFetchTransport,
50
+ fetch,
51
+ init,
52
+ listRemotesVerbose,
53
+ log,
54
+ merge,
55
+ relayListClonePeers,
56
+ relayResolveRemote,
57
+ push,
58
+ rebaseStart,
59
+ resolveSshEd25519PublicKey,
60
+ signGitPayloadSshEd25519,
61
+ status,
62
+ statusText,
63
+ switchBranch,
64
+ verifyCommitSshEd25519,
65
+ verifyGitPayloadSshEd25519,
66
+ writeString,
67
+ } from "@mizchi/bit";
68
+
69
+ declare const backend: BitBackend;
70
+
71
+ init(backend, "/repo", "main");
72
+ writeString(backend, "/repo/hello.txt", "hello\n");
73
+ console.log(status(backend, "/repo").untracked);
74
+
75
+ add(backend, "/repo", ["hello.txt"]);
76
+ const commitId = commit(
77
+ backend,
78
+ "/repo",
79
+ "initial commit",
80
+ "Example <example@example.com>",
81
+ 1700000000,
82
+ );
83
+
84
+ console.log(commitId);
85
+ console.log(branchList(backend, "/repo"));
86
+ console.log(log(backend, "/repo", 10));
87
+
88
+ const mergeResult = merge(
89
+ backend,
90
+ "/repo",
91
+ "feature",
92
+ "merge feature",
93
+ "Example <example@example.com>",
94
+ );
95
+ console.log(mergeResult.status);
96
+
97
+ const rebaseResult = rebaseStart(backend, "/repo", "origin/main");
98
+ console.log(rebaseResult.status);
99
+
100
+ switchBranch(backend, "/repo", "feature", { create: true });
101
+ const amendedCommitId = commitAmend(
102
+ backend,
103
+ "/repo",
104
+ "amended commit",
105
+ "Example <example@example.com>",
106
+ 1700000002,
107
+ );
108
+ console.log(await statusText(backend, "/repo"));
109
+ console.log(listRemotesVerbose(backend, "/repo"));
110
+
111
+ const payload = buildCommitPayload(
112
+ backend,
113
+ "/repo",
114
+ "signed commit",
115
+ "Example <example@example.com>",
116
+ 1700000001,
117
+ );
118
+ const privateKeyPem = [
119
+ "-----BEGIN PRIVATE KEY-----",
120
+ "...",
121
+ "-----END PRIVATE KEY-----",
122
+ ].join("\n");
123
+ const publicKey = await resolveSshEd25519PublicKey(privateKeyPem);
124
+ const signature = await signGitPayloadSshEd25519(privateKeyPem, payload);
125
+
126
+ const signedCommitId = commitSignedChecked(
127
+ backend,
128
+ "/repo",
129
+ "signed commit",
130
+ "Example <example@example.com>",
131
+ payload,
132
+ signature,
133
+ 1700000001,
134
+ );
135
+
136
+ const signedCommitIdFromSigner = await commitWithSigner(
137
+ backend,
138
+ "/repo",
139
+ "signed commit",
140
+ "Example <example@example.com>",
141
+ (payloadToSign) => signGitPayloadSshEd25519(privateKeyPem, payloadToSign),
142
+ 1700000001,
143
+ );
144
+
145
+ console.log(payload);
146
+ console.log(signedCommitId);
147
+ console.log(signedCommitIdFromSigner);
148
+ console.log(await verifyGitPayloadSshEd25519(publicKey, payload, signature));
149
+ console.log(await verifyCommitSshEd25519(backend, "/repo", "HEAD", publicKey));
150
+
151
+ const transport = createFetchTransport(fetch);
152
+ const fetched = await fetch(
153
+ backend,
154
+ "/repo",
155
+ "https://github.com/example/repo.git",
156
+ transport,
157
+ { refspec: "main" },
158
+ );
159
+ console.log(fetched);
160
+
161
+ await push(
162
+ backend,
163
+ "/repo",
164
+ "https://github.com/example/repo.git",
165
+ transport,
166
+ { refname: "refs/heads/main" },
167
+ );
168
+ ```
169
+
170
+ ## Relay Remotes
171
+
172
+ When your client connects to peers through `bit-relay`, pass the relay URL directly.
173
+ `fetch()` and `push()` will resolve the relay remote first, then talk to the selected peer.
174
+
175
+ ```ts
176
+ import {
177
+ createFetchTransport,
178
+ fetch,
179
+ push,
180
+ relayListClonePeers,
181
+ relayResolveRemote,
182
+ } from "@mizchi/bit";
183
+
184
+ const transport = createFetchTransport(globalThis.fetch);
185
+
186
+ const peers = await relayListClonePeers(
187
+ "relay+https://relay.example.com/base?room=team-a&room_token=token-1",
188
+ transport,
189
+ { authToken: "relay-auth-token" },
190
+ );
191
+
192
+ const resolved = await relayResolveRemote(
193
+ "relay+https://relay.example.com/base?room=team-a",
194
+ transport,
195
+ { preferredSender: "node-b" },
196
+ );
197
+
198
+ await fetch(
199
+ backend,
200
+ "/repo",
201
+ "relay+https://relay.example.com/base?room=team-a",
202
+ transport,
203
+ {
204
+ refspec: "main",
205
+ preferredRepo: "repo-b",
206
+ authToken: "relay-auth-token",
207
+ },
208
+ );
209
+
210
+ await push(
211
+ backend,
212
+ "/repo",
213
+ "relay+https://relay.example.com/base/AbCd1234",
214
+ transport,
215
+ { refname: "refs/heads/main" },
216
+ );
217
+
218
+ console.log(peers);
219
+ console.log(resolved.url);
220
+ ```
221
+
222
+ Supported relay forms:
223
+
224
+ - `relay+https://relay.example.com/base?room=team-a`
225
+ - `relay+http://127.0.0.1:8787`
226
+ - `relay://relay.example.com`
227
+ - direct session URL such as `relay+https://relay.example.com/base/AbCd1234`
228
+
229
+ For direct session URLs, the library resolves to the relay proxy path `/git/<sessionId>`.
230
+ For relay base URLs, the library polls clone announcements and selects a peer by:
231
+
232
+ 1. `preferredSender`
233
+ 2. `preferredRepo`
234
+ 3. first available sender
235
+
236
+ ## Backend Contract
237
+
238
+ You can pass any backend object directly as the first argument.
239
+ The backend contract is synchronous.
240
+
241
+ ```ts
242
+ import { init, writeString, add, commit } from "@mizchi/bit";
243
+
244
+ const backend = {
245
+ mkdirP(path) {},
246
+ writeFile(path, content) {},
247
+ writeString(path, content) {},
248
+ removeFile(path) {},
249
+ removeDir(path) {},
250
+ readFile(path) {
251
+ return new Uint8Array();
252
+ },
253
+ readdir(path) {
254
+ return [];
255
+ },
256
+ isDir(path) {
257
+ return false;
258
+ },
259
+ isFile(path) {
260
+ return false;
261
+ },
262
+ };
263
+
264
+ init(backend, "/repo", "main");
265
+ writeString(backend, "/repo/hello.txt", "hello\n");
266
+ add(backend, "/repo", ["hello.txt"]);
267
+ commit(backend, "/repo", "initial commit", "Example <example@example.com>");
268
+ ```
269
+
270
+ Required backend methods:
271
+
272
+ - `mkdirP(path)`
273
+ - `writeFile(path, bytes)`
274
+ - `writeString(path, content)`
275
+ - `removeFile(path)`
276
+ - `removeDir(path)`
277
+ - `readFile(path)`
278
+ - `readdir(path)`
279
+ - `isDir(path)`
280
+ - `isFile(path)`
281
+
282
+ `readFile(path)` may return:
283
+
284
+ - `Uint8Array`
285
+ - `ArrayBuffer`
286
+ - `ArrayLike<number>`
287
+ - `string`
288
+
289
+ ## API Notes
290
+
291
+ - Every operation takes the backend as its first argument.
292
+ - `createMemoryBackend()` returns an in-memory backend for tests, workers, and browser-style environments.
293
+ - There is no `createHost()` wrapper API. Pass the backend object itself through your call sites.
294
+ - `buildCommitPayload()` returns the unsigned commit payload text you should hand to your signer.
295
+ - `commitSigned()` writes a signed commit from an armored signature string.
296
+ - `commitSignedChecked()` writes a signed commit only if the supplied payload still matches the current index/tree state.
297
+ - `commitWithSigner()` is the ergonomic async wrapper that signs `buildCommitPayload()` output and finalizes through `commitSignedChecked()`.
298
+ - `resolveSshEd25519PublicKey()` derives an OpenSSH public key line from a PKCS#8 Ed25519 private key PEM.
299
+ - `signGitPayloadSshEd25519()` signs commit payload text and returns Git-compatible `-----BEGIN SSH SIGNATURE-----` armor.
300
+ - `verifyGitPayloadSshEd25519()` verifies a payload/signature pair against an OpenSSH `ssh-ed25519 ...` public key line.
301
+ - `verifyCommitSshEd25519()` extracts `gpgsig` from a commit and verifies it against an OpenSSH `ssh-ed25519 ...` public key line.
302
+ - JS signed-commit support currently targets `SSH + Ed25519 + commit + SHA-1 repo` only.
303
+ - JS verification is cryptographic verification against the supplied public key only. Git trust policy such as `allowedSignersFile`, principals, and revocation is not applied.
304
+ - `commitAmend()` exposes `git commit --amend` style history replacement.
305
+ - `revParse()` and `showRef()` expose ref resolution helpers.
306
+ - `merge()` resolves a revision string such as `feature`, `origin/main`, or `HEAD^`.
307
+ - `rebaseStart()` / `rebaseStartWithOnto()` / `rebaseContinue()` / `rebaseAbort()` / `rebaseSkip()` expose the rebase state machine.
308
+ - `statusText()` exposes the human-readable porcelain status summary.
309
+ - `restore()` and `reset()` expose the working tree/index reset flow.
310
+ - `switchBranch()` exposes branch switching with `{ create, checkoutFiles }`.
311
+ - `branchRename()` exposes `git branch -m` style renames.
312
+ - `tagList()` / `tagCreateLightweight()` / `tagCreateAnnotated()` / `tagDelete()` expose tag operations.
313
+ - `stashList()` / `stashPush()` / `stashApply()` / `stashPop()` / `stashDrop()` expose stash operations.
314
+ - `cherryPick()` exposes single-commit cherry-pick with `noCommit`, `messageSuffix`, and `signoffCommitter` options.
315
+ - `diffWorktree()` / `diffWorktreeStat()` and `diffIndex()` / `diffIndexStat()` return unified diff lines and `--stat`-style summaries.
316
+ - `listRemotes()`, `listRemotesVerbose()`, and `getRemoteUrl()` expose read-only remote config helpers.
317
+ - `sparseCheckoutInit()` / `sparseCheckoutSet()` / `sparseCheckoutAdd()` / `sparseCheckoutDisable()` / `sparseCheckoutReapply()` expose sparse checkout control.
318
+ - `sparseCheckoutEnabled()`, `sparseCheckoutConeEnabled()`, `sparseCheckoutPatterns()`, and `sparseCheckoutDisplayPatterns()` expose sparse checkout state.
319
+ - `fetch()` and `push()` are async and require an injected transport. Use `createFetchTransport(fetch)` for browser or worker `fetch`.
320
+ - `fetch()` and `push()` also understand relay signaling remotes and will resolve a peer before using Git smart HTTP.
321
+ - `relayListClonePeers()` returns `{ sender, cloneUrl, repo }[]`.
322
+ - `relayResolveRemote()` returns `{ via, url, sender, repo, sessionId }`.
323
+ - `status()` returns camelCase fields such as `stagedAdded`, `stagedDeleted`, and `untracked`.
324
+ - `branchList()` returns `{ name, commitId, isCurrent }`.
325
+ - `log()` returns `{ id, author, message, timestamp }`.
326
+ - `rm()` accepts `rm(backend, root, paths, { cached: true })`.
327
+
328
+ ## Bundle Size
329
+
330
+ A minimal bundle that exercises only `init/writeString/status` measured approximately:
331
+
332
+ - raw: about `139 KB`
333
+ - gzip: about `34 KB`
334
+
335
+ A representative bundle that exercises `init/add/status/commit/branch/checkout/mv/rm/log`
336
+ measured approximately:
337
+
338
+ - raw: about `256 KB`
339
+ - gzip: about `63 KB`
340
+
341
+ Exact size depends on your entrypoint and bundler settings.
package/bin/bit.mjs ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, join } from "node:path";
5
+
6
+ const require = createRequire(import.meta.url);
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const bitCjs = join(__dirname, "..", "bit.cjs");
9
+
10
+ // MoonBit CLI output assumes CommonJS even when the package itself is ESM.
11
+ require(bitCjs);