gflows 0.1.12 → 0.1.14

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.
@@ -5,13 +5,8 @@
5
5
  * @module commands/finish
6
6
  */
7
7
 
8
- import type { BranchType, ParsedArgs } from "../types.js";
8
+ import { getBranchTypeMeta, resolveConfig } from "../config.js";
9
9
  import { EXIT_USER, VERSION_REGEX } from "../constants.js";
10
- import {
11
- getBranchTypeMeta,
12
- getPrefixForType,
13
- resolveConfig,
14
- } from "../config.js";
15
10
  import { BranchNotFoundError } from "../errors.js";
16
11
  import {
17
12
  assertNoRebaseOrMerge,
@@ -27,6 +22,7 @@ import {
27
22
  tagExists,
28
23
  } from "../git.js";
29
24
  import { hint, success } from "../out.js";
25
+ import type { BranchType, ParsedArgs } from "../types.js";
30
26
 
31
27
  /** Normalizes version to vX.Y.Z for tag name. */
32
28
  function normalizeTagVersion(version: string): string {
@@ -39,7 +35,7 @@ function normalizeTagVersion(version: string): string {
39
35
  */
40
36
  async function getWorkflowBranches(
41
37
  cwd: string,
42
- prefixes: Record<BranchType, string>
38
+ prefixes: Record<BranchType, string>,
43
39
  ): Promise<string[]> {
44
40
  const all = await branchList(cwd, { dryRun: false, verbose: false });
45
41
  const workflow: string[] = [];
@@ -59,16 +55,9 @@ async function getWorkflowBranches(
59
55
  */
60
56
  function parseBranchTypeAndVersion(
61
57
  branchName: string,
62
- prefixes: Record<BranchType, string>
58
+ prefixes: Record<BranchType, string>,
63
59
  ): { type: BranchType; version?: string } | null {
64
- for (const type of [
65
- "release",
66
- "hotfix",
67
- "feature",
68
- "bugfix",
69
- "chore",
70
- "spike",
71
- ] as BranchType[]) {
60
+ for (const type of ["release", "hotfix", "feature", "bugfix", "chore", "spike"] as BranchType[]) {
72
61
  const prefix = prefixes[type];
73
62
  if (prefix && branchName.startsWith(prefix)) {
74
63
  const suffix = branchName.slice(prefix.length);
@@ -93,7 +82,7 @@ export async function run(args: ParsedArgs): Promise<void> {
93
82
  const config = resolveConfig(
94
83
  repoRoot,
95
84
  { main: args.main, dev: args.dev, remote: args.remote },
96
- { verbose: args.verbose }
85
+ { verbose: args.verbose },
97
86
  );
98
87
 
99
88
  const opts: { dryRun: boolean; verbose: boolean } = {
@@ -106,9 +95,7 @@ export async function run(args: ParsedArgs): Promise<void> {
106
95
  // Resolve branch to finish (and guard main/dev early so we can error before picker)
107
96
  let branchToFinish: string;
108
97
  const explicitBranch =
109
- typeof args.branch === "string" && args.branch.trim() !== ""
110
- ? args.branch.trim()
111
- : undefined;
98
+ typeof args.branch === "string" && args.branch.trim() !== "" ? args.branch.trim() : undefined;
112
99
 
113
100
  if (explicitBranch) {
114
101
  branchToFinish = explicitBranch;
@@ -127,7 +114,7 @@ export async function run(args: ParsedArgs): Promise<void> {
127
114
  const current = await getCurrentBranch(repoRoot, opts);
128
115
  if (!current) {
129
116
  console.error(
130
- "gflows finish: HEAD is detached. Checkout a branch or specify one with -B <name>."
117
+ "gflows finish: HEAD is detached. Checkout a branch or specify one with -B <name>.",
131
118
  );
132
119
  process.exit(EXIT_USER);
133
120
  }
@@ -137,7 +124,7 @@ export async function run(args: ParsedArgs): Promise<void> {
137
124
  // Refuse finish on main or dev
138
125
  if (branchToFinish === config.main || branchToFinish === config.dev) {
139
126
  console.error(
140
- `gflows finish: cannot finish the long-lived branch '${branchToFinish}'. Finish a workflow branch (feature, bugfix, etc.) instead.`
127
+ `gflows finish: cannot finish the long-lived branch '${branchToFinish}'. Finish a workflow branch (feature, bugfix, etc.) instead.`,
141
128
  );
142
129
  process.exit(2);
143
130
  }
@@ -147,13 +134,13 @@ export async function run(args: ParsedArgs): Promise<void> {
147
134
  const type: BranchType | undefined = args.type ?? parsed?.type ?? undefined;
148
135
  if (!type) {
149
136
  console.error(
150
- `gflows finish: cannot determine branch type for '${branchToFinish}'. Specify type (e.g. gflows finish feature) or use a branch name with a known prefix.`
137
+ `gflows finish: cannot determine branch type for '${branchToFinish}'. Specify type (e.g. gflows finish feature) or use a branch name with a known prefix.`,
151
138
  );
152
139
  process.exit(EXIT_USER);
153
140
  }
154
141
  if (parsed && parsed.type !== type) {
155
142
  console.error(
156
- `gflows finish: branch '${branchToFinish}' matches type '${parsed.type}', but '${type}' was specified.`
143
+ `gflows finish: branch '${branchToFinish}' matches type '${parsed.type}', but '${type}' was specified.`,
157
144
  );
158
145
  process.exit(EXIT_USER);
159
146
  }
@@ -175,7 +162,7 @@ export async function run(args: ParsedArgs): Promise<void> {
175
162
  }
176
163
  } else if (meta.tagOnFinish && !version) {
177
164
  console.error(
178
- `gflows finish: release/hotfix branch '${branchToFinish}' has no valid version segment. Use format release/vX.Y.Z or hotfix/vX.Y.Z.`
165
+ `gflows finish: release/hotfix branch '${branchToFinish}' has no valid version segment. Use format release/vX.Y.Z or hotfix/vX.Y.Z.`,
179
166
  );
180
167
  process.exit(EXIT_USER);
181
168
  }
@@ -184,38 +171,33 @@ export async function run(args: ParsedArgs): Promise<void> {
184
171
  const branches = await branchList(repoRoot, { ...opts, dryRun: false });
185
172
  if (!branches.includes(branchToFinish)) {
186
173
  throw new BranchNotFoundError(
187
- `Branch '${branchToFinish}' not found. Specify an existing local branch with -B <name>.`
174
+ `Branch '${branchToFinish}' not found. Specify an existing local branch with -B <name>.`,
188
175
  );
189
176
  }
190
177
 
191
178
  const noFf = args.noFf;
179
+ if (meta.mergeTarget === "dev") {
180
+ await checkout(repoRoot, config.dev, opts);
181
+ await merge(repoRoot, branchToFinish, { ...opts, noFf });
182
+ } else {
183
+ // main-then-dev: merge into main first, then merge main into dev
184
+ await checkout(repoRoot, config.main, opts);
185
+ await merge(repoRoot, branchToFinish, { ...opts, noFf });
192
186
 
193
- try {
194
- if (meta.mergeTarget === "dev") {
195
- await checkout(repoRoot, config.dev, opts);
196
- await merge(repoRoot, branchToFinish, { ...opts, noFf });
197
- } else {
198
- // main-then-dev: merge into main first, then merge main into dev
199
- await checkout(repoRoot, config.main, opts);
200
- await merge(repoRoot, branchToFinish, { ...opts, noFf });
201
-
202
- if (meta.tagOnFinish && version && !args.noTag) {
203
- const tagName = normalizeTagVersion(version);
204
- await tag(repoRoot, tagName, {
205
- ...opts,
206
- sign: args.signTag,
207
- tagMessage: args.tagMessage,
208
- });
209
- if (!args.quiet && !args.dryRun) {
210
- success(`gflows: created tag '${tagName}'.`);
211
- }
187
+ if (meta.tagOnFinish && version && !args.noTag) {
188
+ const tagName = normalizeTagVersion(version);
189
+ await tag(repoRoot, tagName, {
190
+ ...opts,
191
+ sign: args.signTag,
192
+ tagMessage: args.tagMessage,
193
+ });
194
+ if (!args.quiet && !args.dryRun) {
195
+ success(`gflows: created tag '${tagName}'.`);
212
196
  }
213
-
214
- await checkout(repoRoot, config.dev, opts);
215
- await merge(repoRoot, config.main, { ...opts, noFf });
216
197
  }
217
- } catch (err) {
218
- throw err;
198
+
199
+ await checkout(repoRoot, config.dev, opts);
200
+ await merge(repoRoot, config.main, { ...opts, noFf });
219
201
  }
220
202
 
221
203
  // Optional: delete the finished branch
@@ -263,16 +245,10 @@ export async function run(args: ParsedArgs): Promise<void> {
263
245
  if (meta.mergeTarget === "main-then-dev") {
264
246
  refsToPush.push(config.main);
265
247
  }
266
- const pushCode = await push(
267
- repoRoot,
268
- remote,
269
- refsToPush,
270
- didCreateTag,
271
- opts
272
- );
248
+ const pushCode = await push(repoRoot, remote, refsToPush, didCreateTag, opts);
273
249
  if (pushCode !== 0) {
274
250
  console.error(
275
- "gflows: merge and tag succeeded locally, but push failed. Retry with `git push` or `gflows finish ... --push`."
251
+ "gflows: merge and tag succeeded locally, but push failed. Retry with `git push` or `gflows finish ... --push`.",
276
252
  );
277
253
  process.exit(2);
278
254
  }
@@ -32,7 +32,7 @@ Types: feature (-f), bugfix (-b), chore (-c), release (-r), hotfix (-x), spike (
32
32
 
33
33
  Common flags:
34
34
  -p, --push Push after init/start/finish
35
- -P, --no-push Do not push (finish: prompts "Do you want to push?" when neither -p nor -P)
35
+ -P, --no-push Do not push (init/start: push is default; finish: prompts when neither -p nor -P)
36
36
  --main <name> Main branch (init: persist to .gflows.json)
37
37
  --dev <name> Dev branch (init: persist to .gflows.json)
38
38
  -R, --remote <name> Remote for push (init: persist to .gflows.json)
@@ -6,13 +6,7 @@
6
6
 
7
7
  import { resolveConfig, writeConfigFile } from "../config.js";
8
8
  import { BranchNotFoundError, NotRepoError } from "../errors.js";
9
- import {
10
- branchList,
11
- push,
12
- resolveRepoRoot,
13
- revParse,
14
- runGit,
15
- } from "../git.js";
9
+ import { branchList, push, resolveRepoRoot, revParse, runGit } from "../git.js";
16
10
  import { banner, hint, success } from "../out.js";
17
11
  import type { ParsedArgs } from "../types.js";
18
12
 
@@ -33,7 +27,7 @@ export async function run(args: ParsedArgs): Promise<void> {
33
27
  dev: args.dev,
34
28
  remote: args.remote,
35
29
  },
36
- { verbose: args.verbose }
30
+ { verbose: args.verbose },
37
31
  );
38
32
 
39
33
  if (!args.quiet) {
@@ -44,7 +38,7 @@ export async function run(args: ParsedArgs): Promise<void> {
44
38
  ` dev ${config.dev}`,
45
39
  ` remote ${config.remote}`,
46
40
  "",
47
- "→ Dev from main. Use --push to push.",
41
+ "→ Dev from main. Use --no-push to skip pushing.",
48
42
  ]);
49
43
  }
50
44
 
@@ -60,7 +54,7 @@ export async function run(args: ParsedArgs): Promise<void> {
60
54
  if (err instanceof NotRepoError) throw err;
61
55
  if (err instanceof BranchNotFoundError) {
62
56
  throw new BranchNotFoundError(
63
- `Main branch '${config.main}' does not exist. Create an initial commit and the main branch first.`
57
+ `Main branch '${config.main}' does not exist. Create an initial commit and the main branch first.`,
64
58
  );
65
59
  }
66
60
  throw err;
@@ -76,12 +70,12 @@ export async function run(args: ParsedArgs): Promise<void> {
76
70
  }
77
71
  }
78
72
 
79
- const doPush = args.push && !args.noPush;
73
+ const doPush = !args.noPush;
80
74
  if (doPush) {
81
75
  const pushCode = await push(repoRoot, config.remote, [config.dev], false, opts);
82
76
  if (pushCode !== 0) {
83
77
  throw new Error(
84
- `Push failed. Local branch '${config.dev}' was created. Retry with \`git push ${config.remote} ${config.dev}\` or \`gflows init --push\`.`
78
+ `Push failed. Local branch '${config.dev}' was created. Retry with \`git push ${config.remote} ${config.dev}\` or \`gflows init\`.`,
85
79
  );
86
80
  }
87
81
  if (!args.quiet && !args.dryRun) {
@@ -89,7 +83,8 @@ export async function run(args: ParsedArgs): Promise<void> {
89
83
  }
90
84
  }
91
85
 
92
- const hasConfigFlags = args.main !== undefined || args.dev !== undefined || args.remote !== undefined;
86
+ const hasConfigFlags =
87
+ args.main !== undefined || args.dev !== undefined || args.remote !== undefined;
93
88
  if (!args.dryRun && hasConfigFlags) {
94
89
  writeConfigFile(repoRoot, {
95
90
  ...(args.main !== undefined && { main: args.main }),
@@ -5,22 +5,13 @@
5
5
  * @module commands/list
6
6
  */
7
7
 
8
- import type { BranchType } from "../types.js";
9
- import type { ParsedArgs } from "../types.js";
10
- import type { ResolvedConfig } from "../types.js";
11
8
  import { resolveConfig } from "../config.js";
12
9
  import { NotRepoError } from "../errors.js";
13
10
  import { branchList, fetch, resolveRepoRoot } from "../git.js";
14
11
  import { hint } from "../out.js";
12
+ import type { BranchType, ParsedArgs, ResolvedConfig } from "../types.js";
15
13
 
16
- const BRANCH_TYPES: BranchType[] = [
17
- "feature",
18
- "bugfix",
19
- "chore",
20
- "release",
21
- "hotfix",
22
- "spike",
23
- ];
14
+ const BRANCH_TYPES: BranchType[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
24
15
 
25
16
  /**
26
17
  * Returns branch names that match any workflow prefix. If typeFilter is set,
@@ -29,13 +20,11 @@ const BRANCH_TYPES: BranchType[] = [
29
20
  function filterWorkflowBranches(
30
21
  allBranches: string[],
31
22
  config: ResolvedConfig,
32
- typeFilter: BranchType | undefined
23
+ typeFilter: BranchType | undefined,
33
24
  ): string[] {
34
25
  const { main, dev, prefixes } = config;
35
26
  const prefixesToMatch =
36
- typeFilter !== undefined
37
- ? [prefixes[typeFilter]]
38
- : BRANCH_TYPES.map((t) => prefixes[t]);
27
+ typeFilter !== undefined ? [prefixes[typeFilter]] : BRANCH_TYPES.map((t) => prefixes[t]);
39
28
 
40
29
  return allBranches.filter((b) => {
41
30
  if (b === main || b === dev) return false;
@@ -61,7 +50,7 @@ export async function run(args: ParsedArgs): Promise<void> {
61
50
  const config = resolveConfig(
62
51
  root,
63
52
  { main: args.main, dev: args.dev, remote: args.remote },
64
- { verbose: !!verbose }
53
+ { verbose: !!verbose },
65
54
  );
66
55
 
67
56
  if (includeRemote && !dryRun) {
@@ -76,23 +65,17 @@ export async function run(args: ParsedArgs): Promise<void> {
76
65
  verbose: !!verbose,
77
66
  });
78
67
 
79
- const workflowBranches = filterWorkflowBranches(
80
- allBranches,
81
- config,
82
- typeFilter
83
- );
68
+ const workflowBranches = filterWorkflowBranches(allBranches, config, typeFilter);
84
69
 
85
- const sorted = [...workflowBranches].sort();
70
+ // Show main and dev first (when present), then workflow branches
71
+ const mainAndDev = [config.main, config.dev].filter((b) => allBranches.includes(b));
72
+ const sorted = [...mainAndDev, ...[...workflowBranches].sort()];
86
73
 
87
74
  for (const b of sorted) {
88
75
  console.log(b);
89
76
  }
90
77
 
91
- if (!quiet && sorted.length === 0) {
92
- console.error("No workflow branches found.");
93
- // Hint: suggest creating first workflow branch
94
- hint("Run gflows start <type> <name> to create a workflow branch.");
95
- } else if (!quiet && sorted.length > 0) {
78
+ if (!quiet && sorted.length > 0) {
96
79
  // Hint: suggest switching to a listed branch
97
80
  hint("Use gflows switch <branch> to switch to a branch.");
98
81
  }
@@ -4,11 +4,9 @@
4
4
  * @module commands/start
5
5
  */
6
6
 
7
- import type { BranchType, ParsedArgs } from "../types.js";
8
- import { EXIT_USER, VERSION_REGEX } from "../constants.js";
9
7
  import { getPrefixForType, resolveConfig } from "../config.js";
8
+ import { EXIT_USER, VERSION_REGEX } from "../constants.js";
10
9
  import { BranchNotFoundError, DirtyWorkingTreeError, InvalidVersionError } from "../errors.js";
11
- import { hint, success } from "../out.js";
12
10
  import {
13
11
  assertNoRebaseOrMerge,
14
12
  assertNotDetached,
@@ -21,16 +19,13 @@ import {
21
19
  runGit,
22
20
  validateBranchName,
23
21
  } from "../git.js";
22
+ import { hint, success } from "../out.js";
23
+ import type { BranchType, ParsedArgs } from "../types.js";
24
24
 
25
25
  /**
26
26
  * Returns the base branch name for the given type and fromMain flag (main vs dev).
27
27
  */
28
- function getBaseBranch(
29
- type: BranchType,
30
- fromMain: boolean,
31
- main: string,
32
- dev: string
33
- ): string {
28
+ function getBaseBranch(type: BranchType, fromMain: boolean, main: string, dev: string): string {
34
29
  if (type === "hotfix") return main;
35
30
  if (type === "bugfix" && fromMain) return main;
36
31
  return dev;
@@ -45,7 +40,9 @@ function getBaseBranch(
45
40
  */
46
41
  export async function run(args: ParsedArgs): Promise<void> {
47
42
  if (!args.type || args.name === undefined || args.name === "") {
48
- console.error("gflows start: requires type and name (e.g. gflows start feature my-feat). Use 'gflows help' for usage.");
43
+ console.error(
44
+ "gflows start: requires type and name (e.g. gflows start feature my-feat). Use 'gflows help' for usage.",
45
+ );
49
46
  process.exit(EXIT_USER);
50
47
  }
51
48
 
@@ -55,7 +52,7 @@ export async function run(args: ParsedArgs): Promise<void> {
55
52
  const config = resolveConfig(
56
53
  repoRoot,
57
54
  { main: args.main, dev: args.dev, remote: args.remote },
58
- { verbose: args.verbose }
55
+ { verbose: args.verbose },
59
56
  );
60
57
 
61
58
  const opts = {
@@ -79,7 +76,7 @@ export async function run(args: ParsedArgs): Promise<void> {
79
76
  if (type === "release" || type === "hotfix") {
80
77
  if (!VERSION_REGEX.test(name)) {
81
78
  throw new InvalidVersionError(
82
- `Invalid version '${name}'. Use format vX.Y.Z or X.Y.Z (e.g. v1.2.0).`
79
+ `Invalid version '${name}'. Use format vX.Y.Z or X.Y.Z (e.g. v1.2.0).`,
83
80
  );
84
81
  }
85
82
  } else {
@@ -89,10 +86,10 @@ export async function run(args: ParsedArgs): Promise<void> {
89
86
  const base = getBaseBranch(type, args.fromMain, config.main, config.dev);
90
87
 
91
88
  // Ensure base exists (local or create from remote after fetch)
92
- let baseExists = false;
89
+ let _baseExists = false;
93
90
  try {
94
91
  await revParse(repoRoot, base, [], { dryRun: false, verbose: opts.verbose });
95
- baseExists = true;
92
+ _baseExists = true;
96
93
  } catch {
97
94
  // Fetch and try remote ref
98
95
  await fetch(repoRoot, config.remote, opts);
@@ -102,10 +99,10 @@ export async function run(args: ParsedArgs): Promise<void> {
102
99
  if (!opts.dryRun) {
103
100
  await runGit(["branch", base, remoteRef], { cwd: repoRoot, ...opts, dryRun: false });
104
101
  }
105
- baseExists = true;
102
+ _baseExists = true;
106
103
  } catch {
107
104
  throw new BranchNotFoundError(
108
- `Base branch '${base}' not found locally or on ${config.remote}. Create it or push it first.`
105
+ `Base branch '${base}' not found locally or on ${config.remote}. Create it or push it first.`,
109
106
  );
110
107
  }
111
108
  }
@@ -116,7 +113,7 @@ export async function run(args: ParsedArgs): Promise<void> {
116
113
  const branches = await branchList(repoRoot, { ...opts, dryRun: false });
117
114
  if (branches.includes(fullBranchName)) {
118
115
  throw new BranchNotFoundError(
119
- `Branch '${fullBranchName}' already exists. Use a different name or switch to it.`
116
+ `Branch '${fullBranchName}' already exists. Use a different name or switch to it.`,
120
117
  );
121
118
  }
122
119
 
@@ -132,7 +129,7 @@ export async function run(args: ParsedArgs): Promise<void> {
132
129
  const pushCode = await push(repoRoot, remote, [fullBranchName], false, opts);
133
130
  if (pushCode !== 0) {
134
131
  throw new Error(
135
- `Push failed. Local branch '${fullBranchName}' was created. Retry with \`git push ${remote} ${fullBranchName}\` or \`gflows start ... --push\`.`
132
+ `Push failed. Local branch '${fullBranchName}' was created. Retry with \`git push ${remote} ${fullBranchName}\` or \`gflows start ... --push\`.`,
136
133
  );
137
134
  }
138
135
  if (!args.quiet && !args.dryRun) {
@@ -6,28 +6,13 @@
6
6
  * @module commands/status
7
7
  */
8
8
 
9
- import type { BranchType, ParsedArgs } from "../types.js";
10
- import type { ResolvedConfig } from "../types.js";
11
- import {
12
- getBranchTypeMeta,
13
- resolveConfig,
14
- } from "../config.js";
9
+ import { getBranchTypeMeta, resolveConfig } from "../config.js";
15
10
  import { NotRepoError } from "../errors.js";
16
- import {
17
- getAheadBehind,
18
- getCurrentBranch,
19
- resolveRepoRoot,
20
- } from "../git.js";
11
+ import { getAheadBehind, getCurrentBranch, resolveRepoRoot } from "../git.js";
21
12
  import { hint } from "../out.js";
13
+ import type { BranchType, ParsedArgs, ResolvedConfig } from "../types.js";
22
14
 
23
- const BRANCH_TYPES: BranchType[] = [
24
- "feature",
25
- "bugfix",
26
- "chore",
27
- "release",
28
- "hotfix",
29
- "spike",
30
- ];
15
+ const BRANCH_TYPES: BranchType[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
31
16
 
32
17
  /**
33
18
  * Classifies a branch name into a workflow type, "main", "dev", or null (unknown).
@@ -35,7 +20,7 @@ const BRANCH_TYPES: BranchType[] = [
35
20
  */
36
21
  function classifyBranch(
37
22
  branchName: string,
38
- config: ResolvedConfig
23
+ config: ResolvedConfig,
39
24
  ): BranchType | "main" | "dev" | null {
40
25
  if (branchName === config.main) return "main";
41
26
  if (branchName === config.dev) return "dev";
@@ -54,7 +39,7 @@ function classifyBranch(
54
39
  */
55
40
  function formatMergeTarget(
56
41
  mergeTarget: "main" | "dev" | "main-then-dev",
57
- config: ResolvedConfig
42
+ config: ResolvedConfig,
58
43
  ): string {
59
44
  if (mergeTarget === "main-then-dev") {
60
45
  return `${config.main}, then ${config.dev}`;
@@ -78,7 +63,7 @@ export async function run(args: ParsedArgs): Promise<void> {
78
63
  const config = resolveConfig(
79
64
  root,
80
65
  { main: args.main, dev: args.dev, remote: args.remote },
81
- { verbose: !!verbose }
66
+ { verbose: !!verbose },
82
67
  );
83
68
  const current = await getCurrentBranch(root, {
84
69
  dryRun: !!dryRun,
@@ -129,12 +114,10 @@ export async function run(args: ParsedArgs): Promise<void> {
129
114
  console.log(`Merge target(s): ${mergeTargetDisplay}`);
130
115
  }
131
116
 
132
- const { ahead, behind } = await getAheadBehind(
133
- root,
134
- baseBranch,
135
- current,
136
- { dryRun: !!dryRun, verbose: !!verbose }
137
- );
117
+ const { ahead, behind } = await getAheadBehind(root, baseBranch, current, {
118
+ dryRun: !!dryRun,
119
+ verbose: !!verbose,
120
+ });
138
121
 
139
122
  if (!quiet) {
140
123
  console.log(`Ahead/behind: ${ahead} ahead, ${behind} behind`);
@@ -3,38 +3,24 @@
3
3
  * @module commands/switch
4
4
  */
5
5
 
6
- import type { BranchType } from "../types.js";
7
- import type { ParsedArgs } from "../types.js";
8
6
  import { resolveConfig } from "../config.js";
9
7
  import { EXIT_OK, EXIT_USER } from "../constants.js";
10
8
  import { NotRepoError } from "../errors.js";
11
- import {
12
- branchList,
13
- checkout,
14
- resolveRepoRoot,
15
- } from "../git.js";
9
+ import { branchList, checkout, resolveRepoRoot } from "../git.js";
16
10
  import { hint, success } from "../out.js";
11
+ import type { BranchType, ParsedArgs } from "../types.js";
17
12
 
18
- const BRANCH_TYPES: BranchType[] = [
19
- "feature",
20
- "bugfix",
21
- "chore",
22
- "release",
23
- "hotfix",
24
- "spike",
25
- ];
13
+ const BRANCH_TYPES: BranchType[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
26
14
 
27
15
  /**
28
16
  * Returns local branch names that match any workflow prefix (feature/, bugfix/, etc.).
29
17
  */
30
18
  function getWorkflowBranches(
31
19
  allBranches: string[],
32
- prefixes: Record<BranchType, string>
20
+ prefixes: Record<BranchType, string>,
33
21
  ): string[] {
34
22
  const prefixed = BRANCH_TYPES.map((t) => prefixes[t]).filter(Boolean);
35
- return allBranches.filter((b) =>
36
- prefixed.some((p) => p && b.startsWith(p))
37
- );
23
+ return allBranches.filter((b) => prefixed.some((p) => p && b.startsWith(p)));
38
24
  }
39
25
 
40
26
  /**
@@ -74,17 +60,20 @@ export async function run(args: ParsedArgs): Promise<void> {
74
60
  const isTTY = typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY;
75
61
  if (!isTTY) {
76
62
  console.error(
77
- "gflows switch: no branch name given and stdin is not a TTY. Pass a branch name (e.g. gflows switch feature/my-branch) or run from an interactive terminal."
63
+ "gflows switch: no branch name given and stdin is not a TTY. Pass a branch name (e.g. gflows switch feature/my-branch) or run from an interactive terminal.",
78
64
  );
79
65
  process.exit(EXIT_USER);
80
66
  }
81
67
 
82
68
  const allLocal = await branchList(root, { dryRun, verbose: args.verbose });
83
69
  const workflowBranches = getWorkflowBranches(allLocal, config.prefixes);
70
+ // Include main and dev so we always show whatever branches exist
71
+ const mainAndDev = [config.main, config.dev].filter((b) => allLocal.includes(b));
72
+ const choices = [...mainAndDev, ...workflowBranches];
84
73
 
85
- if (workflowBranches.length === 0) {
74
+ if (choices.length === 0) {
86
75
  if (!quiet) {
87
- console.error("No workflow branches found. Create one with 'gflows start <type> <name>'.");
76
+ console.error("No branches found.");
88
77
  }
89
78
  process.exit(EXIT_OK);
90
79
  }
@@ -92,7 +81,7 @@ export async function run(args: ParsedArgs): Promise<void> {
92
81
  const { select } = await import("@inquirer/prompts");
93
82
  const chosen = await select({
94
83
  message: "Switch to branch",
95
- choices: workflowBranches.map((b) => ({ name: b, value: b })),
84
+ choices: choices.map((b) => ({ name: b, value: b })),
96
85
  });
97
86
 
98
87
  if (typeof chosen !== "string") {