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.
- package/.better-commits.json +4 -0
- package/.github/workflows/test.yml +27 -0
- package/.opencode/package-lock.json +115 -0
- package/.opencode/plans/cli-args.md +182 -0
- package/.svelte-kit/ambient.d.ts +289 -0
- package/.svelte-kit/generated/client/app.js +28 -0
- package/.svelte-kit/generated/client/matchers.js +1 -0
- package/.svelte-kit/generated/client/nodes/0.js +1 -0
- package/.svelte-kit/generated/client/nodes/1.js +1 -0
- package/.svelte-kit/tsconfig.json +49 -0
- package/0001-feat-branch-124-update-worktrees-feature.patch +316 -0
- package/dist/branch.js +27 -1
- package/dist/chunk-OFJCRS3N.js +4 -0
- package/dist/chunk-SIF4LZUS.js +1 -0
- package/dist/index.js +44 -19
- package/dist/init.js +1 -1
- package/docs/ai-skills.yaml +48 -0
- package/docs/clack.md +143 -0
- package/docs/valibot.md +228 -0
- package/package.json +12 -9
- package/readme.md +18 -2
- package/src/args.test.ts +102 -0
- package/src/args.ts +101 -7
- package/src/branch-args.test.ts +72 -0
- package/src/branch-args.ts +106 -0
- package/src/branch-help.ts +114 -0
- package/src/branch.ts +67 -238
- package/src/help.ts +131 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +73 -495
- package/src/prompts/branch-checkout.prompt.ts +36 -0
- package/src/prompts/branch-confirm.prompt.ts +134 -0
- package/src/prompts/branch-description.prompt.ts +37 -0
- package/src/prompts/branch-runnable.ts +13 -0
- package/src/prompts/branch-ticket.prompt.ts +41 -0
- package/src/prompts/branch-type.prompt.ts +43 -0
- package/src/prompts/branch-user.prompt.ts +50 -0
- package/src/prompts/branch-version.prompt.ts +41 -0
- package/src/prompts/commit-body.prompt.ts +57 -0
- package/src/prompts/commit-confirm.prompt.ts +119 -0
- package/src/prompts/commit-footer.prompt.ts +195 -0
- package/src/prompts/commit-scope.prompt.ts +73 -0
- package/src/prompts/commit-status.prompt.ts +75 -0
- package/src/prompts/commit-ticket.prompt.ts +82 -0
- package/src/prompts/commit-title.prompt.ts +98 -0
- package/src/prompts/commit-type.prompt.ts +93 -0
- package/src/prompts/runnable.ts +13 -0
- package/src/utils/build-branch.test.ts +141 -0
- package/src/utils/build-branch.ts +46 -0
- package/src/utils/build-commit-string.test.ts +253 -0
- package/src/utils/build-commit-string.ts +158 -0
- package/src/utils/commit-title-size.ts +24 -0
- package/src/utils/infer.test.ts +83 -0
- package/src/utils/infer.ts +114 -0
- package/src/utils/messages.ts +25 -0
- package/src/utils/no-interactive-branch-validation.test.ts +170 -0
- package/src/utils/no-interactive-validation.test.ts +174 -0
- package/src/utils/no-interactive-validation.ts +190 -0
- package/src/utils.ts +59 -66
- package/src/valibot-consts.ts +2 -2
- package/src/valibot-state.test.ts +48 -0
- package/src/valibot-state.ts +133 -130
- package/tsconfig.json +3 -2
- package/vitest.config.ts +8 -0
- 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
|
|
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 {
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 (
|
|
43
|
-
|
|
44
|
-
p.log.step(
|
|
45
|
-
|
|
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
|
-
|
|
81
|
-
config
|
|
82
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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 (
|
|
76
|
+
if (!flags.interactive) {
|
|
142
77
|
try {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
: `\n\n${commit_state.trailer}`;
|
|
515
|
-
}
|
|
93
|
+
const prompts_to_run = flags.interactive
|
|
94
|
+
? promptCtors
|
|
95
|
+
: [CommitConfirmPrompt];
|
|
516
96
|
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
}
|