better-commits 1.23.2 → 1.23.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.
Files changed (68) hide show
  1. package/dist/branch.js +628 -19
  2. package/dist/chunk-GAAS3VS3.js +922 -0
  3. package/dist/chunk-H5CLUQIL.js +313 -0
  4. package/dist/index.js +1122 -41
  5. package/dist/init.js +44 -1
  6. package/package.json +12 -4
  7. package/readme.md +4 -2
  8. package/.better-commits.json +0 -52
  9. package/.github/workflows/publish.yml +0 -34
  10. package/.github/workflows/test.yml +0 -27
  11. package/.prettierignore +0 -5
  12. package/.prettierrc +0 -1
  13. package/dist/chunk-43H72S6V.js +0 -1
  14. package/dist/chunk-B7AGSPP3.js +0 -261
  15. package/src/args.test.ts +0 -128
  16. package/src/args.ts +0 -125
  17. package/src/branch-args.test.ts +0 -75
  18. package/src/branch-args.ts +0 -107
  19. package/src/branch-help.ts +0 -125
  20. package/src/branch.ts +0 -97
  21. package/src/default-config-template.ts +0 -258
  22. package/src/git.test.ts +0 -64
  23. package/src/git.ts +0 -72
  24. package/src/help.ts +0 -138
  25. package/src/index.test.ts +0 -7
  26. package/src/index.ts +0 -101
  27. package/src/init.test.ts +0 -123
  28. package/src/init.ts +0 -46
  29. package/src/prompts/autocomplete-multiselect.test.ts +0 -129
  30. package/src/prompts/autocomplete-multiselect.ts +0 -249
  31. package/src/prompts/branch-checkout.prompt.ts +0 -36
  32. package/src/prompts/branch-confirm.prompt.test.ts +0 -89
  33. package/src/prompts/branch-confirm.prompt.ts +0 -149
  34. package/src/prompts/branch-description.prompt.ts +0 -37
  35. package/src/prompts/branch-runnable.ts +0 -13
  36. package/src/prompts/branch-scope.prompt.ts +0 -59
  37. package/src/prompts/branch-ticket.prompt.ts +0 -41
  38. package/src/prompts/branch-type.prompt.ts +0 -46
  39. package/src/prompts/branch-user.prompt.ts +0 -50
  40. package/src/prompts/branch-version.prompt.ts +0 -41
  41. package/src/prompts/commit-body.prompt.ts +0 -51
  42. package/src/prompts/commit-confirm.prompt.ts +0 -123
  43. package/src/prompts/commit-footer.prompt.ts +0 -195
  44. package/src/prompts/commit-scope.prompt.ts +0 -91
  45. package/src/prompts/commit-status.prompt.ts +0 -66
  46. package/src/prompts/commit-ticket.prompt.ts +0 -82
  47. package/src/prompts/commit-title.prompt.ts +0 -98
  48. package/src/prompts/commit-type.prompt.ts +0 -96
  49. package/src/prompts/runnable.ts +0 -13
  50. package/src/utils/build-branch.test.ts +0 -159
  51. package/src/utils/build-branch.ts +0 -48
  52. package/src/utils/build-commit-string.test.ts +0 -273
  53. package/src/utils/build-commit-string.ts +0 -163
  54. package/src/utils/commit-title-size.ts +0 -24
  55. package/src/utils/infer.test.ts +0 -174
  56. package/src/utils/infer.ts +0 -160
  57. package/src/utils/messages.ts +0 -25
  58. package/src/utils/no-interactive-branch-validation.test.ts +0 -193
  59. package/src/utils/no-interactive-validation.test.ts +0 -174
  60. package/src/utils/no-interactive-validation.ts +0 -213
  61. package/src/utils.test.ts +0 -164
  62. package/src/utils.ts +0 -235
  63. package/src/valibot-consts.ts +0 -117
  64. package/src/valibot-state.test.ts +0 -57
  65. package/src/valibot-state.ts +0 -276
  66. package/tsconfig.json +0 -15
  67. package/tsup.config.ts +0 -12
  68. package/vitest.config.ts +0 -8
package/dist/index.js CHANGED
@@ -1,61 +1,1142 @@
1
1
  #! /usr/bin/env node
2
- import{a as ft,b as ht,d as bt,e as N,f as j,g as D,h as f,i as I,j as S,k as et,l as yt,m as it}from"./chunk-43H72S6V.js";import{a as ct,c as mt,f as n,i as lt,k as pt,l as _t,m as ut,o as T,p as gt,q as dt,r as g,s as d}from"./chunk-B7AGSPP3.js";import{chdir as Kt}from"process";import*as z from"@clack/prompts";import{ValiError as Ht,parse as Ot}from"valibot";import Ut from"configstore";import*as V from"@clack/prompts";var p=class{constructor(t,e,o){this.config=t;this.commit_state=e;this.prompt_cache=o}};var L=class extends p{async run(){if(this.#i){let{initial_value:t,message:e}=this.#e,s=await(this.config.commit_type.autocomplete?V.autocomplete:V.select)({message:e,initialValue:t,maxItems:this.#s,options:this.#t});V.isCancel(s)&&process.exit(0),this.#r(s)}}get#i(){return this.config.commit_type.enable}get#e(){let t=g(this.prompt_cache,"commit_type");if(t)return{initial_value:t,message:f("Commit type")};if(this.config.commit_type.infer_type_from_branch){let e=N(this.#t,n.git_args);if(e)return{message:I("Commit type"),initial_value:e}}return{initial_value:this.config.commit_type.initial_value,message:"Select a commit type"}}get#t(){return this.config.commit_type.options}get#o(){return this.#t.reduce((t,e)=>({...t,[e.value]:{emoji:e.emoji??"",trailer:e.trailer??""}}),{})}get#s(){return this.config.commit_type.max_items}#r(t){d(this.prompt_cache,"commit_type",t);let e=this.#o;this.commit_state.trailer=e[t].trailer,this.commit_state.type=this.config.commit_type.append_emoji_to_commit&&this.config.commit_type.emoji_commit_position==="Start"?`${e[t].emoji} ${t}`.trim():t}};import*as k from"@clack/prompts";var G=class extends p{async run(){if(!this.#i)return;let{initial_value:t,message:e}=this.#e(),s=await(this.config.commit_scope.autocomplete?k.autocomplete:k.select)({message:e,initialValue:t,maxItems:this.#t,options:this.#o});k.isCancel(s)&&process.exit(0),await this.#r(s)}get#i(){return this.config.commit_scope.enable}#e(){let t=g(this.prompt_cache,"commit_scope");if(t)return{initial_value:t,message:f("Commit scope")};if(this.config.commit_scope.infer_scope_from_branch){let e=D(this.#o,n.git_args);if(e)return{initial_value:e,message:I("Commit scope")}}return{initial_value:this.config.commit_scope.initial_value,message:"Select a commit scope"}}get#t(){return this.config.commit_scope.max_items}get#o(){return this.config.commit_scope.options}get#s(){return this.config.commit_scope.custom_scope}async#r(t){d(this.prompt_cache,"commit_scope",t);let e=t;if(e===ct&&this.#s){let o=await k.text({message:"Write a custom scope",placeholder:""});k.isCancel(o)&&process.exit(0),e=o??""}this.commit_state.scope=e}};import*as B from"@clack/prompts";var M=class extends p{async run(){let{initial_value:t,message:e}=this.#o();if(this.commit_state.ticket=t,this.#e){let o=await B.text({message:e,placeholder:"",initialValue:t});B.isCancel(o)&&process.exit(0),d(this.prompt_cache,"commit_ticket",o),this.commit_state.ticket=o??""}this.#t&&this.commit_state.ticket&&!this.commit_state.ticket.startsWith("#")&&(this.commit_state.ticket="#"+this.commit_state.ticket)}get#i(){return this.config.check_ticket.infer_ticket}get#e(){return this.config.check_ticket.confirm_ticket}get#t(){return this.config.check_ticket.prepend_hashtag==="Always"}#o(){let t=g(this.prompt_cache,"commit_ticket");if(t)return{initial_value:t,message:f("Ticket / issue")};if(this.#i){let e=j({append_hashtag:this.config.check_ticket.append_hashtag,prepend_hashtag:this.config.check_ticket.prepend_hashtag},n.git_args);if(e)return{initial_value:e,message:I("Ticket / issue")}}return{initial_value:this.commit_state.ticket,message:S("Add ticket / issue")}}};import*as q from"@clack/prompts";var W=class extends p{async run(){let{initial_value:t,message:e}=this.#i(),o=await q.text({message:e,initialValue:t,placeholder:"",validate:s=>this.#e(s)});q.isCancel(o)&&process.exit(0),this.#n(o??"")}#i(){let t=g(this.prompt_cache,"commit_title");return t?{initial_value:t,message:f("Commit title")}:{initial_value:this.commit_state.title,message:"Write a brief title describing the commit"}}#e(t){if(!t)return"Please enter a title";if(this.#o(t)>this.#t)return`Exceeded max length. Title max [${this.#t}]`}get#t(){return this.config.commit_title.max_size}#o(t){return ft({type:this.commit_state.type,scope:this.commit_state.scope,ticket:this.commit_state.ticket,title:t},{include_ticket:this.config.check_ticket.add_to_title})}get#s(){return this.config.commit_type.options.reduce((t,e)=>({...t,[e.value]:{emoji:e.emoji??""}}),{})}#r(t){return this.config.commit_type.append_emoji_to_commit&&this.config.commit_type.emoji_commit_position==="After-Colon"?`${this.#s[this.commit_state.type]?.emoji??""} ${t}`.trim():t}#n(t){d(this.prompt_cache,"commit_title",t),this.commit_state.title=dt(this.#r(t))}};import*as H from"@clack/prompts";var K=class extends p{async run(){if(!this.#i)return;let{initial_value:t,message:e}=this.#e(),o=await H.text({message:e,initialValue:t,placeholder:"",validate:s=>this.#t(s)});H.isCancel(o)&&process.exit(0),this.#o(o??"")}get#i(){return this.config.commit_body.enable}#e(){let t=g(this.prompt_cache,"commit_body");return t?{initial_value:t,message:f("Commit body")}:{initial_value:"",message:S("Write a detailed description of the changes")}}#t(t){if(this.config.commit_body.required&&!t)return"Please enter a description"}#o(t){d(this.prompt_cache,"commit_body",t),this.commit_state.body=t}};import*as $ from"@clack/prompts";var U=class extends p{async run(){if(!this.#i)return;let{initial_values:t,message:e}=this.#o(),o=await $.multiselect({message:e,initialValues:t,options:this.#e,required:!1});$.isCancel(o)&&process.exit(0);let s=this.#r(o),r=await this.#n(s);this.#m(o,s,r)}get#i(){return this.config.commit_footer.enable}get#e(){let t=new Set(this.config.commit_footer.options);return lt.filter(e=>t.has(e.value))}get#t(){return this.#e.map(t=>t.value)}#o(){let t=g(this.prompt_cache,"commit_footer");return t?{initial_values:this.#s(t),message:et(f("Commit footers"))}:{initial_values:this.config.commit_footer.initial_value.filter(o=>this.#t.includes(o)),message:et(S("Select optional footers"))}}#s(t){return t.split(",").map(e=>e.trim()).filter(e=>this.#t.includes(e))}#r(t){return{includes_breaking_change:t.includes("breaking-change"),includes_deprecated:t.includes("deprecated"),includes_closes:t.includes("closes"),includes_custom:t.includes("custom"),includes_trailer:t.includes("trailer")}}async#n(t){let e={breaking_title:"",breaking_body:"",deprecated_title:"",deprecated_body:"",custom_footer:""};return t.includes_breaking_change&&(e.breaking_title=await this.#c("Breaking changes: Write a short title / summary"),e.breaking_body=await this.#a(S("Breaking Changes: Write a description & migration instructions"))),t.includes_deprecated&&(e.deprecated_title=await this.#c("Deprecated: Write a short title / summary"),e.deprecated_body=await this.#a(S("Deprecated: Write a description"))),t.includes_custom&&(e.custom_footer=await this.#a("Write a custom footer")),e}async#c(t){let e=await $.text({message:t,placeholder:"",validate:o=>{if(!o)return"Please enter a title / summary"}});return $.isCancel(e)&&process.exit(0),e??""}async#a(t){let e=await $.text({message:t,placeholder:""});return $.isCancel(e)&&process.exit(0),e??""}#m(t,e,o){d(this.prompt_cache,"commit_footer",t.join(",")),this.commit_state.breaking_title=o.breaking_title,this.commit_state.breaking_body=o.breaking_body,this.commit_state.deprecates_title=o.deprecated_title,this.commit_state.deprecates_body=o.deprecated_body,this.commit_state.custom_footer=o.custom_footer,this.commit_state.closes=e.includes_closes?"Closes:":"",e.includes_trailer||(this.commit_state.trailer="")}};import*as h from"@clack/prompts";import{execSync as Ct}from"child_process";import u from"picocolors";function ot({commit_state:i,config:t,colorize:e=!1,escape_quotes:o=!1,include_trailer:s=!1}){let r="";if(i.type&&(r+=e?u.blue(i.type):i.type),i.scope){let c=e?u.cyan(i.scope):i.scope;r+=`(${c})`}let a=i.ticket,l=t.check_ticket.surround;if(i.ticket&&l){let c=l.charAt(0),b=l.charAt(1);a=`${c}${i.ticket}${b}`}let C=t.check_ticket.title_position==="beginning";a&&t.check_ticket.add_to_title&&C&&(r=`${e?u.magenta(a):a} ${r}`);let x=t.check_ticket.title_position==="before-colon";if(a&&t.check_ticket.add_to_title&&x){let c=i.scope||i.type&&!t.check_ticket.surround?" ":"";r+=e?u.magenta(c+a):c+a}i.breaking_title&&t.breaking_change.add_exclamation_to_title&&(r+=e?u.red("!"):"!"),(i.scope||i.type||a&&x)&&(r+=": ");let y=t.check_ticket.title_position==="start",A=t.check_ticket.title_position==="end";if(a&&t.check_ticket.add_to_title&&y&&(r+=e?u.magenta(a)+" ":a+" "),i.title&&(r+=e?u.reset(i.title):i.title),a&&t.check_ticket.add_to_title&&A&&(r+=" "+(e?u.magenta(a):a)),i.body){let c=i.body;t.commit_body.split_by_period&&(c=c.replace(/\.\s+/g,`.
3
- `));let O=c.split("\\n").map(F=>e?u.reset(F.trim()):F.trim()).join(`
4
- `);r+=`
2
+ import {
3
+ a_for_all_message,
4
+ cache_message,
5
+ create_strict_commit_state,
6
+ dry_run_message,
7
+ get_commit_title_size,
8
+ infer_not_interactive,
9
+ infer_scope_from_git,
10
+ infer_ticket_from_git,
11
+ infer_type_from_git,
12
+ inferred_message,
13
+ optional_message,
14
+ space_to_select_message
15
+ } from "./chunk-H5CLUQIL.js";
16
+ import {
17
+ COMMIT_FOOTER_OPTIONS,
18
+ CUSTOM_SCOPE_KEY,
19
+ CommitState,
20
+ NOOP_PROMPT_CACHE,
21
+ addNewLine,
22
+ clean_commit_title,
23
+ flags,
24
+ get_git_root,
25
+ get_package_version,
26
+ get_value_from_cache,
27
+ load_setup,
28
+ set_value_cache
29
+ } from "./chunk-GAAS3VS3.js";
5
30
 
6
- ${O}`}if(i.breaking_title){let c=e?u.red(`BREAKING CHANGE: ${i.breaking_title}`):`BREAKING CHANGE: ${i.breaking_title}`;r+=`
31
+ // src/index.ts
32
+ import { chdir } from "process";
33
+ import * as p10 from "@clack/prompts";
34
+ import { ValiError, parse } from "valibot";
35
+ import Configstore from "configstore";
7
36
 
8
- ${c}`}if(i.breaking_body){let c=e?u.red(i.breaking_body):i.breaking_body;r+=`
37
+ // src/prompts/commit-type.prompt.ts
38
+ import * as p from "@clack/prompts";
9
39
 
10
- ${c}`}if(i.deprecates_title){let c=e?u.yellow(`DEPRECATED: ${i.deprecates_title}`):`DEPRECATED: ${i.deprecates_title}`;r+=`
40
+ // src/prompts/runnable.ts
41
+ var Runnable = class {
42
+ constructor(config2, commit_state, prompt_cache) {
43
+ this.config = config2;
44
+ this.commit_state = commit_state;
45
+ this.prompt_cache = prompt_cache;
46
+ }
47
+ };
11
48
 
12
- ${c}`}if(i.deprecates_body){let c=e?u.yellow(i.deprecates_body):i.deprecates_body;r+=`
49
+ // src/prompts/commit-type.prompt.ts
50
+ var CommitTypePrompt = class extends Runnable {
51
+ async run() {
52
+ if (this.#is_enabled) {
53
+ const { initial_value, message } = this.#initial_value;
54
+ const prompt_type = this.config.commit_type.autocomplete ? p.autocomplete : p.select;
55
+ const commit_type = await prompt_type({
56
+ message,
57
+ initialValue: initial_value,
58
+ maxItems: this.#max_items,
59
+ options: this.#options
60
+ });
61
+ if (p.isCancel(commit_type))
62
+ process.exit(0);
63
+ this.#run_post_effects(commit_type);
64
+ }
65
+ }
66
+ get #is_enabled() {
67
+ return this.config.commit_type.enable;
68
+ }
69
+ get #initial_value() {
70
+ const cache_value = get_value_from_cache(this.prompt_cache, "commit_type");
71
+ if (cache_value)
72
+ return {
73
+ initial_value: cache_value,
74
+ message: cache_message("Commit type")
75
+ };
76
+ if (this.config.commit_type.infer_type_from_branch) {
77
+ const type_from_branch = infer_type_from_git(
78
+ this.#options,
79
+ flags.git_args
80
+ );
81
+ if (type_from_branch) {
82
+ return {
83
+ message: inferred_message("Commit type"),
84
+ initial_value: type_from_branch
85
+ };
86
+ }
87
+ }
88
+ return {
89
+ initial_value: this.config.commit_type.initial_value,
90
+ message: "Select a commit type"
91
+ };
92
+ }
93
+ get #options() {
94
+ return this.config.commit_type.options;
95
+ }
96
+ get #value_to_data() {
97
+ return this.#options.reduce(
98
+ (acc, curr) => ({
99
+ ...acc,
100
+ [curr.value]: {
101
+ emoji: curr.emoji ?? "",
102
+ trailer: curr.trailer ?? ""
103
+ }
104
+ }),
105
+ {}
106
+ );
107
+ }
108
+ get #max_items() {
109
+ return this.config.commit_type.max_items;
110
+ }
111
+ #run_post_effects(prompt_result) {
112
+ set_value_cache(this.prompt_cache, "commit_type", prompt_result);
113
+ const value_to_data = this.#value_to_data;
114
+ this.commit_state.trailer = value_to_data[prompt_result].trailer;
115
+ this.commit_state.type = this.config.commit_type.append_emoji_to_commit && this.config.commit_type.emoji_commit_position === "Start" ? `${value_to_data[prompt_result].emoji} ${prompt_result}`.trim() : prompt_result;
116
+ }
117
+ };
13
118
 
14
- ${c}`}if(i.custom_footer){let b=i.custom_footer.split("\\n").map(O=>e?u.reset(O.trim()):O.trim()).join(`
15
- `);r+=`
119
+ // src/prompts/commit-scope.prompt.ts
120
+ import * as p2 from "@clack/prompts";
121
+ var CommitScopePrompt = class extends Runnable {
122
+ async run() {
123
+ if (!this.#is_enabled)
124
+ return;
125
+ const { initial_value, message } = this.#get_initial_value();
126
+ const prompt_type = this.config.commit_scope.autocomplete ? p2.autocomplete : p2.select;
127
+ let commit_scope = await prompt_type({
128
+ message,
129
+ initialValue: initial_value,
130
+ maxItems: this.#max_items,
131
+ options: this.#options
132
+ });
133
+ if (p2.isCancel(commit_scope))
134
+ process.exit(0);
135
+ await this.#post_run_effects(commit_scope);
136
+ }
137
+ get #is_enabled() {
138
+ return this.config.commit_scope.enable;
139
+ }
140
+ #get_initial_value() {
141
+ const cache_value = get_value_from_cache(this.prompt_cache, "commit_scope");
142
+ if (cache_value) {
143
+ return {
144
+ initial_value: cache_value,
145
+ message: cache_message("Commit scope")
146
+ };
147
+ }
148
+ if (this.config.commit_scope.infer_scope_from_branch) {
149
+ const scope_from_branch = infer_scope_from_git(
150
+ this.#options,
151
+ flags.git_args
152
+ );
153
+ if (scope_from_branch) {
154
+ return {
155
+ initial_value: scope_from_branch,
156
+ message: inferred_message("Commit scope")
157
+ };
158
+ }
159
+ }
160
+ return {
161
+ initial_value: this.config.commit_scope.initial_value,
162
+ message: "Select a commit scope"
163
+ };
164
+ }
165
+ get #max_items() {
166
+ return this.config.commit_scope.max_items;
167
+ }
168
+ get #options() {
169
+ return this.config.commit_scope.options;
170
+ }
171
+ get #custom_scope_enabled() {
172
+ return this.config.commit_scope.custom_scope;
173
+ }
174
+ async #post_run_effects(prompt_result) {
175
+ set_value_cache(this.prompt_cache, "commit_scope", prompt_result);
176
+ let commit_scope_value = prompt_result;
177
+ if (commit_scope_value === CUSTOM_SCOPE_KEY && this.#custom_scope_enabled) {
178
+ const commit_scope = await p2.text({
179
+ message: "Write a custom scope",
180
+ placeholder: ""
181
+ });
182
+ if (p2.isCancel(commit_scope))
183
+ process.exit(0);
184
+ commit_scope_value = commit_scope ?? "";
185
+ }
186
+ this.commit_state.scope = commit_scope_value;
187
+ }
188
+ };
16
189
 
17
- ${b}`}return i.closes&&i.ticket&&(r+=e?`
190
+ // src/prompts/commit-ticket.prompt.ts
191
+ import * as p3 from "@clack/prompts";
192
+ var CommitTicketPrompt = class extends Runnable {
193
+ async run() {
194
+ const { initial_value, message } = this.#get_initial_value();
195
+ this.commit_state.ticket = initial_value;
196
+ if (this.#confirm_ticket_enabled) {
197
+ const user_commit_ticket = await p3.text({
198
+ message,
199
+ placeholder: "",
200
+ initialValue: initial_value
201
+ });
202
+ if (p3.isCancel(user_commit_ticket))
203
+ process.exit(0);
204
+ set_value_cache(this.prompt_cache, "commit_ticket", user_commit_ticket);
205
+ this.commit_state.ticket = user_commit_ticket ?? "";
206
+ }
207
+ if (this.#prepend_hashtag_always && this.commit_state.ticket && !this.commit_state.ticket.startsWith("#")) {
208
+ this.commit_state.ticket = "#" + this.commit_state.ticket;
209
+ }
210
+ }
211
+ get #infer_ticket_enabled() {
212
+ return this.config.check_ticket.infer_ticket;
213
+ }
214
+ get #confirm_ticket_enabled() {
215
+ return this.config.check_ticket.confirm_ticket;
216
+ }
217
+ get #prepend_hashtag_always() {
218
+ return this.config.check_ticket.prepend_hashtag === "Always";
219
+ }
220
+ #get_initial_value() {
221
+ const cache_value = get_value_from_cache(
222
+ this.prompt_cache,
223
+ "commit_ticket"
224
+ );
225
+ if (cache_value) {
226
+ return {
227
+ initial_value: cache_value,
228
+ message: cache_message("Ticket / issue")
229
+ };
230
+ }
231
+ if (this.#infer_ticket_enabled) {
232
+ const inferred_value = infer_ticket_from_git(
233
+ {
234
+ append_hashtag: this.config.check_ticket.append_hashtag,
235
+ prepend_hashtag: this.config.check_ticket.prepend_hashtag
236
+ },
237
+ flags.git_args
238
+ );
239
+ if (inferred_value) {
240
+ return {
241
+ initial_value: inferred_value,
242
+ message: inferred_message("Ticket / issue")
243
+ };
244
+ }
245
+ }
246
+ return {
247
+ initial_value: this.commit_state.ticket,
248
+ message: optional_message("Add ticket / issue")
249
+ };
250
+ }
251
+ };
18
252
 
19
- ${u.reset(i.closes)} ${u.magenta(i.ticket)}`:`
253
+ // src/prompts/commit-title.prompt.ts
254
+ import * as p4 from "@clack/prompts";
255
+ var CommitTitlePrompt = class extends Runnable {
256
+ async run() {
257
+ const { initial_value, message } = this.#get_initial_value();
258
+ const commit_title = await p4.text({
259
+ message,
260
+ initialValue: initial_value,
261
+ placeholder: "",
262
+ validate: (value) => this.#validate(value)
263
+ });
264
+ if (p4.isCancel(commit_title))
265
+ process.exit(0);
266
+ this.#run_post_effects(commit_title ?? "");
267
+ }
268
+ #get_initial_value() {
269
+ const cache_value = get_value_from_cache(this.prompt_cache, "commit_title");
270
+ if (cache_value) {
271
+ return {
272
+ initial_value: cache_value,
273
+ message: cache_message("Commit title")
274
+ };
275
+ }
276
+ return {
277
+ initial_value: this.commit_state.title,
278
+ message: "Write a brief title describing the commit"
279
+ };
280
+ }
281
+ #validate(value) {
282
+ if (!value)
283
+ return "Please enter a title";
284
+ if (this.#get_size(value) > this.#max_size) {
285
+ return `Exceeded max length. Title max [${this.#max_size}]`;
286
+ }
287
+ }
288
+ get #max_size() {
289
+ return this.config.commit_title.max_size;
290
+ }
291
+ #get_size(value) {
292
+ return get_commit_title_size(
293
+ {
294
+ type: this.commit_state.type,
295
+ scope: this.commit_state.scope,
296
+ ticket: this.commit_state.ticket,
297
+ title: value
298
+ },
299
+ {
300
+ include_ticket: this.config.check_ticket.add_to_title
301
+ }
302
+ );
303
+ }
304
+ // TODO: Extract to a Runnable abstract function?
305
+ get #value_to_data() {
306
+ return this.config.commit_type.options.reduce(
307
+ (acc, curr) => ({
308
+ ...acc,
309
+ [curr.value]: {
310
+ emoji: curr.emoji ?? ""
311
+ }
312
+ }),
313
+ {}
314
+ );
315
+ }
316
+ #title_with_emoji(title) {
317
+ if (this.config.commit_type.append_emoji_to_commit && this.config.commit_type.emoji_commit_position === "After-Colon") {
318
+ const emoji = this.#value_to_data[this.commit_state.type]?.emoji ?? "";
319
+ return `${emoji} ${title}`.trim();
320
+ }
321
+ return title;
322
+ }
323
+ #run_post_effects(prompt_result) {
324
+ set_value_cache(this.prompt_cache, "commit_title", prompt_result);
325
+ this.commit_state.title = clean_commit_title(
326
+ this.#title_with_emoji(prompt_result)
327
+ );
328
+ }
329
+ };
20
330
 
21
- ${i.closes} ${i.ticket}`),s&&i.trailer&&(r+=e?`
331
+ // src/prompts/commit-body.prompt.ts
332
+ import * as p5 from "@clack/prompts";
333
+ var CommitBodyPrompt = class extends Runnable {
334
+ async run() {
335
+ if (!this.#is_enabled)
336
+ return;
337
+ const { initial_value, message } = this.#get_initial_value();
338
+ const commit_body = await p5.text({
339
+ message,
340
+ initialValue: initial_value,
341
+ placeholder: "",
342
+ validate: (value) => this.#validate(value)
343
+ });
344
+ if (p5.isCancel(commit_body))
345
+ process.exit(0);
346
+ this.#run_post_effects(commit_body ?? "");
347
+ }
348
+ get #is_enabled() {
349
+ return this.config.commit_body.enable;
350
+ }
351
+ #get_initial_value() {
352
+ const cache_value = get_value_from_cache(this.prompt_cache, "commit_body");
353
+ if (cache_value) {
354
+ return {
355
+ initial_value: cache_value,
356
+ message: cache_message("Commit body")
357
+ };
358
+ }
359
+ return {
360
+ initial_value: "",
361
+ message: optional_message("Write a detailed description of the changes")
362
+ };
363
+ }
364
+ #validate(value) {
365
+ if (this.config.commit_body.required && !value) {
366
+ return "Please enter a description";
367
+ }
368
+ }
369
+ #run_post_effects(prompt_result) {
370
+ set_value_cache(this.prompt_cache, "commit_body", prompt_result);
371
+ this.commit_state.body = prompt_result;
372
+ }
373
+ };
22
374
 
23
- ${u.dim(i.trailer)}`:`
375
+ // src/prompts/commit-footer.prompt.ts
376
+ import * as p6 from "@clack/prompts";
377
+ var CommitFooterPrompt = class extends Runnable {
378
+ async run() {
379
+ if (!this.#is_enabled)
380
+ return;
381
+ const { initial_values, message } = this.#get_initial_value();
382
+ const commit_footer = await p6.multiselect({
383
+ message,
384
+ initialValues: initial_values,
385
+ options: this.#options,
386
+ required: false
387
+ });
388
+ if (p6.isCancel(commit_footer))
389
+ process.exit(0);
390
+ const selection = this.#get_selection(commit_footer);
391
+ const footer_inputs = await this.#get_footer_inputs(selection);
392
+ this.#run_post_effects(commit_footer, selection, footer_inputs);
393
+ }
394
+ get #is_enabled() {
395
+ return this.config.commit_footer.enable;
396
+ }
397
+ get #options() {
398
+ const allowed_values = new Set(this.config.commit_footer.options);
399
+ return COMMIT_FOOTER_OPTIONS.filter(
400
+ (option) => allowed_values.has(option.value)
401
+ );
402
+ }
403
+ get #available_option_values() {
404
+ return this.#options.map((option) => option.value);
405
+ }
406
+ #get_initial_value() {
407
+ const cache_value = get_value_from_cache(
408
+ this.prompt_cache,
409
+ "commit_footer"
410
+ );
411
+ if (cache_value) {
412
+ return {
413
+ initial_values: this.#parse_cache_value(cache_value),
414
+ message: space_to_select_message(cache_message("Commit footers"))
415
+ };
416
+ }
417
+ const initial_values = this.config.commit_footer.initial_value.filter(
418
+ (value) => this.#available_option_values.includes(value)
419
+ );
420
+ return {
421
+ initial_values,
422
+ message: space_to_select_message(
423
+ optional_message("Select optional footers")
424
+ )
425
+ };
426
+ }
427
+ #parse_cache_value(cache_value) {
428
+ return cache_value.split(",").map((value) => value.trim()).filter(
429
+ (value) => this.#available_option_values.includes(value)
430
+ );
431
+ }
432
+ #get_selection(commit_footer) {
433
+ return {
434
+ includes_breaking_change: commit_footer.includes("breaking-change"),
435
+ includes_deprecated: commit_footer.includes("deprecated"),
436
+ includes_closes: commit_footer.includes("closes"),
437
+ includes_custom: commit_footer.includes("custom"),
438
+ includes_trailer: commit_footer.includes("trailer")
439
+ };
440
+ }
441
+ async #get_footer_inputs(selection) {
442
+ const footer_inputs = {
443
+ breaking_title: "",
444
+ breaking_body: "",
445
+ deprecated_title: "",
446
+ deprecated_body: "",
447
+ custom_footer: ""
448
+ };
449
+ if (selection.includes_breaking_change) {
450
+ footer_inputs.breaking_title = await this.#required_text(
451
+ "Breaking changes: Write a short title / summary"
452
+ );
453
+ footer_inputs.breaking_body = await this.#optional_text(
454
+ optional_message(
455
+ "Breaking Changes: Write a description & migration instructions"
456
+ )
457
+ );
458
+ }
459
+ if (selection.includes_deprecated) {
460
+ footer_inputs.deprecated_title = await this.#required_text(
461
+ "Deprecated: Write a short title / summary"
462
+ );
463
+ footer_inputs.deprecated_body = await this.#optional_text(
464
+ optional_message("Deprecated: Write a description")
465
+ );
466
+ }
467
+ if (selection.includes_custom) {
468
+ footer_inputs.custom_footer = await this.#optional_text(
469
+ "Write a custom footer"
470
+ );
471
+ }
472
+ return footer_inputs;
473
+ }
474
+ async #required_text(message) {
475
+ const response = await p6.text({
476
+ message,
477
+ placeholder: "",
478
+ validate: (value) => {
479
+ if (!value)
480
+ return "Please enter a title / summary";
481
+ }
482
+ });
483
+ if (p6.isCancel(response))
484
+ process.exit(0);
485
+ return response ?? "";
486
+ }
487
+ async #optional_text(message) {
488
+ const response = await p6.text({
489
+ message,
490
+ placeholder: ""
491
+ });
492
+ if (p6.isCancel(response))
493
+ process.exit(0);
494
+ return response ?? "";
495
+ }
496
+ #run_post_effects(commit_footer, selection, footer_inputs) {
497
+ set_value_cache(
498
+ this.prompt_cache,
499
+ "commit_footer",
500
+ commit_footer.join(",")
501
+ );
502
+ this.commit_state.breaking_title = footer_inputs.breaking_title;
503
+ this.commit_state.breaking_body = footer_inputs.breaking_body;
504
+ this.commit_state.deprecates_title = footer_inputs.deprecated_title;
505
+ this.commit_state.deprecates_body = footer_inputs.deprecated_body;
506
+ this.commit_state.custom_footer = footer_inputs.custom_footer;
507
+ this.commit_state.closes = selection.includes_closes ? "Closes:" : "";
508
+ if (!selection.includes_trailer) {
509
+ this.commit_state.trailer = "";
510
+ }
511
+ }
512
+ };
24
513
 
25
- ${i.trailer}`),o&&(r=r.replaceAll('"','\\"').replaceAll("`","\\`")),r}import{execSync as kt}from"child_process";import*as P from"@clack/prompts";import X from"picocolors";var vt=["M","T","R","D","A","C"];function st(){let i="";try{i=kt(`git ${n.git_args} status --porcelain`,{stdio:"pipe"}).toString()}catch(s){return P.log.error(X.red("Failed to git status"+s)),{index:[],work_tree:[]}}let t=i.split(`
26
- `),e=[],o=[];return t.forEach(s=>{let r=s.trimEnd();if(!r)return;let a=r.substring(2).trim(),l=r.charAt(0).trim(),C=r.charAt(1).trim();(l==="?"||C==="?")&&e.push(a),vt.includes(l)&&o.push(a),vt.includes(C)&&e.push(a)}),{index:o,work_tree:e}}function $t(i){let t=i.join(" ");if(t)try{kt(`git ${n.git_args} add ${t}`,{stdio:"pipe"}).toString(),P.log.success(X.green("Changes successfully staged"))}catch{P.log.error(X.red("Failed to stage changes"))}}function Y(){st().index.length||(P.log.error(X.red('no changes added to commit (use "git add" and/or "git commit -a")')),process.exit(0))}var E=class extends p{async run(){n.interactive||Y(),this.#i&&(Ct(`${this.#n} --edit`,this.#o),process.exit(0)),this.#e&&h.note(ot({commit_state:this.commit_state,config:this.config,colorize:!0,escape_quotes:!1,include_trailer:!0}),"Commit Preview",{format:e=>e}),await this.#a()||(h.log.info("Exiting without commit"),process.exit(0));try{h.log.info(n.dry_run?it("Committing changes..."):"Committing changes..."),Ct(this.#n,n.dry_run?this.#s:this.#o)}catch(e){h.log.error("Something went wrong when committing: "+e);return}this.#m()}get#i(){return n.interactive&&this.config.confirm_with_editor}get#e(){return this.config.print_commit_output}get#t(){return this.config.confirm_commit}get#o(){return this.config.overrides.shell?{shell:this.config.overrides.shell,stdio:"inherit"}:{stdio:"inherit"}}get#s(){return this.config.overrides.shell?{shell:this.config.overrides.shell,stdio:"pipe"}:{stdio:"pipe"}}get#r(){return this.commit_state.trailer?`--trailer="${this.commit_state.trailer}"`:""}get#n(){return`git ${n.git_args} commit -m "${ot({commit_state:this.commit_state,config:this.config,colorize:!1,escape_quotes:!0,include_trailer:!1})}" ${this.#r} ${this.#c}`.trim()}get#c(){return n.dry_run?"--dry-run --porcelain --untracked-files=no":""}async#a(){if(!n.interactive||!this.#t)return!0;let t=await h.confirm({message:n.dry_run?it("Confirm Commit?"):"Confirm Commit?"});return h.isCancel(t)&&process.exit(0),t}#m(){h.log.success("Commit Complete");let t=this.prompt_cache.get("username");this.prompt_cache.clear(),t&&this.prompt_cache.set("username",t)}};import*as w from"@clack/prompts";import J from"picocolors";import{styleText as m}from"node:util";import{AutocompletePrompt as Pt}from"@clack/core";import{S_BAR as R,S_BAR_END as Et,S_CHECKBOX_INACTIVE as xt,S_CHECKBOX_SELECTED as Rt,limitOptions as Tt,settings as Nt,symbol as jt}from"@clack/prompts";function Dt(i,t){if(!i)return!0;let e=(t.label??String(t.value??"")).toLowerCase(),o=(t.hint??"").toLowerCase(),s=String(t.value).toLowerCase(),r=i.toLowerCase();return e.includes(r)||o.includes(r)||s.includes(r)}function Lt(i,t){return i===""||t.ctrl===!0&&t.name==="a"}var rt=class extends Pt{constructor(e){super({options:e.options,multiple:!0,filter:e.filter??((o,s)=>Dt(o,s)),validate:o=>e.required&&(!Array.isArray(o)||o.length===0)?"Please select at least one item":e.validate?.(o),initialValue:e.initialValues,signal:e.signal,input:e.input,output:e.output,render(){let o=e.withGuide??Nt.withGuide,s=`${o?`${m("gray",R)}
27
- `:""}${jt(this.state)} ${e.message}
28
- `,r=this.userInput,a=this.userInputWithCursor,l=this.options,C=this.filteredOptions.length!==l.length?m("dim",` (${this.filteredOptions.length} match${this.filteredOptions.length===1?"":"es"})`):"";switch(this.state){case"submit":return`${s}${o?`${m("gray",R)} `:""}${m("dim",`${this.selectedValues.length} items selected`)}`;case"cancel":return`${s}${o?`${m("gray",R)} `:""}${m(["strikethrough","dim"],r)}`;default:{let x=this.state==="error"?"yellow":"cyan",y=o?`${m(x,R)} `:"",A=o?m(x,Et):"",c=[`${m("dim","\u2191/\u2193")} to navigate`,`${m("dim","Space/Tab:")} select`,`${m("dim","Ctrl+a:")} select visible`,`${m("dim","Enter:")} confirm`,`${m("dim","Type:")} to search`],b=this.filteredOptions.length===0&&r?[`${y}${m("yellow","No matches found")}`]:[],O=this.state==="error"?[`${y}${m("yellow",this.error)}`]:[],F=[...`${s}${o?m(x,R):""}`.split(`
29
- `),`${y}${m("dim","Search:")} ${a}${C}`,...b,...O],nt=[`${y}${c.join(" \u2022 ")}`,A],It=Tt({cursor:this.cursor,options:this.filteredOptions,style:(v,Vt)=>{let At=this.selectedValues.includes(v.value),tt=v.label??String(v.value??""),Ft=v.hint&&this.focusedValue!==void 0&&v.value===this.focusedValue?m("dim",` (${v.hint})`):"",at=At?m("green",Rt):m("dim",xt);return v.disabled?`${m("gray",xt)} ${m(["strikethrough","gray"],tt)}`:Vt?`${at} ${tt}${Ft}`:`${at} ${m("dim",tt)}`},maxItems:e.maxItems,output:e.output,rowPadding:F.length+nt.length});return[...F,...It.map(v=>`${y}${v}`),...nt].join(`
30
- `)}}}});this.promptOptions=e;this.on("key",(o,s)=>{if(s.name==="space"&&!this.isNavigating&&this.focusedValue!==void 0){this.toggleSelected(this.focusedValue),this.#e();return}Lt(o,s)&&(this.#i(),this.isNavigating=!0,this.#e())})}_isActionKey(e,o){return super._isActionKey(e,o)||this.multiple&&o.name==="space"&&e!==void 0&&e!==""}#i(){let e=this.filteredOptions.filter(s=>!s.disabled).map(s=>s.value);if(!e.length)return;let o=e.every(s=>this.selectedValues.includes(s));this.selectedValues=o?this.selectedValues.filter(s=>!e.includes(s)):[...this.selectedValues,...e.filter(s=>!this.selectedValues.includes(s))]}#e(){let e=this.rl;e?.write("",{ctrl:!0,name:"e"}),this._cursor=e?.cursor??this.userInput.length}};function St(i){return new rt(i).prompt()}var Q=class extends p{async run(){if(!this.#i)return;let t=st();if(this.#e(t),t.work_tree.length){let e=await this.#o(t.work_tree);e.length&&$t(e)}Y()}get#i(){return this.config.check_status}#e(t){w.log.step(J.black(J.bgGreen(" Checking Git Status ")));let e=this.#t(t.index,J.green);if(w.log.success(`Changes to be committed:
31
- `+e),!t.work_tree.length)return;let o=this.#t(t.work_tree,J.red);w.log.error(`Changes not staged for commit:
32
- `+o)}#t(t,e){return t.reduce((o,s,r)=>e(o+s+gt(t,r)),"")}async#o(t){let e=this.config.check_status_autocomplete?await St({message:"Some files have not been staged, add them now?",options:t.map(o=>({value:o,label:o})),required:!1}):await w.multiselect({message:yt("Some files have not been staged, add them now?"),options:t.map(o=>({value:o,label:o})),required:!1});return w.isCancel(e)&&process.exit(0),e}};import{execSync as Gt}from"child_process";import _ from"picocolors";var Mt={"better-branch":"Create a branch or worktree from a guided prompt flow.","better-commits-init":"Create a .better-commits.jsonc config in this repository."},Bt={"--no-interactive":"Run without tui prompts.","--dry-run":"Print the commit command without creating a commit.","--help":"Show help information and exit."},Wt={"--type":"Set commit type (can be inferred from branch).","--scope":"Set commit scope (can be inferred from branch).","--title":"Set commit title/description.","--body":"Set commit body text.","--ticket":"Set ticket / issue (can be inferred from branch).","--closes":"Set closes footer (true/false).","--trailer":"Set trailer footer value.","--breaking-title":"Set breaking-change title footer.","--breaking-body":"Set breaking-change body footer.","--deprecates-title":"Set deprecates footer title text.","--deprecates-body":"Set deprecates footer body text.","--custom-footer":"Set a custom footer line."},qt={"--git-dir":"Set the path to the .git directory.","--work-tree":"Set the path to the working tree root."};function Z(i){let o=" ";return Object.entries(i).map(([s,r])=>{let a=Math.max(2,26-s.length);return`${o}${s}${" ".repeat(a)}${r}`}).join(`
33
- `)}function wt(i,t){let e=T(),o="(none)";try{o=Gt(`git ${n.git_args} branch --show-current`,{stdio:"pipe"}).toString().trim()||"(none)"}catch{}let s=N(i.commit_type.options,n.git_args)||"Unknown",r=i.check_ticket.infer_ticket?j({append_hashtag:i.check_ticket.append_hashtag,prepend_hashtag:i.check_ticket.prepend_hashtag},n.git_args)||"Unknown":"Infer Disabled",a=i.commit_scope.infer_scope_from_branch?D(i.commit_scope.options,n.git_args)||"Unknown":"Infer Disabled",l=i.commit_type.options.map(b=>b.value).join(", ").trim(),C=i.commit_scope.options.map(b=>b.value).join(", ").trim(),x=Z(Bt),y=Z(qt),A=Z(Wt),c=Z(Mt);console.log(`
34
- ${_.green("\uF489 better-commits")} ${_.gray("v"+e)}
514
+ // src/prompts/commit-confirm.prompt.ts
515
+ import * as p8 from "@clack/prompts";
516
+ import { execSync as execSync2 } from "child_process";
35
517
 
36
- ${_.gray("BRANCH")}
37
- ${o}
38
- ${_.gray("Type")} ${_.blue(s)} ${_.gray("\xB7")} ${_.gray("Scope")} ${_.cyan(a)} ${_.gray("\xB7")} ${_.gray("Ticket")} ${_.magenta(r)}
518
+ // src/utils/build-commit-string.ts
519
+ import color from "picocolors";
520
+ function build_commit_string({
521
+ commit_state,
522
+ config: config2,
523
+ colorize = false,
524
+ escape_quotes = false,
525
+ include_trailer = false
526
+ }) {
527
+ let commit_string = "";
528
+ if (commit_state.type) {
529
+ commit_string += colorize ? color.blue(commit_state.type) : commit_state.type;
530
+ }
531
+ if (commit_state.scope) {
532
+ const scope = colorize ? color.cyan(commit_state.scope) : commit_state.scope;
533
+ commit_string += `(${scope})`;
534
+ }
535
+ let title_ticket = commit_state.ticket;
536
+ const surround = config2.check_ticket.surround;
537
+ if (commit_state.ticket && surround) {
538
+ const open_token = surround.charAt(0);
539
+ const close_token = surround.charAt(1);
540
+ title_ticket = `${open_token}${commit_state.ticket}${close_token}`;
541
+ }
542
+ const position_beginning = config2.check_ticket.title_position === "beginning";
543
+ if (title_ticket && config2.check_ticket.add_to_title && position_beginning) {
544
+ commit_string = `${colorize ? color.magenta(title_ticket) : title_ticket} ${commit_string}`;
545
+ }
546
+ const position_before_colon = config2.check_ticket.title_position === "before-colon";
547
+ if (title_ticket && config2.check_ticket.add_to_title && position_before_colon) {
548
+ const spacing = commit_state.scope || commit_state.type && !config2.check_ticket.surround ? " " : "";
549
+ commit_string += colorize ? color.magenta(spacing + title_ticket) : spacing + title_ticket;
550
+ }
551
+ if (commit_state.breaking_title && config2.breaking_change.add_exclamation_to_title) {
552
+ commit_string += colorize ? color.red("!") : "!";
553
+ }
554
+ if (commit_state.scope || commit_state.type || title_ticket && position_before_colon) {
555
+ commit_string += ": ";
556
+ }
557
+ const position_start = config2.check_ticket.title_position === "start";
558
+ const position_end = config2.check_ticket.title_position === "end";
559
+ if (title_ticket && config2.check_ticket.add_to_title && position_start) {
560
+ commit_string += colorize ? color.magenta(title_ticket) + " " : title_ticket + " ";
561
+ }
562
+ if (commit_state.title) {
563
+ commit_string += colorize ? color.reset(commit_state.title) : commit_state.title;
564
+ }
565
+ if (title_ticket && config2.check_ticket.add_to_title && position_end) {
566
+ commit_string += " " + (colorize ? color.magenta(title_ticket) : title_ticket);
567
+ }
568
+ if (commit_state.body) {
569
+ let body = commit_state.body;
570
+ if (config2.commit_body.split_by_period) {
571
+ body = body.replace(/\.\s+/g, ".\n");
572
+ }
573
+ const temp = body.split("\\n");
574
+ const res = temp.map((value) => colorize ? color.reset(value.trim()) : value.trim()).join("\n");
575
+ commit_string += `
39
576
 
40
- ${_.gray("CONFIGURATION")}
41
- ${t}
577
+ ${res}`;
578
+ }
579
+ if (commit_state.breaking_title) {
580
+ const title = colorize ? color.red(`BREAKING CHANGE: ${commit_state.breaking_title}`) : `BREAKING CHANGE: ${commit_state.breaking_title}`;
581
+ commit_string += `
42
582
 
43
- ${_.gray("Types")}
44
- ${l}
583
+ ${title}`;
584
+ }
585
+ if (commit_state.breaking_body) {
586
+ const body = colorize ? color.red(commit_state.breaking_body) : commit_state.breaking_body;
587
+ commit_string += `
45
588
 
46
- ${_.gray("Scopes")}
47
- ${C}
589
+ ${body}`;
590
+ }
591
+ if (commit_state.deprecates_title) {
592
+ const title = colorize ? color.yellow(`DEPRECATED: ${commit_state.deprecates_title}`) : `DEPRECATED: ${commit_state.deprecates_title}`;
593
+ commit_string += `
48
594
 
49
- ${_.gray("CLI FLAGS")}
50
- ${x}
595
+ ${title}`;
596
+ }
597
+ if (commit_state.deprecates_body) {
598
+ const body = colorize ? color.yellow(commit_state.deprecates_body) : commit_state.deprecates_body;
599
+ commit_string += `
51
600
 
52
- ${_.gray("Commit Flags")}
53
- ${A}
601
+ ${body}`;
602
+ }
603
+ if (commit_state.custom_footer) {
604
+ const temp = commit_state.custom_footer.split("\\n");
605
+ const res = temp.map((value) => colorize ? color.reset(value.trim()) : value.trim()).join("\n");
606
+ commit_string += `
54
607
 
55
- ${_.gray("Git Flags (Advanced)")}
56
- ${y}
608
+ ${res}`;
609
+ }
610
+ if (commit_state.closes && commit_state.ticket) {
611
+ commit_string += colorize ? `
57
612
 
58
- ${_.gray("ADDITIONAL COMMANDS")}
59
- ${c}
613
+ ${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}` : `
60
614
 
61
- `)}var Xt=[Q,L,G,M,W,K,U,E],{config:Yt,config_source:Jt}=_t();Qt(Yt,Jt);async function Qt(i,t){if(Kt(ut()),n.version){let l=T();z.log.step("Better Commits v"+l);return}if(n.help){wt(i,t);return}let e=bt(i),o={...n.commit_state,type:(n.commit_state.type||e?.type)??"",scope:(n.commit_state.scope||e?.scope)??"",ticket:(n.commit_state.ticket||e?.ticket)??""},s=Ot(mt,o);if(!n.interactive)try{Ot(ht(i),s)}catch(l){l instanceof Ht?z.log.error(`Invalid commit input: ${l.message}`):z.log.error(`Failed to validate commit input: ${l}`),process.exit(0)}let r=i.cache_last_value?new Ut("better-commits"):pt,a=n.interactive?Xt:[E];for(let l of a)await new l(i,s,r).run()}export{Qt as main};
615
+ ${commit_state.closes} ${commit_state.ticket}`;
616
+ }
617
+ if (include_trailer && commit_state.trailer) {
618
+ commit_string += colorize ? `
619
+
620
+ ${color.dim(commit_state.trailer)}` : `
621
+
622
+ ${commit_state.trailer}`;
623
+ }
624
+ if (escape_quotes) {
625
+ commit_string = commit_string.replaceAll('"', '\\"').replaceAll("`", "\\`");
626
+ }
627
+ return commit_string;
628
+ }
629
+
630
+ // src/git.ts
631
+ import { execSync } from "child_process";
632
+ import * as p7 from "@clack/prompts";
633
+ import color2 from "picocolors";
634
+ var porcelain_states = ["M", "T", "R", "D", "A", "C"];
635
+ function git_status() {
636
+ let status = "";
637
+ try {
638
+ status = execSync(`git ${flags.git_args} status --porcelain`, {
639
+ stdio: "pipe"
640
+ }).toString();
641
+ } catch (err) {
642
+ p7.log.error(color2.red("Failed to git status" + err));
643
+ return { index: [], work_tree: [] };
644
+ }
645
+ const lines = status.split("\n");
646
+ const work_tree = [];
647
+ const index = [];
648
+ lines.forEach((v) => {
649
+ const line = v.trimEnd();
650
+ if (!line)
651
+ return;
652
+ const path_plus_file = line.substring(2).trim();
653
+ const first_char = line.charAt(0).trim();
654
+ const second_char = line.charAt(1).trim();
655
+ if (first_char === "?" || second_char === "?") {
656
+ work_tree.push(path_plus_file);
657
+ }
658
+ if (porcelain_states.includes(first_char)) {
659
+ index.push(path_plus_file);
660
+ }
661
+ if (porcelain_states.includes(second_char)) {
662
+ work_tree.push(path_plus_file);
663
+ }
664
+ });
665
+ return { index, work_tree };
666
+ }
667
+ function git_add(files) {
668
+ const space_delimited_files = files.join(" ");
669
+ if (space_delimited_files) {
670
+ try {
671
+ execSync(`git ${flags.git_args} add ${space_delimited_files}`, {
672
+ stdio: "pipe"
673
+ }).toString();
674
+ p7.log.success(color2.green("Changes successfully staged"));
675
+ } catch (err) {
676
+ p7.log.error(color2.red("Failed to stage changes"));
677
+ }
678
+ }
679
+ }
680
+ function ensure_staged_changes() {
681
+ const updated_status = git_status();
682
+ if (updated_status.index.length)
683
+ return;
684
+ p7.log.error(
685
+ color2.red(
686
+ 'no changes added to commit (use "git add" and/or "git commit -a")'
687
+ )
688
+ );
689
+ process.exit(0);
690
+ }
691
+
692
+ // src/prompts/commit-confirm.prompt.ts
693
+ var CommitConfirmPrompt = class extends Runnable {
694
+ async run() {
695
+ if (!flags.interactive)
696
+ ensure_staged_changes();
697
+ if (this.#confirm_with_editor) {
698
+ execSync2(`${this.#commit_command} --edit`, this.#git_command_options);
699
+ process.exit(0);
700
+ }
701
+ if (this.#print_commit_output) {
702
+ p8.note(
703
+ build_commit_string({
704
+ commit_state: this.commit_state,
705
+ config: this.config,
706
+ colorize: true,
707
+ escape_quotes: false,
708
+ include_trailer: true
709
+ }),
710
+ "Commit Preview",
711
+ { format: (line) => line }
712
+ );
713
+ }
714
+ const continue_commit = await this.#get_continue_commit();
715
+ if (!continue_commit) {
716
+ p8.log.info("Exiting without commit");
717
+ process.exit(0);
718
+ }
719
+ try {
720
+ p8.log.info(
721
+ flags.dry_run ? dry_run_message("Committing changes...") : "Committing changes..."
722
+ );
723
+ execSync2(
724
+ this.#commit_command,
725
+ flags.dry_run ? this.#git_command_options_quiet : this.#git_command_options
726
+ );
727
+ } catch (err) {
728
+ p8.log.error("Something went wrong when committing: " + err);
729
+ return;
730
+ }
731
+ this.#run_post_effects();
732
+ }
733
+ get #confirm_with_editor() {
734
+ return flags.interactive && this.config.confirm_with_editor;
735
+ }
736
+ get #print_commit_output() {
737
+ return this.config.print_commit_output;
738
+ }
739
+ get #confirm_commit() {
740
+ return this.config.confirm_commit;
741
+ }
742
+ get #git_command_options() {
743
+ return this.config.overrides.shell ? { shell: this.config.overrides.shell, stdio: "inherit" } : { stdio: "inherit" };
744
+ }
745
+ get #git_command_options_quiet() {
746
+ return this.config.overrides.shell ? { shell: this.config.overrides.shell, stdio: "pipe" } : { stdio: "pipe" };
747
+ }
748
+ get #trailer_arg() {
749
+ return this.commit_state.trailer ? `--trailer="${this.commit_state.trailer}"` : "";
750
+ }
751
+ get #commit_command() {
752
+ return `git ${flags.git_args} commit -m "${build_commit_string({
753
+ commit_state: this.commit_state,
754
+ config: this.config,
755
+ colorize: false,
756
+ escape_quotes: true,
757
+ include_trailer: false
758
+ })}" ${this.#trailer_arg} ${this.#dry_run_args}`.trim();
759
+ }
760
+ get #dry_run_args() {
761
+ return flags.dry_run ? "--dry-run --porcelain --untracked-files=no" : "";
762
+ }
763
+ async #get_continue_commit() {
764
+ if (!flags.interactive)
765
+ return true;
766
+ if (!this.#confirm_commit)
767
+ return true;
768
+ const continue_commit = await p8.confirm({
769
+ message: flags.dry_run ? dry_run_message("Confirm Commit?") : "Confirm Commit?"
770
+ });
771
+ if (p8.isCancel(continue_commit))
772
+ process.exit(0);
773
+ return continue_commit;
774
+ }
775
+ #run_post_effects() {
776
+ p8.log.success("Commit Complete");
777
+ const user_name = this.prompt_cache.get("username");
778
+ this.prompt_cache.clear();
779
+ if (user_name)
780
+ this.prompt_cache.set("username", user_name);
781
+ }
782
+ };
783
+
784
+ // src/prompts/commit-status.prompt.ts
785
+ import * as p9 from "@clack/prompts";
786
+ import color3 from "picocolors";
787
+
788
+ // src/prompts/autocomplete-multiselect.ts
789
+ import { styleText } from "node:util";
790
+ import { AutocompletePrompt } from "@clack/core";
791
+ import {
792
+ S_BAR,
793
+ S_BAR_END,
794
+ S_CHECKBOX_INACTIVE,
795
+ S_CHECKBOX_SELECTED,
796
+ limitOptions,
797
+ settings,
798
+ symbol
799
+ } from "@clack/prompts";
800
+ function getFilteredOption(searchText, option) {
801
+ if (!searchText)
802
+ return true;
803
+ const label = (option.label ?? String(option.value ?? "")).toLowerCase();
804
+ const hint = (option.hint ?? "").toLowerCase();
805
+ const value = String(option.value).toLowerCase();
806
+ const term = searchText.toLowerCase();
807
+ return label.includes(term) || hint.includes(term) || value.includes(term);
808
+ }
809
+ function is_ctrl_a(char, key) {
810
+ return char === "" || key.ctrl === true && key.name === "a";
811
+ }
812
+ var AutocompleteMultiselectPrompt = class extends AutocompletePrompt {
813
+ constructor(promptOptions) {
814
+ super({
815
+ options: promptOptions.options,
816
+ multiple: true,
817
+ filter: promptOptions.filter ?? ((search, opt) => getFilteredOption(search, opt)),
818
+ validate: (value) => {
819
+ if (promptOptions.required && (!Array.isArray(value) || value.length === 0)) {
820
+ return "Please select at least one item";
821
+ }
822
+ return promptOptions.validate?.(value);
823
+ },
824
+ initialValue: promptOptions.initialValues,
825
+ signal: promptOptions.signal,
826
+ input: promptOptions.input,
827
+ output: promptOptions.output,
828
+ render() {
829
+ const hasGuide = promptOptions.withGuide ?? settings.withGuide;
830
+ const title = `${hasGuide ? `${styleText("gray", S_BAR)}
831
+ ` : ""}${symbol(this.state)} ${promptOptions.message}
832
+ `;
833
+ const userInput = this.userInput;
834
+ const searchText = this.userInputWithCursor;
835
+ const options = this.options;
836
+ const matches = this.filteredOptions.length !== options.length ? styleText(
837
+ "dim",
838
+ ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})`
839
+ ) : "";
840
+ switch (this.state) {
841
+ case "submit": {
842
+ return `${title}${hasGuide ? `${styleText("gray", S_BAR)} ` : ""}${styleText("dim", `${this.selectedValues.length} items selected`)}`;
843
+ }
844
+ case "cancel": {
845
+ return `${title}${hasGuide ? `${styleText("gray", S_BAR)} ` : ""}${styleText(["strikethrough", "dim"], userInput)}`;
846
+ }
847
+ default: {
848
+ const barStyle = this.state === "error" ? "yellow" : "cyan";
849
+ const guidePrefix = hasGuide ? `${styleText(barStyle, S_BAR)} ` : "";
850
+ const guidePrefixEnd = hasGuide ? styleText(barStyle, S_BAR_END) : "";
851
+ const instructions = [
852
+ `${styleText("dim", "\u2191/\u2193")} to navigate`,
853
+ `${styleText("dim", "Space/Tab:")} select`,
854
+ `${styleText("dim", "Ctrl+a:")} select visible`,
855
+ `${styleText("dim", "Enter:")} confirm`,
856
+ `${styleText("dim", "Type:")} to search`
857
+ ];
858
+ const noResults = this.filteredOptions.length === 0 && userInput ? [`${guidePrefix}${styleText("yellow", "No matches found")}`] : [];
859
+ const errorMessage = this.state === "error" ? [`${guidePrefix}${styleText("yellow", this.error)}`] : [];
860
+ const headerLines = [
861
+ ...`${title}${hasGuide ? styleText(barStyle, S_BAR) : ""}`.split(
862
+ "\n"
863
+ ),
864
+ `${guidePrefix}${styleText("dim", "Search:")} ${searchText}${matches}`,
865
+ ...noResults,
866
+ ...errorMessage
867
+ ];
868
+ const footerLines = [
869
+ `${guidePrefix}${instructions.join(" \u2022 ")}`,
870
+ guidePrefixEnd
871
+ ];
872
+ const displayOptions = limitOptions({
873
+ cursor: this.cursor,
874
+ options: this.filteredOptions,
875
+ style: (option, active) => {
876
+ const isSelected = this.selectedValues.includes(option.value);
877
+ const label = option.label ?? String(option.value ?? "");
878
+ const hint = option.hint && this.focusedValue !== void 0 && option.value === this.focusedValue ? styleText("dim", ` (${option.hint})`) : "";
879
+ const checkbox = isSelected ? styleText("green", S_CHECKBOX_SELECTED) : styleText("dim", S_CHECKBOX_INACTIVE);
880
+ if (option.disabled) {
881
+ return `${styleText("gray", S_CHECKBOX_INACTIVE)} ${styleText(["strikethrough", "gray"], label)}`;
882
+ }
883
+ if (active) {
884
+ return `${checkbox} ${label}${hint}`;
885
+ }
886
+ return `${checkbox} ${styleText("dim", label)}`;
887
+ },
888
+ maxItems: promptOptions.maxItems,
889
+ output: promptOptions.output,
890
+ rowPadding: headerLines.length + footerLines.length
891
+ });
892
+ return [
893
+ ...headerLines,
894
+ ...displayOptions.map((option) => `${guidePrefix}${option}`),
895
+ ...footerLines
896
+ ].join("\n");
897
+ }
898
+ }
899
+ }
900
+ });
901
+ this.promptOptions = promptOptions;
902
+ this.on("key", (char, key) => {
903
+ if (key.name === "space" && !this.isNavigating && this.focusedValue !== void 0) {
904
+ this.toggleSelected(this.focusedValue);
905
+ this.#restore_cursor_to_end();
906
+ return;
907
+ }
908
+ if (!is_ctrl_a(char, key))
909
+ return;
910
+ this.#toggle_all_visible();
911
+ this.isNavigating = true;
912
+ this.#restore_cursor_to_end();
913
+ });
914
+ }
915
+ _isActionKey(char, key) {
916
+ return super._isActionKey(char, key) || this.multiple && key.name === "space" && char !== void 0 && char !== "";
917
+ }
918
+ #toggle_all_visible() {
919
+ const visible_values = this.filteredOptions.filter((option) => !option.disabled).map((option) => option.value);
920
+ if (!visible_values.length)
921
+ return;
922
+ const every_visible_selected = visible_values.every(
923
+ (value) => this.selectedValues.includes(value)
924
+ );
925
+ this.selectedValues = every_visible_selected ? this.selectedValues.filter((value) => !visible_values.includes(value)) : [
926
+ ...this.selectedValues,
927
+ ...visible_values.filter(
928
+ (value) => !this.selectedValues.includes(value)
929
+ )
930
+ ];
931
+ }
932
+ #restore_cursor_to_end() {
933
+ const rl = this.rl;
934
+ rl?.write("", { ctrl: true, name: "e" });
935
+ this._cursor = rl?.cursor ?? this.userInput.length;
936
+ }
937
+ };
938
+ function autocompleteMultiselect(opts) {
939
+ return new AutocompleteMultiselectPrompt(opts).prompt();
940
+ }
941
+
942
+ // src/prompts/commit-status.prompt.ts
943
+ var CommitStatusPrompt = class extends Runnable {
944
+ async run() {
945
+ if (!this.#is_enabled)
946
+ return;
947
+ const status = git_status();
948
+ this.#log_status(status);
949
+ if (status.work_tree.length) {
950
+ const selected_for_staging = await this.#select_for_staging(
951
+ status.work_tree
952
+ );
953
+ if (selected_for_staging.length) {
954
+ git_add(selected_for_staging);
955
+ }
956
+ }
957
+ ensure_staged_changes();
958
+ }
959
+ get #is_enabled() {
960
+ return this.config.check_status;
961
+ }
962
+ #log_status(status) {
963
+ p9.log.step(color3.black(color3.bgGreen(" Checking Git Status ")));
964
+ const staged_files = this.#format_files(status.index, color3.green);
965
+ p9.log.success("Changes to be committed:\n" + staged_files);
966
+ if (!status.work_tree.length)
967
+ return;
968
+ const unstaged_files = this.#format_files(status.work_tree, color3.red);
969
+ p9.log.error("Changes not staged for commit:\n" + unstaged_files);
970
+ }
971
+ #format_files(files, colorize) {
972
+ return files.reduce(
973
+ (acc, curr, index) => colorize(acc + curr + addNewLine(files, index)),
974
+ ""
975
+ );
976
+ }
977
+ async #select_for_staging(work_tree) {
978
+ const selected_for_staging = this.config.check_status_autocomplete ? await autocompleteMultiselect({
979
+ message: "Some files have not been staged, add them now?",
980
+ options: work_tree.map((v) => ({ value: v, label: v })),
981
+ required: false
982
+ }) : await p9.multiselect({
983
+ message: a_for_all_message("Some files have not been staged, add them now?"),
984
+ options: work_tree.map((v) => ({ value: v, label: v })),
985
+ required: false
986
+ });
987
+ if (p9.isCancel(selected_for_staging))
988
+ process.exit(0);
989
+ return selected_for_staging;
990
+ }
991
+ };
992
+
993
+ // src/help.ts
994
+ import { execSync as execSync3 } from "child_process";
995
+ import color4 from "picocolors";
996
+ var ADDITIONAL_COMMAND_DEFINITIONS = {
997
+ "better-branch": "Create a branch or worktree from a guided prompt flow.",
998
+ "better-commits-init": "Create a .better-commits.jsonc config in this repository."
999
+ };
1000
+ var CLI_FLAG_DEFINITIONS = {
1001
+ "--no-interactive": "Run without tui prompts.",
1002
+ "--dry-run": "Print the commit command without creating a commit.",
1003
+ "--help": "Show help information and exit."
1004
+ };
1005
+ var COMMIT_FLAG_DEFINITIONS = {
1006
+ "--type": "Set commit type (can be inferred from branch).",
1007
+ "--scope": "Set commit scope (can be inferred from branch).",
1008
+ "--title": "Set commit title/description.",
1009
+ "--body": "Set commit body text.",
1010
+ "--ticket": "Set ticket / issue (can be inferred from branch).",
1011
+ "--closes": "Set closes footer (true/false).",
1012
+ "--trailer": "Set trailer footer value.",
1013
+ "--breaking-title": "Set breaking-change title footer.",
1014
+ "--breaking-body": "Set breaking-change body footer.",
1015
+ "--deprecates-title": "Set deprecates footer title text.",
1016
+ "--deprecates-body": "Set deprecates footer body text.",
1017
+ "--custom-footer": "Set a custom footer line."
1018
+ };
1019
+ var GIT_FLAG_DEFINITIONS = {
1020
+ "--git-dir": "Set the path to the .git directory.",
1021
+ "--work-tree": "Set the path to the working tree root."
1022
+ };
1023
+ function to_definition_lines(definitions) {
1024
+ const description_column = 26;
1025
+ const minimum_spacing = 2;
1026
+ const indent = " ";
1027
+ return Object.entries(definitions).map(([name, description]) => {
1028
+ const spaces = Math.max(
1029
+ minimum_spacing,
1030
+ description_column - name.length
1031
+ );
1032
+ return `${indent}${name}${" ".repeat(spaces)}${description}`;
1033
+ }).join("\n");
1034
+ }
1035
+ function print_help_text(config2, config_source2) {
1036
+ const version = get_package_version();
1037
+ let branch = "(none)";
1038
+ try {
1039
+ branch = execSync3(`git ${flags.git_args} branch --show-current`, { stdio: "pipe" }).toString().trim() || "(none)";
1040
+ } catch {
1041
+ }
1042
+ const inferred_type = infer_type_from_git(config2.commit_type.options, flags.git_args) || "Unknown";
1043
+ const inferred_ticket = config2.check_ticket.infer_ticket ? infer_ticket_from_git(
1044
+ {
1045
+ append_hashtag: config2.check_ticket.append_hashtag,
1046
+ prepend_hashtag: config2.check_ticket.prepend_hashtag
1047
+ },
1048
+ flags.git_args
1049
+ ) || "Unknown" : "Infer Disabled";
1050
+ const inferred_scope = config2.commit_scope.infer_scope_from_branch ? infer_scope_from_git(config2.commit_scope.options, flags.git_args) || "Unknown" : "Infer Disabled";
1051
+ const types = config2.commit_type.options.map((option) => option.value).join(", ").trim();
1052
+ const scopes = config2.commit_scope.options.map((option) => option.value).join(", ").trim();
1053
+ const cli_flags = to_definition_lines(CLI_FLAG_DEFINITIONS);
1054
+ const git_flags = to_definition_lines(GIT_FLAG_DEFINITIONS);
1055
+ const commit_flags = to_definition_lines(COMMIT_FLAG_DEFINITIONS);
1056
+ const additional_commands = to_definition_lines(
1057
+ ADDITIONAL_COMMAND_DEFINITIONS
1058
+ );
1059
+ console.log(`
1060
+ ${color4.green("\uF489 better-commits")} ${color4.gray("v" + version)}
1061
+
1062
+ ${color4.gray("BRANCH")}
1063
+ ${branch}
1064
+ ${color4.gray("Type")} ${color4.blue(inferred_type)} ${color4.gray("\xB7")} ${color4.gray("Scope")} ${color4.cyan(inferred_scope)} ${color4.gray("\xB7")} ${color4.gray("Ticket")} ${color4.magenta(inferred_ticket)}
1065
+
1066
+ ${color4.gray("CONFIGURATION")}
1067
+ ${config_source2}
1068
+
1069
+ ${color4.gray("Types")}
1070
+ ${types}
1071
+
1072
+ ${color4.gray("Scopes")}
1073
+ ${scopes}
1074
+
1075
+ ${color4.gray("CLI FLAGS")}
1076
+ ${cli_flags}
1077
+
1078
+ ${color4.gray("Commit Flags")}
1079
+ ${commit_flags}
1080
+
1081
+ ${color4.gray("Git Flags (Advanced)")}
1082
+ ${git_flags}
1083
+
1084
+ ${color4.gray("ADDITIONAL COMMANDS")}
1085
+ ${additional_commands}
1086
+
1087
+ `);
1088
+ }
1089
+
1090
+ // src/index.ts
1091
+ var promptCtors = [
1092
+ CommitStatusPrompt,
1093
+ CommitTypePrompt,
1094
+ CommitScopePrompt,
1095
+ CommitTicketPrompt,
1096
+ CommitTitlePrompt,
1097
+ CommitBodyPrompt,
1098
+ CommitFooterPrompt,
1099
+ CommitConfirmPrompt
1100
+ ];
1101
+ var { config, config_source } = load_setup();
1102
+ main(config, config_source);
1103
+ async function main(config2, config_source2) {
1104
+ chdir(get_git_root());
1105
+ if (flags.version) {
1106
+ const version = get_package_version();
1107
+ p10.log.step("Better Commits v" + version);
1108
+ return;
1109
+ }
1110
+ if (flags.help) {
1111
+ print_help_text(config2, config_source2);
1112
+ return;
1113
+ }
1114
+ const infer_state = infer_not_interactive(config2);
1115
+ const flags_plus_infer = {
1116
+ ...flags.commit_state,
1117
+ type: (flags.commit_state.type || infer_state?.type) ?? "",
1118
+ scope: (flags.commit_state.scope || infer_state?.scope) ?? "",
1119
+ ticket: (flags.commit_state.ticket || infer_state?.ticket) ?? ""
1120
+ };
1121
+ const commit_state = parse(CommitState, flags_plus_infer);
1122
+ if (!flags.interactive) {
1123
+ try {
1124
+ parse(create_strict_commit_state(config2), commit_state);
1125
+ } catch (err) {
1126
+ if (err instanceof ValiError) {
1127
+ p10.log.error(`Invalid commit input: ${err.message}`);
1128
+ } else {
1129
+ p10.log.error(`Failed to validate commit input: ${err}`);
1130
+ }
1131
+ process.exit(0);
1132
+ }
1133
+ }
1134
+ const prompt_cache = config2.cache_last_value ? new Configstore("better-commits") : NOOP_PROMPT_CACHE;
1135
+ const prompts_to_run = flags.interactive ? promptCtors : [CommitConfirmPrompt];
1136
+ for (const Prompt of prompts_to_run) {
1137
+ await new Prompt(config2, commit_state, prompt_cache).run();
1138
+ }
1139
+ }
1140
+ export {
1141
+ main
1142
+ };