@optique/git 0.9.0-dev.215 → 0.9.0-dev.227

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
@@ -24,97 +24,51 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  const __optique_core_message = __toESM(require("@optique/core/message"));
25
25
  const __optique_core_nonempty = __toESM(require("@optique/core/nonempty"));
26
26
  const isomorphic_git = __toESM(require("isomorphic-git"));
27
+ const node_fs_promises = __toESM(require("node:fs/promises"));
27
28
  const node_process = __toESM(require("node:process"));
28
29
 
29
30
  //#region src/index.ts
31
+ const gitFs = {
32
+ readFile: node_fs_promises.readFile,
33
+ writeFile: node_fs_promises.writeFile,
34
+ mkdir: node_fs_promises.mkdir,
35
+ rmdir: node_fs_promises.rmdir,
36
+ unlink: node_fs_promises.unlink,
37
+ readdir: node_fs_promises.readdir,
38
+ readlink: node_fs_promises.readlink,
39
+ symlink: node_fs_promises.symlink,
40
+ stat: node_fs_promises.stat,
41
+ lstat: node_fs_promises.lstat
42
+ };
30
43
  const METAVAR_BRANCH = "BRANCH";
31
44
  const METAVAR_TAG = "TAG";
32
45
  const METAVAR_REMOTE = "REMOTE";
33
- let defaultFs = null;
34
- let fsLoading = null;
35
- async function getDefaultFs() {
36
- if (defaultFs) return await defaultFs;
37
- if (fsLoading) return await fsLoading;
38
- fsLoading = (async () => {
39
- const nodeFs = await import("node:fs/promises");
40
- const { TextDecoder } = await import("node:util");
41
- const decoder = new TextDecoder();
42
- defaultFs = {
43
- async readFile(path) {
44
- const data = await nodeFs.readFile(path);
45
- if (path.endsWith("/index") || path.endsWith(".idx")) return data;
46
- return decoder.decode(data);
47
- },
48
- async writeFile(path, data) {
49
- await nodeFs.writeFile(path, data);
50
- },
51
- async mkdir(path, options) {
52
- await nodeFs.mkdir(path, options);
53
- },
54
- async rmdir(path, options) {
55
- await nodeFs.rmdir(path, options);
56
- },
57
- async unlink(path) {
58
- await nodeFs.unlink(path);
59
- },
60
- async readdir(path) {
61
- const entries = await nodeFs.readdir(path, { withFileTypes: false });
62
- return entries.filter((e) => typeof e === "string");
63
- },
64
- async lstat(path) {
65
- return await nodeFs.lstat(path);
66
- },
67
- async stat(path) {
68
- return await nodeFs.stat(path);
69
- },
70
- async readlink(path) {
71
- return await nodeFs.readlink(path);
72
- },
73
- async symlink(target, path) {
74
- await nodeFs.symlink(target, path, "file");
75
- },
76
- async chmod(path, mode) {
77
- await nodeFs.chmod(path, mode);
78
- },
79
- async chown(path, uid, gid) {
80
- await nodeFs.chown(path, uid, gid);
81
- },
82
- async rename(oldPath, newPath) {
83
- await nodeFs.rename(oldPath, newPath);
84
- },
85
- async copyFile(srcPath, destPath) {
86
- await nodeFs.copyFile(srcPath, destPath);
87
- },
88
- async exists(path) {
89
- try {
90
- await nodeFs.stat(path);
91
- return true;
92
- } catch {
93
- return false;
94
- }
95
- }
96
- };
97
- return defaultFs;
98
- })();
99
- return fsLoading;
46
+ function getRepoDir(dirOption) {
47
+ return dirOption ?? (typeof node_process.default !== "undefined" ? node_process.default.cwd() : ".");
48
+ }
49
+ function formatChoiceList(choices) {
50
+ let result = [];
51
+ for (let i = 0; i < choices.length; i++) {
52
+ if (i > 0) result = [...result, ...__optique_core_message.message`, `];
53
+ result = [...result, ...__optique_core_message.message`${choices[i]}`];
54
+ }
55
+ return result;
100
56
  }
101
57
  function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
102
58
  return {
103
59
  $mode: "async",
104
60
  metavar,
105
- async parse(input) {
106
- const fs = options?.fs ?? await getDefaultFs();
107
- const dir = options?.dir ?? (typeof node_process.default !== "undefined" ? node_process.default.cwd() : ".");
61
+ parse(input) {
62
+ const dir = getRepoDir(options?.dir);
108
63
  (0, __optique_core_nonempty.ensureNonEmptyString)(metavar);
109
- return parseFn(fs, dir, input);
64
+ return parseFn(dir, input, options?.errors);
110
65
  },
111
66
  format(value) {
112
67
  return value;
113
68
  },
114
69
  async *suggest(prefix) {
115
- const fs = options?.fs ?? await getDefaultFs();
116
- const dir = options?.dir ?? (typeof node_process.default !== "undefined" ? node_process.default.cwd() : ".");
117
- if (suggestFn) yield* suggestFn(fs, dir, prefix);
70
+ const dir = getRepoDir(options?.dir);
71
+ if (suggestFn) yield* suggestFn(dir, prefix);
118
72
  }
119
73
  };
120
74
  }
@@ -138,30 +92,38 @@ function createAsyncValueParser(options, metavar, parseFn, suggestFn) {
138
92
  */
139
93
  function gitBranch(options) {
140
94
  const metavar = options?.metavar ?? METAVAR_BRANCH;
141
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
95
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
142
96
  try {
143
97
  const branches = await isomorphic_git.listBranches({
144
- fs,
98
+ fs: gitFs,
145
99
  dir
146
100
  });
147
101
  if (branches.includes(input)) return {
148
102
  success: true,
149
103
  value: input
150
104
  };
105
+ if (errors?.notFound) return {
106
+ success: false,
107
+ error: errors.notFound(input, branches)
108
+ };
151
109
  return {
152
110
  success: false,
153
- error: __optique_core_message.message`Branch ${(0, __optique_core_message.text)(input)} does not exist. Available branches: ${branches.join(", ")}`
111
+ error: __optique_core_message.message`Branch ${(0, __optique_core_message.text)(input)} does not exist. Available branches: ${formatChoiceList(branches)}`
154
112
  };
155
113
  } catch {
114
+ if (errors?.listFailed) return {
115
+ success: false,
116
+ error: errors.listFailed(dir)
117
+ };
156
118
  return {
157
119
  success: false,
158
120
  error: __optique_core_message.message`Failed to list branches. Ensure ${(0, __optique_core_message.text)(dir)} is a valid git repository.`
159
121
  };
160
122
  }
161
- }, async function* suggestBranch(fs, dir, prefix) {
123
+ }, async function* suggestBranch(dir, prefix) {
162
124
  try {
163
125
  const branches = await isomorphic_git.listBranches({
164
- fs,
126
+ fs: gitFs,
165
127
  dir
166
128
  });
167
129
  for (const branch of branches) if (branch.startsWith(prefix)) yield {
@@ -192,10 +154,10 @@ function gitBranch(options) {
192
154
  */
193
155
  function gitRemoteBranch(remote, options) {
194
156
  const metavar = options?.metavar ?? METAVAR_BRANCH;
195
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
157
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
196
158
  try {
197
159
  const branches = await isomorphic_git.listBranches({
198
- fs,
160
+ fs: gitFs,
199
161
  dir,
200
162
  remote
201
163
  });
@@ -203,20 +165,28 @@ function gitRemoteBranch(remote, options) {
203
165
  success: true,
204
166
  value: input
205
167
  };
168
+ if (errors?.notFound) return {
169
+ success: false,
170
+ error: errors.notFound(input, branches)
171
+ };
206
172
  return {
207
173
  success: false,
208
- error: __optique_core_message.message`Remote branch ${(0, __optique_core_message.text)(input)} does not exist on remote ${(0, __optique_core_message.text)(remote)}. Available branches: ${branches.join(", ")}`
174
+ error: __optique_core_message.message`Remote branch ${(0, __optique_core_message.text)(input)} does not exist on remote ${(0, __optique_core_message.text)(remote)}. Available branches: ${formatChoiceList(branches)}`
209
175
  };
210
176
  } catch {
177
+ if (errors?.listFailed) return {
178
+ success: false,
179
+ error: errors.listFailed(dir)
180
+ };
211
181
  return {
212
182
  success: false,
213
183
  error: __optique_core_message.message`Failed to list remote branches. Ensure remote ${(0, __optique_core_message.text)(remote)} exists.`
214
184
  };
215
185
  }
216
- }, async function* suggestRemoteBranch(fs, dir, prefix) {
186
+ }, async function* suggestRemoteBranch(dir, prefix) {
217
187
  try {
218
188
  const branches = await isomorphic_git.listBranches({
219
- fs,
189
+ fs: gitFs,
220
190
  dir,
221
191
  remote
222
192
  });
@@ -230,47 +200,44 @@ function gitRemoteBranch(remote, options) {
230
200
  /**
231
201
  * Creates a value parser that validates tag names.
232
202
  *
233
- * This parser uses isomorphic-git to verify that the provided input
234
- * matches an existing tag in the repository.
235
- *
236
203
  * @param options Configuration options for the parser.
237
204
  * @returns A value parser that accepts existing tag names.
238
205
  * @since 0.9.0
239
- *
240
- * @example
241
- * ~~~~ typescript
242
- * import { gitTag } from "@optique/git";
243
- * import { option } from "@optique/core/primitives";
244
- *
245
- * const parser = option("-t", "--tag", gitTag());
246
- * ~~~~
247
206
  */
248
207
  function gitTag(options) {
249
208
  const metavar = options?.metavar ?? METAVAR_TAG;
250
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
209
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
251
210
  try {
252
211
  const tags = await isomorphic_git.listTags({
253
- fs,
212
+ fs: gitFs,
254
213
  dir
255
214
  });
256
215
  if (tags.includes(input)) return {
257
216
  success: true,
258
217
  value: input
259
218
  };
219
+ if (errors?.notFound) return {
220
+ success: false,
221
+ error: errors.notFound(input, tags)
222
+ };
260
223
  return {
261
224
  success: false,
262
- error: __optique_core_message.message`Tag ${(0, __optique_core_message.text)(input)} does not exist. Available tags: ${tags.join(", ")}`
225
+ error: __optique_core_message.message`Tag ${(0, __optique_core_message.text)(input)} does not exist. Available tags: ${formatChoiceList(tags)}`
263
226
  };
264
227
  } catch {
228
+ if (errors?.listFailed) return {
229
+ success: false,
230
+ error: errors.listFailed(dir)
231
+ };
265
232
  return {
266
233
  success: false,
267
234
  error: __optique_core_message.message`Failed to list tags. Ensure ${(0, __optique_core_message.text)(dir)} is a valid git repository.`
268
235
  };
269
236
  }
270
- }, async function* suggestTag(fs, dir, prefix) {
237
+ }, async function* suggestTag(dir, prefix) {
271
238
  try {
272
239
  const tags = await isomorphic_git.listTags({
273
- fs,
240
+ fs: gitFs,
274
241
  dir
275
242
  });
276
243
  for (const tag of tags) if (tag.startsWith(prefix)) yield {
@@ -283,52 +250,48 @@ function gitTag(options) {
283
250
  /**
284
251
  * Creates a value parser that validates remote names.
285
252
  *
286
- * This parser uses isomorphic-git to verify that the provided input
287
- * matches an existing remote in the repository.
288
- *
289
253
  * @param options Configuration options for the parser.
290
254
  * @returns A value parser that accepts existing remote names.
291
255
  * @since 0.9.0
292
- *
293
- * @example
294
- * ~~~~ typescript
295
- * import { gitRemote } from "@optique/git";
296
- * import { option } from "@optique/core/primitives";
297
- *
298
- * const parser = option("-r", "--remote", gitRemote());
299
- * ~~~~
300
256
  */
301
257
  function gitRemote(options) {
302
258
  const metavar = options?.metavar ?? METAVAR_REMOTE;
303
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
259
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
304
260
  try {
305
261
  const remotes = await isomorphic_git.listRemotes({
306
- fs,
262
+ fs: gitFs,
307
263
  dir
308
264
  });
309
- const remoteNames = [];
310
- for (const r of remotes) if ("remote" in r && typeof r.remote === "string") remoteNames.push(r.remote);
311
- if (remoteNames.includes(input)) return {
265
+ const names = remotes.map((r) => r.remote);
266
+ if (names.includes(input)) return {
312
267
  success: true,
313
268
  value: input
314
269
  };
270
+ if (errors?.notFound) return {
271
+ success: false,
272
+ error: errors.notFound(input, names)
273
+ };
315
274
  return {
316
275
  success: false,
317
- error: __optique_core_message.message`Remote ${(0, __optique_core_message.text)(input)} does not exist. Available remotes: ${remoteNames.join(", ")}`
276
+ error: __optique_core_message.message`Remote ${(0, __optique_core_message.text)(input)} does not exist. Available remotes: ${formatChoiceList(names)}`
318
277
  };
319
278
  } catch {
279
+ if (errors?.listFailed) return {
280
+ success: false,
281
+ error: errors.listFailed(dir)
282
+ };
320
283
  return {
321
284
  success: false,
322
285
  error: __optique_core_message.message`Failed to list remotes. Ensure ${(0, __optique_core_message.text)(dir)} is a valid git repository.`
323
286
  };
324
287
  }
325
- }, async function* suggestRemote(fs, dir, prefix) {
288
+ }, async function* suggestRemote(dir, prefix) {
326
289
  try {
327
290
  const remotes = await isomorphic_git.listRemotes({
328
- fs,
291
+ fs: gitFs,
329
292
  dir
330
293
  });
331
- for (const r of remotes) if ("remote" in r && typeof r.remote === "string" && r.remote.startsWith(prefix)) yield {
294
+ for (const r of remotes) if (r.remote.startsWith(prefix)) yield {
332
295
  kind: "literal",
333
296
  text: r.remote
334
297
  };
@@ -338,119 +301,119 @@ function gitRemote(options) {
338
301
  /**
339
302
  * Creates a value parser that validates commit SHAs.
340
303
  *
341
- * This parser uses isomorphic-git to verify that the provided input
342
- * is a valid commit SHA (full or shortened) that exists in the repository.
304
+ * This parser resolves the provided commit reference to its full 40-character
305
+ * OID.
343
306
  *
344
307
  * @param options Configuration options for the parser.
345
- * @returns A value parser that accepts valid commit SHAs.
308
+ * @returns A value parser that accepts existing commit SHAs.
346
309
  * @since 0.9.0
347
- *
348
- * @example
349
- * ~~~~ typescript
350
- * import { gitCommit } from "@optique/git";
351
- * import { option } from "@optique/core/primitives";
352
- *
353
- * const parser = option("-c", "--commit", gitCommit());
354
- * ~~~~
355
310
  */
356
311
  function gitCommit(options) {
357
312
  const metavar = options?.metavar ?? "COMMIT";
358
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
313
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
314
+ try {
315
+ (0, __optique_core_nonempty.ensureNonEmptyString)(input);
316
+ } catch {
317
+ if (errors?.invalidFormat) return {
318
+ success: false,
319
+ error: errors.invalidFormat(input)
320
+ };
321
+ return {
322
+ success: false,
323
+ error: __optique_core_message.message`Invalid commit SHA: ${(0, __optique_core_message.text)(input)}`
324
+ };
325
+ }
326
+ if (input.length < 4 || input.length > 40) {
327
+ if (errors?.invalidFormat) return {
328
+ success: false,
329
+ error: errors.invalidFormat(input)
330
+ };
331
+ return {
332
+ success: false,
333
+ error: __optique_core_message.message`Commit ${(0, __optique_core_message.text)(input)} must be between 4 and 40 characters.`
334
+ };
335
+ }
359
336
  try {
360
337
  const oid = await isomorphic_git.expandOid({
361
- fs,
338
+ fs: gitFs,
362
339
  dir,
363
340
  oid: input
364
341
  });
365
- await isomorphic_git.readObject({
366
- fs,
367
- dir,
368
- oid
369
- });
370
342
  return {
371
343
  success: true,
372
344
  value: oid
373
345
  };
374
346
  } catch {
347
+ if (errors?.notFound) return {
348
+ success: false,
349
+ error: errors.notFound(input)
350
+ };
375
351
  return {
376
352
  success: false,
377
353
  error: __optique_core_message.message`Commit ${(0, __optique_core_message.text)(input)} does not exist. Provide a valid commit SHA.`
378
354
  };
379
355
  }
380
- }, async function* suggestCommit(fs, dir, prefix) {
381
- try {
382
- const branches = await isomorphic_git.listBranches({
383
- fs,
384
- dir
385
- });
386
- const commits = [];
387
- for (const branch of branches.slice(0, 10)) try {
388
- const oid = await isomorphic_git.resolveRef({
389
- fs,
390
- dir,
391
- ref: branch
392
- });
393
- if (oid.startsWith(prefix)) commits.push(oid);
394
- } catch {}
395
- for (const commit of [...new Set(commits)].slice(0, 10)) yield {
396
- kind: "literal",
397
- text: commit
398
- };
399
- } catch {}
400
356
  });
401
357
  }
402
358
  /**
403
- * Creates a value parser that validates any git reference
404
- * (branches, tags, or commits).
359
+ * Creates a value parser that validates any git reference.
405
360
  *
406
- * This parser uses isomorphic-git to verify that the provided input
407
- * resolves to a valid git reference (branch, tag, or commit SHA).
361
+ * Accepts branch names, tag names, or commit SHAs and resolves them to the
362
+ * corresponding commit OID.
408
363
  *
409
364
  * @param options Configuration options for the parser.
410
- * @returns A value parser that accepts branches, tags, or commit SHAs.
365
+ * @returns A value parser that accepts any git reference.
411
366
  * @since 0.9.0
412
- *
413
- * @example
414
- * ~~~~ typescript
415
- * import { gitRef } from "@optique/git";
416
- * import { option } from "@optique/core/primitives";
417
- *
418
- * const parser = option("--ref", gitRef());
419
- * ~~~~
420
367
  */
421
368
  function gitRef(options) {
422
369
  const metavar = options?.metavar ?? "REF";
423
- return createAsyncValueParser(options, metavar, async (fs, dir, input) => {
370
+ return createAsyncValueParser(options, metavar, async (dir, input, errors) => {
424
371
  try {
425
- const oid = await isomorphic_git.resolveRef({
426
- fs,
372
+ const resolved = await isomorphic_git.resolveRef({
373
+ fs: gitFs,
427
374
  dir,
428
375
  ref: input
429
376
  });
430
377
  return {
431
378
  success: true,
432
- value: oid
379
+ value: resolved
433
380
  };
434
381
  } catch {
435
- return {
436
- success: false,
437
- error: __optique_core_message.message`Reference ${(0, __optique_core_message.text)(input)} does not exist. Provide a valid branch, tag, or commit SHA.`
438
- };
382
+ try {
383
+ const oid = await isomorphic_git.expandOid({
384
+ fs: gitFs,
385
+ dir,
386
+ oid: input
387
+ });
388
+ return {
389
+ success: true,
390
+ value: oid
391
+ };
392
+ } catch {
393
+ if (errors?.notFound) return {
394
+ success: false,
395
+ error: errors.notFound(input)
396
+ };
397
+ return {
398
+ success: false,
399
+ error: __optique_core_message.message`Reference ${(0, __optique_core_message.text)(input)} does not exist. Provide a valid branch, tag, or commit SHA.`
400
+ };
401
+ }
439
402
  }
440
- }, async function* suggestRef(fs, dir, prefix) {
403
+ }, async function* suggestRef(dir, prefix) {
441
404
  try {
442
405
  const branches = await isomorphic_git.listBranches({
443
- fs,
406
+ fs: gitFs,
407
+ dir
408
+ });
409
+ const tags = await isomorphic_git.listTags({
410
+ fs: gitFs,
444
411
  dir
445
412
  });
446
413
  for (const branch of branches) if (branch.startsWith(prefix)) yield {
447
414
  kind: "literal",
448
415
  text: branch
449
416
  };
450
- const tags = await isomorphic_git.listTags({
451
- fs,
452
- dir
453
- });
454
417
  for (const tag of tags) if (tag.startsWith(prefix)) yield {
455
418
  kind: "literal",
456
419
  text: tag
@@ -459,51 +422,20 @@ function gitRef(options) {
459
422
  });
460
423
  }
461
424
  /**
462
- * Creates a factory for git parsers with shared configuration.
463
- *
464
- * This function returns an object with methods for creating individual git
465
- * parsers that share the same configuration (filesystem and directory).
425
+ * Creates a set of git parsers with shared configuration.
466
426
  *
467
- * @param options Shared configuration options for all parsers.
468
- * @returns An object with methods for creating individual git parsers.
427
+ * @param options Shared configuration for the parsers.
428
+ * @returns An object containing git parsers.
469
429
  * @since 0.9.0
470
- *
471
- * @example
472
- * ~~~~ typescript
473
- * import { createGitParsers } from "@optique/git";
474
- *
475
- * const git = createGitParsers({ dir: "/path/to/repo" });
476
- *
477
- * const branchParser = git.branch();
478
- * const tagParser = git.tag();
479
- * ~~~~
480
430
  */
481
431
  function createGitParsers(options) {
482
432
  return {
483
- branch: (parserOptions) => gitBranch({
484
- ...options,
485
- ...parserOptions
486
- }),
487
- remoteBranch: (remote, parserOptions) => gitRemoteBranch(remote, {
488
- ...options,
489
- ...parserOptions
490
- }),
491
- tag: (parserOptions) => gitTag({
492
- ...options,
493
- ...parserOptions
494
- }),
495
- remote: (parserOptions) => gitRemote({
496
- ...options,
497
- ...parserOptions
498
- }),
499
- commit: (parserOptions) => gitCommit({
500
- ...options,
501
- ...parserOptions
502
- }),
503
- ref: (parserOptions) => gitRef({
504
- ...options,
505
- ...parserOptions
506
- })
433
+ branch: (branchOptions) => gitBranch(branchOptions ?? options),
434
+ remoteBranch: (remote, branchOptions) => gitRemoteBranch(remote, branchOptions ?? options),
435
+ tag: (tagOptions) => gitTag(tagOptions ?? options),
436
+ remote: (remoteOptions) => gitRemote(remoteOptions ?? options),
437
+ commit: (commitOptions) => gitCommit(commitOptions ?? options),
438
+ ref: (refOptions) => gitRef(refOptions ?? options)
507
439
  };
508
440
  }
509
441