gflows 0.1.1
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/LICENSE +21 -0
- package/README.md +549 -0
- package/package.json +38 -0
- package/src/cli.ts +358 -0
- package/src/commands/bump.ts +213 -0
- package/src/commands/completion.ts +353 -0
- package/src/commands/delete.ts +133 -0
- package/src/commands/finish.ts +275 -0
- package/src/commands/help.ts +53 -0
- package/src/commands/init.ts +70 -0
- package/src/commands/list.ts +89 -0
- package/src/commands/start.ts +141 -0
- package/src/commands/status.ts +137 -0
- package/src/commands/switch.ts +102 -0
- package/src/commands/version.ts +27 -0
- package/src/config.ts +229 -0
- package/src/constants.ts +38 -0
- package/src/errors.ts +96 -0
- package/src/git.ts +481 -0
- package/src/index.ts +84 -0
- package/src/types.ts +124 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completion command: print shell completion script for bash, zsh, or fish.
|
|
3
|
+
* Supports completion for commands, types (feature, bugfix, etc.), and when
|
|
4
|
+
* applicable branch names from local workflow branches (switch, delete, finish -B).
|
|
5
|
+
* @module commands/completion
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ParsedArgs } from "../types.js";
|
|
9
|
+
import { EXIT_USER } from "../constants.js";
|
|
10
|
+
|
|
11
|
+
const COMMANDS = [
|
|
12
|
+
"init",
|
|
13
|
+
"start",
|
|
14
|
+
"finish",
|
|
15
|
+
"switch",
|
|
16
|
+
"delete",
|
|
17
|
+
"list",
|
|
18
|
+
"bump",
|
|
19
|
+
"completion",
|
|
20
|
+
"status",
|
|
21
|
+
"help",
|
|
22
|
+
"version",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const BRANCH_TYPES = [
|
|
26
|
+
"feature",
|
|
27
|
+
"bugfix",
|
|
28
|
+
"chore",
|
|
29
|
+
"release",
|
|
30
|
+
"hotfix",
|
|
31
|
+
"spike",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const COMPLETION_SHELLS = ["bash", "zsh", "fish"] as const;
|
|
35
|
+
|
|
36
|
+
const BUMP_DIRECTIONS = ["up", "down"];
|
|
37
|
+
const BUMP_TYPES = ["patch", "minor", "major"];
|
|
38
|
+
|
|
39
|
+
/** Literal "${" for embedding in shell scripts (avoids template interpolation). */
|
|
40
|
+
const D = "${";
|
|
41
|
+
|
|
42
|
+
function bashScript(): string {
|
|
43
|
+
return `# Bash completion for gflows
|
|
44
|
+
# Install: source <(gflows completion bash) (add to .bashrc) or copy to /etc/bash_completion.d/
|
|
45
|
+
|
|
46
|
+
_gflows() {
|
|
47
|
+
local cur prev words cword cmd_idx cmd
|
|
48
|
+
words=(${D}COMP_WORDS[@]})
|
|
49
|
+
cword=\$COMP_CWORD
|
|
50
|
+
cur="${D}words[cword]:-}"
|
|
51
|
+
prev="${D}words[cword-1]:-}"
|
|
52
|
+
|
|
53
|
+
# Find command index (first positional; skip -C/--path and its value)
|
|
54
|
+
cmd_idx=1
|
|
55
|
+
while (( cmd_idx < cword )); do
|
|
56
|
+
if [[ "${D}words[cmd_idx]}" == "-C" ]] || [[ "${D}words[cmd_idx]}" == "--path" ]]; then
|
|
57
|
+
(( cmd_idx += 2 ))
|
|
58
|
+
elif [[ "${D}words[cmd_idx]}" == -* ]]; then
|
|
59
|
+
(( cmd_idx++ ))
|
|
60
|
+
else
|
|
61
|
+
break
|
|
62
|
+
fi
|
|
63
|
+
done
|
|
64
|
+
cmd="${D}words[cmd_idx]:-}"
|
|
65
|
+
|
|
66
|
+
# Resolve -C/--path for gflows list (branch names)
|
|
67
|
+
_gflows_path() {
|
|
68
|
+
local i path=""
|
|
69
|
+
for ((i=1; i<cword; i++)); do
|
|
70
|
+
if [[ "${D}words[i]}" == "-C" ]] || [[ "${D}words[i]}" == "--path" ]]; then
|
|
71
|
+
if ((i+1 < cword)); then path="${D}words[i+1]}"; fi
|
|
72
|
+
break
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
if [[ -n "\$path" ]]; then
|
|
76
|
+
gflows -C "\$path" list 2>/dev/null
|
|
77
|
+
else
|
|
78
|
+
gflows list 2>/dev/null
|
|
79
|
+
fi
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Completing -C/--path value: suggest directories
|
|
83
|
+
if [[ "\$prev" == "-C" ]] || [[ "\$prev" == "--path" ]]; then
|
|
84
|
+
compopt -o dirnames 2>/dev/null
|
|
85
|
+
COMPREPLY=($(compgen -d -S / -- "\$cur"))
|
|
86
|
+
return
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# First positional: command
|
|
90
|
+
if (( cword == cmd_idx )); then
|
|
91
|
+
COMPREPLY=($(compgen -W "${COMMANDS.join(" ")}" -- "\$cur"))
|
|
92
|
+
return
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# After command: type/name/branches/shell/bump by command
|
|
96
|
+
case "$cmd" in
|
|
97
|
+
completion)
|
|
98
|
+
COMPREPLY=($(compgen -W "${COMPLETION_SHELLS.join(" ")}" -- "\$cur"))
|
|
99
|
+
;;
|
|
100
|
+
start)
|
|
101
|
+
if (( cword == cmd_idx + 1 )); then
|
|
102
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "\$cur"))
|
|
103
|
+
else
|
|
104
|
+
COMPREPLY=()
|
|
105
|
+
fi
|
|
106
|
+
;;
|
|
107
|
+
finish)
|
|
108
|
+
if [[ "\$prev" == "-B" ]] || [[ "\$prev" == "--branch" ]]; then
|
|
109
|
+
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "\$cur"))
|
|
110
|
+
elif (( cword == cmd_idx + 1 )); then
|
|
111
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "\$cur"))
|
|
112
|
+
else
|
|
113
|
+
COMPREPLY=()
|
|
114
|
+
fi
|
|
115
|
+
;;
|
|
116
|
+
list)
|
|
117
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "\$cur"))
|
|
118
|
+
;;
|
|
119
|
+
switch|delete)
|
|
120
|
+
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "\$cur"))
|
|
121
|
+
;;
|
|
122
|
+
bump)
|
|
123
|
+
if (( cword == cmd_idx + 1 )); then
|
|
124
|
+
COMPREPLY=($(compgen -W "${BUMP_DIRECTIONS.join(" ")}" -- "\$cur"))
|
|
125
|
+
elif (( cword == cmd_idx + 2 )); then
|
|
126
|
+
COMPREPLY=($(compgen -W "${BUMP_TYPES.join(" ")}" -- "\$cur"))
|
|
127
|
+
else
|
|
128
|
+
COMPREPLY=()
|
|
129
|
+
fi
|
|
130
|
+
;;
|
|
131
|
+
*)
|
|
132
|
+
COMPREPLY=()
|
|
133
|
+
;;
|
|
134
|
+
esac
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
complete -F _gflows gflows 2>/dev/null || complete -o bashdefault -o default -F _gflows gflows
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function zshScript(): string {
|
|
142
|
+
return `# Zsh completion for gflows
|
|
143
|
+
# Install: source <(gflows completion zsh) (add to .zshrc) or save to ${D}fpath[1]}/_gflows
|
|
144
|
+
|
|
145
|
+
_gflows_list_branches() {
|
|
146
|
+
local -a path
|
|
147
|
+
local i=1
|
|
148
|
+
while (( i < CURRENT )); do
|
|
149
|
+
if [[ "${D}words[i]}" == "-C" ]] || [[ "${D}words[i]}" == "--path" ]]; then
|
|
150
|
+
(( i+1 < CURRENT )) && path=(-C "${D}words[i+1]}")
|
|
151
|
+
break
|
|
152
|
+
fi
|
|
153
|
+
(( i++ ))
|
|
154
|
+
done
|
|
155
|
+
(( ${D}#path[@]} > 0 )) && gflows "${D}path[@]}" list 2>/dev/null || gflows list 2>/dev/null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_gflows() {
|
|
159
|
+
local cur context state line cmd cmd_idx i
|
|
160
|
+
_arguments -C -s -S \\
|
|
161
|
+
'-C+[run as if in dir]:dir:_files -/' \\
|
|
162
|
+
'--path=+[run as if in dir]:dir:_files -/' \\
|
|
163
|
+
'-h[show help]' \\
|
|
164
|
+
'--help[show help]' \\
|
|
165
|
+
'-V[show version]' \\
|
|
166
|
+
'--version[show version]' \\
|
|
167
|
+
'1:command:(${COMMANDS.join(" ")})' \\
|
|
168
|
+
'*::args:->args'
|
|
169
|
+
|
|
170
|
+
case $state in
|
|
171
|
+
args)
|
|
172
|
+
cur=\$words[CURRENT]
|
|
173
|
+
cmd_idx=1
|
|
174
|
+
while (( cmd_idx < CURRENT )); do
|
|
175
|
+
if [[ "${D}words[cmd_idx]}" == "-C" ]] || [[ "${D}words[cmd_idx]}" == "--path" ]]; then
|
|
176
|
+
(( cmd_idx += 2 ))
|
|
177
|
+
elif [[ "${D}words[cmd_idx]}" == -* ]]; then
|
|
178
|
+
(( cmd_idx++ ))
|
|
179
|
+
else
|
|
180
|
+
cmd="${D}words[cmd_idx]}"
|
|
181
|
+
break
|
|
182
|
+
fi
|
|
183
|
+
done
|
|
184
|
+
case "$cmd" in
|
|
185
|
+
completion)
|
|
186
|
+
_values "shell" ${COMPLETION_SHELLS.map((s) => `"${s}"`).join(" ")}
|
|
187
|
+
;;
|
|
188
|
+
start)
|
|
189
|
+
_values "type" ${BRANCH_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
190
|
+
;;
|
|
191
|
+
finish)
|
|
192
|
+
if [[ "${D}words[CURRENT-1]}" == "-B" ]] || [[ "${D}words[CURRENT-1]}" == "--branch" ]]; then
|
|
193
|
+
_values "branch" \$(_gflows_list_branches)
|
|
194
|
+
else
|
|
195
|
+
_values "type" ${BRANCH_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
196
|
+
fi
|
|
197
|
+
;;
|
|
198
|
+
list)
|
|
199
|
+
_values "type" ${BRANCH_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
200
|
+
;;
|
|
201
|
+
switch|delete)
|
|
202
|
+
_values "branch" \$(_gflows_list_branches)
|
|
203
|
+
;;
|
|
204
|
+
bump)
|
|
205
|
+
if [[ "${D}words[CURRENT-1]}" == "up" ]] || [[ "${D}words[CURRENT-1]}" == "down" ]]; then
|
|
206
|
+
_values "bump-type" ${BUMP_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
207
|
+
else
|
|
208
|
+
_values "direction" ${BUMP_DIRECTIONS.map((d) => `"${d}"`).join(" ")}
|
|
209
|
+
fi
|
|
210
|
+
;;
|
|
211
|
+
esac
|
|
212
|
+
;;
|
|
213
|
+
esac
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
_gflows
|
|
217
|
+
`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function fishScript(): string {
|
|
221
|
+
return `# Fish completion for gflows
|
|
222
|
+
# Install: gflows completion fish | source (add to ~/.config/fish/config.fish) or save to ~/.config/fish/completions/gflows.fish
|
|
223
|
+
|
|
224
|
+
function __gflows_path
|
|
225
|
+
set -l tokens (commandline -opc)
|
|
226
|
+
set -l i 1
|
|
227
|
+
while test $i -le (count $tokens)
|
|
228
|
+
if test "$tokens[$i]" = "-C"; and test (math $i + 1) -le (count $tokens)
|
|
229
|
+
echo $tokens[(math $i + 1)]
|
|
230
|
+
return
|
|
231
|
+
end
|
|
232
|
+
set i (math $i + 1)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
function __gflows_list_branches
|
|
237
|
+
set -l path (__gflows_path)
|
|
238
|
+
if test -n "$path"
|
|
239
|
+
gflows -C "$path" list 2>/dev/null
|
|
240
|
+
else
|
|
241
|
+
gflows list 2>/dev/null
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Commands
|
|
246
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
247
|
+
-a "init" -d "Ensure main, create dev"
|
|
248
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
249
|
+
-a "start" -d "Create workflow branch"
|
|
250
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
251
|
+
-a "finish" -d "Merge and close branch"
|
|
252
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
253
|
+
-a "switch" -d "Switch branch"
|
|
254
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
255
|
+
-a "delete" -d "Delete local branch(es)"
|
|
256
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
257
|
+
-a "list" -d "List branches by type"
|
|
258
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
259
|
+
-a "bump" -d "Bump or rollback version"
|
|
260
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
261
|
+
-a "completion" -d "Print shell completion script"
|
|
262
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
263
|
+
-a "status" -d "Show current branch flow info"
|
|
264
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
265
|
+
-a "help" -d "Show usage"
|
|
266
|
+
complete -c gflows -f -n "not __fish_seen_subcommand_from ${COMMANDS.join(" ")}" \\
|
|
267
|
+
-a "version" -d "Show version"
|
|
268
|
+
|
|
269
|
+
# completion <shell>
|
|
270
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from completion" -a "bash" -d "Bash completion script"
|
|
271
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from completion" -a "zsh" -d "Zsh completion script"
|
|
272
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from completion" -a "fish" -d "Fish completion script"
|
|
273
|
+
|
|
274
|
+
# start <type>
|
|
275
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
276
|
+
-a "feature" -d "Feature branch"
|
|
277
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
278
|
+
-a "bugfix" -d "Bugfix branch"
|
|
279
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
280
|
+
-a "chore" -d "Chore branch"
|
|
281
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
282
|
+
-a "release" -d "Release branch"
|
|
283
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
284
|
+
-a "hotfix" -d "Hotfix branch"
|
|
285
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from start; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}" \\
|
|
286
|
+
-a "spike" -d "Spike/experiment branch"
|
|
287
|
+
|
|
288
|
+
# finish <type> or -B <branch>
|
|
289
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from finish; and not __fish_seen_subcommand_from ${BRANCH_TYPES.join(" ")}; and not __fish_prev_arg -x -l branch -s B" \\
|
|
290
|
+
-a "feature bugfix chore release hotfix spike"
|
|
291
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from finish; and __fish_prev_arg -x -l branch -s B" \\
|
|
292
|
+
-a "(__gflows_list_branches)"
|
|
293
|
+
|
|
294
|
+
# list [type]
|
|
295
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from list" \\
|
|
296
|
+
-a "feature bugfix chore release hotfix spike"
|
|
297
|
+
|
|
298
|
+
# switch <branch>
|
|
299
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from switch" \\
|
|
300
|
+
-a "(__gflows_list_branches)"
|
|
301
|
+
|
|
302
|
+
# delete <branch>
|
|
303
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from delete" \\
|
|
304
|
+
-a "(__gflows_list_branches)"
|
|
305
|
+
|
|
306
|
+
# bump [up|down] [patch|minor|major]
|
|
307
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from bump; and not __fish_seen_subcommand_from ${BUMP_DIRECTIONS.join(" ")}" \\
|
|
308
|
+
-a "up down"
|
|
309
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from bump; and __fish_seen_subcommand_from up down" \\
|
|
310
|
+
-a "patch minor major"
|
|
311
|
+
|
|
312
|
+
# Common flags
|
|
313
|
+
complete -c gflows -x -n "__fish_seen_subcommand_from ${COMMANDS.join(" ")}" -l path -s C -d "Run as if in dir" -a "(__fish_complete_directories)"
|
|
314
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from ${COMMANDS.join(" ")}" -l help -s h -d "Show help"
|
|
315
|
+
complete -c gflows -f -n "__fish_seen_subcommand_from ${COMMANDS.join(" ")}" -l version -s V -d "Show version"
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Runs the completion command: prints the shell completion script for the given shell.
|
|
321
|
+
* Requires one of: bash, zsh, fish. Script supports commands, types, and when applicable
|
|
322
|
+
* branch names from local workflow branches (via `gflows list`).
|
|
323
|
+
*
|
|
324
|
+
* @param args - Parsed CLI args; args.completionShell must be "bash" | "zsh" | "fish".
|
|
325
|
+
*/
|
|
326
|
+
export async function run(args: ParsedArgs): Promise<void> {
|
|
327
|
+
const shell = args.completionShell;
|
|
328
|
+
if (!shell) {
|
|
329
|
+
console.error(
|
|
330
|
+
"gflows: completion requires a shell. Use: gflows completion bash | zsh | fish"
|
|
331
|
+
);
|
|
332
|
+
process.exit(EXIT_USER);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let script: string;
|
|
336
|
+
switch (shell) {
|
|
337
|
+
case "bash":
|
|
338
|
+
script = bashScript();
|
|
339
|
+
break;
|
|
340
|
+
case "zsh":
|
|
341
|
+
script = zshScript();
|
|
342
|
+
break;
|
|
343
|
+
case "fish":
|
|
344
|
+
script = fishScript();
|
|
345
|
+
break;
|
|
346
|
+
default: {
|
|
347
|
+
const _: never = shell;
|
|
348
|
+
script = "";
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
process.stdout.write(script);
|
|
353
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete command: delete local workflow branch(es). Guards main/dev; picker when TTY and no names.
|
|
3
|
+
* @module commands/delete
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BranchType } from "../types.js";
|
|
7
|
+
import type { ParsedArgs } from "../types.js";
|
|
8
|
+
import { resolveConfig } from "../config.js";
|
|
9
|
+
import { EXIT_OK, EXIT_USER } from "../constants.js";
|
|
10
|
+
import { CannotDeleteMainOrDevError, NotRepoError } from "../errors.js";
|
|
11
|
+
import {
|
|
12
|
+
branchList,
|
|
13
|
+
deleteBranch,
|
|
14
|
+
resolveRepoRoot,
|
|
15
|
+
} from "../git.js";
|
|
16
|
+
|
|
17
|
+
const BRANCH_TYPES: BranchType[] = [
|
|
18
|
+
"feature",
|
|
19
|
+
"bugfix",
|
|
20
|
+
"chore",
|
|
21
|
+
"release",
|
|
22
|
+
"hotfix",
|
|
23
|
+
"spike",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns local branch names that match any workflow prefix (feature/, bugfix/, etc.).
|
|
28
|
+
*/
|
|
29
|
+
function getWorkflowBranches(
|
|
30
|
+
allBranches: string[],
|
|
31
|
+
prefixes: Record<BranchType, string>
|
|
32
|
+
): string[] {
|
|
33
|
+
const prefixed = BRANCH_TYPES.map((t) => prefixes[t]).filter(Boolean);
|
|
34
|
+
return allBranches.filter((b) =>
|
|
35
|
+
prefixed.some((p) => p && b.startsWith(p))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run the delete command.
|
|
41
|
+
* With branch name(s) as positionals: delete those branches (guard main/dev).
|
|
42
|
+
* With no names and TTY: show a checkbox picker of workflow branches; if none, exit 1 with message.
|
|
43
|
+
* With no names and not TTY: exit 1 with message to provide branch name(s).
|
|
44
|
+
* Local delete only; never deletes main or dev.
|
|
45
|
+
*/
|
|
46
|
+
export async function run(args: ParsedArgs): Promise<void> {
|
|
47
|
+
const { cwd, branchNames: rawBranchNames, dryRun, quiet } = args;
|
|
48
|
+
|
|
49
|
+
const root = await resolveRepoRoot(cwd).catch((err) => {
|
|
50
|
+
if (err instanceof NotRepoError) throw err;
|
|
51
|
+
throw err;
|
|
52
|
+
});
|
|
53
|
+
const config = resolveConfig(root);
|
|
54
|
+
const { main, dev, prefixes } = config;
|
|
55
|
+
|
|
56
|
+
const fromPositionals = (rawBranchNames ?? [])
|
|
57
|
+
.map((s) => s.trim())
|
|
58
|
+
.filter(Boolean);
|
|
59
|
+
|
|
60
|
+
if (fromPositionals.length > 0) {
|
|
61
|
+
for (const branch of fromPositionals) {
|
|
62
|
+
if (branch === main || branch === dev) {
|
|
63
|
+
throw new CannotDeleteMainOrDevError(
|
|
64
|
+
`Cannot delete the long-lived branch '${branch}'.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const branch of fromPositionals) {
|
|
69
|
+
await deleteBranch(root, branch, {
|
|
70
|
+
dryRun,
|
|
71
|
+
verbose: args.verbose,
|
|
72
|
+
});
|
|
73
|
+
if (!quiet && !dryRun) {
|
|
74
|
+
console.error(`Deleted branch '${branch}'.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isTTY = typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY;
|
|
81
|
+
if (!isTTY) {
|
|
82
|
+
console.error(
|
|
83
|
+
"gflows delete: no branch name(s) given and stdin is not a TTY. Pass branch name(s) (e.g. gflows delete feature/my-branch) or run from an interactive terminal."
|
|
84
|
+
);
|
|
85
|
+
process.exit(EXIT_USER);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const allLocal = await branchList(root, {
|
|
89
|
+
dryRun,
|
|
90
|
+
verbose: args.verbose,
|
|
91
|
+
});
|
|
92
|
+
const workflowBranches = getWorkflowBranches(allLocal, prefixes);
|
|
93
|
+
|
|
94
|
+
if (workflowBranches.length === 0) {
|
|
95
|
+
if (!quiet) {
|
|
96
|
+
console.error(
|
|
97
|
+
"No workflow branches to delete. Create one with 'gflows start <type> <name>'."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
process.exit(EXIT_USER);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { checkbox } = await import("@inquirer/prompts");
|
|
104
|
+
const chosen = await checkbox({
|
|
105
|
+
message: "Delete branch(es)",
|
|
106
|
+
choices: workflowBranches.map((b) => ({ name: b, value: b })),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!Array.isArray(chosen) || chosen.length === 0) {
|
|
110
|
+
if (!quiet) {
|
|
111
|
+
console.error("No branches selected.");
|
|
112
|
+
}
|
|
113
|
+
process.exit(EXIT_OK);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const branch of chosen) {
|
|
117
|
+
if (branch === main || branch === dev) {
|
|
118
|
+
throw new CannotDeleteMainOrDevError(
|
|
119
|
+
`Cannot delete the long-lived branch '${branch}'.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (const branch of chosen) {
|
|
125
|
+
await deleteBranch(root, branch, {
|
|
126
|
+
dryRun,
|
|
127
|
+
verbose: args.verbose,
|
|
128
|
+
});
|
|
129
|
+
if (!quiet && !dryRun) {
|
|
130
|
+
console.error(`Deleted branch '${branch}'.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|