gflows 0.1.9 → 0.1.11
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 +5 -0
- package/package.json +1 -1
- package/src/cli.ts +2 -0
- package/src/commands/bump.ts +6 -4
- package/src/commands/delete.ts +9 -2
- package/src/commands/finish.ts +6 -4
- package/src/commands/help.ts +5 -0
- package/src/commands/init.ts +20 -3
- package/src/commands/list.ts +4 -0
- package/src/commands/start.ts +7 -2
- package/src/commands/status.ts +2 -0
- package/src/commands/switch.ts +5 -2
- package/src/out.ts +81 -0
package/README.md
CHANGED
|
@@ -77,6 +77,11 @@ Available on:
|
|
|
77
77
|
- [npm: gflows](https://www.npmjs.com/package/gflows)
|
|
78
78
|
- [JSR: @alialnaghmoush/gflows](https://jsr.io/@alialnaghmoush/gflows)
|
|
79
79
|
|
|
80
|
+
If installed as a local dev dependency, run via your package runner:
|
|
81
|
+
|
|
82
|
+
- Bun: `bunx gflows ...` (or `bun gflows ...` when available in your Bun setup)
|
|
83
|
+
- npm: `npx gflows ...`
|
|
84
|
+
|
|
80
85
|
**Global install (optional):**
|
|
81
86
|
|
|
82
87
|
**npm**
|
package/package.json
CHANGED
package/src/cli.ts
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,10 @@ 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("Commit the change, then run gflows start release vX.Y.Z to release.");
|
|
212
214
|
}
|
|
213
215
|
}
|
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,12 @@ 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("Use gflows list to see remaining workflow branches.");
|
|
84
|
+
}
|
|
81
85
|
return;
|
|
82
86
|
}
|
|
83
87
|
|
|
@@ -131,7 +135,10 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
131
135
|
verbose: args.verbose,
|
|
132
136
|
});
|
|
133
137
|
if (!quiet && !dryRun) {
|
|
134
|
-
|
|
138
|
+
success(`Deleted branch '${branch}'.`);
|
|
135
139
|
}
|
|
136
140
|
}
|
|
141
|
+
if (!quiet && !dryRun && chosen.length > 0) {
|
|
142
|
+
hint("Use gflows list to see remaining workflow branches.");
|
|
143
|
+
}
|
|
137
144
|
}
|
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,7 +238,7 @@ 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
|
|
|
@@ -265,11 +266,12 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
265
266
|
process.exit(2);
|
|
266
267
|
}
|
|
267
268
|
if (!args.quiet && !args.dryRun) {
|
|
268
|
-
|
|
269
|
+
success(`gflows: pushed to ${remote}.`);
|
|
269
270
|
}
|
|
270
271
|
}
|
|
271
272
|
|
|
272
273
|
if (!args.quiet && !args.dryRun) {
|
|
273
|
-
|
|
274
|
+
success(`gflows: finished '${branchToFinish}' into ${meta.mergeTarget}.`);
|
|
275
|
+
hint("Run gflows start <type> <name> to create a new workflow branch.");
|
|
274
276
|
}
|
|
275
277
|
}
|
package/src/commands/help.ts
CHANGED
|
@@ -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,11 @@ 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("Run gflows start feature <name> to create a workflow branch.");
|
|
106
|
+
}
|
|
90
107
|
}
|
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,8 @@ 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("Run gflows start <type> <name> to create a workflow branch.");
|
|
94
|
+
} else if (!quiet && sorted.length > 0) {
|
|
95
|
+
hint("Use gflows switch <branch> to switch to a branch.");
|
|
92
96
|
}
|
|
93
97
|
}
|
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,11 @@ 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(`When done, run gflows finish ${type} to merge into the target branch.`);
|
|
145
|
+
}
|
|
141
146
|
}
|
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,6 @@ 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(`Run gflows finish ${classification} to merge into ${mergeTargetDisplay}.`);
|
|
140
142
|
}
|
|
141
143
|
}
|
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,8 @@ 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("Use gflows list to see all workflow branches.");
|
|
67
69
|
}
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
@@ -101,6 +103,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
101
103
|
verbose: args.verbose,
|
|
102
104
|
});
|
|
103
105
|
if (!quiet && !dryRun) {
|
|
104
|
-
|
|
106
|
+
success(`Switched to branch '${chosen}'.`);
|
|
107
|
+
hint("Use gflows list to see all workflow branches.");
|
|
105
108
|
}
|
|
106
109
|
}
|
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
|
+
}
|