@optique/git 0.9.0-dev.266 → 0.9.0-dev.268

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/dist/index.cjs CHANGED
@@ -26,31 +26,50 @@ const __optique_core_nonempty = __toESM(require("@optique/core/nonempty"));
26
26
  const isomorphic_git = __toESM(require("isomorphic-git"));
27
27
  const node_fs_promises = __toESM(require("node:fs/promises"));
28
28
  const node_process = __toESM(require("node:process"));
29
+ const __logtape_logtape = __toESM(require("@logtape/logtape"));
29
30
 
30
31
  //#region src/index.ts
32
+ const logger = (0, __logtape_logtape.getLogger)(["optique", "git"]);
31
33
  /**
32
- * Filesystem interface passed to isomorphic-git.
34
+ * Read-only filesystem interface passed to isomorphic-git.
33
35
  *
34
- * Although this package only performs read operations (validation and listing),
35
- * isomorphic-git's FsClient type requires write methods to be present.
36
- * These methods are included for type compatibility but are never called
37
- * by our read-only operations.
36
+ * This package only performs read operations (validation and listing).
37
+ * Write methods are implemented as stubs that throw errors if called,
38
+ * enforcing the read-only contract and preventing accidental writes.
38
39
  */
39
40
  const gitFs = {
40
41
  readFile: node_fs_promises.readFile,
41
- writeFile: node_fs_promises.writeFile,
42
- mkdir: node_fs_promises.mkdir,
43
- rmdir: node_fs_promises.rmdir,
44
- unlink: node_fs_promises.unlink,
42
+ writeFile: () => {
43
+ throw new Error("gitFs is read-only: writeFile is disabled.");
44
+ },
45
+ mkdir: () => {
46
+ throw new Error("gitFs is read-only: mkdir is disabled.");
47
+ },
48
+ rmdir: () => {
49
+ throw new Error("gitFs is read-only: rmdir is disabled.");
50
+ },
51
+ unlink: () => {
52
+ throw new Error("gitFs is read-only: unlink is disabled.");
53
+ },
45
54
  readdir: node_fs_promises.readdir,
46
55
  readlink: node_fs_promises.readlink,
47
- symlink: node_fs_promises.symlink,
56
+ symlink: () => {
57
+ throw new Error("gitFs is read-only: symlink is disabled.");
58
+ },
48
59
  stat: node_fs_promises.stat,
49
60
  lstat: node_fs_promises.lstat
50
61
  };
51
62
  const METAVAR_BRANCH = "BRANCH";
52
63
  const METAVAR_TAG = "TAG";
53
64
  const METAVAR_REMOTE = "REMOTE";
65
+ /**
66
+ * Resolves the repository directory from the provided option or process.cwd().
67
+ *
68
+ * Note: This function does not validate that the directory exists or is
69
+ * accessible. Directory validation is deferred to the Git operations
70
+ * themselves, which will produce appropriate error messages if the directory
71
+ * is invalid or not a Git repository.
72
+ */
54
73
  function getRepoDir(dirOption) {
55
74
  if (dirOption != null) return dirOption;
56
75
  if (typeof node_process.default !== "undefined" && typeof node_process.default.cwd === "function") return node_process.default.cwd();
@@ -64,6 +83,8 @@ function listFailureMessage(error, dir, errors, fallback) {
64
83
  if (hasErrorCode(error, "NotAGitRepositoryError") || hasErrorCode(error, "NotFoundError")) return __optique_core_message.message`${(0, __optique_core_message.value)(dir)} is not a git repository.`;
65
84
  return fallback;
66
85
  }
86
+ /** Default depth for commit suggestions. */
87
+ const DEFAULT_SUGGESTION_DEPTH = 15;
67
88
  function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
68
89
  (0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
69
90
  return {
@@ -78,7 +99,10 @@ function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
78
99
  },
79
100
  async *suggest(prefix) {
80
101
  const dir = getRepoDir(options?.dir);
81
- if (suggestFn) yield* suggestFn(dir, prefix);
102
+ if (suggestFn) {
103
+ const depth = options?.suggestionDepth ?? DEFAULT_SUGGESTION_DEPTH;
104
+ yield* suggestFn(dir, prefix, depth);
105
+ }
82
106
  }
83
107
  };
84
108
  }
@@ -127,7 +151,7 @@ function gitBranch(options) {
127
151
  error: listFailureMessage(error, dir, errors, fallback)
128
152
  };
129
153
  }
130
- }, async function* suggestBranch(dir, prefix) {
154
+ }, async function* suggestBranch(dir, prefix, _depth) {
131
155
  try {
132
156
  const branches = await isomorphic_git.listBranches({
133
157
  fs: gitFs,
@@ -137,7 +161,13 @@ function gitBranch(options) {
137
161
  kind: "literal",
138
162
  text: branch
139
163
  };
140
- } catch {}
164
+ } catch (error) {
165
+ logger.debug("Failed to list branches for suggestions.", {
166
+ dir,
167
+ prefix,
168
+ error
169
+ });
170
+ }
141
171
  });
142
172
  }
143
173
  /**
@@ -187,7 +217,7 @@ function gitRemoteBranch(remote, options) {
187
217
  error: listFailureMessage(error, dir, errors, fallback)
188
218
  };
189
219
  }
190
- }, async function* suggestRemoteBranch(dir, prefix) {
220
+ }, async function* suggestRemoteBranch(dir, prefix, _depth) {
191
221
  try {
192
222
  const branches = await isomorphic_git.listBranches({
193
223
  fs: gitFs,
@@ -198,7 +228,14 @@ function gitRemoteBranch(remote, options) {
198
228
  kind: "literal",
199
229
  text: branch
200
230
  };
201
- } catch {}
231
+ } catch (error) {
232
+ logger.debug("Failed to list remote branches for suggestions.", {
233
+ dir,
234
+ remote,
235
+ prefix,
236
+ error
237
+ });
238
+ }
202
239
  });
203
240
  }
204
241
  /**
@@ -235,7 +272,7 @@ function gitTag(options) {
235
272
  error: listFailureMessage(error, dir, errors, fallback)
236
273
  };
237
274
  }
238
- }, async function* suggestTag(dir, prefix) {
275
+ }, async function* suggestTag(dir, prefix, _depth) {
239
276
  try {
240
277
  const tags = await isomorphic_git.listTags({
241
278
  fs: gitFs,
@@ -245,7 +282,13 @@ function gitTag(options) {
245
282
  kind: "literal",
246
283
  text: tag
247
284
  };
248
- } catch {}
285
+ } catch (error) {
286
+ logger.debug("Failed to list tags for suggestions.", {
287
+ dir,
288
+ prefix,
289
+ error
290
+ });
291
+ }
249
292
  });
250
293
  }
251
294
  /**
@@ -283,7 +326,7 @@ function gitRemote(options) {
283
326
  error: listFailureMessage(error, dir, errors, fallback)
284
327
  };
285
328
  }
286
- }, async function* suggestRemote(dir, prefix) {
329
+ }, async function* suggestRemote(dir, prefix, _depth) {
287
330
  try {
288
331
  const remotes = await isomorphic_git.listRemotes({
289
332
  fs: gitFs,
@@ -293,7 +336,13 @@ function gitRemote(options) {
293
336
  kind: "literal",
294
337
  text: r.remote
295
338
  };
296
- } catch {}
339
+ } catch (error) {
340
+ logger.debug("Failed to list remotes for suggestions.", {
341
+ dir,
342
+ prefix,
343
+ error
344
+ });
345
+ }
297
346
  });
298
347
  }
299
348
  /**
@@ -316,7 +365,7 @@ function gitCommit(options) {
316
365
  };
317
366
  return {
318
367
  success: false,
319
- error: __optique_core_message.message`Invalid commit SHA: ${(0, __optique_core_message.value)(input)}. Commits must be 440 hexadecimal characters.`
368
+ error: __optique_core_message.message`Invalid commit SHA: ${(0, __optique_core_message.value)(input)}. Provide an abbreviated (4+) or full (40) hexadecimal commit SHA.`
320
369
  };
321
370
  }
322
371
  try {
@@ -339,12 +388,12 @@ function gitCommit(options) {
339
388
  error: __optique_core_message.message`Commit ${(0, __optique_core_message.value)(input)} does not exist. Provide a valid commit SHA.`
340
389
  };
341
390
  }
342
- }, async function* suggestCommit(dir, prefix) {
391
+ }, async function* suggestCommit(dir, prefix, depth) {
343
392
  try {
344
393
  const commits = await isomorphic_git.log({
345
394
  fs: gitFs,
346
395
  dir,
347
- depth: 15
396
+ depth
348
397
  });
349
398
  for (const commit of commits) if (commit.oid.startsWith(prefix)) {
350
399
  const shortOid = commit.oid.slice(0, 7);
@@ -355,7 +404,13 @@ function gitCommit(options) {
355
404
  description: __optique_core_message.message`${firstLine}`
356
405
  };
357
406
  }
358
- } catch {}
407
+ } catch (error) {
408
+ logger.debug("Failed to list commits for suggestions.", {
409
+ dir,
410
+ prefix,
411
+ error
412
+ });
413
+ }
359
414
  });
360
415
  }
361
416
  /**
@@ -403,7 +458,7 @@ function gitRef(options) {
403
458
  error: __optique_core_message.message`Reference ${(0, __optique_core_message.value)(input)} does not exist. Provide a valid branch, tag, or commit SHA.`
404
459
  };
405
460
  }
406
- }, async function* suggestRef(dir, prefix) {
461
+ }, async function* suggestRef(dir, prefix, depth) {
407
462
  try {
408
463
  const [branches, tags, commits] = await Promise.all([
409
464
  isomorphic_git.listBranches({
@@ -417,7 +472,7 @@ function gitRef(options) {
417
472
  isomorphic_git.log({
418
473
  fs: gitFs,
419
474
  dir,
420
- depth: 10
475
+ depth
421
476
  })
422
477
  ]);
423
478
  for (const branch of branches) if (branch.startsWith(prefix)) yield {
@@ -437,7 +492,13 @@ function gitRef(options) {
437
492
  description: __optique_core_message.message`${firstLine}`
438
493
  };
439
494
  }
440
- } catch {}
495
+ } catch (error) {
496
+ logger.debug("Failed to list refs for suggestions.", {
497
+ dir,
498
+ prefix,
499
+ error
500
+ });
501
+ }
441
502
  });
442
503
  }
443
504
  /**
package/dist/index.d.cts CHANGED
@@ -27,6 +27,14 @@ interface GitParserOptions {
27
27
  * @since 0.9.0
28
28
  */
29
29
  errors?: GitParserErrors;
30
+ /**
31
+ * Maximum number of recent commits to include in shell completion suggestions.
32
+ * Only applies to `gitCommit()` and `gitRef()` parsers.
33
+ * Defaults to 15.
34
+ *
35
+ * @since 0.9.0
36
+ */
37
+ suggestionDepth?: number;
30
38
  }
31
39
  /**
32
40
  * Custom error messages for git value parsers.
package/dist/index.d.ts CHANGED
@@ -27,6 +27,14 @@ interface GitParserOptions {
27
27
  * @since 0.9.0
28
28
  */
29
29
  errors?: GitParserErrors;
30
+ /**
31
+ * Maximum number of recent commits to include in shell completion suggestions.
32
+ * Only applies to `gitCommit()` and `gitRef()` parsers.
33
+ * Defaults to 15.
34
+ *
35
+ * @since 0.9.0
36
+ */
37
+ suggestionDepth?: number;
30
38
  }
31
39
  /**
32
40
  * Custom error messages for git value parsers.
package/dist/index.js CHANGED
@@ -4,31 +4,50 @@ import * as git from "isomorphic-git";
4
4
  import { expandOid, listBranches, listRemotes, listTags, readObject, resolveRef } from "isomorphic-git";
5
5
  import * as fs from "node:fs/promises";
6
6
  import process from "node:process";
7
+ import { getLogger } from "@logtape/logtape";
7
8
 
8
9
  //#region src/index.ts
10
+ const logger = getLogger(["optique", "git"]);
9
11
  /**
10
- * Filesystem interface passed to isomorphic-git.
12
+ * Read-only filesystem interface passed to isomorphic-git.
11
13
  *
12
- * Although this package only performs read operations (validation and listing),
13
- * isomorphic-git's FsClient type requires write methods to be present.
14
- * These methods are included for type compatibility but are never called
15
- * by our read-only operations.
14
+ * This package only performs read operations (validation and listing).
15
+ * Write methods are implemented as stubs that throw errors if called,
16
+ * enforcing the read-only contract and preventing accidental writes.
16
17
  */
17
18
  const gitFs = {
18
19
  readFile: fs.readFile,
19
- writeFile: fs.writeFile,
20
- mkdir: fs.mkdir,
21
- rmdir: fs.rmdir,
22
- unlink: fs.unlink,
20
+ writeFile: () => {
21
+ throw new Error("gitFs is read-only: writeFile is disabled.");
22
+ },
23
+ mkdir: () => {
24
+ throw new Error("gitFs is read-only: mkdir is disabled.");
25
+ },
26
+ rmdir: () => {
27
+ throw new Error("gitFs is read-only: rmdir is disabled.");
28
+ },
29
+ unlink: () => {
30
+ throw new Error("gitFs is read-only: unlink is disabled.");
31
+ },
23
32
  readdir: fs.readdir,
24
33
  readlink: fs.readlink,
25
- symlink: fs.symlink,
34
+ symlink: () => {
35
+ throw new Error("gitFs is read-only: symlink is disabled.");
36
+ },
26
37
  stat: fs.stat,
27
38
  lstat: fs.lstat
28
39
  };
29
40
  const METAVAR_BRANCH = "BRANCH";
30
41
  const METAVAR_TAG = "TAG";
31
42
  const METAVAR_REMOTE = "REMOTE";
43
+ /**
44
+ * Resolves the repository directory from the provided option or process.cwd().
45
+ *
46
+ * Note: This function does not validate that the directory exists or is
47
+ * accessible. Directory validation is deferred to the Git operations
48
+ * themselves, which will produce appropriate error messages if the directory
49
+ * is invalid or not a Git repository.
50
+ */
32
51
  function getRepoDir(dirOption) {
33
52
  if (dirOption != null) return dirOption;
34
53
  if (typeof process !== "undefined" && typeof process.cwd === "function") return process.cwd();
@@ -42,6 +61,8 @@ function listFailureMessage(error, dir, errors, fallback) {
42
61
  if (hasErrorCode(error, "NotAGitRepositoryError") || hasErrorCode(error, "NotFoundError")) return message`${value(dir)} is not a git repository.`;
43
62
  return fallback;
44
63
  }
64
+ /** Default depth for commit suggestions. */
65
+ const DEFAULT_SUGGESTION_DEPTH = 15;
45
66
  function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
46
67
  ensureNonEmptyString(metavar);
47
68
  return {
@@ -56,7 +77,10 @@ function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
56
77
  },
57
78
  async *suggest(prefix) {
58
79
  const dir = getRepoDir(options?.dir);
59
- if (suggestFn) yield* suggestFn(dir, prefix);
80
+ if (suggestFn) {
81
+ const depth = options?.suggestionDepth ?? DEFAULT_SUGGESTION_DEPTH;
82
+ yield* suggestFn(dir, prefix, depth);
83
+ }
60
84
  }
61
85
  };
62
86
  }
@@ -105,7 +129,7 @@ function gitBranch(options) {
105
129
  error: listFailureMessage(error, dir, errors, fallback)
106
130
  };
107
131
  }
108
- }, async function* suggestBranch(dir, prefix) {
132
+ }, async function* suggestBranch(dir, prefix, _depth) {
109
133
  try {
110
134
  const branches = await git.listBranches({
111
135
  fs: gitFs,
@@ -115,7 +139,13 @@ function gitBranch(options) {
115
139
  kind: "literal",
116
140
  text: branch
117
141
  };
118
- } catch {}
142
+ } catch (error) {
143
+ logger.debug("Failed to list branches for suggestions.", {
144
+ dir,
145
+ prefix,
146
+ error
147
+ });
148
+ }
119
149
  });
120
150
  }
121
151
  /**
@@ -165,7 +195,7 @@ function gitRemoteBranch(remote, options) {
165
195
  error: listFailureMessage(error, dir, errors, fallback)
166
196
  };
167
197
  }
168
- }, async function* suggestRemoteBranch(dir, prefix) {
198
+ }, async function* suggestRemoteBranch(dir, prefix, _depth) {
169
199
  try {
170
200
  const branches = await git.listBranches({
171
201
  fs: gitFs,
@@ -176,7 +206,14 @@ function gitRemoteBranch(remote, options) {
176
206
  kind: "literal",
177
207
  text: branch
178
208
  };
179
- } catch {}
209
+ } catch (error) {
210
+ logger.debug("Failed to list remote branches for suggestions.", {
211
+ dir,
212
+ remote,
213
+ prefix,
214
+ error
215
+ });
216
+ }
180
217
  });
181
218
  }
182
219
  /**
@@ -213,7 +250,7 @@ function gitTag(options) {
213
250
  error: listFailureMessage(error, dir, errors, fallback)
214
251
  };
215
252
  }
216
- }, async function* suggestTag(dir, prefix) {
253
+ }, async function* suggestTag(dir, prefix, _depth) {
217
254
  try {
218
255
  const tags = await git.listTags({
219
256
  fs: gitFs,
@@ -223,7 +260,13 @@ function gitTag(options) {
223
260
  kind: "literal",
224
261
  text: tag
225
262
  };
226
- } catch {}
263
+ } catch (error) {
264
+ logger.debug("Failed to list tags for suggestions.", {
265
+ dir,
266
+ prefix,
267
+ error
268
+ });
269
+ }
227
270
  });
228
271
  }
229
272
  /**
@@ -261,7 +304,7 @@ function gitRemote(options) {
261
304
  error: listFailureMessage(error, dir, errors, fallback)
262
305
  };
263
306
  }
264
- }, async function* suggestRemote(dir, prefix) {
307
+ }, async function* suggestRemote(dir, prefix, _depth) {
265
308
  try {
266
309
  const remotes = await git.listRemotes({
267
310
  fs: gitFs,
@@ -271,7 +314,13 @@ function gitRemote(options) {
271
314
  kind: "literal",
272
315
  text: r.remote
273
316
  };
274
- } catch {}
317
+ } catch (error) {
318
+ logger.debug("Failed to list remotes for suggestions.", {
319
+ dir,
320
+ prefix,
321
+ error
322
+ });
323
+ }
275
324
  });
276
325
  }
277
326
  /**
@@ -294,7 +343,7 @@ function gitCommit(options) {
294
343
  };
295
344
  return {
296
345
  success: false,
297
- error: message`Invalid commit SHA: ${value(input)}. Commits must be 440 hexadecimal characters.`
346
+ error: message`Invalid commit SHA: ${value(input)}. Provide an abbreviated (4+) or full (40) hexadecimal commit SHA.`
298
347
  };
299
348
  }
300
349
  try {
@@ -317,12 +366,12 @@ function gitCommit(options) {
317
366
  error: message`Commit ${value(input)} does not exist. Provide a valid commit SHA.`
318
367
  };
319
368
  }
320
- }, async function* suggestCommit(dir, prefix) {
369
+ }, async function* suggestCommit(dir, prefix, depth) {
321
370
  try {
322
371
  const commits = await git.log({
323
372
  fs: gitFs,
324
373
  dir,
325
- depth: 15
374
+ depth
326
375
  });
327
376
  for (const commit of commits) if (commit.oid.startsWith(prefix)) {
328
377
  const shortOid = commit.oid.slice(0, 7);
@@ -333,7 +382,13 @@ function gitCommit(options) {
333
382
  description: message`${firstLine}`
334
383
  };
335
384
  }
336
- } catch {}
385
+ } catch (error) {
386
+ logger.debug("Failed to list commits for suggestions.", {
387
+ dir,
388
+ prefix,
389
+ error
390
+ });
391
+ }
337
392
  });
338
393
  }
339
394
  /**
@@ -381,7 +436,7 @@ function gitRef(options) {
381
436
  error: message`Reference ${value(input)} does not exist. Provide a valid branch, tag, or commit SHA.`
382
437
  };
383
438
  }
384
- }, async function* suggestRef(dir, prefix) {
439
+ }, async function* suggestRef(dir, prefix, depth) {
385
440
  try {
386
441
  const [branches, tags, commits] = await Promise.all([
387
442
  git.listBranches({
@@ -395,7 +450,7 @@ function gitRef(options) {
395
450
  git.log({
396
451
  fs: gitFs,
397
452
  dir,
398
- depth: 10
453
+ depth
399
454
  })
400
455
  ]);
401
456
  for (const branch of branches) if (branch.startsWith(prefix)) yield {
@@ -415,7 +470,13 @@ function gitRef(options) {
415
470
  description: message`${firstLine}`
416
471
  };
417
472
  }
418
- } catch {}
473
+ } catch (error) {
474
+ logger.debug("Failed to list refs for suggestions.", {
475
+ dir,
476
+ prefix,
477
+ error
478
+ });
479
+ }
419
480
  });
420
481
  }
421
482
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/git",
3
- "version": "0.9.0-dev.266+49a4cc82",
3
+ "version": "0.9.0-dev.268+5fccc2e2",
4
4
  "description": "Git value parsers for Optique",
5
5
  "keywords": [
6
6
  "CLI",
@@ -57,8 +57,9 @@
57
57
  },
58
58
  "sideEffects": false,
59
59
  "dependencies": {
60
- "isomorphic-git": "^1.36.1",
61
- "@optique/core": ""
60
+ "@logtape/logtape": "^1.2.2",
61
+ "@optique/core": "",
62
+ "isomorphic-git": "^1.36.1"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@types/node": "^20.19.9",