better-commits 1.19.0 → 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 -492
  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,519 +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_body.split(".").map((s) => s.trim());
240
- commit_state.body = sentences.join(".\n");
241
- }
242
-
243
- set_value_cache(prompt_cache, "commit_body", commit_state.body);
244
- }
245
-
246
- if (config.commit_footer.enable) {
247
- const cache_footer = get_value_from_cache(
248
- prompt_cache,
249
- "commit_footer",
250
- ).split(",");
251
- const commit_footer = await p.multiselect({
252
- message: `Select optional footers ${SPACE_TO_SELECT}`,
253
- initialValues: cache_footer || config.commit_footer.initial_value,
254
- options: COMMIT_FOOTER_OPTIONS as {
255
- value: Output<typeof V_FOOTER_OPTIONS>;
256
- label: string;
257
- hint: string;
258
- }[],
259
- required: false,
260
- });
261
- if (p.isCancel(commit_footer)) process.exit(0);
262
- set_value_cache(prompt_cache, "commit_footer", commit_footer.join(","));
263
-
264
- if (commit_footer.includes("breaking-change")) {
265
- const breaking_changes_title = await p.text({
266
- message: "Breaking changes: Write a short title / summary",
267
- placeholder: "",
268
- validate: (value) => {
269
- if (!value) return "Please enter a title / summary";
270
- },
271
- });
272
- if (p.isCancel(breaking_changes_title)) process.exit(0);
273
- const breaking_changes_body = await p.text({
274
- message: `Breaking Changes: Write a description & migration instructions ${OPTIONAL_PROMPT}`,
275
- placeholder: "",
276
- });
277
- if (p.isCancel(breaking_changes_body)) process.exit(0);
278
- commit_state.breaking_title = breaking_changes_title;
279
- commit_state.breaking_body = breaking_changes_body;
280
- }
281
-
282
- if (commit_footer.includes("deprecated")) {
283
- const deprecated_title = await p.text({
284
- message: "Deprecated: Write a short title / summary",
285
- placeholder: "",
286
- validate: (value) => {
287
- if (!value) return "Please enter a title / summary";
288
- },
289
- });
290
- if (p.isCancel(deprecated_title)) process.exit(0);
291
- const deprecated_body = await p.text({
292
- message: `Deprecated: Write a description ${OPTIONAL_PROMPT}`,
293
- placeholder: "",
294
- });
295
- if (p.isCancel(deprecated_body)) process.exit(0);
296
- commit_state.deprecates_body = deprecated_body;
297
- commit_state.deprecates_title = deprecated_title;
298
- }
299
-
300
- if (commit_footer.includes("closes")) {
301
- commit_state.closes = "Closes:";
302
- }
303
-
304
- if (commit_footer.includes("custom")) {
305
- const custom_footer = await p.text({
306
- message: "Write a custom footer",
307
- placeholder: "",
308
- });
309
- if (p.isCancel(custom_footer)) process.exit(0);
310
- commit_state.custom_footer = custom_footer;
311
- }
312
-
313
- if (!commit_footer.includes("trailer")) {
314
- commit_state.trailer = "";
85
+ process.exit(0);
315
86
  }
316
87
  }
317
88
 
318
- if (config.confirm_with_editor) {
319
- const options = config.overrides.shell
320
- ? { shell: config.overrides.shell, stdio: "inherit" as StdioOptions }
321
- : { stdio: "inherit" as StdioOptions };
322
- const trailer = commit_state.trailer
323
- ? `--trailer="${commit_state.trailer}"`
324
- : "";
325
- execSync(
326
- `git ${flags.git_args} commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer} --edit`,
327
- options,
328
- );
329
- process.exit(0);
330
- }
331
-
332
- let continue_commit = true;
333
- if (config.print_commit_output) {
334
- p.note(
335
- build_commit_string(commit_state, config, true, false, true),
336
- "Commit Preview",
337
- );
338
- }
339
- if (config.confirm_commit) {
340
- continue_commit = (await p.confirm({
341
- message: "Confirm Commit?",
342
- })) as boolean;
343
- if (p.isCancel(continue_commit)) process.exit(0);
344
- }
345
-
346
- if (!continue_commit) {
347
- p.log.info("Exiting without commit");
348
- process.exit(0);
349
- }
350
-
351
- try {
352
- p.log.info("Committing changes...");
353
- const options = config.overrides.shell
354
- ? { shell: config.overrides.shell, stdio: "inherit" as StdioOptions }
355
- : { stdio: "inherit" as StdioOptions };
356
- const trailer = commit_state.trailer
357
- ? `--trailer="${commit_state.trailer}"`
358
- : "";
359
- execSync(
360
- `git ${flags.git_args} commit -m "${build_commit_string(commit_state, config, false, true, false)}" ${trailer}`,
361
- options,
362
- );
363
- } catch (err) {
364
- p.log.error("Something went wrong when committing: " + err);
365
- }
366
- p.log.success("Commit Complete");
367
-
368
- // Instead of deleting individual keys, just get what we need and clear.
369
- const user_name = prompt_cache.get("username");
370
- prompt_cache.clear();
371
- if (user_name) prompt_cache.set("username", user_name);
372
- }
373
-
374
- function build_commit_string(
375
- commit_state: Output<typeof CommitState>,
376
- config: Output<typeof Config>,
377
- colorize: boolean = false,
378
- escape_quotes: boolean = false,
379
- include_trailer: boolean = false,
380
- ): string {
381
- let commit_string = "";
382
- if (commit_state.type) {
383
- commit_string += colorize
384
- ? color.blue(commit_state.type)
385
- : commit_state.type;
386
- }
387
-
388
- if (commit_state.scope) {
389
- const scope = colorize
390
- ? color.cyan(commit_state.scope)
391
- : commit_state.scope;
392
- commit_string += `(${scope})`;
393
- }
394
-
395
- let title_ticket = commit_state.ticket;
396
- const surround = config.check_ticket.surround;
397
- if (commit_state.ticket && surround) {
398
- const open_token = surround.charAt(0);
399
- const close_token = surround.charAt(1);
400
- title_ticket = `${open_token}${commit_state.ticket}${close_token}`;
401
- }
402
-
403
- const position_beginning = config.check_ticket.title_position === "beginning";
404
- if (title_ticket && config.check_ticket.add_to_title && position_beginning) {
405
- commit_string = `${colorize ? color.magenta(title_ticket) : title_ticket} ${commit_string}`;
406
- }
407
-
408
- const position_before_colon =
409
- config.check_ticket.title_position === "before-colon";
410
- if (
411
- title_ticket &&
412
- config.check_ticket.add_to_title &&
413
- position_before_colon
414
- ) {
415
- const spacing =
416
- commit_state.scope || (commit_state.type && !config.check_ticket.surround)
417
- ? " "
418
- : "";
419
- commit_string += colorize
420
- ? color.magenta(spacing + title_ticket)
421
- : spacing + title_ticket;
422
- }
423
-
424
- if (
425
- commit_state.breaking_title &&
426
- config.breaking_change.add_exclamation_to_title
427
- ) {
428
- commit_string += colorize ? color.red("!") : "!";
429
- }
430
-
431
- if (
432
- commit_state.scope ||
433
- commit_state.type ||
434
- (title_ticket && position_before_colon)
435
- ) {
436
- commit_string += ": ";
437
- }
438
-
439
- const position_start = config.check_ticket.title_position === "start";
440
- const position_end = config.check_ticket.title_position === "end";
441
- if (title_ticket && config.check_ticket.add_to_title && position_start) {
442
- commit_string += colorize
443
- ? color.magenta(title_ticket) + " "
444
- : title_ticket + " ";
445
- }
446
-
447
- if (commit_state.title) {
448
- commit_string += colorize
449
- ? color.reset(commit_state.title)
450
- : commit_state.title;
451
- }
452
-
453
- if (title_ticket && config.check_ticket.add_to_title && position_end) {
454
- commit_string +=
455
- " " + (colorize ? color.magenta(title_ticket) : title_ticket);
456
- }
457
-
458
- if (commit_state.body) {
459
- const temp = commit_state.body.split("\\n"); // literal \n, not new-line.
460
- const res = temp
461
- .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
462
- .join("\n");
463
- commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
464
- }
465
-
466
- if (commit_state.breaking_title) {
467
- const title = colorize
468
- ? color.red(`BREAKING CHANGE: ${commit_state.breaking_title}`)
469
- : `BREAKING CHANGE: ${commit_state.breaking_title}`;
470
- commit_string += `\n\n${title}`;
471
- }
472
-
473
- if (commit_state.breaking_body) {
474
- const body = colorize
475
- ? color.red(commit_state.breaking_body)
476
- : commit_state.breaking_body;
477
- commit_string += `\n\n${body}`;
478
- }
479
-
480
- if (commit_state.deprecates_title) {
481
- const title = colorize
482
- ? color.yellow(`DEPRECATED: ${commit_state.deprecates_title}`)
483
- : `DEPRECATED: ${commit_state.deprecates_title}`;
484
- commit_string += `\n\n${title}`;
485
- }
486
-
487
- if (commit_state.deprecates_body) {
488
- const body = colorize
489
- ? color.yellow(commit_state.deprecates_body)
490
- : commit_state.deprecates_body;
491
- commit_string += `\n\n${body}`;
492
- }
493
-
494
- if (commit_state.custom_footer) {
495
- const temp = commit_state.custom_footer.split("\\n");
496
- const res = temp
497
- .map((v) => (colorize ? color.reset(v.trim()) : v.trim()))
498
- .join("\n");
499
- commit_string += colorize ? `\n\n${res}` : `\n\n${res}`;
500
- }
501
-
502
- if (commit_state.closes && commit_state.ticket) {
503
- commit_string += colorize
504
- ? `\n\n${color.reset(commit_state.closes)} ${color.magenta(commit_state.ticket)}`
505
- : `\n\n${commit_state.closes} ${commit_state.ticket}`;
506
- }
89
+ const prompt_cache = config.cache_last_value
90
+ ? new Configstore("better-commits")
91
+ : NOOP_PROMPT_CACHE;
507
92
 
508
- if (include_trailer && commit_state.trailer) {
509
- commit_string += colorize
510
- ? `\n\n${color.dim(commit_state.trailer)}`
511
- : `\n\n${commit_state.trailer}`;
512
- }
93
+ const prompts_to_run = flags.interactive
94
+ ? promptCtors
95
+ : [CommitConfirmPrompt];
513
96
 
514
- if (escape_quotes) {
515
- commit_string = commit_string.replaceAll('"', '\\"').replaceAll("`", "\\`");
97
+ for (const Prompt of prompts_to_run) {
98
+ await new Prompt(config, commit_state, prompt_cache).run();
516
99
  }
517
-
518
- return commit_string;
519
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
+ }