@kyubiware/commit-mint 0.9.2 → 0.9.3
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/bin.mjs +149 -12
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { cli, command } from "cleye";
|
|
3
3
|
import * as p$1 from "@clack/prompts";
|
|
4
|
-
import { S_BAR, S_BAR_END, S_RADIO_ACTIVE, S_RADIO_INACTIVE, intro, isCancel, limitOptions, log, outro, spinner, symbol } from "@clack/prompts";
|
|
4
|
+
import { S_BAR, S_BAR_END, S_CHECKBOX_ACTIVE, S_CHECKBOX_INACTIVE, S_CHECKBOX_SELECTED, S_RADIO_ACTIVE, S_RADIO_INACTIVE, intro, isCancel, limitOptions, log, outro, spinner, symbol } from "@clack/prompts";
|
|
5
5
|
import { bold, cyan, dim, green, red, yellow } from "kolorist";
|
|
6
6
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import os from "node:os";
|
|
@@ -33,7 +33,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
33
33
|
//#region package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@kyubiware/commit-mint",
|
|
36
|
-
version: "0.9.
|
|
36
|
+
version: "0.9.3",
|
|
37
37
|
description: "🌿 AI-powered git commit tool — auto-group changed files, generate messages, run pre-commit checks",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: { "cmint": "./dist/bin.mjs" },
|
|
@@ -2468,6 +2468,21 @@ function truncate(message, maxLength) {
|
|
|
2468
2468
|
if (collapsed.length <= maxLength) return collapsed;
|
|
2469
2469
|
return `${collapsed.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
2470
2470
|
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Filter noisy lines from vitest/jest test output.
|
|
2473
|
+
* Strips stack trace frames (❯ prefix) and horizontal rule separators (⎯⎯⎯ lines),
|
|
2474
|
+
* while preserving FAIL lines, assertion messages, diff content, and the final summary.
|
|
2475
|
+
*/
|
|
2476
|
+
function filterTestOutput(output) {
|
|
2477
|
+
return output.split("\n").filter((line) => {
|
|
2478
|
+
if (/^\s*[❯>]\s/.test(line)) return false;
|
|
2479
|
+
const stripped = line.replace(/\s/g, "");
|
|
2480
|
+
if (stripped.length > 10) {
|
|
2481
|
+
if ((stripped.match(/[⎯\-═]/g) ?? []).length / stripped.length > .5) return false;
|
|
2482
|
+
}
|
|
2483
|
+
return true;
|
|
2484
|
+
}).join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
2485
|
+
}
|
|
2471
2486
|
async function showCheckFailureMenu(errors, rawStderr, onRetry) {
|
|
2472
2487
|
debug("showCheckFailureMenu: %d errors", errors.length);
|
|
2473
2488
|
let clipboardCopied = false;
|
|
@@ -2507,13 +2522,13 @@ async function showCheckFailureMenu(errors, rawStderr, onRetry) {
|
|
|
2507
2522
|
debug("showCheckFailureMenu: user chose %s", choice);
|
|
2508
2523
|
switch (choice) {
|
|
2509
2524
|
case "copy":
|
|
2510
|
-
if (await copyToClipboard(rawStderr)) {
|
|
2525
|
+
if (await copyToClipboard(filterTestOutput(rawStderr))) {
|
|
2511
2526
|
clipboardCopied = true;
|
|
2512
2527
|
p$1.log.step(green("Copied to clipboard."));
|
|
2513
2528
|
} else p$1.log.warn(red("No clipboard tool found. Install xclip, wl-copy, or xsel."));
|
|
2514
2529
|
continue;
|
|
2515
2530
|
case "view":
|
|
2516
|
-
p$1.note(rawStderr
|
|
2531
|
+
p$1.note(filterTestOutput(rawStderr) || "(no raw output)", "Full error output");
|
|
2517
2532
|
continue;
|
|
2518
2533
|
case "retry":
|
|
2519
2534
|
if (onRetry) return "retried";
|
|
@@ -3570,6 +3585,52 @@ let m = class {
|
|
|
3570
3585
|
}
|
|
3571
3586
|
}
|
|
3572
3587
|
};
|
|
3588
|
+
let nt = class extends m {
|
|
3589
|
+
options;
|
|
3590
|
+
cursor = 0;
|
|
3591
|
+
get _value() {
|
|
3592
|
+
return this.options[this.cursor].value;
|
|
3593
|
+
}
|
|
3594
|
+
get _enabledOptions() {
|
|
3595
|
+
return this.options.filter((t) => t.disabled !== !0);
|
|
3596
|
+
}
|
|
3597
|
+
toggleAll() {
|
|
3598
|
+
const t = this._enabledOptions, s = this.value !== void 0 && this.value.length === t.length;
|
|
3599
|
+
this.value = s ? [] : t.map((e) => e.value);
|
|
3600
|
+
}
|
|
3601
|
+
toggleInvert() {
|
|
3602
|
+
const t = this.value;
|
|
3603
|
+
if (!t) return;
|
|
3604
|
+
const s = this._enabledOptions.filter((e) => !t.includes(e.value));
|
|
3605
|
+
this.value = s.map((e) => e.value);
|
|
3606
|
+
}
|
|
3607
|
+
toggleValue() {
|
|
3608
|
+
this.value === void 0 && (this.value = []);
|
|
3609
|
+
const t = this.value.includes(this._value);
|
|
3610
|
+
this.value = t ? this.value.filter((s) => s !== this._value) : [...this.value, this._value];
|
|
3611
|
+
}
|
|
3612
|
+
constructor(t) {
|
|
3613
|
+
super(t, !1), this.options = t.options, this.value = [...t.initialValues ?? []];
|
|
3614
|
+
const s = Math.max(this.options.findIndex(({ value: e }) => e === t.cursorAt), 0);
|
|
3615
|
+
this.cursor = this.options[s].disabled ? f(s, 1, this.options) : s, this.on("key", (e) => {
|
|
3616
|
+
e === "a" && this.toggleAll(), e === "i" && this.toggleInvert();
|
|
3617
|
+
}), this.on("cursor", (e) => {
|
|
3618
|
+
switch (e) {
|
|
3619
|
+
case "left":
|
|
3620
|
+
case "up":
|
|
3621
|
+
this.cursor = f(this.cursor, -1, this.options);
|
|
3622
|
+
break;
|
|
3623
|
+
case "down":
|
|
3624
|
+
case "right":
|
|
3625
|
+
this.cursor = f(this.cursor, 1, this.options);
|
|
3626
|
+
break;
|
|
3627
|
+
case "space":
|
|
3628
|
+
this.toggleValue();
|
|
3629
|
+
break;
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3573
3634
|
var ut = class extends m {
|
|
3574
3635
|
options;
|
|
3575
3636
|
cursor = 0;
|
|
@@ -3598,6 +3659,86 @@ var ut = class extends m {
|
|
|
3598
3659
|
}
|
|
3599
3660
|
};
|
|
3600
3661
|
//#endregion
|
|
3662
|
+
//#region src/ui/file-multiselect.ts
|
|
3663
|
+
/**
|
|
3664
|
+
* Render a checkbox-style option line with 4 visual states:
|
|
3665
|
+
* active-selected, selected, active, inactive
|
|
3666
|
+
*/
|
|
3667
|
+
function renderCheckboxOption(label, selected, active) {
|
|
3668
|
+
if (selected && active) return `${green(S_CHECKBOX_SELECTED)} ${label}`;
|
|
3669
|
+
if (selected) return `${dim(S_CHECKBOX_SELECTED)} ${dim(label)}`;
|
|
3670
|
+
if (active) return `${green(S_CHECKBOX_ACTIVE)} ${label}`;
|
|
3671
|
+
return `${dim(S_CHECKBOX_INACTIVE)} ${dim(label)}`;
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* Build the render function for a MultiSelectPrompt with file-multiselect
|
|
3675
|
+
* styling and a hotkey hint line at the bottom.
|
|
3676
|
+
*/
|
|
3677
|
+
function buildMultiSelectRender(message, options, output) {
|
|
3678
|
+
return function() {
|
|
3679
|
+
const header = `${symbol(this.state)} ${message}`;
|
|
3680
|
+
const value = this.value ?? [];
|
|
3681
|
+
const cursor = this.cursor;
|
|
3682
|
+
switch (this.state) {
|
|
3683
|
+
case "submit": {
|
|
3684
|
+
const labels = value.map((v) => {
|
|
3685
|
+
return options.find((o) => o.value === v)?.label ?? String(v);
|
|
3686
|
+
}).join(dim(", "));
|
|
3687
|
+
return `${header}\n${dim(S_BAR)} ${dim(labels)}`;
|
|
3688
|
+
}
|
|
3689
|
+
case "cancel": {
|
|
3690
|
+
const cancelled = styleText(["strikethrough", "dim"], "Cancelled");
|
|
3691
|
+
return `${header}\n${dim(S_BAR_END)} ${cancelled}`;
|
|
3692
|
+
}
|
|
3693
|
+
default: {
|
|
3694
|
+
const lines = limitOptions({
|
|
3695
|
+
cursor,
|
|
3696
|
+
options,
|
|
3697
|
+
style: (opt, active) => renderCheckboxOption(opt.label, value.includes(opt.value), active),
|
|
3698
|
+
maxItems: 7,
|
|
3699
|
+
output: output ?? process.stdout
|
|
3700
|
+
}).map((line) => `${dim(S_BAR)} ${line}`);
|
|
3701
|
+
const hintLine = "↑/↓ navigate · space select · enter confirm · A select all · N select none";
|
|
3702
|
+
return [
|
|
3703
|
+
header,
|
|
3704
|
+
...lines,
|
|
3705
|
+
`${dim(S_BAR_END)} ${dim(hintLine)}`
|
|
3706
|
+
].join("\n");
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
/**
|
|
3712
|
+
* Multi-select prompt with extended hotkeys for file selection.
|
|
3713
|
+
*
|
|
3714
|
+
* Built-in (from `@clack/core`'s `MultiSelectPrompt`):
|
|
3715
|
+
* `a` — toggle all (select all / deselect all)
|
|
3716
|
+
* `i` — invert selection
|
|
3717
|
+
*
|
|
3718
|
+
* Extended hotkeys:
|
|
3719
|
+
* `A` (shift+A) — select all unconditionally
|
|
3720
|
+
* `N` — select none (deselect all)
|
|
3721
|
+
*/
|
|
3722
|
+
async function fileMultiSelect(message, options, opts) {
|
|
3723
|
+
const required = opts?.required ?? true;
|
|
3724
|
+
const prompt = new nt({
|
|
3725
|
+
options,
|
|
3726
|
+
required,
|
|
3727
|
+
input: opts?.input,
|
|
3728
|
+
output: opts?.output,
|
|
3729
|
+
validate: (values) => {
|
|
3730
|
+
if (required && (!values || values.length === 0)) return "Please select at least one option.";
|
|
3731
|
+
},
|
|
3732
|
+
render: buildMultiSelectRender(message, options, opts?.output)
|
|
3733
|
+
});
|
|
3734
|
+
prompt.on("key", (char, key) => {
|
|
3735
|
+
if (!char) return;
|
|
3736
|
+
if (char === "a" && key?.shift) prompt.value = options.map((o) => o.value);
|
|
3737
|
+
if (char === "n") prompt.value = [];
|
|
3738
|
+
});
|
|
3739
|
+
return await prompt.prompt();
|
|
3740
|
+
}
|
|
3741
|
+
//#endregion
|
|
3601
3742
|
//#region src/ui/toggle-select.ts
|
|
3602
3743
|
const ON_LABEL = styleText("green", "ON");
|
|
3603
3744
|
const OFF_LABEL = styleText("dim", "OFF");
|
|
@@ -3795,14 +3936,10 @@ async function showStagingMenu(files, hasChecks) {
|
|
|
3795
3936
|
files: files.map((f) => f.path),
|
|
3796
3937
|
all: true
|
|
3797
3938
|
};
|
|
3798
|
-
const selected = await
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
value: f.path
|
|
3803
|
-
})),
|
|
3804
|
-
required: true
|
|
3805
|
-
});
|
|
3939
|
+
const selected = await fileMultiSelect("Select files to stage:", sorted.map((f) => ({
|
|
3940
|
+
label: `${statusLabel(f.status)} ${f.path}`,
|
|
3941
|
+
value: f.path
|
|
3942
|
+
})), { required: true });
|
|
3806
3943
|
if (p$1.isCancel(selected)) return null;
|
|
3807
3944
|
return {
|
|
3808
3945
|
files: selected,
|