@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.
- package/README.md +341 -0
- package/bin/bit.mjs +11 -0
- package/bit.cjs +30712 -0
- package/bit.js +8 -0
- package/lib.d.ts +604 -0
- package/lib.js +1261 -0
- package/lib.raw.js +58436 -0
- 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);
|