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.
- package/dist/branch.js +628 -19
- package/dist/chunk-GAAS3VS3.js +922 -0
- package/dist/chunk-H5CLUQIL.js +313 -0
- package/dist/index.js +1122 -41
- package/dist/init.js +44 -1
- package/package.json +12 -4
- package/readme.md +4 -2
- package/.better-commits.json +0 -52
- package/.github/workflows/publish.yml +0 -34
- package/.github/workflows/test.yml +0 -27
- package/.prettierignore +0 -5
- package/.prettierrc +0 -1
- package/dist/chunk-43H72S6V.js +0 -1
- package/dist/chunk-B7AGSPP3.js +0 -261
- package/src/args.test.ts +0 -128
- package/src/args.ts +0 -125
- package/src/branch-args.test.ts +0 -75
- package/src/branch-args.ts +0 -107
- package/src/branch-help.ts +0 -125
- package/src/branch.ts +0 -97
- package/src/default-config-template.ts +0 -258
- package/src/git.test.ts +0 -64
- package/src/git.ts +0 -72
- package/src/help.ts +0 -138
- package/src/index.test.ts +0 -7
- package/src/index.ts +0 -101
- package/src/init.test.ts +0 -123
- package/src/init.ts +0 -46
- package/src/prompts/autocomplete-multiselect.test.ts +0 -129
- package/src/prompts/autocomplete-multiselect.ts +0 -249
- package/src/prompts/branch-checkout.prompt.ts +0 -36
- package/src/prompts/branch-confirm.prompt.test.ts +0 -89
- package/src/prompts/branch-confirm.prompt.ts +0 -149
- package/src/prompts/branch-description.prompt.ts +0 -37
- package/src/prompts/branch-runnable.ts +0 -13
- package/src/prompts/branch-scope.prompt.ts +0 -59
- package/src/prompts/branch-ticket.prompt.ts +0 -41
- package/src/prompts/branch-type.prompt.ts +0 -46
- package/src/prompts/branch-user.prompt.ts +0 -50
- package/src/prompts/branch-version.prompt.ts +0 -41
- package/src/prompts/commit-body.prompt.ts +0 -51
- package/src/prompts/commit-confirm.prompt.ts +0 -123
- package/src/prompts/commit-footer.prompt.ts +0 -195
- package/src/prompts/commit-scope.prompt.ts +0 -91
- package/src/prompts/commit-status.prompt.ts +0 -66
- package/src/prompts/commit-ticket.prompt.ts +0 -82
- package/src/prompts/commit-title.prompt.ts +0 -98
- package/src/prompts/commit-type.prompt.ts +0 -96
- package/src/prompts/runnable.ts +0 -13
- package/src/utils/build-branch.test.ts +0 -159
- package/src/utils/build-branch.ts +0 -48
- package/src/utils/build-commit-string.test.ts +0 -273
- package/src/utils/build-commit-string.ts +0 -163
- package/src/utils/commit-title-size.ts +0 -24
- package/src/utils/infer.test.ts +0 -174
- package/src/utils/infer.ts +0 -160
- package/src/utils/messages.ts +0 -25
- package/src/utils/no-interactive-branch-validation.test.ts +0 -193
- package/src/utils/no-interactive-validation.test.ts +0 -174
- package/src/utils/no-interactive-validation.ts +0 -213
- package/src/utils.test.ts +0 -164
- package/src/utils.ts +0 -235
- package/src/valibot-consts.ts +0 -117
- package/src/valibot-state.test.ts +0 -57
- package/src/valibot-state.ts +0 -276
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -12
- package/vitest.config.ts +0 -8
package/dist/index.js
CHANGED
|
@@ -1,61 +1,1142 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
37
|
+
// src/prompts/commit-type.prompt.ts
|
|
38
|
+
import * as p from "@clack/prompts";
|
|
9
39
|
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
${
|
|
41
|
-
|
|
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
|
-
${
|
|
44
|
-
|
|
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
|
-
${
|
|
47
|
-
|
|
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
|
-
${
|
|
50
|
-
|
|
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
|
-
${
|
|
53
|
-
|
|
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
|
-
${
|
|
56
|
-
|
|
608
|
+
${res}`;
|
|
609
|
+
}
|
|
610
|
+
if (commit_state.closes && commit_state.ticket) {
|
|
611
|
+
commit_string += colorize ? `
|
|
57
612
|
|
|
58
|
-
${
|
|
59
|
-
${c}
|
|
613
|
+
${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}` : `
|
|
60
614
|
|
|
61
|
-
|
|
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
|
+
};
|