preguito 0.2.0 → 0.2.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/dist/cli-sea.cjs CHANGED
@@ -36,7 +36,7 @@ let node_os = require("node:os");
36
36
  let node_util = require("node:util");
37
37
  let node_readline_promises = require("node:readline/promises");
38
38
 
39
- //#region node_modules/commander/lib/error.js
39
+ //#region ../../node_modules/commander/lib/error.js
40
40
  var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
41
41
  /**
42
42
  * CommanderError class
@@ -76,7 +76,7 @@ var require_error = /* @__PURE__ */ __commonJSMin(((exports) => {
76
76
  }));
77
77
 
78
78
  //#endregion
79
- //#region node_modules/commander/lib/argument.js
79
+ //#region ../../node_modules/commander/lib/argument.js
80
80
  var require_argument = /* @__PURE__ */ __commonJSMin(((exports) => {
81
81
  const { InvalidArgumentError } = require_error();
82
82
  var Argument = class {
@@ -201,7 +201,7 @@ var require_argument = /* @__PURE__ */ __commonJSMin(((exports) => {
201
201
  }));
202
202
 
203
203
  //#endregion
204
- //#region node_modules/commander/lib/help.js
204
+ //#region ../../node_modules/commander/lib/help.js
205
205
  var require_help = /* @__PURE__ */ __commonJSMin(((exports) => {
206
206
  const { humanReadableArgName } = require_argument();
207
207
  /**
@@ -663,7 +663,7 @@ var require_help = /* @__PURE__ */ __commonJSMin(((exports) => {
663
663
  }));
664
664
 
665
665
  //#endregion
666
- //#region node_modules/commander/lib/option.js
666
+ //#region ../../node_modules/commander/lib/option.js
667
667
  var require_option = /* @__PURE__ */ __commonJSMin(((exports) => {
668
668
  const { InvalidArgumentError } = require_error();
669
669
  var Option = class {
@@ -955,7 +955,7 @@ var require_option = /* @__PURE__ */ __commonJSMin(((exports) => {
955
955
  }));
956
956
 
957
957
  //#endregion
958
- //#region node_modules/commander/lib/suggestSimilar.js
958
+ //#region ../../node_modules/commander/lib/suggestSimilar.js
959
959
  var require_suggestSimilar = /* @__PURE__ */ __commonJSMin(((exports) => {
960
960
  const maxDistance = 3;
961
961
  function editDistance(a, b) {
@@ -1011,7 +1011,7 @@ var require_suggestSimilar = /* @__PURE__ */ __commonJSMin(((exports) => {
1011
1011
  }));
1012
1012
 
1013
1013
  //#endregion
1014
- //#region node_modules/commander/lib/command.js
1014
+ //#region ../../node_modules/commander/lib/command.js
1015
1015
  var require_command = /* @__PURE__ */ __commonJSMin(((exports) => {
1016
1016
  const EventEmitter = require("node:events").EventEmitter;
1017
1017
  const childProcess = require("node:child_process");
@@ -2912,7 +2912,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2912
2912
  }));
2913
2913
 
2914
2914
  //#endregion
2915
- //#region node_modules/commander/index.js
2915
+ //#region ../../node_modules/commander/index.js
2916
2916
  var require_commander = /* @__PURE__ */ __commonJSMin(((exports) => {
2917
2917
  const { Argument } = require_argument();
2918
2918
  const { Command } = require_command();
@@ -2936,7 +2936,7 @@ var require_commander = /* @__PURE__ */ __commonJSMin(((exports) => {
2936
2936
  }));
2937
2937
 
2938
2938
  //#endregion
2939
- //#region node_modules/commander/esm.mjs
2939
+ //#region ../../node_modules/commander/esm.mjs
2940
2940
  var import_commander = /* @__PURE__ */ __toESM(require_commander(), 1);
2941
2941
  const { program: program$1, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help } = import_commander.default;
2942
2942
 
@@ -3209,7 +3209,7 @@ async function writeConfig(config) {
3209
3209
 
3210
3210
  //#endregion
3211
3211
  //#region src/commands/commit-parser.ts
3212
- function parsePositionalArgs(args, config) {
3212
+ function parsePositionalArgs(args, config, bodyFromFlag) {
3213
3213
  if (args.length === 0) throw new PrequitoError("No arguments provided. Usage: guito c [card_id] [shortcodes] <message...>");
3214
3214
  const context = {};
3215
3215
  let cursor = 0;
@@ -3229,7 +3229,8 @@ function parsePositionalArgs(args, config) {
3229
3229
  const message = messageParts.join(" ");
3230
3230
  return {
3231
3231
  context: mergeContext(config.defaults, context),
3232
- message
3232
+ message,
3233
+ body: bodyFromFlag
3233
3234
  };
3234
3235
  }
3235
3236
  function resolveShortcodes(shortcodesStr, config) {
@@ -3258,6 +3259,29 @@ function resolveShortcodes(shortcodesStr, config) {
3258
3259
  return result;
3259
3260
  }
3260
3261
 
3262
+ //#endregion
3263
+ //#region src/utils/validation.ts
3264
+ const GIT_HASH_PATTERN = /^[a-f0-9]{4,40}$/i;
3265
+ const GIT_REF_FORBIDDEN_CHARS = /[\x00-\x1f\x7f ~^:?*[\\]/;
3266
+ const GIT_REF_FORBIDDEN_SEQUENCES = /\.\.|\.lock(\/|$)|@\{|\/\//;
3267
+ function validateHash(hash) {
3268
+ if (!GIT_HASH_PATTERN.test(hash)) throw new PrequitoError(`Invalid git hash: "${hash}". Expected 4-40 hexadecimal characters.`);
3269
+ }
3270
+ function validateRefName(name, type) {
3271
+ if (!name || name.trim() === "") throw new PrequitoError(`${type} name cannot be empty.`);
3272
+ if (name.startsWith("-")) throw new PrequitoError(`Invalid ${type} name: "${name}". Cannot start with '-'.`);
3273
+ if (name.endsWith(".")) throw new PrequitoError(`Invalid ${type} name: "${name}". Cannot end with '.'.`);
3274
+ if (name.endsWith("/")) throw new PrequitoError(`Invalid ${type} name: "${name}". Cannot end with '/'.`);
3275
+ if (GIT_REF_FORBIDDEN_CHARS.test(name)) throw new PrequitoError(`Invalid ${type} name: "${name}". Contains forbidden characters.`);
3276
+ if (GIT_REF_FORBIDDEN_SEQUENCES.test(name)) throw new PrequitoError(`Invalid ${type} name: "${name}". Contains forbidden pattern.`);
3277
+ }
3278
+ function validateBranchName(branch) {
3279
+ validateRefName(branch, "Branch");
3280
+ }
3281
+ function validateTagName(tag) {
3282
+ validateRefName(tag, "Tag");
3283
+ }
3284
+
3261
3285
  //#endregion
3262
3286
  //#region src/git/operations.ts
3263
3287
  const execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
@@ -3306,11 +3330,16 @@ async function stageAll() {
3306
3330
  await git(["add", "-A"]);
3307
3331
  }
3308
3332
  async function commit(message) {
3309
- return (await git([
3333
+ const parts = message.split("\n\n");
3334
+ const title = parts[0];
3335
+ const body = parts.slice(1).join("\n\n");
3336
+ const args = [
3310
3337
  "commit",
3311
3338
  "-m",
3312
- message
3313
- ])).stdout;
3339
+ title
3340
+ ];
3341
+ if (body) args.push("-m", body);
3342
+ return (await git(args)).stdout;
3314
3343
  }
3315
3344
  async function commitAmend() {
3316
3345
  return (await git([
@@ -3332,6 +3361,7 @@ async function forcePushLease() {
3332
3361
  return result.stdout + result.stderr;
3333
3362
  }
3334
3363
  async function checkout(branch) {
3364
+ validateBranchName(branch);
3335
3365
  await git(["checkout", branch]);
3336
3366
  }
3337
3367
  async function pull() {
@@ -3339,10 +3369,12 @@ async function pull() {
3339
3369
  return result.stdout + result.stderr;
3340
3370
  }
3341
3371
  async function rebase(branch) {
3372
+ validateBranchName(branch);
3342
3373
  const result = await git(["rebase", branch]);
3343
3374
  return result.stdout + result.stderr;
3344
3375
  }
3345
3376
  async function rebaseInteractiveEdit(hash) {
3377
+ validateHash(hash);
3346
3378
  const sedCmd = `sed -i 's/^pick ${hash.slice(0, 7)}/edit ${hash.slice(0, 7)}/'`;
3347
3379
  const result = await git([
3348
3380
  "rebase",
@@ -3369,7 +3401,8 @@ async function rebaseInteractive(count) {
3369
3401
  `HEAD~${count}`
3370
3402
  ]);
3371
3403
  }
3372
- async function pushUpstream$1(branch) {
3404
+ async function pushUpstream(branch) {
3405
+ if (branch !== void 0) validateBranchName(branch);
3373
3406
  const result = await git([
3374
3407
  "push",
3375
3408
  "--set-upstream",
@@ -3379,6 +3412,7 @@ async function pushUpstream$1(branch) {
3379
3412
  return result.stdout + result.stderr;
3380
3413
  }
3381
3414
  async function commitFixup(hash) {
3415
+ validateHash(hash);
3382
3416
  return (await git([
3383
3417
  "commit",
3384
3418
  "--fixup",
@@ -3393,14 +3427,17 @@ async function resetSoft(count = 1) {
3393
3427
  ])).stdout;
3394
3428
  }
3395
3429
  async function createBranch(branch) {
3430
+ validateBranchName(branch);
3396
3431
  await git([
3397
3432
  "checkout",
3398
3433
  "-b",
3399
3434
  branch
3400
3435
  ]);
3401
3436
  }
3402
- async function stash() {
3403
- return (await git(["stash"])).stdout;
3437
+ async function stash(message) {
3438
+ const args = ["stash"];
3439
+ if (message) args.push("-m", message);
3440
+ return (await git(args)).stdout;
3404
3441
  }
3405
3442
  async function stashPop() {
3406
3443
  return (await git(["stash", "pop"])).stdout;
@@ -3424,6 +3461,7 @@ async function logGrep(keyword, count) {
3424
3461
  return (await git(args)).stdout;
3425
3462
  }
3426
3463
  async function logTag(tag) {
3464
+ validateTagName(tag);
3427
3465
  return (await git([
3428
3466
  "log",
3429
3467
  "--oneline",
@@ -3431,12 +3469,44 @@ async function logTag(tag) {
3431
3469
  ])).stdout;
3432
3470
  }
3433
3471
  async function logTagAll(tag) {
3472
+ validateTagName(tag);
3434
3473
  return (await git([
3435
3474
  "log",
3436
3475
  "--oneline",
3437
3476
  tag
3438
3477
  ])).stdout;
3439
3478
  }
3479
+ async function diff(options = []) {
3480
+ return (await git(["diff", ...options])).stdout;
3481
+ }
3482
+ async function stashList() {
3483
+ return (await git(["stash", "list"])).stdout;
3484
+ }
3485
+
3486
+ //#endregion
3487
+ //#region src/utils/command.ts
3488
+ async function requireGitRepo() {
3489
+ if (!await isGitRepo()) throw new PrequitoError("Not inside a git repository.");
3490
+ }
3491
+ function withErrorHandling(fn) {
3492
+ return async (...args) => {
3493
+ try {
3494
+ await fn(...args);
3495
+ } catch (error) {
3496
+ if (error instanceof PrequitoError) {
3497
+ console.error(`✖ ${error.message}`);
3498
+ process.exit(1);
3499
+ }
3500
+ throw error;
3501
+ }
3502
+ };
3503
+ }
3504
+ function parseCount(value, defaultValue = 1) {
3505
+ if (value === void 0) return defaultValue;
3506
+ const num = parseInt(value, 10);
3507
+ if (isNaN(num) || num <= 0) throw new PrequitoError("Count must be a positive integer.");
3508
+ return num;
3509
+ }
3440
3510
 
3441
3511
  //#endregion
3442
3512
  //#region src/utils/spinner.ts
@@ -3483,36 +3553,23 @@ function spinner(message, options) {
3483
3553
  //#endregion
3484
3554
  //#region src/commands/commit.ts
3485
3555
  function registerCommitCommand(program) {
3486
- program.command("c").alias("commit").description("Templated commit (e.g. guito c 42 f \"msg\" -p)").argument("[args...]", "Card ID, shortcodes, and message").option("-p, --push", "Push after committing").option("-f, --force", "Push with --force-with-lease after committing").option("-d, --dry-run", "Show the generated message without executing").option("-S, --no-stage", "Skip auto-staging (git add -A)").action(async (args, opts) => {
3487
- try {
3488
- await executeCommit(args, opts);
3489
- } catch (error) {
3490
- if (error instanceof PrequitoError) {
3491
- console.error(`\u2716 ${error.message}`);
3492
- process.exit(1);
3493
- }
3494
- throw error;
3495
- }
3496
- });
3556
+ program.command("c").alias("commit").description("Templated commit (e.g. guito c 42 f \"msg\" -p)").argument("[args...]", "Card ID, shortcodes, and message").option("-p, --push", "Push after committing").option("-f, --force", "Push with --force-with-lease after committing").option("-d, --dry-run", "Show the generated message without executing").option("-S, --no-stage", "Skip auto-staging (git add -A)").action(withErrorHandling(async (args, opts) => {
3557
+ await executeCommit(args, opts);
3558
+ }));
3497
3559
  }
3498
3560
  async function executeCommit(args, opts) {
3499
- if (!await isGitRepo()) {
3500
- console.error("✖ Not inside a git repository.");
3501
- process.exit(1);
3502
- }
3561
+ await requireGitRepo();
3503
3562
  const config = await loadConfigOrDefault();
3504
- const { context, message } = parsePositionalArgs(args, config);
3505
- const commitMessage = renderTemplate(config.template, context, message);
3563
+ const { context, message, body } = parsePositionalArgs(args, config, typeof opts.body === "string" ? opts.body : void 0);
3564
+ const commitTitle = renderTemplate(config.template, context, message);
3565
+ const commitMessage = body ? `${commitTitle}\n\n${body}` : commitTitle;
3506
3566
  if (opts.dryRun) {
3507
3567
  console.log(commitMessage);
3508
3568
  return;
3509
3569
  }
3510
3570
  if (opts.stage !== false) await stageAll();
3511
- if (!await hasStagedChanges()) {
3512
- console.error("✖ No staged changes to commit.");
3513
- process.exit(1);
3514
- }
3515
- const stopCommit = spinner(`Committing: ${commitMessage}`);
3571
+ if (!await hasStagedChanges()) throw new PrequitoError("No staged changes to commit.");
3572
+ const stopCommit = spinner(`Committing: ${commitTitle}`);
3516
3573
  await commit(commitMessage);
3517
3574
  stopCommit("✔ Committed.");
3518
3575
  if (opts.force) {
@@ -3529,34 +3586,11 @@ async function executeCommit(args, opts) {
3529
3586
  //#endregion
3530
3587
  //#region src/commands/amend-push.ts
3531
3588
  function registerAmendPushCommands(program) {
3532
- program.command("ap").description("Amend last commit + force push (git push --force)").action(async () => {
3533
- try {
3534
- await amendAndPush(false);
3535
- } catch (error) {
3536
- if (error instanceof PrequitoError) {
3537
- console.error(`✖ ${error.message}`);
3538
- process.exit(1);
3539
- }
3540
- throw error;
3541
- }
3542
- });
3543
- program.command("apl").description("Amend last commit + safe force push (--force-with-lease)").action(async () => {
3544
- try {
3545
- await amendAndPush(true);
3546
- } catch (error) {
3547
- if (error instanceof PrequitoError) {
3548
- console.error(`✖ ${error.message}`);
3549
- process.exit(1);
3550
- }
3551
- throw error;
3552
- }
3553
- });
3589
+ program.command("ap").description("Amend last commit + force push (git push --force)").action(withErrorHandling(() => amendAndPush(false)));
3590
+ program.command("apl").description("Amend last commit + safe force push (--force-with-lease)").action(withErrorHandling(() => amendAndPush(true)));
3554
3591
  }
3555
3592
  async function amendAndPush(useLease) {
3556
- if (!await isGitRepo()) {
3557
- console.error("✖ Not inside a git repository.");
3558
- process.exit(1);
3559
- }
3593
+ await requireGitRepo();
3560
3594
  const stopStage = spinner("Staging all changes...");
3561
3595
  await stageAll();
3562
3596
  stopStage("✔ Staged.");
@@ -3577,45 +3611,12 @@ async function amendAndPush(useLease) {
3577
3611
  //#endregion
3578
3612
  //#region src/commands/rebase.ts
3579
3613
  function registerRebaseCommands(program) {
3580
- program.command("r <branch>").alias("rebase").description("Rebase on <branch> (checkout, pull, rebase) (e.g. guito r main)").action(async (branch) => {
3581
- try {
3582
- await quickRebase(branch);
3583
- } catch (error) {
3584
- if (error instanceof PrequitoError) {
3585
- console.error(`✖ ${error.message}`);
3586
- process.exit(1);
3587
- }
3588
- throw error;
3589
- }
3590
- });
3591
- program.command("ri <count>").description("Interactive rebase last <count> commits (e.g. guito ri 3)").action(async (count) => {
3592
- try {
3593
- await interactiveRebase(count);
3594
- } catch (error) {
3595
- if (error instanceof PrequitoError) {
3596
- console.error(`✖ ${error.message}`);
3597
- process.exit(1);
3598
- }
3599
- throw error;
3600
- }
3601
- });
3602
- program.command("re <hash>").description("Edit rebase at <hash> (e.g. guito re abc123)").action(async (hash) => {
3603
- try {
3604
- await editRebase(hash);
3605
- } catch (error) {
3606
- if (error instanceof PrequitoError) {
3607
- console.error(`✖ ${error.message}`);
3608
- process.exit(1);
3609
- }
3610
- throw error;
3611
- }
3612
- });
3614
+ program.command("r <branch>").alias("rebase").description("Rebase on <branch> (checkout, pull, rebase) (e.g. guito r main)").action(withErrorHandling(quickRebase));
3615
+ program.command("ri <count>").description("Interactive rebase last <count> commits (e.g. guito ri 3)").action(withErrorHandling(interactiveRebase));
3616
+ program.command("re <hash>").description("Edit rebase at <hash> (e.g. guito re abc123)").action(withErrorHandling(editRebase));
3613
3617
  }
3614
3618
  async function quickRebase(branch) {
3615
- if (!await isGitRepo()) {
3616
- console.error("✖ Not inside a git repository.");
3617
- process.exit(1);
3618
- }
3619
+ await requireGitRepo();
3619
3620
  const currentBranch = await getCurrentBranch();
3620
3621
  console.log(`→ Current branch: ${currentBranch}`);
3621
3622
  let stop = spinner(`Checking out ${branch}...`);
@@ -3632,23 +3633,13 @@ async function quickRebase(branch) {
3632
3633
  stop("✔ Rebase complete.");
3633
3634
  }
3634
3635
  async function interactiveRebase(count) {
3635
- if (!await isGitRepo()) {
3636
- console.error("✖ Not inside a git repository.");
3637
- process.exit(1);
3638
- }
3639
- const num = parseInt(count, 10);
3640
- if (isNaN(num) || num <= 0) {
3641
- console.error("✖ Count must be a positive integer.");
3642
- process.exit(1);
3643
- }
3636
+ await requireGitRepo();
3637
+ const num = parseCount(count);
3644
3638
  console.log(`→ Starting interactive rebase for the last ${num} commit(s)...`);
3645
3639
  await rebaseInteractive(num);
3646
3640
  }
3647
3641
  async function editRebase(hash) {
3648
- if (!await isGitRepo()) {
3649
- console.error("✖ Not inside a git repository.");
3650
- process.exit(1);
3651
- }
3642
+ await requireGitRepo();
3652
3643
  console.log(`→ Starting edit rebase on commit ${hash}...`);
3653
3644
  await rebaseInteractiveEdit(hash);
3654
3645
  console.log("✔ Rebase paused at the target commit. Make your changes, then run:");
@@ -3969,76 +3960,32 @@ function registerConfigCommand(program) {
3969
3960
  //#endregion
3970
3961
  //#region src/commands/push.ts
3971
3962
  function registerPushCommands(program) {
3972
- program.command("p").alias("push").description("Push current branch (git push)").action(async () => {
3973
- try {
3974
- await executePush();
3975
- } catch (error) {
3976
- if (error instanceof PrequitoError) {
3977
- console.error(`✖ ${error.message}`);
3978
- process.exit(1);
3979
- }
3980
- throw error;
3981
- }
3982
- });
3983
- program.command("pu").description("Push + set upstream (git push --set-upstream origin <branch>)").action(async () => {
3984
- try {
3985
- await pushUpstream();
3986
- } catch (error) {
3987
- if (error instanceof PrequitoError) {
3988
- console.error(`✖ ${error.message}`);
3989
- process.exit(1);
3990
- }
3991
- throw error;
3992
- }
3993
- });
3994
- }
3995
- async function executePush() {
3996
- if (!await isGitRepo()) {
3997
- console.error("✖ Not inside a git repository.");
3998
- process.exit(1);
3999
- }
4000
- const stop = spinner("Pushing...");
4001
- await push();
4002
- stop("✔ Pushed.");
4003
- }
4004
- async function pushUpstream() {
4005
- if (!await isGitRepo()) {
4006
- console.error("✖ Not inside a git repository.");
4007
- process.exit(1);
4008
- }
4009
- const branch = await getCurrentBranch();
4010
- const stop = spinner(`Pushing with --set-upstream origin ${branch}...`);
4011
- await pushUpstream$1(branch);
4012
- stop("✔ Pushed.");
3963
+ program.command("p").alias("push").description("Push current branch (git push)").action(withErrorHandling(async () => {
3964
+ await requireGitRepo();
3965
+ const stop = spinner("Pushing...");
3966
+ await push();
3967
+ stop("✔ Pushed.");
3968
+ }));
3969
+ program.command("pu").description("Push + set upstream (git push --set-upstream origin <branch>)").action(withErrorHandling(async () => {
3970
+ await requireGitRepo();
3971
+ const branch = await getCurrentBranch();
3972
+ const stop = spinner(`Pushing with --set-upstream origin ${branch}...`);
3973
+ await pushUpstream(branch);
3974
+ stop("✔ Pushed.");
3975
+ }));
4013
3976
  }
4014
3977
 
4015
3978
  //#endregion
4016
3979
  //#region src/commands/fixup.ts
4017
3980
  function registerFixupCommand(program) {
4018
- program.command("cf <hash>").description("Fixup commit for <hash> (e.g. guito cf abc123 -f)").option("-p, --push", "Push after creating the fixup commit").option("-f, --force", "Push with --force-with-lease after creating").action(async (hash, opts) => {
4019
- try {
4020
- await executeFixup(hash, opts);
4021
- } catch (error) {
4022
- if (error instanceof PrequitoError) {
4023
- console.error(`✖ ${error.message}`);
4024
- process.exit(1);
4025
- }
4026
- throw error;
4027
- }
4028
- });
3981
+ program.command("cf <hash>").description("Fixup commit for <hash> (e.g. guito cf abc123 -f)").option("-p, --push", "Push after creating the fixup commit").option("-f, --force", "Push with --force-with-lease after creating").action(withErrorHandling(executeFixup));
4029
3982
  }
4030
3983
  async function executeFixup(hash, opts) {
4031
- if (!await isGitRepo()) {
4032
- console.error("✖ Not inside a git repository.");
4033
- process.exit(1);
4034
- }
3984
+ await requireGitRepo();
4035
3985
  const stopStage = spinner("Staging all changes...");
4036
3986
  await stageAll();
4037
3987
  stopStage("✔ Staged.");
4038
- if (!await hasStagedChanges()) {
4039
- console.error("✖ No staged changes to commit.");
4040
- process.exit(1);
4041
- }
3988
+ if (!await hasStagedChanges()) throw new PrequitoError("No staged changes to commit.");
4042
3989
  const stopCommit = spinner(`Creating fixup commit for ${hash}...`);
4043
3990
  await commitFixup(hash);
4044
3991
  stopCommit("✔ Fixup commit created.");
@@ -4056,221 +4003,125 @@ async function executeFixup(hash, opts) {
4056
4003
  //#endregion
4057
4004
  //#region src/commands/undo.ts
4058
4005
  function registerUndoCommand(program) {
4059
- program.command("u [count]").alias("undo").description("Undo last N commits, keep changes staged (e.g. guito u 3)").action(async (count) => {
4060
- try {
4061
- await executeUndo(count);
4062
- } catch (error) {
4063
- if (error instanceof PrequitoError) {
4064
- console.error(`✖ ${error.message}`);
4065
- process.exit(1);
4066
- }
4067
- throw error;
4068
- }
4069
- });
4070
- }
4071
- async function executeUndo(count) {
4072
- if (!await isGitRepo()) {
4073
- console.error("✖ Not inside a git repository.");
4074
- process.exit(1);
4075
- }
4076
- const num = count ? parseInt(count, 10) : 1;
4077
- if (isNaN(num) || num <= 0) {
4078
- console.error("✖ Count must be a positive integer.");
4079
- process.exit(1);
4080
- }
4081
- const stop = spinner(`Undoing last ${num} commit(s)...`);
4082
- await resetSoft(num);
4083
- stop(`✔ Undid last ${num} commit(s). Changes are staged.`);
4084
- const st = await status();
4085
- if (st) console.log(st);
4006
+ program.command("u [count]").alias("undo").description("Undo last N commits, keep changes staged (e.g. guito u 3)").action(withErrorHandling(async (count) => {
4007
+ await requireGitRepo();
4008
+ const num = parseCount(count);
4009
+ console.log(`→ Undoing last ${num} commit(s)...`);
4010
+ await resetSoft(num);
4011
+ console.log(`✔ Undid last ${num} commit(s). Changes are staged.`);
4012
+ const st = await status();
4013
+ if (st) console.log(st);
4014
+ }));
4086
4015
  }
4087
4016
 
4088
4017
  //#endregion
4089
4018
  //#region src/commands/status.ts
4090
4019
  function registerStatusCommand(program) {
4091
- program.command("s").alias("status").description("Short status (git status --short)").action(async () => {
4092
- try {
4093
- await executeStatus();
4094
- } catch (error) {
4095
- if (error instanceof PrequitoError) {
4096
- console.error(`✖ ${error.message}`);
4097
- process.exit(1);
4098
- }
4099
- throw error;
4100
- }
4101
- });
4102
- }
4103
- async function executeStatus() {
4104
- if (!await isGitRepo()) {
4105
- console.error("✖ Not inside a git repository.");
4106
- process.exit(1);
4107
- }
4108
- const output = await status();
4109
- if (output) console.log(output.trimEnd());
4110
- else console.log("✨ Nothing to commit, working tree clean.");
4020
+ program.command("s").alias("status").description("Short status (git status --short)").action(withErrorHandling(async () => {
4021
+ await requireGitRepo();
4022
+ const output = await status();
4023
+ if (output) console.log(output.trimEnd());
4024
+ else console.log("✨ Nothing to commit, working tree clean.");
4025
+ }));
4111
4026
  }
4112
4027
 
4113
4028
  //#endregion
4114
4029
  //#region src/commands/switch.ts
4115
4030
  function registerSwitchCommand(program) {
4116
- program.command("sw <branch>").alias("switch").description("Switch branch, -n to create (e.g. guito sw -n feature/x)").option("-n, --new", "Create a new branch").action(async (branch, opts) => {
4117
- try {
4118
- await executeSwitch(branch, opts);
4119
- } catch (error) {
4120
- if (error instanceof PrequitoError) {
4121
- console.error(`✖ ${error.message}`);
4122
- process.exit(1);
4123
- }
4124
- throw error;
4125
- }
4126
- });
4127
- }
4128
- async function executeSwitch(branch, opts) {
4129
- if (!await isGitRepo()) {
4130
- console.error("✖ Not inside a git repository.");
4131
- process.exit(1);
4132
- }
4133
- const stop = spinner(opts.new ? `Creating and switching to ${branch}...` : `Switching to ${branch}...`);
4134
- if (opts.new) await createBranch(branch);
4135
- else await checkout(branch);
4136
- stop(`✔ On branch ${branch}.`);
4031
+ program.command("sw <branch>").alias("switch").description("Switch branch, -n to create (e.g. guito sw -n feature/x)").option("-n, --new", "Create a new branch").action(withErrorHandling(async (branch, opts) => {
4032
+ await requireGitRepo();
4033
+ if (opts.new) {
4034
+ console.log(`→ Creating and switching to ${branch}...`);
4035
+ await createBranch(branch);
4036
+ } else {
4037
+ console.log(`→ Switching to ${branch}...`);
4038
+ await checkout(branch);
4039
+ }
4040
+ console.log(`✔ On branch ${branch}.`);
4041
+ }));
4137
4042
  }
4138
4043
 
4139
4044
  //#endregion
4140
4045
  //#region src/commands/stash.ts
4141
4046
  function registerStashCommands(program) {
4142
- program.command("st").description("Stash all changes (git stash)").action(async () => {
4143
- try {
4144
- await executeStash();
4145
- } catch (error) {
4146
- if (error instanceof PrequitoError) {
4147
- console.error(`✖ ${error.message}`);
4148
- process.exit(1);
4149
- }
4150
- throw error;
4151
- }
4152
- });
4153
- program.command("stp").description("Pop the latest stash (git stash pop)").action(async () => {
4154
- try {
4155
- await executeStashPop();
4156
- } catch (error) {
4157
- if (error instanceof PrequitoError) {
4158
- console.error(`✖ ${error.message}`);
4159
- process.exit(1);
4160
- }
4161
- throw error;
4162
- }
4163
- });
4164
- }
4165
- async function executeStash() {
4166
- if (!await isGitRepo()) {
4167
- console.error("✖ Not inside a git repository.");
4168
- process.exit(1);
4169
- }
4170
- const stop = spinner("Stashing changes...");
4171
- await stash();
4172
- stop("✔ Stashed.");
4173
- }
4174
- async function executeStashPop() {
4175
- if (!await isGitRepo()) {
4176
- console.error("✖ Not inside a git repository.");
4177
- process.exit(1);
4178
- }
4179
- const stop = spinner("Restoring stashed changes...");
4180
- await stashPop();
4181
- stop("✔ Restored.");
4047
+ program.command("st").description("Stash all changes (git stash)").option("-m, --message <message>", "Stash with a descriptive message").action(withErrorHandling(async (opts) => {
4048
+ await requireGitRepo();
4049
+ if (opts.message) console.log(`→ Stashing changes: ${opts.message}...`);
4050
+ else console.log("→ Stashing changes...");
4051
+ await stash(opts.message);
4052
+ console.log("✔ Stashed.");
4053
+ }));
4054
+ program.command("stp").description("Pop the latest stash (git stash pop)").action(withErrorHandling(async () => {
4055
+ await requireGitRepo();
4056
+ console.log("→ Restoring stashed changes...");
4057
+ await stashPop();
4058
+ console.log("✔ Restored.");
4059
+ }));
4060
+ program.command("stl").description("List all stashes (git stash list)").action(withErrorHandling(async () => {
4061
+ await requireGitRepo();
4062
+ const output = await stashList();
4063
+ if (output) console.log(output.trimEnd());
4064
+ else console.log("✨ No stashes found.");
4065
+ }));
4182
4066
  }
4183
4067
 
4184
4068
  //#endregion
4185
4069
  //#region src/commands/log.ts
4186
4070
  function registerLogCommand(program) {
4187
- program.command("l [count]").alias("log").description("Compact log, default last 10 (e.g. guito l 20)").action(async (count) => {
4188
- try {
4189
- await executeLog(count);
4190
- } catch (error) {
4191
- if (error instanceof PrequitoError) {
4192
- console.error(`✖ ${error.message}`);
4193
- process.exit(1);
4194
- }
4195
- throw error;
4196
- }
4197
- });
4198
- }
4199
- async function executeLog(count) {
4200
- if (!await isGitRepo()) {
4201
- console.error("✖ Not inside a git repository.");
4202
- process.exit(1);
4203
- }
4204
- const num = count ? parseInt(count, 10) : 10;
4205
- if (isNaN(num) || num <= 0) {
4206
- console.error("✖ Count must be a positive integer.");
4207
- process.exit(1);
4208
- }
4209
- const output = await logOneline(num);
4210
- if (output) console.log(output.trimEnd());
4211
- else console.log("✨ No commits found.");
4071
+ program.command("l [count]").alias("log").description("Compact log, default last 10 (e.g. guito l 20)").action(withErrorHandling(async (count) => {
4072
+ await requireGitRepo();
4073
+ const num = parseCount(count, 10);
4074
+ const output = await logOneline(num);
4075
+ if (output) console.log(output.trimEnd());
4076
+ else console.log("✨ No commits found.");
4077
+ }));
4212
4078
  }
4213
4079
 
4214
4080
  //#endregion
4215
4081
  //#region src/commands/find.ts
4216
4082
  function registerFindCommands(program) {
4217
- program.command("f <keyword>").alias("find").description("Search commits by message (e.g. guito f \"login\")").option("-n, --number <count>", "Limit number of results").action(async (keyword, opts) => {
4218
- try {
4219
- await executeFind(keyword, opts);
4220
- } catch (error) {
4221
- if (error instanceof PrequitoError) {
4222
- console.error(`✖ ${error.message}`);
4223
- process.exit(1);
4224
- }
4225
- throw error;
4226
- }
4227
- });
4228
- program.command("t <tag>").alias("tag").description("Commits since <tag> (e.g. guito t v1.0.0)").option("-a, --all", "Show all commits reachable from the tag").action(async (tag, opts) => {
4229
- try {
4230
- await executeTag(tag, opts);
4231
- } catch (error) {
4232
- if (error instanceof PrequitoError) {
4233
- console.error(`✖ ${error.message}`);
4234
- process.exit(1);
4235
- }
4236
- throw error;
4237
- }
4238
- });
4239
- }
4240
- async function executeFind(keyword, opts) {
4241
- if (!await isGitRepo()) {
4242
- console.error("✖ Not inside a git repository.");
4243
- process.exit(1);
4244
- }
4245
- const count = opts.number ? parseInt(opts.number, 10) : void 0;
4246
- const stop = spinner(`Searching for "${keyword}"...`);
4247
- const output = await logGrep(keyword, count);
4248
- stop("");
4249
- if (output) console.log(output.trimEnd());
4250
- else console.log(`✨ No commits found matching "${keyword}".`);
4251
- }
4252
- async function executeTag(tag, opts) {
4253
- if (!await isGitRepo()) {
4254
- console.error("✖ Not inside a git repository.");
4255
- process.exit(1);
4256
- }
4257
- if (opts.all) {
4258
- console.log(`🏷️ Commits reachable from ${tag}:`);
4259
- const output = await logTagAll(tag);
4083
+ program.command("f <keyword>").alias("find").description("Search commits by message (e.g. guito f \"login\")").option("-n, --number <count>", "Limit number of results").action(withErrorHandling(async (keyword, opts) => {
4084
+ await requireGitRepo();
4085
+ const count = opts.number ? parseInt(opts.number, 10) : void 0;
4086
+ const output = await logGrep(keyword, count);
4260
4087
  if (output) console.log(output.trimEnd());
4261
- else console.log("✨ No commits found.");
4262
- } else {
4263
- console.log(`🏷️ Commits since ${tag}:`);
4264
- const output = await logTag(tag);
4088
+ else console.log(`✨ No commits found matching "${keyword}".`);
4089
+ }));
4090
+ program.command("t <tag>").alias("tag").description("Commits since <tag> (e.g. guito t v1.0.0)").option("-a, --all", "Show all commits reachable from the tag").action(withErrorHandling(async (tag, opts) => {
4091
+ await requireGitRepo();
4092
+ if (opts.all) {
4093
+ console.log(`🏷️ Commits reachable from ${tag}:`);
4094
+ const output = await logTagAll(tag);
4095
+ if (output) console.log(output.trimEnd());
4096
+ else console.log("✨ No commits found.");
4097
+ } else {
4098
+ console.log(`🏷️ Commits since ${tag}:`);
4099
+ const output = await logTag(tag);
4100
+ if (output) console.log(output.trimEnd());
4101
+ else console.log("✨ No commits since this tag.");
4102
+ }
4103
+ }));
4104
+ }
4105
+
4106
+ //#endregion
4107
+ //#region src/commands/diff.ts
4108
+ function registerDiffCommand(program) {
4109
+ program.command("d").alias("diff").description("Show changes (git diff)").option("-s, --staged", "Show staged changes only").option("--stat", "Show diffstat summary").option("-n, --name-only", "Show only names of changed files").action(withErrorHandling(async (opts) => {
4110
+ await requireGitRepo();
4111
+ const options = [];
4112
+ if (opts.staged) options.push("--staged");
4113
+ if (opts.stat) options.push("--stat");
4114
+ if (opts.nameOnly) options.push("--name-only");
4115
+ const output = await diff(options);
4265
4116
  if (output) console.log(output.trimEnd());
4266
- else console.log("✨ No commits since this tag.");
4267
- }
4117
+ else console.log("✨ No changes.");
4118
+ }));
4268
4119
  }
4269
4120
 
4270
4121
  //#endregion
4271
4122
  //#region src/cli.ts
4272
4123
  const program = new Command();
4273
- program.name("guito").description("preguito - a lazy git CLI with commit templates and shortcuts").version("0.1.0").addHelpText("before", "\n🦥 preguito v0.1.0\n A lazy git CLI with commit templates and shortcuts.\n");
4124
+ program.name("guito").description("preguito - a lazy git CLI with commit templates and shortcuts").version("0.2.2").addHelpText("before", "\n🦥 preguito v0.1.0\n A lazy git CLI with commit templates and shortcuts.\n");
4274
4125
  registerCommitCommand(program);
4275
4126
  registerAmendPushCommands(program);
4276
4127
  registerRebaseCommands(program);
@@ -4284,6 +4135,7 @@ registerSwitchCommand(program);
4284
4135
  registerStashCommands(program);
4285
4136
  registerLogCommand(program);
4286
4137
  registerFindCommands(program);
4138
+ registerDiffCommand(program);
4287
4139
  program.parse();
4288
4140
 
4289
4141
  //#endregion