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/utils.ts CHANGED
@@ -3,10 +3,9 @@ import { execSync } from "child_process";
3
3
  import fs from "fs";
4
4
  import { homedir } from "os";
5
5
  import color from "picocolors";
6
- import { Output, ValiError, parse } from "valibot";
6
+ import { InferOutput, ValiError, parse } from "valibot";
7
7
  import { Config } from "./valibot-state";
8
8
  import { V_BRANCH_ACTIONS } from "./valibot-consts";
9
- import { argv } from "process";
10
9
  import { flags } from "./args";
11
10
  import Configstore from "configstore";
12
11
 
@@ -16,19 +15,6 @@ export const A_FOR_ALL = `${color.dim(
16
15
  "(<space> to select, <a> to select all)",
17
16
  )}`;
18
17
  export const OPTIONAL_PROMPT = `${color.dim("(optional)")}`;
19
- export const CACHE_PROMPT = `${color.dim("(value will be saved)")}`;
20
- export const REGEX_SLASH_TAG = new RegExp(/\/(\w+-\d+)/);
21
- export const REGEX_START_TAG = new RegExp(/^(\w+-\d+)/);
22
- export const REGEX_START_UND = new RegExp(/^([A-Z]+-[\[a-zA-Z\]\d]+)_/);
23
- export const REGEX_SLASH_UND = new RegExp(/\/([A-Z]+-[\[a-zA-Z\]\d]+)_/);
24
-
25
- // TODO: This might conflict with version from better-branch
26
- // - Maybe negative lookup against .
27
- // - Maybe check the order
28
- // - Maybe use order to split and check values
29
- export const REGEX_SLASH_NUM = new RegExp(/\/(\d+)/);
30
- export const REGEX_START_NUM = new RegExp(/^(\d+)/);
31
-
32
18
  export const COMMIT_FOOTER_OPTIONS = [
33
19
  {
34
20
  value: "closes",
@@ -49,7 +35,7 @@ export const COMMIT_FOOTER_OPTIONS = [
49
35
  { value: "custom", label: "custom", hint: "Add a custom footer" },
50
36
  ];
51
37
  export const BRANCH_ACTION_OPTIONS: {
52
- value: Output<typeof V_BRANCH_ACTIONS>;
38
+ value: InferOutput<typeof V_BRANCH_ACTIONS>;
53
39
  label: string;
54
40
  hint?: string;
55
41
  }[] = [
@@ -63,47 +49,64 @@ export const NOOP_PROMPT_CACHE = {
63
49
  clear: () => {},
64
50
  } as unknown as Configstore;
65
51
 
52
+ export type ConfigSource = "repository" | "global" | "none";
53
+
54
+ export type LoadedSetup = {
55
+ config: InferOutput<typeof Config>;
56
+ config_source: ConfigSource;
57
+ };
58
+
66
59
  /* LOAD */
67
60
  export function load_setup(
68
61
  cli_name = " better-commits ",
69
- ): Output<typeof Config> {
62
+ git_args = flags.git_args,
63
+ ): LoadedSetup {
70
64
  console.clear();
71
65
  p.intro(`${color.bgCyan(color.black(cli_name))}`);
72
66
 
73
- set_non_configuration_arguments();
74
-
75
67
  let global_config = null;
76
68
  const home_path = get_default_config_path();
77
69
  if (fs.existsSync(home_path)) {
78
- p.log.step("Found global config");
79
70
  global_config = read_config_from_path(home_path);
80
71
  }
81
72
 
82
- const root = get_git_root();
73
+ const root = get_git_root(git_args);
83
74
  const root_path = `${root}/${CONFIG_FILE_NAME}`;
84
75
  if (fs.existsSync(root_path)) {
85
- p.log.step("Found repository config");
76
+ p.log.step("Reading from Repository Config");
86
77
  const repo_config = read_config_from_path(root_path);
87
- return global_config
88
- ? {
89
- ...repo_config,
90
- overrides: global_config.overrides.shell
91
- ? global_config.overrides
92
- : repo_config.overrides,
93
- confirm_with_editor: global_config.confirm_with_editor,
94
- cache_last_value: global_config.cache_last_value,
95
- }
96
- : repo_config;
78
+ return {
79
+ config: global_config
80
+ ? {
81
+ ...repo_config,
82
+ overrides: global_config.overrides.shell
83
+ ? global_config.overrides
84
+ : repo_config.overrides,
85
+ confirm_with_editor: global_config.confirm_with_editor,
86
+ cache_last_value: global_config.cache_last_value,
87
+ }
88
+ : repo_config,
89
+ config_source: "repository",
90
+ };
97
91
  }
98
92
 
99
- if (global_config) return global_config;
93
+ if (global_config) {
94
+ p.log.step("Reading from Global Config");
95
+ return {
96
+ config: global_config,
97
+ config_source: "global",
98
+ };
99
+ }
100
100
 
101
101
  const default_config = parse(Config, {});
102
102
  p.log.step(
103
103
  "Config not found. Generating default .better-commit.json at $HOME",
104
104
  );
105
105
  fs.writeFileSync(home_path, JSON.stringify(default_config, null, 4));
106
- return default_config;
106
+ return {
107
+ config: default_config,
108
+ config_source: "none",
109
+ };
107
110
  }
108
111
 
109
112
  function read_config_from_path(config_path: string) {
@@ -118,13 +121,19 @@ function read_config_from_path(config_path: string) {
118
121
  return validate_config(res);
119
122
  }
120
123
 
121
- function validate_config(config: Output<typeof Config>): Output<typeof Config> {
124
+ function validate_config(config: unknown): InferOutput<typeof Config> {
122
125
  try {
123
126
  return parse(Config, config);
124
127
  } catch (err: any) {
125
128
  if (err instanceof ValiError) {
126
129
  const first_issue_path = err.issues[0].path ?? [];
127
- const dot_path = first_issue_path.map((item) => item.key).join(".");
130
+ const dot_path = first_issue_path
131
+ .map((item: { key?: unknown }) => item.key)
132
+ .filter(
133
+ (key: unknown): key is string | number =>
134
+ typeof key === "string" || typeof key === "number",
135
+ )
136
+ .join(".");
128
137
  p.log.error(
129
138
  `Invalid Configuration: ${color.red(dot_path)}\n` + err.message,
130
139
  );
@@ -134,37 +143,13 @@ function validate_config(config: Output<typeof Config>): Output<typeof Config> {
134
143
  }
135
144
  /* END LOAD */
136
145
 
137
- export function infer_type_from_branch(types: string[]): string {
138
- let branch = "";
139
- try {
140
- branch = execSync(`git ${flags.git_args} branch --show-current`, {
141
- stdio: "pipe",
142
- }).toString();
143
- } catch (err) {
144
- return "";
145
- }
146
- const found = types.find((t) => {
147
- const start_dash = new RegExp(`^${t}-`);
148
- const between_dash = new RegExp(`-${t}-`);
149
- const before_slash = new RegExp(`${t}\/`);
150
- const re = [
151
- branch.match(start_dash),
152
- branch.match(between_dash),
153
- branch.match(before_slash),
154
- ].filter((v) => v != null);
155
- return re?.length;
156
- });
157
-
158
- return found ?? "";
159
- }
160
-
161
146
  /*
162
147
  rev-parse will fail in a --bare repository root
163
148
  */
164
- export function get_git_root(): string {
149
+ export function get_git_root(git_args = flags.git_args): string {
165
150
  let path = ".";
166
151
  try {
167
- path = execSync(`git ${flags.git_args} rev-parse --show-toplevel`)
152
+ path = execSync(`git ${git_args} rev-parse --show-toplevel`)
168
153
  .toString()
169
154
  .trim();
170
155
  } catch (err) {
@@ -179,6 +164,18 @@ export function get_default_config_path(): string {
179
164
  return homedir() + "/" + CONFIG_FILE_NAME;
180
165
  }
181
166
 
167
+ export function get_package_version(): string {
168
+ try {
169
+ const package_json = JSON.parse(
170
+ fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"),
171
+ ) as { version?: string };
172
+
173
+ return package_json.version ?? "unknown";
174
+ } catch {
175
+ return "unknown";
176
+ }
177
+ }
178
+
182
179
  export function addNewLine(arr: string[], i: number) {
183
180
  return i === arr.length - 1 ? "" : "\n";
184
181
  }
@@ -192,10 +189,6 @@ export function clean_commit_title(title: string): string {
192
189
  return title.trim();
193
190
  }
194
191
 
195
- function set_non_configuration_arguments() {
196
- flags.git_args = `${argv[2] ?? ""} ${argv[3] ?? ""}`.trim();
197
- }
198
-
199
192
  export function get_value_from_cache(
200
193
  config_store: Configstore,
201
194
  key: string,
@@ -1,7 +1,7 @@
1
1
  import * as v from "valibot";
2
2
 
3
3
  export const CUSTOM_SCOPE_KEY: "custom" = "custom";
4
- export const FOOTER_OPTION_VALUES: v.Output<typeof V_FOOTER_OPTIONS>[] = [
4
+ export const FOOTER_OPTION_VALUES: v.InferOutput<typeof V_FOOTER_OPTIONS>[] = [
5
5
  "closes",
6
6
  "trailer",
7
7
  "breaking-change",
@@ -30,7 +30,7 @@ export const V_BRANCH_CONFIG_FIELDS = v.picklist([
30
30
  "branch_ticket",
31
31
  "branch_description",
32
32
  ]);
33
- export const BRANCH_ORDER_DEFAULTS: v.Output<typeof V_BRANCH_FIELDS>[] = [
33
+ export const BRANCH_ORDER_DEFAULTS: v.InferOutput<typeof V_BRANCH_FIELDS>[] = [
34
34
  "user",
35
35
  "version",
36
36
  "type",
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ValiError, parse } from "valibot";
3
+ import { Config } from "./valibot-state";
4
+
5
+ describe("Config", () => {
6
+ it("parses a defaulted config from empty input", () => {
7
+ const config = parse(Config, {});
8
+
9
+ expect(config.commit_type.initial_value).toBe("feat");
10
+ expect(config.commit_scope.initial_value).toBe("app");
11
+ expect(config.commit_type.options.length).toBeGreaterThan(0);
12
+ expect(config.commit_scope.options.length).toBeGreaterThan(0);
13
+ });
14
+
15
+ it("adds the custom scope option when custom_scope is enabled", () => {
16
+ const config = parse(Config, {
17
+ commit_scope: {
18
+ custom_scope: true,
19
+ initial_value: "custom",
20
+ options: [{ value: "app", label: "app" }],
21
+ },
22
+ });
23
+
24
+ expect(config.commit_scope.options.map((option) => option.value)).toContain(
25
+ "custom",
26
+ );
27
+ });
28
+
29
+ it("rejects commit_type.initial_value values outside options", () => {
30
+ expect(() =>
31
+ parse(Config, {
32
+ commit_type: {
33
+ initial_value: "unknown",
34
+ options: [{ value: "feat", label: "feat" }],
35
+ },
36
+ }),
37
+ ).toThrowError(ValiError);
38
+
39
+ expect(() =>
40
+ parse(Config, {
41
+ commit_type: {
42
+ initial_value: "unknown",
43
+ options: [{ value: "feat", label: "feat" }],
44
+ },
45
+ }),
46
+ ).toThrow(/must exist in options/);
47
+ });
48
+ });
@@ -10,113 +10,114 @@ import {
10
10
  V_FOOTER_OPTIONS,
11
11
  } from "./valibot-consts";
12
12
 
13
- export const Config = v.object({
14
- check_status: v.optional(v.boolean(), true),
15
- commit_type: v.transform(
16
- v.optional(
17
- v.object(
18
- {
19
- enable: v.optional(v.boolean(), true),
20
- initial_value: v.optional(v.string(), "feat"),
21
- max_items: v.optional(v.number([v.minValue(1)]), 20),
22
- infer_type_from_branch: v.optional(v.boolean(), true),
23
- append_emoji_to_label: v.optional(v.boolean(), false),
24
- append_emoji_to_commit: v.optional(v.boolean(), false),
25
- emoji_commit_position: v.optional(
26
- v.picklist(["Start", "After-Colon"]),
27
- "Start",
28
- ),
29
- options: v.optional(
30
- v.array(
31
- v.object({
32
- value: v.string(),
33
- label: v.optional(v.string()),
34
- hint: v.optional(v.string()),
35
- emoji: v.optional(v.string([v.emoji()]), undefined),
36
- trailer: v.optional(v.string()),
37
- }),
38
- ),
39
- DEFAULT_TYPE_OPTIONS,
40
- ),
41
- },
42
- [
43
- v.custom(
44
- (val) =>
45
- val.options.map((v) => v.value).includes(val.initial_value),
46
- (val) => {
47
- const input = val.input as { initial_value: string };
48
- return `Type: initial_value "${input.initial_value}" must exist in options`;
49
- },
50
- ),
51
- ],
13
+ const CommitTypeConfig = v.pipe(
14
+ v.optional(
15
+ v.object({
16
+ enable: v.optional(v.boolean(), true),
17
+ initial_value: v.optional(v.string(), "feat"),
18
+ max_items: v.optional(v.pipe(v.number(), v.minValue(1)), 20),
19
+ infer_type_from_branch: v.optional(v.boolean(), true),
20
+ append_emoji_to_label: v.optional(v.boolean(), false),
21
+ append_emoji_to_commit: v.optional(v.boolean(), false),
22
+ emoji_commit_position: v.optional(
23
+ v.picklist(["Start", "After-Colon"]),
24
+ "Start",
25
+ ),
26
+ options: v.optional(
27
+ v.array(
28
+ v.object({
29
+ value: v.string(),
30
+ label: v.optional(v.string()),
31
+ hint: v.optional(v.string()),
32
+ emoji: v.optional(v.pipe(v.string(), v.emoji())),
33
+ trailer: v.optional(v.string()),
34
+ }),
35
+ ),
36
+ DEFAULT_TYPE_OPTIONS,
52
37
  ),
53
- {},
54
- ),
55
- (val) => {
56
- const options =
57
- val.options.map((v) => ({
58
- ...v,
59
- label:
60
- v.emoji && val.append_emoji_to_label
61
- ? `${v.emoji} ${v.label}`
62
- : v.label,
63
- })) ?? [];
64
- return { ...val, options };
65
- },
38
+ }),
39
+ {},
66
40
  ),
67
- commit_scope: v.transform(
68
- v.optional(
69
- v.object(
70
- {
71
- enable: v.optional(v.boolean(), true),
72
- custom_scope: v.optional(v.boolean(), false),
73
- max_items: v.optional(v.number([v.minValue(1)]), 20),
74
- initial_value: v.optional(v.string(), "app"),
75
- options: v.optional(
76
- v.array(
77
- v.object({
78
- value: v.string(),
79
- label: v.optional(v.string()),
80
- hint: v.optional(v.string()),
81
- }),
82
- ),
83
- DEFAULT_SCOPE_OPTIONS,
84
- ),
85
- },
86
- [
87
- v.custom(
88
- (val) => {
89
- const options = val.options.map((v) => v.value);
90
- if (val.custom_scope) options.push(CUSTOM_SCOPE_KEY);
91
- return options.includes(val.initial_value);
92
- },
93
- (val) => {
94
- const input = val.input as { initial_value: string };
95
- return `Scope: initial_value "${input.initial_value}" must exist in options`;
96
- },
97
- ),
98
- ],
41
+ v.rawCheck(({ dataset, addIssue }) => {
42
+ if (
43
+ dataset.typed &&
44
+ !dataset.value.options.some(
45
+ (option) => option.value === dataset.value.initial_value,
46
+ )
47
+ ) {
48
+ addIssue({
49
+ message: `Type: initial_value "${dataset.value.initial_value}" must exist in options`,
50
+ });
51
+ }
52
+ }),
53
+ v.transform((value) => ({
54
+ ...value,
55
+ options: value.options.map((option) => ({
56
+ ...option,
57
+ label:
58
+ option.emoji && value.append_emoji_to_label
59
+ ? `${option.emoji} ${option.label}`
60
+ : option.label,
61
+ })),
62
+ })),
63
+ );
64
+
65
+ const CommitScopeConfig = v.pipe(
66
+ v.optional(
67
+ v.object({
68
+ enable: v.optional(v.boolean(), true),
69
+ custom_scope: v.optional(v.boolean(), false),
70
+ max_items: v.optional(v.pipe(v.number(), v.minValue(1)), 20),
71
+ initial_value: v.optional(v.string(), "app"),
72
+ options: v.optional(
73
+ v.array(
74
+ v.object({
75
+ value: v.string(),
76
+ label: v.optional(v.string()),
77
+ hint: v.optional(v.string()),
78
+ }),
79
+ ),
80
+ DEFAULT_SCOPE_OPTIONS,
99
81
  ),
100
- {},
101
- ),
102
- (val) => {
103
- const options = val.options.map((v) => v.value);
104
- if (val.custom_scope && !options.includes(CUSTOM_SCOPE_KEY)) {
105
- return {
106
- ...val,
107
- options: [
108
- ...val.options,
109
- {
110
- label: CUSTOM_SCOPE_KEY,
111
- value: CUSTOM_SCOPE_KEY,
112
- hint: "Write a custom scope",
113
- },
114
- ],
115
- };
116
- }
117
- return val;
118
- },
82
+ }),
83
+ {},
119
84
  ),
85
+ v.rawCheck(({ dataset, addIssue }) => {
86
+ if (!dataset.typed) return;
87
+
88
+ const option_values = dataset.value.options.map((option) => option.value);
89
+ if (dataset.value.custom_scope) option_values.push(CUSTOM_SCOPE_KEY);
90
+
91
+ if (!option_values.includes(dataset.value.initial_value)) {
92
+ addIssue({
93
+ message: `Scope: initial_value "${dataset.value.initial_value}" must exist in options`,
94
+ });
95
+ }
96
+ }),
97
+ v.transform((value) => {
98
+ const option_values = value.options.map((option) => option.value);
99
+ if (value.custom_scope && !option_values.includes(CUSTOM_SCOPE_KEY)) {
100
+ return {
101
+ ...value,
102
+ options: [
103
+ ...value.options,
104
+ {
105
+ label: CUSTOM_SCOPE_KEY,
106
+ value: CUSTOM_SCOPE_KEY,
107
+ hint: "Write a custom scope",
108
+ },
109
+ ],
110
+ };
111
+ }
112
+
113
+ return value;
114
+ }),
115
+ );
116
+
117
+ export const Config = v.object({
118
+ check_status: v.optional(v.boolean(), true),
119
+ commit_type: CommitTypeConfig,
120
+ commit_scope: CommitScopeConfig,
120
121
  check_ticket: v.optional(
121
122
  v.object({
122
123
  infer_ticket: v.optional(v.boolean(), true),
@@ -137,7 +138,7 @@ export const Config = v.object({
137
138
  ),
138
139
  commit_title: v.optional(
139
140
  v.object({
140
- max_size: v.optional(v.number([v.minValue(1)]), 70),
141
+ max_size: v.optional(v.pipe(v.number(), v.minValue(1)), 70),
141
142
  }),
142
143
  {},
143
144
  ),
@@ -204,7 +205,7 @@ export const Config = v.object({
204
205
  ),
205
206
  branch_description: v.optional(
206
207
  v.object({
207
- max_length: v.optional(v.number([v.minValue(1)]), 70),
208
+ max_length: v.optional(v.pipe(v.number(), v.minValue(1)), 70),
208
209
  separator: v.optional(v.picklist(["", "/", "-", "_"]), ""),
209
210
  }),
210
211
  {},
@@ -231,32 +232,34 @@ export const Config = v.object({
231
232
  ),
232
233
  });
233
234
 
234
- export const CommitState = v.optional(
235
- v.object({
236
- type: v.optional(v.string(), ""),
237
- scope: v.optional(v.string(), ""),
238
- title: v.optional(v.string(), ""),
239
- body: v.optional(v.string(), ""),
240
- closes: v.optional(v.string(), ""),
241
- ticket: v.optional(v.string(), ""),
242
- breaking_title: v.optional(v.string(), ""),
243
- breaking_body: v.optional(v.string(), ""),
244
- deprecates: v.optional(v.string(), ""),
245
- deprecates_title: v.optional(v.string(), ""),
246
- deprecates_body: v.optional(v.string(), ""),
247
- custom_footer: v.optional(v.string(), ""),
248
- trailer: v.optional(v.string(), ""),
249
- }),
250
- {},
251
- );
235
+ export const COMMIT_STATE_ENTRIES = {
236
+ type: v.optional(v.string(), ""),
237
+ scope: v.optional(v.string(), ""),
238
+ title: v.optional(v.string(), ""),
239
+ body: v.optional(v.string(), ""),
240
+ closes: v.optional(v.string(), ""),
241
+ ticket: v.optional(v.string(), ""),
242
+ breaking_title: v.optional(v.string(), ""),
243
+ breaking_body: v.optional(v.string(), ""),
244
+ deprecates: v.optional(v.string(), ""),
245
+ deprecates_title: v.optional(v.string(), ""),
246
+ deprecates_body: v.optional(v.string(), ""),
247
+ custom_footer: v.optional(v.string(), ""),
248
+ trailer: v.optional(v.string(), ""),
249
+ };
250
+
251
+ export const CommitState = v.optional(v.object(COMMIT_STATE_ENTRIES), {});
252
+
253
+ export const BRANCH_STATE_ENTRIES = {
254
+ user: v.optional(v.string(), ""),
255
+ type: v.optional(v.string(), ""),
256
+ ticket: v.optional(v.string(), ""),
257
+ description: v.optional(v.string(), ""),
258
+ version: v.optional(v.string(), ""),
259
+ checkout: v.optional(V_BRANCH_ACTIONS, "branch"),
260
+ };
252
261
 
253
262
  export const BranchState = v.optional(
254
- v.object({
255
- user: v.optional(v.string(), ""),
256
- type: v.optional(v.string(), ""),
257
- ticket: v.optional(v.string(), ""),
258
- description: v.optional(v.string(), ""),
259
- version: v.optional(v.string(), ""),
260
- }),
263
+ v.object(BRANCH_STATE_ENTRIES),
261
264
  {},
262
265
  );
package/tsconfig.json CHANGED
@@ -3,12 +3,13 @@
3
3
  "module": "ESNext",
4
4
  "target": "ESNext",
5
5
  "declaration": true,
6
- "moduleResolution": "node",
6
+ "moduleResolution": "bundler",
7
7
  "emitDeclarationOnly": true,
8
8
  "strict": true,
9
9
  "esModuleInterop": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
11
  "allowJs": true,
12
12
  "types": ["node"]
13
- }
13
+ },
14
+ "exclude": ["dist", "node_modules", ".svelte-kit", ".opencode"]
14
15
  }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: "node",
6
+ include: ["src/**/*.test.ts"],
7
+ },
8
+ });
@@ -1,4 +0,0 @@
1
- import*as p from"valibot";var c="custom",f=["closes","trailer","breaking-change","deprecated","custom"],h=p.picklist(["branch","worktree"]),g=p.picklist(["closes","trailer","breaking-change","deprecated","custom"]),d=p.picklist(["user","version","type","ticket","description"]),$=p.picklist(["branch_user","branch_version","branch_type","branch_ticket","branch_description"]),O=["user","version","type","ticket","description"],x=[{value:"app",label:"app"},{value:"shared",label:"shared"},{value:"server",label:"server"},{value:"tools",label:"tools"},{value:"",label:"none"}],C=[{value:"feat",label:"feat",hint:"A new feature",emoji:"\u{1F31F}",trailer:"Changelog: feature"},{value:"fix",label:"fix",hint:"A bug fix",emoji:"\u{1F41B}",trailer:"Changelog: fix"},{value:"docs",label:"docs",hint:"Documentation only changes",emoji:"\u{1F4DA}",trailer:"Changelog: documentation"},{value:"refactor",label:"refactor",hint:"A code change that neither fixes a bug nor adds a feature",emoji:"\u{1F528}",trailer:"Changelog: refactor"},{value:"perf",label:"perf",hint:"A code change that improves performance",emoji:"\u{1F680}",trailer:"Changelog: performance"},{value:"test",label:"test",hint:"Adding missing tests or correcting existing tests",emoji:"\u{1F6A8}",trailer:"Changelog: test"},{value:"build",label:"build",hint:"Changes that affect the build system or external dependencies",emoji:"\u{1F6A7}",trailer:"Changelog: build"},{value:"ci",label:"ci",hint:"Changes to our CI configuration files and scripts",emoji:"\u{1F916}",trailer:"Changelog: ci"},{value:"chore",label:"chore",hint:"Other changes that do not modify src or test files",emoji:"\u{1F9F9}",trailer:"Changelog: chore"},{value:"",label:"none"}];import*as t from"valibot";var m=t.object({check_status:t.optional(t.boolean(),!0),commit_type:t.transform(t.optional(t.object({enable:t.optional(t.boolean(),!0),initial_value:t.optional(t.string(),"feat"),max_items:t.optional(t.number([t.minValue(1)]),20),infer_type_from_branch:t.optional(t.boolean(),!0),append_emoji_to_label:t.optional(t.boolean(),!1),append_emoji_to_commit:t.optional(t.boolean(),!1),emoji_commit_position:t.optional(t.picklist(["Start","After-Colon"]),"Start"),options:t.optional(t.array(t.object({value:t.string(),label:t.optional(t.string()),hint:t.optional(t.string()),emoji:t.optional(t.string([t.emoji()]),void 0),trailer:t.optional(t.string())})),C)},[t.custom(e=>e.options.map(o=>o.value).includes(e.initial_value),e=>`Type: initial_value "${e.input.initial_value}" must exist in options`)]),{}),e=>{let o=e.options.map(n=>({...n,label:n.emoji&&e.append_emoji_to_label?`${n.emoji} ${n.label}`:n.label}))??[];return{...e,options:o}}),commit_scope:t.transform(t.optional(t.object({enable:t.optional(t.boolean(),!0),custom_scope:t.optional(t.boolean(),!1),max_items:t.optional(t.number([t.minValue(1)]),20),initial_value:t.optional(t.string(),"app"),options:t.optional(t.array(t.object({value:t.string(),label:t.optional(t.string()),hint:t.optional(t.string())})),x)},[t.custom(e=>{let o=e.options.map(n=>n.value);return e.custom_scope&&o.push(c),o.includes(e.initial_value)},e=>`Scope: initial_value "${e.input.initial_value}" must exist in options`)]),{}),e=>{let o=e.options.map(n=>n.value);return e.custom_scope&&!o.includes(c)?{...e,options:[...e.options,{label:c,value:c,hint:"Write a custom scope"}]}:e}),check_ticket:t.optional(t.object({infer_ticket:t.optional(t.boolean(),!0),confirm_ticket:t.optional(t.boolean(),!0),add_to_title:t.optional(t.boolean(),!0),append_hashtag:t.optional(t.boolean(),!1),prepend_hashtag:t.optional(t.picklist(["Never","Always","Prompt"]),"Never"),surround:t.optional(t.picklist(["","()","[]","{}"]),""),title_position:t.optional(t.picklist(["start","end","before-colon","beginning"]),"start")}),{}),commit_title:t.optional(t.object({max_size:t.optional(t.number([t.minValue(1)]),70)}),{}),commit_body:t.optional(t.object({enable:t.optional(t.boolean(),!0),required:t.optional(t.boolean(),!1),split_by_period:t.optional(t.boolean(),!1)}),{}),commit_footer:t.optional(t.object({enable:t.optional(t.boolean(),!0),initial_value:t.optional(t.array(g),[]),options:t.optional(t.array(g),f)}),{}),breaking_change:t.optional(t.object({add_exclamation_to_title:t.optional(t.boolean(),!0)}),{}),cache_last_value:t.optional(t.boolean(),!0),confirm_with_editor:t.optional(t.boolean(),!1),confirm_commit:t.optional(t.boolean(),!0),print_commit_output:t.optional(t.boolean(),!0),branch_pre_commands:t.optional(t.array(t.string()),[]),branch_post_commands:t.optional(t.array(t.string()),[]),worktree_pre_commands:t.optional(t.array(t.string()),[]),worktree_post_commands:t.optional(t.array(t.string()),[]),branch_user:t.optional(t.object({enable:t.optional(t.boolean(),!0),required:t.optional(t.boolean(),!1),separator:t.optional(t.picklist(["/","-","_"]),"/")}),{}),branch_type:t.optional(t.object({enable:t.optional(t.boolean(),!0),separator:t.optional(t.picklist(["/","-","_"]),"/")}),{}),branch_version:t.optional(t.object({enable:t.optional(t.boolean(),!1),required:t.optional(t.boolean(),!1),separator:t.optional(t.picklist(["/","-","_"]),"/")}),{}),branch_ticket:t.optional(t.object({enable:t.optional(t.boolean(),!0),required:t.optional(t.boolean(),!1),separator:t.optional(t.picklist(["/","-","_"]),"-")}),{}),branch_description:t.optional(t.object({max_length:t.optional(t.number([t.minValue(1)]),70),separator:t.optional(t.picklist(["","/","-","_"]),"")}),{}),branch_action_default:t.optional(h,"branch"),branch_order:t.optional(t.array(d),O),enable_worktrees:t.optional(t.boolean(),!0),worktrees:t.optional(t.object({enable:t.optional(t.boolean(),!0),base_path:t.optional(t.string(),".."),folder_template:t.optional(t.string(),"{{repo_name}}-{{ticket}}-{{branch_description}}")}),{}),overrides:t.optional(t.object({shell:t.optional(t.string())}),{})}),H=t.optional(t.object({type:t.optional(t.string(),""),scope:t.optional(t.string(),""),title:t.optional(t.string(),""),body:t.optional(t.string(),""),closes:t.optional(t.string(),""),ticket:t.optional(t.string(),""),breaking_title:t.optional(t.string(),""),breaking_body:t.optional(t.string(),""),deprecates:t.optional(t.string(),""),deprecates_title:t.optional(t.string(),""),deprecates_body:t.optional(t.string(),""),custom_footer:t.optional(t.string(),""),trailer:t.optional(t.string(),"")}),{}),D=t.optional(t.object({user:t.optional(t.string(),""),type:t.optional(t.string(),""),ticket:t.optional(t.string(),""),description:t.optional(t.string(),""),version:t.optional(t.string(),"")}),{});var b=class{#t="";constructor(){}get git_args(){return this.#t}set git_args(o){this.#t=o}},_=new b;import*as i from"@clack/prompts";import{execSync as S}from"child_process";import u from"fs";import{homedir as R}from"os";import a from"picocolors";import{ValiError as N,parse as y}from"valibot";import{argv as E}from"process";var T=".better-commits.json",K=`${a.dim("(<space> to select)")}`,Q=`${a.dim("(<space> to select, <a> to select all)")}`,tt=`${a.dim("(optional)")}`,ot=`${a.dim("(value will be saved)")}`,et=new RegExp(/\/(\w+-\d+)/),nt=new RegExp(/^(\w+-\d+)/),it=new RegExp(/^([A-Z]+-[\[a-zA-Z\]\d]+)_/),rt=new RegExp(/\/([A-Z]+-[\[a-zA-Z\]\d]+)_/),at=new RegExp(/\/(\d+)/),lt=new RegExp(/^(\d+)/),st=[{value:"closes",label:"closes <issue/ticket>",hint:"Attempts to infer ticket from branch"},{value:"trailer",label:"trailer",hint:"Appends trailer based on commit type"},{value:"breaking-change",label:"breaking change",hint:"Add breaking change"},{value:"deprecated",label:"deprecated",hint:"Add deprecated change"},{value:"custom",label:"custom",hint:"Add a custom footer"}],pt=[{value:"branch",label:"Branch"},{value:"worktree",label:"Worktree"}],ct={get:()=>"",set:(e,o)=>{},clear:()=>{}};function vt(e=" better-commits "){console.clear(),i.intro(`${a.bgCyan(a.black(e))}`),P();let o=null,n=I();u.existsSync(n)&&(i.log.step("Found global config"),o=A(n));let l=`${w()}/${T}`;if(u.existsSync(l)){i.log.step("Found repository config");let s=A(l);return o?{...s,overrides:o.overrides.shell?o.overrides:s.overrides,confirm_with_editor:o.confirm_with_editor,cache_last_value:o.cache_last_value}:s}if(o)return o;let v=y(m,{});return i.log.step("Config not found. Generating default .better-commit.json at $HOME"),u.writeFileSync(n,JSON.stringify(v,null,4)),v}function A(e){let o=null;try{o=JSON.parse(u.readFileSync(e,"utf8"))}catch(n){i.log.error(`Invalid JSON file. Exiting.
2
- `+n),process.exit(0)}return j(o)}function j(e){try{return y(m,e)}catch(o){if(o instanceof N){let r=(o.issues[0].path??[]).map(l=>l.key).join(".");i.log.error(`Invalid Configuration: ${a.red(r)}
3
- `+o.message)}process.exit(0)}}function _t(e){let o="";try{o=S(`git ${_.git_args} branch --show-current`,{stdio:"pipe"}).toString()}catch{return""}return e.find(r=>{let l=new RegExp(`^${r}-`),v=new RegExp(`-${r}-`),s=new RegExp(`${r}/`);return[o.match(l),o.match(v),o.match(s)].filter(k=>k!=null)?.length})??""}function w(){let e=".";try{e=S(`git ${_.git_args} rev-parse --show-toplevel`).toString().trim()}catch{i.log.warn("Could not find git root. If in a --bare repository, ignore this warning.")}return e}function I(){return R()+"/"+T}function ut(e,o){return o===e.length-1?"":`
4
- `}function gt(e){let o=e.trim();return o.endsWith(".")?o.substring(0,o.length-1).trim():e.trim()}function P(){_.git_args=`${E[2]??""} ${E[3]??""}`.trim()}function mt(e,o){try{return e.get(o)??""}catch{i.log.warn(`Could not access ${o} from cache. Check that "~/.config" exists. Set "cache_last_value" to false to disable.`)}return""}function bt(e,o,n){try{e.set(o,n)}catch{i.log.warn(`Could not access ${o} from cache. Check that "~/.config" exists. Set "cache_last_value" to false to disable.`)}}export{c as a,m as b,H as c,D as d,_ as e,T as f,K as g,tt as h,ot as i,et as j,nt as k,it as l,rt as m,at as n,lt as o,st as p,pt as q,ct as r,vt as s,_t as t,w as u,ut as v,gt as w,mt as x,bt as y};