gflows 0.1.10 → 0.1.12
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/README.md +101 -11
- package/package.json +1 -1
- package/src/commands/bump.ts +7 -4
- package/src/commands/delete.ts +11 -2
- package/src/commands/finish.ts +22 -7
- package/src/commands/help.ts +6 -1
- package/src/commands/init.ts +21 -3
- package/src/commands/list.ts +6 -0
- package/src/commands/start.ts +8 -2
- package/src/commands/status.ts +3 -0
- package/src/commands/switch.ts +7 -2
- package/src/out.ts +81 -0
package/README.md
CHANGED
|
@@ -171,7 +171,7 @@ gflows finish hotfix --push # merge to main, then dev; tag v1.3.1;
|
|
|
171
171
|
| `delete` | `-L` | Delete local workflow branch(es). Never main/dev. |
|
|
172
172
|
| `list` | `-l` | List workflow branches; optional type filter and remote. |
|
|
173
173
|
| `bump` | — | Bump or rollback package version (patch/minor/major). |
|
|
174
|
-
| `completion` | — | Print shell completion script (bash
|
|
174
|
+
| `completion` | — | Print shell completion script (bash/zsh/fish). |
|
|
175
175
|
| `status` | `-t` | Show current branch, type, base, merge target(s), ahead/behind. |
|
|
176
176
|
| `help` | `-h` | Show usage and quick reference. |
|
|
177
177
|
| `version` | `-V` | Show version. |
|
|
@@ -179,13 +179,30 @@ gflows finish hotfix --push # merge to main, then dev; tag v1.3.1;
|
|
|
179
179
|
|
|
180
180
|
**Branch types (for start/finish/list):** `feature` (`-f`), `bugfix` (`-b`), `chore` (`-c`), `release` (`-r`), `hotfix` (`-x`), `spike` (`-e`).
|
|
181
181
|
|
|
182
|
+
**Common flags** (used by multiple commands):
|
|
183
|
+
|
|
184
|
+
| Flag | Short | Description |
|
|
185
|
+
|------|-------|-------------|
|
|
186
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
187
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
188
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
189
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
190
|
+
| `--push` | `-p` | Push after init/start/finish. |
|
|
191
|
+
| `--no-push` | `-P` | Do not push. |
|
|
192
|
+
| `--main <name>` | — | Main branch override. |
|
|
193
|
+
| `--dev <name>` | — | Dev branch override. |
|
|
194
|
+
| `--remote <name>` | `-R` | Remote for push. |
|
|
195
|
+
| `--from <branch>` | `-o` | Base branch override (start). |
|
|
196
|
+
| `--branch <name>` | `-B` | Branch name (finish). |
|
|
197
|
+
| `--yes` | `-y` | Skip confirmations. |
|
|
198
|
+
|
|
182
199
|
---
|
|
183
200
|
|
|
184
201
|
### init
|
|
185
202
|
|
|
186
203
|
Ensures the **main** branch exists (exits with error if not). Creates **dev** from main if it does not exist; does nothing if dev already exists. Does not rewrite or force-push.
|
|
187
204
|
|
|
188
|
-
You can set and persist config with `**--main`**, `**--dev
|
|
205
|
+
You can set and persist config with `**--main`**, `**--dev`**, and `**-R`/`--remote**`. Any of these flags cause init to write or update `.gflows.json` with the given values (after a successful init; skipped with `--dry-run`).
|
|
189
206
|
|
|
190
207
|
**Examples:**
|
|
191
208
|
|
|
@@ -197,7 +214,18 @@ gflows init -C ../other-repo # run in another directory
|
|
|
197
214
|
gflows init --dry-run # log intended actions only
|
|
198
215
|
```
|
|
199
216
|
|
|
200
|
-
**Flags:**
|
|
217
|
+
**Flags:**
|
|
218
|
+
|
|
219
|
+
| Flag | Short | Description |
|
|
220
|
+
|------|-------|-------------|
|
|
221
|
+
| `--push` | `-p` | Push dev to remote after creating. |
|
|
222
|
+
| `--main <name>` | — | Main branch name (persisted to `.gflows.json` when provided). |
|
|
223
|
+
| `--dev <name>` | — | Dev branch name (persisted to `.gflows.json` when provided). |
|
|
224
|
+
| `--remote <name>` | `-R` | Remote name (persisted to `.gflows.json` when provided). |
|
|
225
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
226
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
227
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
228
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
201
229
|
|
|
202
230
|
---
|
|
203
231
|
|
|
@@ -220,7 +248,18 @@ gflows start feature api-v2 --push # create branch and push to rem
|
|
|
220
248
|
gflows start chore deps-update -C ./backend # run in subdirectory
|
|
221
249
|
```
|
|
222
250
|
|
|
223
|
-
**Flags:**
|
|
251
|
+
**Flags:**
|
|
252
|
+
|
|
253
|
+
| Flag | Short | Description |
|
|
254
|
+
|------|-------|-------------|
|
|
255
|
+
| `--force` | — | Allow dirty working tree. |
|
|
256
|
+
| `--push` | `-p` | Push new branch to remote after creating. |
|
|
257
|
+
| `--from <branch>` | `-o` | Base branch override (e.g. `-o main` for bugfix). |
|
|
258
|
+
| `--remote <name>` | `-R` | Remote for push. |
|
|
259
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
260
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
261
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
262
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
224
263
|
|
|
225
264
|
---
|
|
226
265
|
|
|
@@ -245,7 +284,25 @@ gflows finish -y # skip "Delete branch after finish?"
|
|
|
245
284
|
|
|
246
285
|
**Branch resolution:** If you omit the branch name, gflows uses the current branch. With `-B` and no value in a TTY, it shows a picker of workflow branches. Without a TTY, you must pass the branch name explicitly.
|
|
247
286
|
|
|
248
|
-
**Flags:**
|
|
287
|
+
**Flags:**
|
|
288
|
+
|
|
289
|
+
| Flag | Short | Description |
|
|
290
|
+
|------|-------|-------------|
|
|
291
|
+
| `--branch <name>` | `-B` | Branch to finish (current branch if omitted; picker in TTY when `-B` with no value). |
|
|
292
|
+
| `--no-ff` | — | Always create a merge commit. |
|
|
293
|
+
| `--delete` | `-D` | Delete branch after finish. |
|
|
294
|
+
| `--no-delete` | `-N` | Do not delete branch after finish. |
|
|
295
|
+
| `--push` | `-p` | Push after merge (finish prompts "Do you want to push?" when neither `-p` nor `-P`). |
|
|
296
|
+
| `--no-push` | `-P` | Do not push. |
|
|
297
|
+
| `--sign` | `-s` | Sign the tag (release/hotfix; GPG). |
|
|
298
|
+
| `--no-tag` | `-T` | Do not create tag (release/hotfix). |
|
|
299
|
+
| `--tag-message <msg>` | `-M` | Tag message. |
|
|
300
|
+
| `--message <msg>` | `-m` | Merge message. |
|
|
301
|
+
| `--yes` | `-y` | Skip confirmations (e.g. "Delete branch after finish?"). |
|
|
302
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
303
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
304
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
305
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
249
306
|
|
|
250
307
|
---
|
|
251
308
|
|
|
@@ -261,7 +318,13 @@ gflows switch feature/auth-refactor
|
|
|
261
318
|
gflows -W feature/auth-refactor # same with short command
|
|
262
319
|
```
|
|
263
320
|
|
|
264
|
-
**Flags:**
|
|
321
|
+
**Flags:**
|
|
322
|
+
|
|
323
|
+
| Flag | Short | Description |
|
|
324
|
+
|------|-------|-------------|
|
|
325
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
326
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
327
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
265
328
|
|
|
266
329
|
---
|
|
267
330
|
|
|
@@ -277,7 +340,13 @@ gflows delete feature/old-spike
|
|
|
277
340
|
gflows delete feature/one feature/two # delete multiple
|
|
278
341
|
```
|
|
279
342
|
|
|
280
|
-
**Flags:**
|
|
343
|
+
**Flags:**
|
|
344
|
+
|
|
345
|
+
| Flag | Short | Description |
|
|
346
|
+
|------|-------|-------------|
|
|
347
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
348
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
349
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
281
350
|
|
|
282
351
|
---
|
|
283
352
|
|
|
@@ -295,7 +364,15 @@ gflows list -r feature # remote + local feature branches
|
|
|
295
364
|
gflows list --include-remote
|
|
296
365
|
```
|
|
297
366
|
|
|
298
|
-
**Flags:**
|
|
367
|
+
**Flags:**
|
|
368
|
+
|
|
369
|
+
| Flag | Short | Description |
|
|
370
|
+
|------|-------|-------------|
|
|
371
|
+
| `--include-remote` | `-r` | Include remote-tracking branches (may run `git fetch`). |
|
|
372
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
373
|
+
| `--dry-run` | `-d` | Log intended actions only. |
|
|
374
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
375
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
299
376
|
|
|
300
377
|
---
|
|
301
378
|
|
|
@@ -317,7 +394,14 @@ gflows bump # interactive (direction + type) when T
|
|
|
317
394
|
gflows bump --dry-run # print old → new, no file writes
|
|
318
395
|
```
|
|
319
396
|
|
|
320
|
-
**Flags:**
|
|
397
|
+
**Flags:**
|
|
398
|
+
|
|
399
|
+
| Flag | Short | Description |
|
|
400
|
+
|------|-------|-------------|
|
|
401
|
+
| `--dry-run` | `-d` | Print old → new version only; no file writes. |
|
|
402
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
403
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
404
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
321
405
|
|
|
322
406
|
---
|
|
323
407
|
|
|
@@ -332,7 +416,13 @@ gflows status
|
|
|
332
416
|
gflows -t
|
|
333
417
|
```
|
|
334
418
|
|
|
335
|
-
**Flags:**
|
|
419
|
+
**Flags:**
|
|
420
|
+
|
|
421
|
+
| Flag | Short | Description |
|
|
422
|
+
|------|-------|-------------|
|
|
423
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
424
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
425
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
336
426
|
|
|
337
427
|
---
|
|
338
428
|
|
|
@@ -386,7 +476,7 @@ Configuration is **optional**. Override branch names, remote, and branch **prefi
|
|
|
386
476
|
**Resolution order** (later overrides earlier):
|
|
387
477
|
|
|
388
478
|
1. Built-in defaults (`main`, `dev`, `origin`, and default prefixes).
|
|
389
|
-
2. Repo config file: `**.gflows.json`** in repo root, or `**gflows
|
|
479
|
+
2. Repo config file: `**.gflows.json`** in repo root, or `**gflows`** key in `**package.json**`.
|
|
390
480
|
3. CLI (e.g. `--main`, `--dev`, `-R`/`--remote`).
|
|
391
481
|
|
|
392
482
|
Only include keys you want to override; the rest stay default. Invalid or malformed config is ignored (with an optional warning when using `-v`).
|
package/package.json
CHANGED
package/src/commands/bump.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { ParsedArgs } from "../types.js";
|
|
|
10
10
|
import type { BumpDirection, BumpType } from "../types.js";
|
|
11
11
|
import { EXIT_OK, EXIT_USER } from "../constants.js";
|
|
12
12
|
import { InvalidVersionError } from "../errors.js";
|
|
13
|
+
import { hint, success } from "../out.js";
|
|
13
14
|
|
|
14
15
|
const PACKAGE_JSON = "package.json";
|
|
15
16
|
const JSR_JSON = "jsr.json";
|
|
@@ -195,8 +196,8 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
195
196
|
|
|
196
197
|
if (dryRun) {
|
|
197
198
|
if (!quiet) {
|
|
198
|
-
|
|
199
|
-
|
|
199
|
+
success(`Would bump version: ${oldVersion} → ${newVersion}`);
|
|
200
|
+
success(`Would update: ${filesToUpdate.join(", ")}`);
|
|
200
201
|
}
|
|
201
202
|
process.exit(EXIT_OK);
|
|
202
203
|
}
|
|
@@ -205,9 +206,11 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
205
206
|
const jsrUpdated = syncJsrVersion(cwd, newVersion);
|
|
206
207
|
|
|
207
208
|
if (!quiet) {
|
|
208
|
-
|
|
209
|
+
success(`Bumped version: ${oldVersion} → ${newVersion}`);
|
|
209
210
|
const updated = [PACKAGE_JSON];
|
|
210
211
|
if (jsrUpdated) updated.push(JSR_JSON);
|
|
211
|
-
|
|
212
|
+
success(`Updated: ${updated.join(", ")}`);
|
|
213
|
+
// Hint: suggest next step — commit and start release branch
|
|
214
|
+
hint("Commit the change, then run gflows start release vX.Y.Z to release.");
|
|
212
215
|
}
|
|
213
216
|
}
|
package/src/commands/delete.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
deleteBranch,
|
|
14
14
|
resolveRepoRoot,
|
|
15
15
|
} from "../git.js";
|
|
16
|
+
import { hint, success } from "../out.js";
|
|
16
17
|
|
|
17
18
|
const BRANCH_TYPES: BranchType[] = [
|
|
18
19
|
"feature",
|
|
@@ -75,9 +76,13 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
75
76
|
verbose: args.verbose,
|
|
76
77
|
});
|
|
77
78
|
if (!quiet && !dryRun) {
|
|
78
|
-
|
|
79
|
+
success(`Deleted branch '${branch}'.`);
|
|
79
80
|
}
|
|
80
81
|
}
|
|
82
|
+
if (!quiet && !dryRun) {
|
|
83
|
+
// Hint: suggest listing remaining branches
|
|
84
|
+
hint("Use gflows list to see remaining workflow branches.");
|
|
85
|
+
}
|
|
81
86
|
return;
|
|
82
87
|
}
|
|
83
88
|
|
|
@@ -131,7 +136,11 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
131
136
|
verbose: args.verbose,
|
|
132
137
|
});
|
|
133
138
|
if (!quiet && !dryRun) {
|
|
134
|
-
|
|
139
|
+
success(`Deleted branch '${branch}'.`);
|
|
135
140
|
}
|
|
136
141
|
}
|
|
142
|
+
if (!quiet && !dryRun && chosen.length > 0) {
|
|
143
|
+
// Hint: suggest listing remaining branches
|
|
144
|
+
hint("Use gflows list to see remaining workflow branches.");
|
|
145
|
+
}
|
|
137
146
|
}
|
package/src/commands/finish.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
tag,
|
|
27
27
|
tagExists,
|
|
28
28
|
} from "../git.js";
|
|
29
|
+
import { hint, success } from "../out.js";
|
|
29
30
|
|
|
30
31
|
/** Normalizes version to vX.Y.Z for tag name. */
|
|
31
32
|
function normalizeTagVersion(version: string): string {
|
|
@@ -206,7 +207,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
206
207
|
tagMessage: args.tagMessage,
|
|
207
208
|
});
|
|
208
209
|
if (!args.quiet && !args.dryRun) {
|
|
209
|
-
|
|
210
|
+
success(`gflows: created tag '${tagName}'.`);
|
|
210
211
|
}
|
|
211
212
|
}
|
|
212
213
|
|
|
@@ -237,14 +238,25 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
237
238
|
if (shouldDelete && !opts.dryRun) {
|
|
238
239
|
await deleteBranch(repoRoot, branchToFinish, opts);
|
|
239
240
|
if (!args.quiet) {
|
|
240
|
-
|
|
241
|
+
success(`gflows: deleted branch '${branchToFinish}'.`);
|
|
241
242
|
}
|
|
242
243
|
}
|
|
243
244
|
|
|
244
|
-
const
|
|
245
|
-
const didCreateTag = !!(
|
|
245
|
+
const createdTagName =
|
|
246
246
|
meta.mergeTarget === "main-then-dev" && meta.tagOnFinish && version && !args.noTag
|
|
247
|
-
|
|
247
|
+
? normalizeTagVersion(version)
|
|
248
|
+
: undefined;
|
|
249
|
+
const didCreateTag = Boolean(createdTagName);
|
|
250
|
+
|
|
251
|
+
let doPush = args.push && !args.noPush;
|
|
252
|
+
if (!args.push && !args.noPush && isTTY) {
|
|
253
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
254
|
+
doPush = await confirm({
|
|
255
|
+
message: "Do you want to push?",
|
|
256
|
+
default: true,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
248
260
|
if (doPush) {
|
|
249
261
|
const remote = args.remote ?? config.remote;
|
|
250
262
|
const refsToPush: string[] = [config.dev];
|
|
@@ -265,11 +277,14 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
265
277
|
process.exit(2);
|
|
266
278
|
}
|
|
267
279
|
if (!args.quiet && !args.dryRun) {
|
|
268
|
-
|
|
280
|
+
success(`gflows: pushed to ${remote}.`);
|
|
269
281
|
}
|
|
270
282
|
}
|
|
271
283
|
|
|
272
284
|
if (!args.quiet && !args.dryRun) {
|
|
273
|
-
|
|
285
|
+
const tagSuffix = createdTagName ? ` (tag ${createdTagName})` : "";
|
|
286
|
+
success(`gflows: finished '${branchToFinish}' into ${meta.mergeTarget}${tagSuffix}.`);
|
|
287
|
+
// Hint: suggest next step — create a new workflow branch
|
|
288
|
+
hint("Run gflows start <type> <name> to create a new workflow branch.");
|
|
274
289
|
}
|
|
275
290
|
}
|
package/src/commands/help.ts
CHANGED
|
@@ -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
|
|
35
|
+
-P, --no-push Do not push (finish: prompts "Do you want to push?" 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)
|
|
@@ -50,6 +50,11 @@ Finish: --no-ff Always create merge commit; -D/--delete, -N/--no-delete;
|
|
|
50
50
|
List: -r, --include-remote Include remote-tracking branches
|
|
51
51
|
|
|
52
52
|
Exit codes: 0 success, 1 usage/validation, 2 Git or system error.
|
|
53
|
+
|
|
54
|
+
Hints:
|
|
55
|
+
• gflows init then gflows start feature <name> — set up and create first branch
|
|
56
|
+
• gflows finish <type> — merge current workflow branch (use -B <name> to specify branch)
|
|
57
|
+
• gflows list -r — include remote branches; gflows status — show current branch flow
|
|
53
58
|
`;
|
|
54
59
|
console.log(out.trim());
|
|
55
60
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
revParse,
|
|
14
14
|
runGit,
|
|
15
15
|
} from "../git.js";
|
|
16
|
+
import { banner, hint, success } from "../out.js";
|
|
16
17
|
import type { ParsedArgs } from "../types.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -35,6 +36,18 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
35
36
|
{ verbose: args.verbose }
|
|
36
37
|
);
|
|
37
38
|
|
|
39
|
+
if (!args.quiet) {
|
|
40
|
+
banner("gflows init", [
|
|
41
|
+
"Setting up main + dev workflow",
|
|
42
|
+
"",
|
|
43
|
+
` main ${config.main}`,
|
|
44
|
+
` dev ${config.dev}`,
|
|
45
|
+
` remote ${config.remote}`,
|
|
46
|
+
"",
|
|
47
|
+
"→ Dev from main. Use --push to push.",
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
const opts = {
|
|
39
52
|
dryRun: args.dryRun,
|
|
40
53
|
verbose: args.verbose,
|
|
@@ -59,7 +72,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
59
72
|
if (!devExists) {
|
|
60
73
|
await runGit(["branch", config.dev, config.main], { cwd: repoRoot, ...opts });
|
|
61
74
|
if (!args.quiet && !args.dryRun) {
|
|
62
|
-
|
|
75
|
+
success(`gflows: created branch '${config.dev}' from '${config.main}'.`);
|
|
63
76
|
}
|
|
64
77
|
}
|
|
65
78
|
|
|
@@ -72,7 +85,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
72
85
|
);
|
|
73
86
|
}
|
|
74
87
|
if (!args.quiet && !args.dryRun) {
|
|
75
|
-
|
|
88
|
+
success(`gflows: pushed '${config.dev}' to '${config.remote}'.`);
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -84,7 +97,12 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
84
97
|
...(args.remote !== undefined && { remote: args.remote }),
|
|
85
98
|
});
|
|
86
99
|
if (!args.quiet) {
|
|
87
|
-
|
|
100
|
+
success("gflows: updated .gflows.json with provided options.");
|
|
88
101
|
}
|
|
89
102
|
}
|
|
103
|
+
|
|
104
|
+
if (!args.quiet) {
|
|
105
|
+
// Hint: suggest next step — create first workflow branch
|
|
106
|
+
hint("Run gflows start feature <name> to create a workflow branch.");
|
|
107
|
+
}
|
|
90
108
|
}
|
package/src/commands/list.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { ResolvedConfig } from "../types.js";
|
|
|
11
11
|
import { resolveConfig } from "../config.js";
|
|
12
12
|
import { NotRepoError } from "../errors.js";
|
|
13
13
|
import { branchList, fetch, resolveRepoRoot } from "../git.js";
|
|
14
|
+
import { hint } from "../out.js";
|
|
14
15
|
|
|
15
16
|
const BRANCH_TYPES: BranchType[] = [
|
|
16
17
|
"feature",
|
|
@@ -89,5 +90,10 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
89
90
|
|
|
90
91
|
if (!quiet && sorted.length === 0) {
|
|
91
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) {
|
|
96
|
+
// Hint: suggest switching to a listed branch
|
|
97
|
+
hint("Use gflows switch <branch> to switch to a branch.");
|
|
92
98
|
}
|
|
93
99
|
}
|
package/src/commands/start.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type { BranchType, ParsedArgs } from "../types.js";
|
|
|
8
8
|
import { EXIT_USER, VERSION_REGEX } from "../constants.js";
|
|
9
9
|
import { getPrefixForType, resolveConfig } from "../config.js";
|
|
10
10
|
import { BranchNotFoundError, DirtyWorkingTreeError, InvalidVersionError } from "../errors.js";
|
|
11
|
+
import { hint, success } from "../out.js";
|
|
11
12
|
import {
|
|
12
13
|
assertNoRebaseOrMerge,
|
|
13
14
|
assertNotDetached,
|
|
@@ -122,7 +123,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
122
123
|
await runGit(["checkout", "-b", fullBranchName, base], { cwd: repoRoot, ...opts });
|
|
123
124
|
|
|
124
125
|
if (!args.quiet && !args.dryRun) {
|
|
125
|
-
|
|
126
|
+
success(`gflows: created and checked out branch '${fullBranchName}' from '${base}'.`);
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
const doPush = args.push && !args.noPush;
|
|
@@ -135,7 +136,12 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
135
136
|
);
|
|
136
137
|
}
|
|
137
138
|
if (!args.quiet && !args.dryRun) {
|
|
138
|
-
|
|
139
|
+
success(`gflows: pushed '${fullBranchName}' to '${remote}'.`);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
142
|
+
|
|
143
|
+
if (!args.quiet && !args.dryRun) {
|
|
144
|
+
// Hint: suggest next step — merge branch when done
|
|
145
|
+
hint(`When done, run gflows finish ${type} to merge into the target branch.`);
|
|
146
|
+
}
|
|
141
147
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
getCurrentBranch,
|
|
19
19
|
resolveRepoRoot,
|
|
20
20
|
} from "../git.js";
|
|
21
|
+
import { hint } from "../out.js";
|
|
21
22
|
|
|
22
23
|
const BRANCH_TYPES: BranchType[] = [
|
|
23
24
|
"feature",
|
|
@@ -137,5 +138,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
137
138
|
|
|
138
139
|
if (!quiet) {
|
|
139
140
|
console.log(`Ahead/behind: ${ahead} ahead, ${behind} behind`);
|
|
141
|
+
// Hint: suggest next step — finish current branch
|
|
142
|
+
hint(`Run gflows finish ${classification} to merge into ${mergeTargetDisplay}.`);
|
|
140
143
|
}
|
|
141
144
|
}
|
package/src/commands/switch.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
checkout,
|
|
14
14
|
resolveRepoRoot,
|
|
15
15
|
} from "../git.js";
|
|
16
|
+
import { hint, success } from "../out.js";
|
|
16
17
|
|
|
17
18
|
const BRANCH_TYPES: BranchType[] = [
|
|
18
19
|
"feature",
|
|
@@ -63,7 +64,9 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
63
64
|
verbose: args.verbose,
|
|
64
65
|
});
|
|
65
66
|
if (!quiet && !dryRun) {
|
|
66
|
-
|
|
67
|
+
success(`Switched to branch '${branchName}'.`);
|
|
68
|
+
// Hint: suggest listing branches
|
|
69
|
+
hint("Use gflows list to see all workflow branches.");
|
|
67
70
|
}
|
|
68
71
|
return;
|
|
69
72
|
}
|
|
@@ -101,6 +104,8 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
101
104
|
verbose: args.verbose,
|
|
102
105
|
});
|
|
103
106
|
if (!quiet && !dryRun) {
|
|
104
|
-
|
|
107
|
+
success(`Switched to branch '${chosen}'.`);
|
|
108
|
+
// Hint: suggest listing branches
|
|
109
|
+
hint("Use gflows list to see all workflow branches.");
|
|
105
110
|
}
|
|
106
111
|
}
|
package/src/out.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stdout helpers for success/info messages so they are not sent to stderr (which many terminals show in red).
|
|
3
|
+
* @module out
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const GREEN = "\x1b[32m";
|
|
7
|
+
const CYAN = "\x1b[36m";
|
|
8
|
+
const BOLD = "\x1b[1m";
|
|
9
|
+
const DIM = "\x1b[2m";
|
|
10
|
+
const RESET = "\x1b[0m";
|
|
11
|
+
const SUCCESS_ICON = "✓";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Whether stdout is a TTY and can safely use ANSI color codes.
|
|
15
|
+
*/
|
|
16
|
+
function isColorCapable(): boolean {
|
|
17
|
+
return typeof process.stdout.isTTY === "boolean" && process.stdout.isTTY;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Prints a success message to stdout with a green checkmark icon.
|
|
22
|
+
* Uses stdout so terminals do not render it as error (red). Disables color when not a TTY (e.g. CI).
|
|
23
|
+
*
|
|
24
|
+
* @param message - One-line success message to print (no trailing newline added).
|
|
25
|
+
*/
|
|
26
|
+
export function success(message: string): void {
|
|
27
|
+
const icon = SUCCESS_ICON;
|
|
28
|
+
const line = isColorCapable() ? `${GREEN}${icon}${RESET} ${message}` : `${icon} ${message}`;
|
|
29
|
+
console.log(line);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Prints a hint line to stdout (dim when TTY). Use after success messages to suggest next steps.
|
|
34
|
+
* Skips color when not a TTY (e.g. CI). Omit when --quiet to keep output minimal.
|
|
35
|
+
*
|
|
36
|
+
* @param message - One-line hint (e.g. "Run gflows start feature <name> to create a branch").
|
|
37
|
+
*/
|
|
38
|
+
export function hint(message: string): void {
|
|
39
|
+
const line = isColorCapable() ? `${DIM}Hint: ${message}${RESET}` : `Hint: ${message}`;
|
|
40
|
+
console.log(line);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const BANNER_INNER_WIDTH = 42;
|
|
44
|
+
|
|
45
|
+
/** Box-drawing characters for table-style banner. */
|
|
46
|
+
const BOX = {
|
|
47
|
+
TL: "╔",
|
|
48
|
+
TR: "╗",
|
|
49
|
+
BL: "╚",
|
|
50
|
+
BR: "╝",
|
|
51
|
+
H: "═",
|
|
52
|
+
V: "║",
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prints a table-style banner to stdout (e.g. for init). Uses cyan/bold when TTY.
|
|
57
|
+
* Skips color when not a TTY (e.g. CI). Supports multiple detail lines.
|
|
58
|
+
*
|
|
59
|
+
* @param title - Main banner line (e.g. "gflows init").
|
|
60
|
+
* @param lines - Optional lines below the title (subtitle, key-value rows, blank "" for spacing).
|
|
61
|
+
*/
|
|
62
|
+
export function banner(title: string, lines?: string[]): void {
|
|
63
|
+
const color = isColorCapable();
|
|
64
|
+
const c = (s: string) => (color ? `${CYAN}${s}${RESET}` : s);
|
|
65
|
+
const inner = BANNER_INNER_WIDTH - 4;
|
|
66
|
+
const top = ` ${c(BOX.TL)}${c(BOX.H.repeat(BANNER_INNER_WIDTH))}${c(BOX.TR)}`;
|
|
67
|
+
const bottom = ` ${c(BOX.BL)}${c(BOX.H.repeat(BANNER_INNER_WIDTH))}${c(BOX.BR)}`;
|
|
68
|
+
const row = (text: string) =>
|
|
69
|
+
" " + c(BOX.V) + " " + text + " ".repeat(Math.max(0, inner - text.length)) + " " + c(BOX.V);
|
|
70
|
+
const titleDisplay = color ? `${CYAN}${BOLD}${title}${RESET}` : title;
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(top);
|
|
73
|
+
console.log(" " + c(BOX.V) + " " + titleDisplay + " ".repeat(Math.max(0, inner - title.length)) + " " + c(BOX.V));
|
|
74
|
+
if (lines?.length) {
|
|
75
|
+
for (const line of lines) {
|
|
76
|
+
console.log(line === "" ? row("") : row(line));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(bottom);
|
|
80
|
+
console.log("");
|
|
81
|
+
}
|