okf-catalog 0.1.0

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 (107) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +10 -0
  4. package/README.md +87 -0
  5. package/dist/bundle/cat-file.js +94 -0
  6. package/dist/bundle/cat-file.js.map +1 -0
  7. package/dist/bundle/contract.js +60 -0
  8. package/dist/bundle/contract.js.map +1 -0
  9. package/dist/bundle/frontmatter.js +106 -0
  10. package/dist/bundle/frontmatter.js.map +1 -0
  11. package/dist/bundle/git-tree.js +109 -0
  12. package/dist/bundle/git-tree.js.map +1 -0
  13. package/dist/bundle/index-file.js +90 -0
  14. package/dist/bundle/index-file.js.map +1 -0
  15. package/dist/bundle/links.js +57 -0
  16. package/dist/bundle/links.js.map +1 -0
  17. package/dist/bundle/load.js +363 -0
  18. package/dist/bundle/load.js.map +1 -0
  19. package/dist/bundle/manifest.js +95 -0
  20. package/dist/bundle/manifest.js.map +1 -0
  21. package/dist/bundle/markdown.js +210 -0
  22. package/dist/bundle/markdown.js.map +1 -0
  23. package/dist/bundle/model.js +7 -0
  24. package/dist/bundle/model.js.map +1 -0
  25. package/dist/bundle/page.js +374 -0
  26. package/dist/bundle/page.js.map +1 -0
  27. package/dist/bundle/paths.js +36 -0
  28. package/dist/bundle/paths.js.map +1 -0
  29. package/dist/bundle/reserved.js +56 -0
  30. package/dist/bundle/reserved.js.map +1 -0
  31. package/dist/bundle/timestamp.js +49 -0
  32. package/dist/bundle/timestamp.js.map +1 -0
  33. package/dist/catalog/model.js +32 -0
  34. package/dist/catalog/model.js.map +1 -0
  35. package/dist/catalog/nearest.js +56 -0
  36. package/dist/catalog/nearest.js.map +1 -0
  37. package/dist/catalog/outputs.js +438 -0
  38. package/dist/catalog/outputs.js.map +1 -0
  39. package/dist/catalog/provenance.js +37 -0
  40. package/dist/catalog/provenance.js.map +1 -0
  41. package/dist/catalog/runtime.js +2 -0
  42. package/dist/catalog/runtime.js.map +1 -0
  43. package/dist/catalog/text.js +140 -0
  44. package/dist/catalog/text.js.map +1 -0
  45. package/dist/cli.js +81 -0
  46. package/dist/cli.js.map +1 -0
  47. package/dist/commands/check.js +155 -0
  48. package/dist/commands/check.js.map +1 -0
  49. package/dist/commands/pack.js +182 -0
  50. package/dist/commands/pack.js.map +1 -0
  51. package/dist/commands/serve.js +452 -0
  52. package/dist/commands/serve.js.map +1 -0
  53. package/dist/config/company-config.js +272 -0
  54. package/dist/config/company-config.js.map +1 -0
  55. package/dist/derive/derived-document.js +42 -0
  56. package/dist/derive/derived-document.js.map +1 -0
  57. package/dist/engine/qmd-render.js +54 -0
  58. package/dist/engine/qmd-render.js.map +1 -0
  59. package/dist/engine/qmd.js +241 -0
  60. package/dist/engine/qmd.js.map +1 -0
  61. package/dist/fs/cache-dir.js +140 -0
  62. package/dist/fs/cache-dir.js.map +1 -0
  63. package/dist/fs/company-lock.js +162 -0
  64. package/dist/fs/company-lock.js.map +1 -0
  65. package/dist/fs/walk.js +168 -0
  66. package/dist/fs/walk.js.map +1 -0
  67. package/dist/log.js +65 -0
  68. package/dist/log.js.map +1 -0
  69. package/dist/mcp/server.js +30 -0
  70. package/dist/mcp/server.js.map +1 -0
  71. package/dist/mcp/stdio.js +45 -0
  72. package/dist/mcp/stdio.js.map +1 -0
  73. package/dist/mcp/tools.js +249 -0
  74. package/dist/mcp/tools.js.map +1 -0
  75. package/dist/report/report.js +43 -0
  76. package/dist/report/report.js.map +1 -0
  77. package/dist/search/engine.js +2 -0
  78. package/dist/search/engine.js.map +1 -0
  79. package/dist/search/query.js +91 -0
  80. package/dist/search/query.js.map +1 -0
  81. package/dist/search/search.js +224 -0
  82. package/dist/search/search.js.map +1 -0
  83. package/dist/search/snippet.js +95 -0
  84. package/dist/search/snippet.js.map +1 -0
  85. package/dist/serve/poller.js +119 -0
  86. package/dist/serve/poller.js.map +1 -0
  87. package/dist/serve/runtime.js +390 -0
  88. package/dist/serve/runtime.js.map +1 -0
  89. package/dist/source/git-runner.js +232 -0
  90. package/dist/source/git-runner.js.map +1 -0
  91. package/dist/source/git.js +457 -0
  92. package/dist/source/git.js.map +1 -0
  93. package/dist/source/local.js +20 -0
  94. package/dist/source/local.js.map +1 -0
  95. package/dist/source/source.js +2 -0
  96. package/dist/source/source.js.map +1 -0
  97. package/npm-shrinkwrap.json +5482 -0
  98. package/package.json +80 -0
  99. package/plugin/claude-code/.claude-plugin/plugin.json +15 -0
  100. package/plugin/claude-code/.mcp.json +10 -0
  101. package/plugin/claude-code/skills/okf-catalog/SKILL.md +15 -0
  102. package/recipes/publish/README.md +32 -0
  103. package/recipes/publish/checkers.lock +435 -0
  104. package/recipes/publish/checkers.txt +5 -0
  105. package/recipes/publish/pack.sh +60 -0
  106. package/recipes/publish/publish.yml +88 -0
  107. package/recipes/publish/push.sh +63 -0
@@ -0,0 +1,457 @@
1
+ import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { createBatchReader } from "../bundle/cat-file.js";
4
+ import { parseLsTree, validateTree } from "../bundle/git-tree.js";
5
+ import { walkBundle } from "../fs/walk.js";
6
+ import { GitError, redactCredentials } from "./git-runner.js";
7
+ /** An error whose message names the source as configured; the detail (git's words) is for the log. */
8
+ export class SourceError extends Error {
9
+ detail;
10
+ constructor(message, detail) {
11
+ super(message);
12
+ this.name = "SourceError";
13
+ if (detail !== undefined)
14
+ this.detail = detail;
15
+ }
16
+ }
17
+ const REPO = "repo.git";
18
+ const STATE = "state.json";
19
+ const TREE_NAME = /^tree-[0-9a-f]{40,64}$/;
20
+ const COMMIT = /^[0-9a-f]{40,64}$/;
21
+ const BATCH_MODE = "core.sshCommand=ssh -o BatchMode=yes";
22
+ /** Git's own words for a lock file left behind by a killed process; nothing else triggers a fresh clone. */
23
+ export const isStaleLockMessage = (stderr) => /Unable to create '[^']*\.lock': File exists/.test(stderr);
24
+ /**
25
+ * The ssh setting a transport command carries: BatchMode, so a passphrase prompt cannot hang a stdio server,
26
+ * unless the person's own `GIT_SSH_COMMAND`, `GIT_SSH` or configured `core.sshCommand` is in charge.
27
+ */
28
+ export function sshBatchSetting(env, configured) {
29
+ if (env.GIT_SSH_COMMAND !== undefined || env.GIT_SSH !== undefined)
30
+ return [];
31
+ if (configured !== undefined && configured.length > 0)
32
+ return [];
33
+ return [BATCH_MODE];
34
+ }
35
+ const TIMEOUT_MS = {
36
+ clone: 300_000,
37
+ fetch: 300_000,
38
+ lsRemote: 30_000,
39
+ list: 300_000,
40
+ extract: 300_000,
41
+ };
42
+ const emptyWalk = (fatal) => ({
43
+ files: [],
44
+ hidden: [],
45
+ hiddenFolders: [],
46
+ refusals: [],
47
+ fatal,
48
+ });
49
+ /**
50
+ * A published branch as the server's own derived copy (D47): a bare shallow clone for transport, each fetched commit
51
+ * listed and validated before anything is written, then extracted blob by blob into a fresh folder named by the
52
+ * commit, which the walker reads. Git never writes a working tree here, so no attribute, filter, hook or
53
+ * line-ending rule can touch the bytes.
54
+ */
55
+ export function createGitSource(options) {
56
+ const { repository, branch, bundlePath, workDir, caps, runner, clock, log } = options;
57
+ const repoDir = join(workDir, REPO);
58
+ const statePath = join(workDir, STATE);
59
+ const described = redactCredentials(repository);
60
+ let firstLoad = true;
61
+ let fromDisk = false;
62
+ /**
63
+ * The commit last refused or served, what `changed()` compares with: kept in memory only and seeded from the
64
+ * served commit, so a restart tries a commit again that an older configuration refused (bite 5 review BR2).
65
+ */
66
+ let attempted;
67
+ let attemptedSeeded = false;
68
+ const seedAttempted = (state) => {
69
+ if (attemptedSeeded)
70
+ return;
71
+ attemptedSeeded = true;
72
+ attempted = state?.served;
73
+ };
74
+ /** A file-system failure as the model may see it: a fixed sentence; the path travels as detail for the log. */
75
+ const fsFailure = (verb, error) => new SourceError(`the source folder under the cache could not be ${verb}; the log has the detail`, error.message);
76
+ const treeDir = (commit) => join(workDir, `tree-${commit}`);
77
+ function readState() {
78
+ try {
79
+ const parsed = JSON.parse(readFileSync(statePath, "utf8"));
80
+ if (typeof parsed.repository !== "string" || typeof parsed.branch !== "string")
81
+ return undefined;
82
+ // A commit field that is not a hash is dropped: a name could point outside the work folder.
83
+ const hex = (value) => typeof value === "string" && COMMIT.test(value) ? value : undefined;
84
+ const state = { repository: parsed.repository, branch: parsed.branch };
85
+ const extracted = hex(parsed.extracted);
86
+ const served = hex(parsed.served);
87
+ if (extracted !== undefined)
88
+ state.extracted = extracted;
89
+ if (served !== undefined)
90
+ state.served = served;
91
+ for (const key of ["fetchedAt", "extractedAt", "servedFetchedAt"]) {
92
+ const value = parsed[key];
93
+ if (typeof value === "string")
94
+ state[key] = value;
95
+ }
96
+ return state;
97
+ }
98
+ catch {
99
+ // no state, or not ours to read
100
+ }
101
+ return undefined;
102
+ }
103
+ /** An extracted tree is a real folder of the work folder, never a link. */
104
+ function treeExists(commit) {
105
+ try {
106
+ const stat = lstatSync(treeDir(commit));
107
+ return stat.isDirectory() && !stat.isSymbolicLink();
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ const writeState = (state) => {
114
+ try {
115
+ const tmp = `${statePath}.${process.pid}.tmp`;
116
+ writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
117
+ renameSync(tmp, statePath);
118
+ }
119
+ catch (error) {
120
+ throw fsFailure("written", error);
121
+ }
122
+ };
123
+ function ensureWorkDir() {
124
+ try {
125
+ mkdirSync(workDir, { recursive: true, mode: 0o700 });
126
+ }
127
+ catch (error) {
128
+ throw fsFailure("created", error);
129
+ }
130
+ if (lstatSync(workDir).isSymbolicLink())
131
+ throw new SourceError("the source folder under the cache is a symbolic link; remove it so the server can create its own");
132
+ }
133
+ /** Everything here is derived: when the repository or branch changed, start over. */
134
+ function resetWorkDir(reason) {
135
+ log?.warn("source.recloned", { reason });
136
+ for (const name of readdirSync(workDir))
137
+ rmSync(join(workDir, name), { recursive: true, force: true });
138
+ }
139
+ function recloneRepo(reason) {
140
+ log?.warn("source.recloned", { reason });
141
+ rmSync(repoDir, { recursive: true, force: true });
142
+ }
143
+ /** Whether git can still read the clone: its configuration must name the remote (a clone with it gone cannot be fetched into). */
144
+ async function cloneReadable() {
145
+ try {
146
+ const result = await runner.run(["config", "--get", "remote.origin.url"], {
147
+ cwd: workDir,
148
+ gitDir: repoDir,
149
+ timeoutMs: TIMEOUT_MS.lsRemote,
150
+ });
151
+ return result.stdout.toString("utf8").trim() === repository;
152
+ }
153
+ catch {
154
+ return false;
155
+ }
156
+ }
157
+ const failure = (verb, error) => {
158
+ const detail = error instanceof GitError
159
+ ? error.stderr || error.message
160
+ : redactCredentials(error.message);
161
+ return new SourceError(`the repository ${described} could not be ${verb}; the log has git's message`, detail);
162
+ };
163
+ const staleLock = (error) => error instanceof GitError && isStaleLockMessage(error.stderr);
164
+ /** The person's configured `core.sshCommand`, read where git would read it (includes count); nothing when unset. */
165
+ async function configuredSshCommand() {
166
+ try {
167
+ const result = await runner.run(["config", "--get", "core.sshCommand"], {
168
+ cwd: workDir,
169
+ ...(existsSync(join(repoDir, "HEAD")) ? { gitDir: repoDir } : {}),
170
+ timeoutMs: TIMEOUT_MS.lsRemote,
171
+ });
172
+ const value = result.stdout.toString("utf8").trim();
173
+ return value.length > 0 ? value : undefined;
174
+ }
175
+ catch {
176
+ return undefined;
177
+ }
178
+ }
179
+ let transportConfig = [];
180
+ const clone = () => runner.run([
181
+ "clone",
182
+ "--bare",
183
+ "--depth=1",
184
+ "--single-branch",
185
+ `--branch=${branch}`,
186
+ "--no-tags",
187
+ "--template=",
188
+ "--",
189
+ repository,
190
+ REPO,
191
+ ], { cwd: workDir, timeoutMs: TIMEOUT_MS.clone, extraConfig: transportConfig });
192
+ const fetch = () => runner.run(["fetch", "--depth=1", "--", "origin", `+refs/heads/${branch}:refs/remotes/origin/${branch}`], { cwd: workDir, gitDir: repoDir, timeoutMs: TIMEOUT_MS.fetch, extraConfig: transportConfig });
193
+ const resolveCommit = async () => {
194
+ const result = await runner.run(["rev-parse", "--verify", "--end-of-options", `refs/remotes/origin/${branch}^{commit}`], { cwd: workDir, gitDir: repoDir, timeoutMs: TIMEOUT_MS.lsRemote });
195
+ const commit = result.stdout.toString("utf8").trim();
196
+ if (!/^[0-9a-f]{40,64}$/.test(commit))
197
+ throw new SourceError(`the repository ${described} has no commit on branch ${branch}`);
198
+ return commit;
199
+ };
200
+ /** The tree listing, stopped one entry past the file cap. */
201
+ async function list(commit) {
202
+ const chunks = [];
203
+ let entries = 0;
204
+ const result = await runner.run(["ls-tree", "-r", "-t", "-l", "-z", "--full-tree", commit], {
205
+ cwd: workDir,
206
+ gitDir: repoDir,
207
+ timeoutMs: TIMEOUT_MS.list,
208
+ onStdout: (chunk) => {
209
+ chunks.push(chunk);
210
+ for (const byte of chunk)
211
+ if (byte === 0)
212
+ entries += 1;
213
+ return entries > caps.files ? "stop" : "continue";
214
+ },
215
+ });
216
+ if (result.stopped)
217
+ return {
218
+ refusal: {
219
+ path: "",
220
+ rule: "too-many-files",
221
+ detail: `more than ${caps.files} tree entries`,
222
+ },
223
+ };
224
+ try {
225
+ return { entries: parseLsTree(Buffer.concat(chunks)) };
226
+ }
227
+ catch (error) {
228
+ return { refusal: { path: "", rule: "path-escape", detail: error.message } };
229
+ }
230
+ }
231
+ /** Writes every blob of the listing, in order, into a fresh folder; raw bytes through `cat-file --batch`. */
232
+ async function extract(commit, entries) {
233
+ const blobs = entries
234
+ .filter((e) => e.type === "blob")
235
+ .map((e) => ({ sha: e.sha, size: e.size ?? 0, path: e.path }));
236
+ const partial = join(workDir, `tree-${commit}.partial-${process.pid}`);
237
+ rmSync(partial, { recursive: true, force: true });
238
+ mkdirSync(partial, { recursive: true, mode: 0o700 });
239
+ let problem;
240
+ const reader = createBatchReader(blobs, (entry, bytes) => {
241
+ const target = join(partial, entry.path);
242
+ try {
243
+ mkdirSync(dirname(target), { recursive: true, mode: 0o700 });
244
+ writeFileSync(target, bytes, { mode: 0o600 });
245
+ }
246
+ catch (error) {
247
+ throw fsFailure("written", error);
248
+ }
249
+ });
250
+ try {
251
+ if (blobs.length > 0) {
252
+ await runner.run(["cat-file", "--batch"], {
253
+ cwd: workDir,
254
+ gitDir: repoDir,
255
+ timeoutMs: TIMEOUT_MS.extract,
256
+ input: `${blobs.map((b) => b.sha).join("\n")}\n`,
257
+ onStdout: (chunk) => {
258
+ try {
259
+ reader.feed(chunk);
260
+ return "continue";
261
+ }
262
+ catch (error) {
263
+ problem = error;
264
+ return "stop";
265
+ }
266
+ },
267
+ });
268
+ }
269
+ if (problem !== undefined)
270
+ throw problem;
271
+ reader.finish();
272
+ rmSync(treeDir(commit), { recursive: true, force: true });
273
+ renameSync(partial, treeDir(commit));
274
+ }
275
+ catch (error) {
276
+ rmSync(partial, { recursive: true, force: true });
277
+ if (error instanceof SourceError)
278
+ throw error;
279
+ throw error instanceof GitError
280
+ ? failure("read", error)
281
+ : new SourceError(`the repository ${described} could not be extracted; the log has the detail`, error.message);
282
+ }
283
+ }
284
+ function walkTree(commit, fetchedAt, fresh) {
285
+ const tree = treeDir(commit);
286
+ const root = bundlePath === "." ? tree : join(tree, bundlePath);
287
+ try {
288
+ return { walk: walkBundle(root, caps), published: { commit, fetchedAt }, fresh };
289
+ }
290
+ catch (error) {
291
+ const original = error.message;
292
+ throw new SourceError(original.split(root).join(`${described}:${bundlePath}`).split(tree).join(described), original);
293
+ }
294
+ }
295
+ function servedOnDisk(state) {
296
+ if (state?.served === undefined || !treeExists(state.served))
297
+ return undefined;
298
+ const when = state.servedFetchedAt ?? state.fetchedAt;
299
+ const fetchedAt = when === undefined ? clock() : new Date(when);
300
+ return walkTree(state.served, Number.isNaN(fetchedAt.getTime()) ? clock() : fetchedAt, false);
301
+ }
302
+ /** Removes extracted trees that are neither served nor the newest, and any partial folder. */
303
+ function prune(state) {
304
+ for (const name of readdirSync(workDir)) {
305
+ const isTree = TREE_NAME.test(name);
306
+ const isPartial = name.startsWith("tree-") && name.includes(".partial-");
307
+ if (!isTree && !isPartial)
308
+ continue;
309
+ if (isTree && (name === `tree-${state.served}` || name === `tree-${state.extracted}`))
310
+ continue;
311
+ const full = join(workDir, name);
312
+ const stat = lstatSync(full);
313
+ if (stat.isDirectory() && !stat.isSymbolicLink())
314
+ rmSync(full, { recursive: true, force: true });
315
+ }
316
+ }
317
+ async function load() {
318
+ ensureWorkDir();
319
+ let state = readState();
320
+ if (state !== undefined && (state.repository !== repository || state.branch !== branch)) {
321
+ resetWorkDir("the configuration names another repository or branch");
322
+ state = undefined;
323
+ }
324
+ seedAttempted(state);
325
+ if (firstLoad) {
326
+ firstLoad = false;
327
+ const onDisk = servedOnDisk(state);
328
+ if (onDisk !== undefined) {
329
+ fromDisk = true;
330
+ return onDisk;
331
+ }
332
+ }
333
+ transportConfig = sshBatchSetting(runner.env, await configuredSshCommand());
334
+ if (!existsSync(join(repoDir, "HEAD"))) {
335
+ rmSync(repoDir, { recursive: true, force: true });
336
+ try {
337
+ await clone();
338
+ }
339
+ catch (error) {
340
+ throw failure("cloned", error);
341
+ }
342
+ }
343
+ try {
344
+ await fetch();
345
+ }
346
+ catch (error) {
347
+ // A lock file a killed git left behind, or a clone git itself can no longer read: both are derived, so a
348
+ // fresh clone replaces them; any other failure (the network, the credential) is reported as it is.
349
+ if (staleLock(error))
350
+ recloneRepo("a lock file was left behind by a killed git");
351
+ else if (!(await cloneReadable()))
352
+ recloneRepo("the clone could not be read");
353
+ else
354
+ throw failure("fetched", error);
355
+ try {
356
+ await clone();
357
+ await fetch();
358
+ }
359
+ catch (again) {
360
+ throw failure("fetched", again);
361
+ }
362
+ }
363
+ let commit;
364
+ try {
365
+ commit = await resolveCommit();
366
+ }
367
+ catch (error) {
368
+ throw error instanceof SourceError ? error : failure("read", error);
369
+ }
370
+ const fetchedAt = clock();
371
+ const base = { repository, branch, ...(state ?? {}) };
372
+ // What was attempted is recorded when a commit is refused or served, never here (bite 5 review M1): a
373
+ // commit the runtime fails to index must look moved to the poller until it is served.
374
+ if (state?.extracted === commit && treeExists(commit)) {
375
+ writeState({ ...base, fetchedAt: fetchedAt.toISOString() });
376
+ return walkTree(commit, fetchedAt, false);
377
+ }
378
+ const listing = await list(commit).catch((error) => {
379
+ throw failure("read", error);
380
+ });
381
+ const refused = (refusal) => {
382
+ attempted = commit;
383
+ writeState({ ...base, fetchedAt: fetchedAt.toISOString() });
384
+ return { walk: emptyWalk(refusal), published: { commit, fetchedAt }, fresh: false };
385
+ };
386
+ if ("refusal" in listing)
387
+ return refused(listing.refusal);
388
+ const refusal = validateTree(listing.entries, caps, bundlePath);
389
+ if (refusal !== undefined)
390
+ return refused(refusal);
391
+ await extract(commit, listing.entries);
392
+ const next = {
393
+ ...base,
394
+ extracted: commit,
395
+ extractedAt: fetchedAt.toISOString(),
396
+ fetchedAt: fetchedAt.toISOString(),
397
+ };
398
+ writeState(next);
399
+ // Trees the loader refused would pile up otherwise: keep the served one and this one (bite 5 review BR16).
400
+ prune(next);
401
+ return walkTree(commit, fetchedAt, true);
402
+ }
403
+ return {
404
+ kind: "git",
405
+ workDir,
406
+ load,
407
+ loadServed: async () => servedOnDisk(readState()),
408
+ served: (commit) => {
409
+ attempted = commit;
410
+ const current = readState();
411
+ const state = { repository, branch, ...(current ?? {}), served: commit };
412
+ if (current?.extracted === commit && current.extractedAt !== undefined)
413
+ state.servedFetchedAt = current.extractedAt;
414
+ writeState(state);
415
+ prune(state);
416
+ },
417
+ discard: (commit) => {
418
+ if (!COMMIT.test(commit) || !treeExists(commit))
419
+ return;
420
+ rmSync(treeDir(commit), { recursive: true, force: true });
421
+ const current = readState();
422
+ if (current?.extracted === commit) {
423
+ const { extracted: _e, extractedAt: _a, ...rest } = current;
424
+ writeState(rest);
425
+ }
426
+ },
427
+ changed: async () => {
428
+ if (!existsSync(join(repoDir, "HEAD")))
429
+ return "moved";
430
+ let result;
431
+ try {
432
+ result = await runner.run(["ls-remote", "--", "origin", `refs/heads/${branch}`], {
433
+ cwd: workDir,
434
+ gitDir: repoDir,
435
+ timeoutMs: TIMEOUT_MS.lsRemote,
436
+ extraConfig: transportConfig,
437
+ });
438
+ }
439
+ catch (error) {
440
+ throw failure("asked", error);
441
+ }
442
+ const line = result.stdout
443
+ .toString("utf8")
444
+ .split("\n")
445
+ .find((l) => l.endsWith(`\trefs/heads/${branch}`));
446
+ if (line === undefined)
447
+ return "gone";
448
+ const remote = line.split("\t")[0] ?? "";
449
+ seedAttempted(readState());
450
+ return remote === attempted ? "same" : "moved";
451
+ },
452
+ describe: () => described,
453
+ abort: () => runner.abort(),
454
+ startedFromDisk: () => fromDisk,
455
+ };
456
+ }
457
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/source/git.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n existsSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createBatchReader } from \"../bundle/cat-file.js\";\nimport { parseLsTree, type TreeEntry, validateTree } from \"../bundle/git-tree.js\";\nimport type { Caps, Refusal } from \"../bundle/model.js\";\nimport { type WalkResult, walkBundle } from \"../fs/walk.js\";\nimport type { Log } from \"../log.js\";\nimport { GitError, type GitRunner, redactCredentials } from \"./git-runner.js\";\nimport type { Loaded, RemoteChange, Source } from \"./source.js\";\n\nexport interface GitSourceOptions {\n repository: string;\n branch: string;\n /** `.` or a relative path inside the published tree. */\n bundlePath: string;\n /** The folder this source owns, under the process's work folder: `<work>/source`. */\n workDir: string;\n caps: Caps;\n runner: GitRunner;\n clock: () => Date;\n log?: Log;\n}\n\nexport interface GitSource extends Source {\n readonly kind: \"git\";\n readonly workDir: string;\n loadServed(): Promise<Loaded | undefined>;\n served(commit: string): void;\n discard(commit: string): void;\n changed(): Promise<RemoteChange>;\n abort(): void;\n /** Whether the first load answered from the tree on disk, so the poller should fetch at once. */\n startedFromDisk(): boolean;\n}\n\n/** An error whose message names the source as configured; the detail (git's words) is for the log. */\nexport class SourceError extends Error {\n detail?: string;\n constructor(message: string, detail?: string) {\n super(message);\n this.name = \"SourceError\";\n if (detail !== undefined) this.detail = detail;\n }\n}\n\ninterface State {\n repository: string;\n branch: string;\n /** The commit whose tree is extracted and complete, and when it was fetched. */\n extracted?: string;\n extractedAt?: string;\n /** The commit the runtime last swapped in, and when that one was fetched. */\n served?: string;\n servedFetchedAt?: string;\n /** When the last fetch succeeded. */\n fetchedAt?: string;\n}\n\nconst REPO = \"repo.git\";\nconst STATE = \"state.json\";\nconst TREE_NAME = /^tree-[0-9a-f]{40,64}$/;\nconst COMMIT = /^[0-9a-f]{40,64}$/;\nconst BATCH_MODE = \"core.sshCommand=ssh -o BatchMode=yes\";\n\n/** Git's own words for a lock file left behind by a killed process; nothing else triggers a fresh clone. */\nexport const isStaleLockMessage = (stderr: string): boolean =>\n /Unable to create '[^']*\\.lock': File exists/.test(stderr);\n\n/**\n * The ssh setting a transport command carries: BatchMode, so a passphrase prompt cannot hang a stdio server,\n * unless the person's own `GIT_SSH_COMMAND`, `GIT_SSH` or configured `core.sshCommand` is in charge.\n */\nexport function sshBatchSetting(env: NodeJS.ProcessEnv, configured: string | undefined): string[] {\n if (env.GIT_SSH_COMMAND !== undefined || env.GIT_SSH !== undefined) return [];\n if (configured !== undefined && configured.length > 0) return [];\n return [BATCH_MODE];\n}\nconst TIMEOUT_MS = {\n clone: 300_000,\n fetch: 300_000,\n lsRemote: 30_000,\n list: 300_000,\n extract: 300_000,\n};\nconst emptyWalk = (fatal: Refusal): WalkResult => ({\n files: [],\n hidden: [],\n hiddenFolders: [],\n refusals: [],\n fatal,\n});\n\n/**\n * A published branch as the server's own derived copy (D47): a bare shallow clone for transport, each fetched commit\n * listed and validated before anything is written, then extracted blob by blob into a fresh folder named by the\n * commit, which the walker reads. Git never writes a working tree here, so no attribute, filter, hook or\n * line-ending rule can touch the bytes.\n */\nexport function createGitSource(options: GitSourceOptions): GitSource {\n const { repository, branch, bundlePath, workDir, caps, runner, clock, log } = options;\n const repoDir = join(workDir, REPO);\n const statePath = join(workDir, STATE);\n const described = redactCredentials(repository);\n let firstLoad = true;\n let fromDisk = false;\n /**\n * The commit last refused or served, what `changed()` compares with: kept in memory only and seeded from the\n * served commit, so a restart tries a commit again that an older configuration refused (bite 5 review BR2).\n */\n let attempted: string | undefined;\n let attemptedSeeded = false;\n const seedAttempted = (state: State | undefined): void => {\n if (attemptedSeeded) return;\n attemptedSeeded = true;\n attempted = state?.served;\n };\n /** A file-system failure as the model may see it: a fixed sentence; the path travels as detail for the log. */\n const fsFailure = (verb: string, error: unknown): SourceError =>\n new SourceError(\n `the source folder under the cache could not be ${verb}; the log has the detail`,\n (error as Error).message,\n );\n\n const treeDir = (commit: string): string => join(workDir, `tree-${commit}`);\n\n function readState(): State | undefined {\n try {\n const parsed = JSON.parse(readFileSync(statePath, \"utf8\")) as State;\n if (typeof parsed.repository !== \"string\" || typeof parsed.branch !== \"string\")\n return undefined;\n // A commit field that is not a hash is dropped: a name could point outside the work folder.\n const hex = (value: unknown): string | undefined =>\n typeof value === \"string\" && COMMIT.test(value) ? value : undefined;\n const state: State = { repository: parsed.repository, branch: parsed.branch };\n const extracted = hex(parsed.extracted);\n const served = hex(parsed.served);\n if (extracted !== undefined) state.extracted = extracted;\n if (served !== undefined) state.served = served;\n for (const key of [\"fetchedAt\", \"extractedAt\", \"servedFetchedAt\"] as const) {\n const value = parsed[key];\n if (typeof value === \"string\") state[key] = value;\n }\n return state;\n } catch {\n // no state, or not ours to read\n }\n return undefined;\n }\n\n /** An extracted tree is a real folder of the work folder, never a link. */\n function treeExists(commit: string): boolean {\n try {\n const stat = lstatSync(treeDir(commit));\n return stat.isDirectory() && !stat.isSymbolicLink();\n } catch {\n return false;\n }\n }\n const writeState = (state: State): void => {\n try {\n const tmp = `${statePath}.${process.pid}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\\n`, { mode: 0o600 });\n renameSync(tmp, statePath);\n } catch (error) {\n throw fsFailure(\"written\", error);\n }\n };\n\n function ensureWorkDir(): void {\n try {\n mkdirSync(workDir, { recursive: true, mode: 0o700 });\n } catch (error) {\n throw fsFailure(\"created\", error);\n }\n if (lstatSync(workDir).isSymbolicLink())\n throw new SourceError(\n \"the source folder under the cache is a symbolic link; remove it so the server can create its own\",\n );\n }\n /** Everything here is derived: when the repository or branch changed, start over. */\n function resetWorkDir(reason: string): void {\n log?.warn(\"source.recloned\", { reason });\n for (const name of readdirSync(workDir))\n rmSync(join(workDir, name), { recursive: true, force: true });\n }\n function recloneRepo(reason: string): void {\n log?.warn(\"source.recloned\", { reason });\n rmSync(repoDir, { recursive: true, force: true });\n }\n /** Whether git can still read the clone: its configuration must name the remote (a clone with it gone cannot be fetched into). */\n async function cloneReadable(): Promise<boolean> {\n try {\n const result = await runner.run([\"config\", \"--get\", \"remote.origin.url\"], {\n cwd: workDir,\n gitDir: repoDir,\n timeoutMs: TIMEOUT_MS.lsRemote,\n });\n return result.stdout.toString(\"utf8\").trim() === repository;\n } catch {\n return false;\n }\n }\n\n const failure = (verb: string, error: unknown): SourceError => {\n const detail =\n error instanceof GitError\n ? error.stderr || error.message\n : redactCredentials((error as Error).message);\n return new SourceError(\n `the repository ${described} could not be ${verb}; the log has git's message`,\n detail,\n );\n };\n const staleLock = (error: unknown): boolean =>\n error instanceof GitError && isStaleLockMessage(error.stderr);\n\n /** The person's configured `core.sshCommand`, read where git would read it (includes count); nothing when unset. */\n async function configuredSshCommand(): Promise<string | undefined> {\n try {\n const result = await runner.run([\"config\", \"--get\", \"core.sshCommand\"], {\n cwd: workDir,\n ...(existsSync(join(repoDir, \"HEAD\")) ? { gitDir: repoDir } : {}),\n timeoutMs: TIMEOUT_MS.lsRemote,\n });\n const value = result.stdout.toString(\"utf8\").trim();\n return value.length > 0 ? value : undefined;\n } catch {\n return undefined;\n }\n }\n let transportConfig: string[] = [];\n\n const clone = (): Promise<unknown> =>\n runner.run(\n [\n \"clone\",\n \"--bare\",\n \"--depth=1\",\n \"--single-branch\",\n `--branch=${branch}`,\n \"--no-tags\",\n \"--template=\",\n \"--\",\n repository,\n REPO,\n ],\n { cwd: workDir, timeoutMs: TIMEOUT_MS.clone, extraConfig: transportConfig },\n );\n const fetch = (): Promise<unknown> =>\n runner.run(\n [\"fetch\", \"--depth=1\", \"--\", \"origin\", `+refs/heads/${branch}:refs/remotes/origin/${branch}`],\n { cwd: workDir, gitDir: repoDir, timeoutMs: TIMEOUT_MS.fetch, extraConfig: transportConfig },\n );\n const resolveCommit = async (): Promise<string> => {\n const result = await runner.run(\n [\"rev-parse\", \"--verify\", \"--end-of-options\", `refs/remotes/origin/${branch}^{commit}`],\n { cwd: workDir, gitDir: repoDir, timeoutMs: TIMEOUT_MS.lsRemote },\n );\n const commit = result.stdout.toString(\"utf8\").trim();\n if (!/^[0-9a-f]{40,64}$/.test(commit))\n throw new SourceError(`the repository ${described} has no commit on branch ${branch}`);\n return commit;\n };\n\n /** The tree listing, stopped one entry past the file cap. */\n async function list(commit: string): Promise<{ entries: TreeEntry[] } | { refusal: Refusal }> {\n const chunks: Buffer[] = [];\n let entries = 0;\n const result = await runner.run([\"ls-tree\", \"-r\", \"-t\", \"-l\", \"-z\", \"--full-tree\", commit], {\n cwd: workDir,\n gitDir: repoDir,\n timeoutMs: TIMEOUT_MS.list,\n onStdout: (chunk) => {\n chunks.push(chunk);\n for (const byte of chunk) if (byte === 0) entries += 1;\n return entries > caps.files ? \"stop\" : \"continue\";\n },\n });\n if (result.stopped)\n return {\n refusal: {\n path: \"\",\n rule: \"too-many-files\",\n detail: `more than ${caps.files} tree entries`,\n },\n };\n try {\n return { entries: parseLsTree(Buffer.concat(chunks)) };\n } catch (error) {\n return { refusal: { path: \"\", rule: \"path-escape\", detail: (error as Error).message } };\n }\n }\n\n /** Writes every blob of the listing, in order, into a fresh folder; raw bytes through `cat-file --batch`. */\n async function extract(commit: string, entries: readonly TreeEntry[]): Promise<void> {\n const blobs = entries\n .filter((e) => e.type === \"blob\")\n .map((e) => ({ sha: e.sha, size: e.size ?? 0, path: e.path }));\n const partial = join(workDir, `tree-${commit}.partial-${process.pid}`);\n rmSync(partial, { recursive: true, force: true });\n mkdirSync(partial, { recursive: true, mode: 0o700 });\n let problem: Error | undefined;\n const reader = createBatchReader(blobs, (entry, bytes) => {\n const target = join(partial, entry.path);\n try {\n mkdirSync(dirname(target), { recursive: true, mode: 0o700 });\n writeFileSync(target, bytes, { mode: 0o600 });\n } catch (error) {\n throw fsFailure(\"written\", error);\n }\n });\n try {\n if (blobs.length > 0) {\n await runner.run([\"cat-file\", \"--batch\"], {\n cwd: workDir,\n gitDir: repoDir,\n timeoutMs: TIMEOUT_MS.extract,\n input: `${blobs.map((b) => b.sha).join(\"\\n\")}\\n`,\n onStdout: (chunk) => {\n try {\n reader.feed(chunk);\n return \"continue\";\n } catch (error) {\n problem = error as Error;\n return \"stop\";\n }\n },\n });\n }\n if (problem !== undefined) throw problem;\n reader.finish();\n rmSync(treeDir(commit), { recursive: true, force: true });\n renameSync(partial, treeDir(commit));\n } catch (error) {\n rmSync(partial, { recursive: true, force: true });\n if (error instanceof SourceError) throw error;\n throw error instanceof GitError\n ? failure(\"read\", error)\n : new SourceError(\n `the repository ${described} could not be extracted; the log has the detail`,\n (error as Error).message,\n );\n }\n }\n\n function walkTree(commit: string, fetchedAt: Date, fresh: boolean): Loaded {\n const tree = treeDir(commit);\n const root = bundlePath === \".\" ? tree : join(tree, bundlePath);\n try {\n return { walk: walkBundle(root, caps), published: { commit, fetchedAt }, fresh };\n } catch (error) {\n const original = (error as Error).message;\n throw new SourceError(\n original.split(root).join(`${described}:${bundlePath}`).split(tree).join(described),\n original,\n );\n }\n }\n\n function servedOnDisk(state: State | undefined): Loaded | undefined {\n if (state?.served === undefined || !treeExists(state.served)) return undefined;\n const when = state.servedFetchedAt ?? state.fetchedAt;\n const fetchedAt = when === undefined ? clock() : new Date(when);\n return walkTree(state.served, Number.isNaN(fetchedAt.getTime()) ? clock() : fetchedAt, false);\n }\n\n /** Removes extracted trees that are neither served nor the newest, and any partial folder. */\n function prune(state: State): void {\n for (const name of readdirSync(workDir)) {\n const isTree = TREE_NAME.test(name);\n const isPartial = name.startsWith(\"tree-\") && name.includes(\".partial-\");\n if (!isTree && !isPartial) continue;\n if (isTree && (name === `tree-${state.served}` || name === `tree-${state.extracted}`))\n continue;\n const full = join(workDir, name);\n const stat = lstatSync(full);\n if (stat.isDirectory() && !stat.isSymbolicLink())\n rmSync(full, { recursive: true, force: true });\n }\n }\n\n async function load(): Promise<Loaded> {\n ensureWorkDir();\n let state = readState();\n if (state !== undefined && (state.repository !== repository || state.branch !== branch)) {\n resetWorkDir(\"the configuration names another repository or branch\");\n state = undefined;\n }\n seedAttempted(state);\n if (firstLoad) {\n firstLoad = false;\n const onDisk = servedOnDisk(state);\n if (onDisk !== undefined) {\n fromDisk = true;\n return onDisk;\n }\n }\n transportConfig = sshBatchSetting(runner.env, await configuredSshCommand());\n if (!existsSync(join(repoDir, \"HEAD\"))) {\n rmSync(repoDir, { recursive: true, force: true });\n try {\n await clone();\n } catch (error) {\n throw failure(\"cloned\", error);\n }\n }\n try {\n await fetch();\n } catch (error) {\n // A lock file a killed git left behind, or a clone git itself can no longer read: both are derived, so a\n // fresh clone replaces them; any other failure (the network, the credential) is reported as it is.\n if (staleLock(error)) recloneRepo(\"a lock file was left behind by a killed git\");\n else if (!(await cloneReadable())) recloneRepo(\"the clone could not be read\");\n else throw failure(\"fetched\", error);\n try {\n await clone();\n await fetch();\n } catch (again) {\n throw failure(\"fetched\", again);\n }\n }\n let commit: string;\n try {\n commit = await resolveCommit();\n } catch (error) {\n throw error instanceof SourceError ? error : failure(\"read\", error);\n }\n const fetchedAt = clock();\n const base: State = { repository, branch, ...(state ?? {}) };\n // What was attempted is recorded when a commit is refused or served, never here (bite 5 review M1): a\n // commit the runtime fails to index must look moved to the poller until it is served.\n if (state?.extracted === commit && treeExists(commit)) {\n writeState({ ...base, fetchedAt: fetchedAt.toISOString() });\n return walkTree(commit, fetchedAt, false);\n }\n const listing = await list(commit).catch((error: unknown) => {\n throw failure(\"read\", error);\n });\n const refused = (refusal: Refusal): Loaded => {\n attempted = commit;\n writeState({ ...base, fetchedAt: fetchedAt.toISOString() });\n return { walk: emptyWalk(refusal), published: { commit, fetchedAt }, fresh: false };\n };\n if (\"refusal\" in listing) return refused(listing.refusal);\n const refusal = validateTree(listing.entries, caps, bundlePath);\n if (refusal !== undefined) return refused(refusal);\n await extract(commit, listing.entries);\n const next: State = {\n ...base,\n extracted: commit,\n extractedAt: fetchedAt.toISOString(),\n fetchedAt: fetchedAt.toISOString(),\n };\n writeState(next);\n // Trees the loader refused would pile up otherwise: keep the served one and this one (bite 5 review BR16).\n prune(next);\n return walkTree(commit, fetchedAt, true);\n }\n\n return {\n kind: \"git\",\n workDir,\n load,\n loadServed: async () => servedOnDisk(readState()),\n served: (commit) => {\n attempted = commit;\n const current = readState();\n const state: State = { repository, branch, ...(current ?? {}), served: commit };\n if (current?.extracted === commit && current.extractedAt !== undefined)\n state.servedFetchedAt = current.extractedAt;\n writeState(state);\n prune(state);\n },\n discard: (commit) => {\n if (!COMMIT.test(commit) || !treeExists(commit)) return;\n rmSync(treeDir(commit), { recursive: true, force: true });\n const current = readState();\n if (current?.extracted === commit) {\n const { extracted: _e, extractedAt: _a, ...rest } = current;\n writeState(rest);\n }\n },\n changed: async (): Promise<RemoteChange> => {\n if (!existsSync(join(repoDir, \"HEAD\"))) return \"moved\";\n let result: Awaited<ReturnType<GitRunner[\"run\"]>>;\n try {\n result = await runner.run([\"ls-remote\", \"--\", \"origin\", `refs/heads/${branch}`], {\n cwd: workDir,\n gitDir: repoDir,\n timeoutMs: TIMEOUT_MS.lsRemote,\n extraConfig: transportConfig,\n });\n } catch (error) {\n throw failure(\"asked\", error);\n }\n const line = result.stdout\n .toString(\"utf8\")\n .split(\"\\n\")\n .find((l) => l.endsWith(`\\trefs/heads/${branch}`));\n if (line === undefined) return \"gone\";\n const remote = line.split(\"\\t\")[0] ?? \"\";\n seedAttempted(readState());\n return remote === attempted ? \"same\" : \"moved\";\n },\n describe: () => described,\n abort: () => runner.abort(),\n startedFromDisk: () => fromDisk,\n };\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import { walkBundle } from "../fs/walk.js";
2
+ export function createLocalSource(source, caps) {
3
+ return {
4
+ kind: "local",
5
+ load: async () => {
6
+ try {
7
+ return { walk: walkBundle(source.path, caps) };
8
+ }
9
+ catch (error) {
10
+ // The model is told the source as configured; the resolved path travels as detail, for the log only.
11
+ const original = error.message;
12
+ const described = new Error(original.split(source.path).join(source.configured));
13
+ described.detail = original;
14
+ throw described;
15
+ }
16
+ },
17
+ describe: () => source.configured,
18
+ };
19
+ }
20
+ //# sourceMappingURL=local.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local.js","sourceRoot":"","sources":["../../src/source/local.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAQ3C,MAAM,UAAU,iBAAiB,CAC/B,MAA4C,EAC5C,IAAU;IAEV,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,KAAK,IAAqB,EAAE;YAChC,IAAI,CAAC;gBACH,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACjD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,qGAAqG;gBACrG,MAAM,QAAQ,GAAI,KAAe,CAAC,OAAO,CAAC;gBAC1C,MAAM,SAAS,GAAG,IAAI,KAAK,CACzB,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAGpD,CAAC;gBACF,SAAS,CAAC,MAAM,GAAG,QAAQ,CAAC;gBAC5B,MAAM,SAAS,CAAC;YAClB,CAAC;QACH,CAAC;QACD,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU;KAClC,CAAC;AACJ,CAAC","sourcesContent":["import type { Caps } from \"../bundle/model.js\";\nimport { walkBundle } from \"../fs/walk.js\";\nimport type { Loaded, Source } from \"./source.js\";\n\n/** A bundle folder on disk, walked afresh on every load. */\nexport interface LocalSource extends Source {\n kind: \"local\";\n}\n\nexport function createLocalSource(\n source: { path: string; configured: string },\n caps: Caps,\n): LocalSource {\n return {\n kind: \"local\",\n load: async (): Promise<Loaded> => {\n try {\n return { walk: walkBundle(source.path, caps) };\n } catch (error) {\n // The model is told the source as configured; the resolved path travels as detail, for the log only.\n const original = (error as Error).message;\n const described = new Error(\n original.split(source.path).join(source.configured),\n ) as Error & {\n detail?: string;\n };\n described.detail = original;\n throw described;\n }\n },\n describe: () => source.configured,\n };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/source/source.ts"],"names":[],"mappings":"","sourcesContent":["import type { WalkResult } from \"../fs/walk.js\";\n\n/** What a load produced: the walked files, and for a repository source the fetched commit and when. */\nexport interface Loaded {\n walk: WalkResult;\n published?: { commit: string; fetchedAt: Date };\n /** Whether the tree was extracted by this load, or reused from disk (a reused tree the loader refuses is discarded). */\n fresh?: boolean;\n}\n\nexport type RemoteChange = \"same\" | \"moved\" | \"gone\";\n\n/**\n * Where a bundle comes from. `load()` is the whole fetch-and-walk; a repository source also remembers which tree\n * is served, can hand it back when a fresh load fails, can say whether the remote moved, and can be aborted.\n */\nexport interface Source {\n readonly kind: \"local\" | \"git\";\n load(): Promise<Loaded>;\n /** The tree last served, from disk; undefined when there is none. */\n loadServed?(): Promise<Loaded | undefined>;\n /** Told after each swap which commit is served, so older trees can go. */\n served?(commit: string): void;\n /** Removes an extracted tree the loader refused, so the next load extracts it again. */\n discard?(commit: string): void;\n /** Whether the remote moved since the last attempted commit; throws when it cannot be asked. */\n changed?(): Promise<RemoteChange>;\n /** The source as configured, never a resolved or cache path, never a credential. */\n describe(): string;\n /** Ends any transport command in flight. */\n abort?(): void;\n}\n"]}