better-commits 1.19.1 → 1.20.0-cli-flags

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 (65) hide show
  1. package/.better-commits.json +4 -0
  2. package/.github/workflows/test.yml +27 -0
  3. package/.opencode/package-lock.json +115 -0
  4. package/.opencode/plans/cli-args.md +182 -0
  5. package/.svelte-kit/ambient.d.ts +289 -0
  6. package/.svelte-kit/generated/client/app.js +28 -0
  7. package/.svelte-kit/generated/client/matchers.js +1 -0
  8. package/.svelte-kit/generated/client/nodes/0.js +1 -0
  9. package/.svelte-kit/generated/client/nodes/1.js +1 -0
  10. package/.svelte-kit/tsconfig.json +49 -0
  11. package/0001-feat-branch-124-update-worktrees-feature.patch +316 -0
  12. package/dist/branch.js +27 -1
  13. package/dist/chunk-OFJCRS3N.js +4 -0
  14. package/dist/chunk-SIF4LZUS.js +1 -0
  15. package/dist/index.js +44 -19
  16. package/dist/init.js +1 -1
  17. package/docs/ai-skills.yaml +48 -0
  18. package/docs/clack.md +143 -0
  19. package/docs/valibot.md +228 -0
  20. package/package.json +12 -9
  21. package/readme.md +18 -2
  22. package/src/args.test.ts +102 -0
  23. package/src/args.ts +101 -7
  24. package/src/branch-args.test.ts +72 -0
  25. package/src/branch-args.ts +106 -0
  26. package/src/branch-help.ts +114 -0
  27. package/src/branch.ts +67 -238
  28. package/src/help.ts +131 -0
  29. package/src/index.test.ts +7 -0
  30. package/src/index.ts +73 -495
  31. package/src/prompts/branch-checkout.prompt.ts +36 -0
  32. package/src/prompts/branch-confirm.prompt.ts +134 -0
  33. package/src/prompts/branch-description.prompt.ts +37 -0
  34. package/src/prompts/branch-runnable.ts +13 -0
  35. package/src/prompts/branch-ticket.prompt.ts +41 -0
  36. package/src/prompts/branch-type.prompt.ts +43 -0
  37. package/src/prompts/branch-user.prompt.ts +50 -0
  38. package/src/prompts/branch-version.prompt.ts +41 -0
  39. package/src/prompts/commit-body.prompt.ts +57 -0
  40. package/src/prompts/commit-confirm.prompt.ts +119 -0
  41. package/src/prompts/commit-footer.prompt.ts +195 -0
  42. package/src/prompts/commit-scope.prompt.ts +73 -0
  43. package/src/prompts/commit-status.prompt.ts +75 -0
  44. package/src/prompts/commit-ticket.prompt.ts +82 -0
  45. package/src/prompts/commit-title.prompt.ts +98 -0
  46. package/src/prompts/commit-type.prompt.ts +93 -0
  47. package/src/prompts/runnable.ts +13 -0
  48. package/src/utils/build-branch.test.ts +141 -0
  49. package/src/utils/build-branch.ts +46 -0
  50. package/src/utils/build-commit-string.test.ts +253 -0
  51. package/src/utils/build-commit-string.ts +158 -0
  52. package/src/utils/commit-title-size.ts +24 -0
  53. package/src/utils/infer.test.ts +83 -0
  54. package/src/utils/infer.ts +114 -0
  55. package/src/utils/messages.ts +25 -0
  56. package/src/utils/no-interactive-branch-validation.test.ts +170 -0
  57. package/src/utils/no-interactive-validation.test.ts +174 -0
  58. package/src/utils/no-interactive-validation.ts +190 -0
  59. package/src/utils.ts +59 -66
  60. package/src/valibot-consts.ts +2 -2
  61. package/src/valibot-state.test.ts +48 -0
  62. package/src/valibot-state.ts +133 -130
  63. package/tsconfig.json +3 -2
  64. package/vitest.config.ts +8 -0
  65. package/dist/chunk-K2RPF2JY.js +0 -4
package/src/index.ts CHANGED
@@ -1,522 +1,100 @@
1
1
  #! /usr/bin/env node
2
2
 
3
- import * as p from "@clack/prompts";
4
- import color from "picocolors";
5
- import { StdioOptions, execSync } from "child_process";
6
3
  import { chdir } from "process";
7
- import { Output, parse } from "valibot";
4
+ import * as p from "@clack/prompts";
5
+ import { InferInput, InferOutput, ValiError, parse } from "valibot";
8
6
  import { CommitState, Config } from "./valibot-state";
9
7
  import {
10
8
  load_setup,
11
- addNewLine,
12
- SPACE_TO_SELECT,
13
- REGEX_SLASH_TAG,
14
- REGEX_SLASH_NUM,
15
- REGEX_START_TAG,
16
- REGEX_START_NUM,
17
- OPTIONAL_PROMPT,
18
- clean_commit_title,
19
- COMMIT_FOOTER_OPTIONS,
20
- infer_type_from_branch,
21
9
  get_git_root,
22
- REGEX_SLASH_UND,
23
- REGEX_START_UND,
24
- get_value_from_cache,
25
- set_value_cache,
26
10
  NOOP_PROMPT_CACHE,
11
+ ConfigSource,
27
12
  } from "./utils";
28
- import { git_add, git_status } from "./git";
29
- import { CUSTOM_SCOPE_KEY, V_FOOTER_OPTIONS } from "./valibot-consts";
30
- import { flags } from "./args";
13
+ import { create_strict_commit_state } from "./utils/no-interactive-validation";
31
14
  import Configstore from "configstore";
32
-
33
- main(load_setup());
34
-
35
- export async function main(config: Output<typeof Config>) {
36
- let commit_state = parse(CommitState, {});
15
+ import { CommitTypePrompt } from "./prompts/commit-type.prompt";
16
+ import { Runnable } from "./prompts/runnable";
17
+ import { CommitScopePrompt } from "./prompts/commit-scope.prompt";
18
+ import { CommitTicketPrompt } from "./prompts/commit-ticket.prompt";
19
+ import { CommitTitlePrompt } from "./prompts/commit-title.prompt";
20
+ import { CommitBodyPrompt } from "./prompts/commit-body.prompt";
21
+ import { CommitFooterPrompt } from "./prompts/commit-footer.prompt";
22
+ import { CommitConfirmPrompt } from "./prompts/commit-confirm.prompt";
23
+ import { CommitStatusPrompt } from "./prompts/commit-status.prompt";
24
+ import { flags } from "./args";
25
+ import { get_package_version } from "./utils";
26
+ import { infer_not_interactive } from "./utils/infer";
27
+ import { print_help_text } from "./help";
28
+
29
+ type PromptCtor = new (
30
+ config: InferOutput<typeof Config>,
31
+ commit_state: InferOutput<typeof CommitState>,
32
+ prompt_cache: Configstore,
33
+ ) => Runnable;
34
+
35
+ const promptCtors: PromptCtor[] = [
36
+ CommitStatusPrompt,
37
+ CommitTypePrompt,
38
+ CommitScopePrompt,
39
+ CommitTicketPrompt,
40
+ CommitTitlePrompt,
41
+ CommitBodyPrompt,
42
+ CommitFooterPrompt,
43
+ CommitConfirmPrompt,
44
+ ];
45
+
46
+ const { config, config_source } = load_setup();
47
+
48
+ main(config, config_source);
49
+
50
+ export async function main(
51
+ config: InferOutput<typeof Config>,
52
+ config_source: ConfigSource,
53
+ ) {
37
54
  chdir(get_git_root());
38
- const prompt_cache = config.cache_last_value
39
- ? new Configstore("better-commits")
40
- : NOOP_PROMPT_CACHE;
41
55
 
42
- if (config.check_status) {
43
- let { index, work_tree } = git_status();
44
- p.log.step(color.black(color.bgGreen(" Checking Git Status ")));
45
- const staged_files = index.reduce(
46
- (acc, curr, i: number) => color.green(acc + curr + addNewLine(index, i)),
47
- "",
48
- );
49
- p.log.success("Changes to be committed:\n" + staged_files);
50
- if (work_tree.length) {
51
- const unstaged_files = work_tree.reduce(
52
- (acc, curr, i: number) =>
53
- color.red(acc + curr + addNewLine(work_tree, i)),
54
- "",
55
- );
56
- p.log.error("Changes not staged for commit:\n" + unstaged_files);
57
- const selected_for_staging = (await p.multiselect({
58
- message: `Some files have not been staged, would you like to add them now? ${SPACE_TO_SELECT}`,
59
- options: [
60
- { value: ".", label: "." },
61
- ...work_tree.map((v) => ({ value: v, label: v })),
62
- ],
63
- required: false,
64
- })) as string[];
65
- if (p.isCancel(selected_for_staging)) process.exit(0);
66
- git_add(selected_for_staging);
67
- }
68
-
69
- let updated_status = git_status();
70
- if (!updated_status.index.length) {
71
- p.log.error(
72
- color.red(
73
- 'no changes added to commit (use "git add" and/or "git commit -a")',
74
- ),
75
- );
76
- process.exit(0);
77
- }
56
+ if (flags.version) {
57
+ const version = get_package_version();
58
+ p.log.step("Better Commits v" + version);
59
+ return;
78
60
  }
79
61
 
80
- const value_to_data: Record<string, { emoji: string; trailer: string }> =
81
- config.commit_type.options.reduce(
82
- (acc, curr) => ({
83
- ...acc,
84
- [curr.value]: {
85
- emoji: curr.emoji ?? "",
86
- trailer: curr.trailer ?? "",
87
- },
88
- }),
89
- {},
90
- );
91
- if (config.commit_type.enable) {
92
- let message = "Select a commit type";
93
- let initial_value = config.commit_type.initial_value;
94
- if (config.commit_type.infer_type_from_branch) {
95
- const options = config.commit_type.options.map((o) => o.value);
96
- const type_from_branch = infer_type_from_branch(options);
97
- if (type_from_branch) {
98
- message = `Commit type inferred from branch ${color.dim("(confirm / edit)")}`;
99
- initial_value = type_from_branch;
100
- }
101
- }
102
- const commit_type = await p.select({
103
- message,
104
- initialValue:
105
- get_value_from_cache(prompt_cache, "commit_type") || initial_value,
106
- maxItems: config.commit_type.max_items,
107
- options: config.commit_type.options,
108
- });
109
- if (p.isCancel(commit_type)) process.exit(0);
110
- set_value_cache(prompt_cache, "commit_type", commit_type);
111
- commit_state.trailer = value_to_data[commit_type].trailer;
112
- commit_state.type =
113
- config.commit_type.append_emoji_to_commit &&
114
- config.commit_type.emoji_commit_position === "Start"
115
- ? `${value_to_data[commit_type].emoji} ${commit_type}`.trim()
116
- : commit_type;
62
+ if (flags.help) {
63
+ print_help_text(config, config_source);
64
+ return;
117
65
  }
118
66
 
119
- if (config.commit_scope.enable) {
120
- let commit_scope = await p.select({
121
- message: "Select a commit scope",
122
- initialValue:
123
- get_value_from_cache(prompt_cache, "commit_scope") ||
124
- config.commit_scope.initial_value,
125
- maxItems: config.commit_scope.max_items,
126
- options: config.commit_scope.options,
127
- });
128
- if (p.isCancel(commit_scope)) process.exit(0);
129
- set_value_cache(prompt_cache, "commit_scope", commit_scope);
67
+ const infer_state = infer_not_interactive(config);
68
+ const flags_plus_infer: InferInput<typeof CommitState> = {
69
+ ...flags.commit_state,
70
+ type: (flags.commit_state.type || infer_state?.type) ?? "",
71
+ ticket: (flags.commit_state.ticket || infer_state?.ticket) ?? "",
72
+ };
130
73
 
131
- if (commit_scope === CUSTOM_SCOPE_KEY && config.commit_scope.custom_scope) {
132
- commit_scope = await p.text({
133
- message: "Write a custom scope",
134
- placeholder: "",
135
- });
136
- if (p.isCancel(commit_scope)) process.exit(0);
137
- }
138
- commit_state.scope = commit_scope;
139
- }
74
+ const commit_state = parse(CommitState, flags_plus_infer);
140
75
 
141
- if (config.check_ticket.infer_ticket) {
76
+ if (!flags.interactive) {
142
77
  try {
143
- const branch = execSync(`git ${flags.git_args} branch --show-current`, {
144
- stdio: "pipe",
145
- }).toString();
146
- const found: string[] = [
147
- branch.match(REGEX_START_UND),
148
- branch.match(REGEX_SLASH_UND),
149
- branch.match(REGEX_SLASH_TAG),
150
- branch.match(REGEX_SLASH_NUM),
151
- branch.match(REGEX_START_TAG),
152
- branch.match(REGEX_START_NUM),
153
- ]
154
- .filter((v) => v != null)
155
- .map((v) => (v && v.length >= 2 ? v[1] : ""));
156
- if (found.length && found[0]) {
157
- commit_state.ticket =
158
- config.check_ticket.append_hashtag ||
159
- config.check_ticket.prepend_hashtag === "Prompt"
160
- ? "#" + found[0]
161
- : found[0];
78
+ parse(create_strict_commit_state(config), commit_state);
79
+ } catch (err) {
80
+ if (err instanceof ValiError) {
81
+ p.log.error(`Invalid --no-interactive commit input: ${err.message}`);
82
+ } else {
83
+ p.log.error(`Failed to validate --no-interactive commit input: ${err}`);
162
84
  }
163
- } catch (err: any) {
164
- // Can't find branch, fail silently
165
- }
166
- }
167
-
168
- if (config.check_ticket.confirm_ticket) {
169
- const user_commit_ticket = await p.text({
170
- message: commit_state.ticket
171
- ? `Ticket / issue inferred from branch ${color.dim("(confirm / edit)")}`
172
- : `Add ticket / issue ${OPTIONAL_PROMPT}`,
173
- placeholder: "",
174
- initialValue:
175
- get_value_from_cache(prompt_cache, "commit_ticket") ||
176
- commit_state.ticket,
177
- });
178
- if (p.isCancel(user_commit_ticket)) process.exit(0);
179
- set_value_cache(prompt_cache, "commit_ticket", user_commit_ticket);
180
- commit_state.ticket = user_commit_ticket ?? "";
181
- }
182
-
183
- if (
184
- config.check_ticket.prepend_hashtag === "Always" &&
185
- commit_state.ticket &&
186
- !commit_state.ticket.startsWith("#")
187
- ) {
188
- commit_state.ticket = "#" + commit_state.ticket;
189
- }
190
-
191
- const commit_title = await p.text({
192
- message: "Write a brief title describing the commit",
193
- initialValue: get_value_from_cache(prompt_cache, "commit_title") || "",
194
- placeholder: "",
195
- validate: (value) => {
196
- if (!value) return "Please enter a title";
197
- const commit_scope_size = commit_state.scope
198
- ? commit_state.scope.length + 2
199
- : 0;
200
- const commit_type_size = commit_state.type.length;
201
- const commit_ticket_size = config.check_ticket.add_to_title
202
- ? commit_state.ticket.length
203
- : 0;
204
- if (
205
- commit_scope_size +
206
- commit_type_size +
207
- commit_ticket_size +
208
- value.length >
209
- config.commit_title.max_size
210
- )
211
- return `Exceeded max length. Title max [${config.commit_title.max_size}]`;
212
- },
213
- });
214
- if (p.isCancel(commit_title)) process.exit(0);
215
- set_value_cache(prompt_cache, "commit_title", commit_title);
216
-
217
- let commit_title_with_emoji = commit_title;
218
- if (
219
- config.commit_type.append_emoji_to_commit &&
220
- config.commit_type.emoji_commit_position === "After-Colon"
221
- )
222
- commit_title_with_emoji = `${value_to_data[commit_state.type].emoji} ${commit_title}`;
223
- commit_state.title = clean_commit_title(commit_title_with_emoji);
224
-
225
- if (config.commit_body.enable) {
226
- const commit_body = await p.text({
227
- message: `Write a detailed description of the changes ${OPTIONAL_PROMPT}`,
228
- initialValue: get_value_from_cache(prompt_cache, "commit_body") || "",
229
- placeholder: "",
230
- validate: (val) => {
231
- if (config.commit_body.required && !val)
232
- return "Please enter a description";
233
- },
234
- });
235
- if (p.isCancel(commit_body)) process.exit(0);
236
-
237
- commit_state.body = commit_body ?? "";
238
- if (config.commit_body.split_by_period) {
239
- const sentences = commit_state.body.split(/\.\s+/).map((s) => s.trim());
240
- commit_state.body = sentences.join(".\n");
241
- }
242
-
243
- // Cache unsplit version for editing.
244
- set_value_cache(prompt_cache, "commit_body", commit_body);
245
- }
246
-
247
- if (config.commit_footer.enable) {
248
- const cache_footer = get_value_from_cache(
249
- prompt_cache,
250
- "commit_footer",
251
- ).split(",");
252
- const commit_footer = await p.multiselect({
253
- message: `Select optional footers ${SPACE_TO_SELECT}`,
254
- initialValues: cache_footer || config.commit_footer.initial_value,
255
- options: COMMIT_FOOTER_OPTIONS as {
256
- value: Output<typeof V_FOOTER_OPTIONS>;
257
- label: string;
258
- hint: string;
259
- }[],
260
- required: false,
261
- });
262
- if (p.isCancel(commit_footer)) process.exit(0);
263
- set_value_cache(prompt_cache, "commit_footer", commit_footer.join(","));
264
-
265
- if (commit_footer.includes("breaking-change")) {
266
- const breaking_changes_title = await p.text({
267
- message: "Breaking changes: Write a short title / summary",
268
- placeholder: "",
269
- validate: (value) => {
270
- if (!value) return "Please enter a title / summary";
271
- },
272
- });
273
- if (p.isCancel(breaking_changes_title)) process.exit(0);
274
- const breaking_changes_body = await p.text({
275
- message: `Breaking Changes: Write a description & migration instructions ${OPTIONAL_PROMPT}`,
276
- placeholder: "",
277
- });
278
- if (p.isCancel(breaking_changes_body)) process.exit(0);
279
- commit_state.breaking_title = breaking_changes_title;
280
- commit_state.breaking_body = breaking_changes_body;
281
- }
282
-
283
- if (commit_footer.includes("deprecated")) {
284
- const deprecated_title = await p.text({
285
- message: "Deprecated: Write a short title / summary",
286
- placeholder: "",
287
- validate: (value) => {
288
- if (!value) return "Please enter a title / summary";
289
- },
290
- });
291
- if (p.isCancel(deprecated_title)) process.exit(0);
292
- const deprecated_body = await p.text({
293
- message: `Deprecated: Write a description ${OPTIONAL_PROMPT}`,
294
- placeholder: "",
295
- });
296
- if (p.isCancel(deprecated_body)) process.exit(0);
297
- commit_state.deprecates_body = deprecated_body;
298
- commit_state.deprecates_title = deprecated_title;
299
- }
300
-
301
- if (commit_footer.includes("closes")) {
302
- commit_state.closes = "Closes:";
303
- }
304
-
305
- if (commit_footer.includes("custom")) {
306
- const custom_footer = await p.text({
307
- message: "Write a custom footer",
308
- placeholder: "",
309
- });
310
- if (p.isCancel(custom_footer)) process.exit(0);
311
- commit_state.custom_footer = custom_footer;
312
- }
313
-
314
- if (!commit_footer.includes("trailer")) {
315
- commit_state.trailer = "";
85
+ process.exit(0);
316
86
  }
317
87
  }
318
88
 
319
- if (config.confirm_with_editor) {
320
- const options = config.overrides.shell
321
- ? { shell: config.overrides.shell, stdio: "inherit" as StdioOptions }
322
- : { stdio: "inherit" as StdioOptions };
323
- const trailer = commit_state.trailer
324
- ? `--trailer="${commit_state.trailer}"`
325
- : "";
326
- execSync(
327
- `git ${flags.git_args} commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer} --edit`,
328
- options,
329
- );
330
- process.exit(0);
331
- }
332
-
333
- let continue_commit = true;
334
- if (config.print_commit_output) {
335
- p.note(
336
- build_commit_string(commit_state, config, true, false, true),
337
- "Commit Preview",
338
- );
339
- }
340
- if (config.confirm_commit) {
341
- continue_commit = (await p.confirm({
342
- message: "Confirm Commit?",
343
- })) as boolean;
344
- if (p.isCancel(continue_commit)) process.exit(0);
345
- }
346
-
347
- if (!continue_commit) {
348
- p.log.info("Exiting without commit");
349
- process.exit(0);
350
- }
351
-
352
- try {
353
- p.log.info("Committing changes...");
354
- const options = config.overrides.shell
355
- ? { shell: config.overrides.shell, stdio: "inherit" as StdioOptions }
356
- : { stdio: "inherit" as StdioOptions };
357
- const trailer = commit_state.trailer
358
- ? `--trailer="${commit_state.trailer}"`
359
- : "";
360
- execSync(
361
- `git ${flags.git_args} commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer}`,
362
- options,
363
- );
364
- } catch (err) {
365
- p.log.error("Something went wrong when committing: " + err);
366
- return;
367
- }
368
-
369
- p.log.success("Commit Complete");
370
-
371
- // Instead of deleting individual keys, just get what we need and clear.
372
- const user_name = prompt_cache.get("username");
373
- prompt_cache.clear();
374
- if (user_name) prompt_cache.set("username", user_name);
375
- }
376
-
377
- function build_commit_string(
378
- commit_state: Output<typeof CommitState>,
379
- config: Output<typeof Config>,
380
- colorize: boolean = false,
381
- escape_quotes: boolean = false,
382
- include_trailer: boolean = false,
383
- ): string {
384
- let commit_string = "";
385
- if (commit_state.type) {
386
- commit_string += colorize
387
- ? color.blue(commit_state.type)
388
- : commit_state.type;
389
- }
390
-
391
- if (commit_state.scope) {
392
- const scope = colorize
393
- ? color.cyan(commit_state.scope)
394
- : commit_state.scope;
395
- commit_string += `(${scope})`;
396
- }
397
-
398
- let title_ticket = commit_state.ticket;
399
- const surround = config.check_ticket.surround;
400
- if (commit_state.ticket && surround) {
401
- const open_token = surround.charAt(0);
402
- const close_token = surround.charAt(1);
403
- title_ticket = `${open_token}${commit_state.ticket}${close_token}`;
404
- }
405
-
406
- const position_beginning = config.check_ticket.title_position === "beginning";
407
- if (title_ticket && config.check_ticket.add_to_title && position_beginning) {
408
- commit_string = `${colorize ? color.magenta(title_ticket) : title_ticket} ${commit_string}`;
409
- }
410
-
411
- const position_before_colon =
412
- config.check_ticket.title_position === "before-colon";
413
- if (
414
- title_ticket &&
415
- config.check_ticket.add_to_title &&
416
- position_before_colon
417
- ) {
418
- const spacing =
419
- commit_state.scope || (commit_state.type && !config.check_ticket.surround)
420
- ? " "
421
- : "";
422
- commit_string += colorize
423
- ? color.magenta(spacing + title_ticket)
424
- : spacing + title_ticket;
425
- }
426
-
427
- if (
428
- commit_state.breaking_title &&
429
- config.breaking_change.add_exclamation_to_title
430
- ) {
431
- commit_string += colorize ? color.red("!") : "!";
432
- }
433
-
434
- if (
435
- commit_state.scope ||
436
- commit_state.type ||
437
- (title_ticket && position_before_colon)
438
- ) {
439
- commit_string += ": ";
440
- }
441
-
442
- const position_start = config.check_ticket.title_position === "start";
443
- const position_end = config.check_ticket.title_position === "end";
444
- if (title_ticket && config.check_ticket.add_to_title && position_start) {
445
- commit_string += colorize
446
- ? color.magenta(title_ticket) + " "
447
- : title_ticket + " ";
448
- }
449
-
450
- if (commit_state.title) {
451
- commit_string += colorize
452
- ? color.reset(commit_state.title)
453
- : commit_state.title;
454
- }
455
-
456
- if (title_ticket && config.check_ticket.add_to_title && position_end) {
457
- commit_string +=
458
- " " + (colorize ? color.magenta(title_ticket) : title_ticket);
459
- }
460
-
461
- if (commit_state.body) {
462
- const temp = commit_state.body.split("\\n"); // literal \n, not new-line.
463
- const res = temp
464
- .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
465
- .join("\n");
466
- commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
467
- }
468
-
469
- if (commit_state.breaking_title) {
470
- const title = colorize
471
- ? color.red(`BREAKING CHANGE: ${commit_state.breaking_title}`)
472
- : `BREAKING CHANGE: ${commit_state.breaking_title}`;
473
- commit_string += `\n\n${title}`;
474
- }
475
-
476
- if (commit_state.breaking_body) {
477
- const body = colorize
478
- ? color.red(commit_state.breaking_body)
479
- : commit_state.breaking_body;
480
- commit_string += `\n\n${body}`;
481
- }
482
-
483
- if (commit_state.deprecates_title) {
484
- const title = colorize
485
- ? color.yellow(`DEPRECATED: ${commit_state.deprecates_title}`)
486
- : `DEPRECATED: ${commit_state.deprecates_title}`;
487
- commit_string += `\n\n${title}`;
488
- }
489
-
490
- if (commit_state.deprecates_body) {
491
- const body = colorize
492
- ? color.yellow(commit_state.deprecates_body)
493
- : commit_state.deprecates_body;
494
- commit_string += `\n\n${body}`;
495
- }
496
-
497
- if (commit_state.custom_footer) {
498
- const temp = commit_state.custom_footer.split("\\n");
499
- const res = temp
500
- .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
501
- .join("\n");
502
- commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
503
- }
504
-
505
- if (commit_state.closes && commit_state.ticket) {
506
- commit_string += colorize
507
- ? `\n\n${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}`
508
- : `\n\n${commit_state.closes} ${commit_state.ticket}`;
509
- }
89
+ const prompt_cache = config.cache_last_value
90
+ ? new Configstore("better-commits")
91
+ : NOOP_PROMPT_CACHE;
510
92
 
511
- if (include_trailer && commit_state.trailer) {
512
- commit_string += colorize
513
- ? `\n\n${color.dim(commit_state.trailer)}`
514
- : `\n\n${commit_state.trailer}`;
515
- }
93
+ const prompts_to_run = flags.interactive
94
+ ? promptCtors
95
+ : [CommitConfirmPrompt];
516
96
 
517
- if (escape_quotes) {
518
- commit_string = commit_string.replaceAll('"', '\\"').replaceAll("`", "\\`");
97
+ for (const Prompt of prompts_to_run) {
98
+ await new Prompt(config, commit_state, prompt_cache).run();
519
99
  }
520
-
521
- return commit_string;
522
100
  }
@@ -0,0 +1,36 @@
1
+ import { InferOutput } from "valibot";
2
+ import { BranchRunnable } from "./branch-runnable";
3
+ import { V_BRANCH_ACTIONS } from "../valibot-consts";
4
+ import * as p from "@clack/prompts";
5
+ import { BRANCH_ACTION_OPTIONS } from "../utils";
6
+
7
+ export class BranchCheckoutPrompt extends BranchRunnable {
8
+ async run(): Promise<void> {
9
+ if (this.#is_enabled) {
10
+ const branch_or_worktree = await p.select({
11
+ message: this.#message,
12
+ initialValue: this.#initival_values,
13
+ options: BRANCH_ACTION_OPTIONS,
14
+ });
15
+
16
+ if (p.isCancel(branch_or_worktree)) process.exit();
17
+ this.#post_run_effects(branch_or_worktree);
18
+ }
19
+ }
20
+
21
+ get #message() {
22
+ return `Checkout a branch or create a worktree?`;
23
+ }
24
+
25
+ get #is_enabled() {
26
+ return this.config.worktrees.enable;
27
+ }
28
+
29
+ get #initival_values() {
30
+ return this.branch_state.checkout || this.config.branch_action_default;
31
+ }
32
+
33
+ #post_run_effects(value: InferOutput<typeof V_BRANCH_ACTIONS>) {
34
+ this.branch_state.checkout = value;
35
+ }
36
+ }