gflows 0.1.12 → 0.1.13
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 +99 -81
- package/package.json +1 -1
- package/src/cli.ts +46 -1
- package/src/commands/bump.ts +1 -1
- package/src/commands/list.ts +6 -6
- package/src/commands/switch.ts +8 -3
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# gFlows
|
|
2
2
|
|
|
3
3
|
A lightweight CLI for consistent Git branching workflows: long-lived **main** (production) and **dev** (integration), plus short-lived workflow branches with clear merge targets. Built for [Bun](https://bun.sh) and TypeScript; **scriptable** and **safe by default**—no history rewriting, predictable exit codes, and optional interactive pickers only when running in a TTY.
|
|
4
4
|
|
|
@@ -181,20 +181,22 @@ gflows finish hotfix --push # merge to main, then dev; tag v1.3.1;
|
|
|
181
181
|
|
|
182
182
|
**Common flags** (used by multiple commands):
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
|
187
|
-
| `--
|
|
188
|
-
| `--
|
|
189
|
-
| `--
|
|
190
|
-
| `--
|
|
191
|
-
| `--
|
|
192
|
-
| `--
|
|
193
|
-
| `--
|
|
194
|
-
| `--
|
|
195
|
-
| `--
|
|
196
|
-
| `--
|
|
197
|
-
| `--
|
|
184
|
+
|
|
185
|
+
| Flag | Short | Description |
|
|
186
|
+
| ----------------- | ----- | ------------------------------------- |
|
|
187
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
188
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
189
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
190
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
191
|
+
| `--push` | `-p` | Push after init/start/finish. |
|
|
192
|
+
| `--no-push` | `-P` | Do not push. |
|
|
193
|
+
| `--main <name>` | — | Main branch override. |
|
|
194
|
+
| `--dev <name>` | — | Dev branch override. |
|
|
195
|
+
| `--remote <name>` | `-R` | Remote for push. |
|
|
196
|
+
| `--from <branch>` | `-o` | Base branch override (start). |
|
|
197
|
+
| `--branch <name>` | `-B` | Branch name (finish). |
|
|
198
|
+
| `--yes` | `-y` | Skip confirmations. |
|
|
199
|
+
|
|
198
200
|
|
|
199
201
|
---
|
|
200
202
|
|
|
@@ -216,16 +218,18 @@ gflows init --dry-run # log intended actions only
|
|
|
216
218
|
|
|
217
219
|
**Flags:**
|
|
218
220
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
|
222
|
-
| `--
|
|
223
|
-
| `--
|
|
224
|
-
| `--
|
|
225
|
-
| `--
|
|
226
|
-
| `--
|
|
227
|
-
| `--
|
|
228
|
-
| `--
|
|
221
|
+
|
|
222
|
+
| Flag | Short | Description |
|
|
223
|
+
| ----------------- | ----- | ------------------------------------------------------------- |
|
|
224
|
+
| `--push` | `-p` | Push dev to remote after creating. |
|
|
225
|
+
| `--main <name>` | — | Main branch name (persisted to `.gflows.json` when provided). |
|
|
226
|
+
| `--dev <name>` | — | Dev branch name (persisted to `.gflows.json` when provided). |
|
|
227
|
+
| `--remote <name>` | `-R` | Remote name (persisted to `.gflows.json` when provided). |
|
|
228
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
229
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
230
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
231
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
232
|
+
|
|
229
233
|
|
|
230
234
|
---
|
|
231
235
|
|
|
@@ -250,16 +254,18 @@ gflows start chore deps-update -C ./backend # run in subdirectory
|
|
|
250
254
|
|
|
251
255
|
**Flags:**
|
|
252
256
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
|
256
|
-
| `--
|
|
257
|
-
| `--
|
|
258
|
-
| `--
|
|
259
|
-
| `--
|
|
260
|
-
| `--
|
|
261
|
-
| `--
|
|
262
|
-
| `--
|
|
257
|
+
|
|
258
|
+
| Flag | Short | Description |
|
|
259
|
+
| ----------------- | ----- | ------------------------------------------------- |
|
|
260
|
+
| `--force` | — | Allow dirty working tree. |
|
|
261
|
+
| `--push` | `-p` | Push new branch to remote after creating. |
|
|
262
|
+
| `--from <branch>` | `-o` | Base branch override (e.g. `-o main` for bugfix). |
|
|
263
|
+
| `--remote <name>` | `-R` | Remote for push. |
|
|
264
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
265
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
266
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
267
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
268
|
+
|
|
263
269
|
|
|
264
270
|
---
|
|
265
271
|
|
|
@@ -286,23 +292,25 @@ gflows finish -y # skip "Delete branch after finish?"
|
|
|
286
292
|
|
|
287
293
|
**Flags:**
|
|
288
294
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
|
292
|
-
| `--
|
|
293
|
-
| `--
|
|
294
|
-
| `--
|
|
295
|
-
| `--
|
|
296
|
-
| `--
|
|
297
|
-
| `--
|
|
298
|
-
| `--
|
|
299
|
-
| `--tag
|
|
300
|
-
| `--message <msg>` | `-
|
|
301
|
-
| `--
|
|
302
|
-
| `--
|
|
303
|
-
| `--
|
|
304
|
-
| `--
|
|
305
|
-
| `--
|
|
295
|
+
|
|
296
|
+
| Flag | Short | Description |
|
|
297
|
+
| --------------------- | ----- | ------------------------------------------------------------------------------------ |
|
|
298
|
+
| `--branch <name>` | `-B` | Branch to finish (current branch if omitted; picker in TTY when `-B` with no value). |
|
|
299
|
+
| `--no-ff` | — | Always create a merge commit. |
|
|
300
|
+
| `--delete` | `-D` | Delete branch after finish. |
|
|
301
|
+
| `--no-delete` | `-N` | Do not delete branch after finish. |
|
|
302
|
+
| `--push` | `-p` | Push after merge (finish prompts "Do you want to push?" when neither `-p` nor `-P`). |
|
|
303
|
+
| `--no-push` | `-P` | Do not push. |
|
|
304
|
+
| `--sign` | `-s` | Sign the tag (release/hotfix; GPG). |
|
|
305
|
+
| `--no-tag` | `-T` | Do not create tag (release/hotfix). |
|
|
306
|
+
| `--tag-message <msg>` | `-M` | Tag message. |
|
|
307
|
+
| `--message <msg>` | `-m` | Merge message. |
|
|
308
|
+
| `--yes` | `-y` | Skip confirmations (e.g. "Delete branch after finish?"). |
|
|
309
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
310
|
+
| `--dry-run` | `-d` | Log intended actions only; no writes. |
|
|
311
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
312
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
313
|
+
|
|
306
314
|
|
|
307
315
|
---
|
|
308
316
|
|
|
@@ -320,11 +328,13 @@ gflows -W feature/auth-refactor # same with short command
|
|
|
320
328
|
|
|
321
329
|
**Flags:**
|
|
322
330
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
|
326
|
-
| `--
|
|
327
|
-
| `--
|
|
331
|
+
|
|
332
|
+
| Flag | Short | Description |
|
|
333
|
+
| -------------- | ----- | --------------------- |
|
|
334
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
335
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
336
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
337
|
+
|
|
328
338
|
|
|
329
339
|
---
|
|
330
340
|
|
|
@@ -342,11 +352,13 @@ gflows delete feature/one feature/two # delete multiple
|
|
|
342
352
|
|
|
343
353
|
**Flags:**
|
|
344
354
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
|
348
|
-
| `--
|
|
349
|
-
| `--
|
|
355
|
+
|
|
356
|
+
| Flag | Short | Description |
|
|
357
|
+
| -------------- | ----- | --------------------- |
|
|
358
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
359
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
360
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
361
|
+
|
|
350
362
|
|
|
351
363
|
---
|
|
352
364
|
|
|
@@ -366,13 +378,15 @@ gflows list --include-remote
|
|
|
366
378
|
|
|
367
379
|
**Flags:**
|
|
368
380
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
|
372
|
-
| `--
|
|
373
|
-
| `--
|
|
374
|
-
| `--
|
|
375
|
-
| `--
|
|
381
|
+
|
|
382
|
+
| Flag | Short | Description |
|
|
383
|
+
| ------------------ | ----- | ------------------------------------------------------- |
|
|
384
|
+
| `--include-remote` | `-r` | Include remote-tracking branches (may run `git fetch`). |
|
|
385
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
386
|
+
| `--dry-run` | `-d` | Log intended actions only. |
|
|
387
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
388
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
389
|
+
|
|
376
390
|
|
|
377
391
|
---
|
|
378
392
|
|
|
@@ -396,12 +410,14 @@ gflows bump --dry-run # print old → new, no file writes
|
|
|
396
410
|
|
|
397
411
|
**Flags:**
|
|
398
412
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
|
402
|
-
| `--
|
|
403
|
-
| `--
|
|
404
|
-
| `--
|
|
413
|
+
|
|
414
|
+
| Flag | Short | Description |
|
|
415
|
+
| -------------- | ----- | --------------------------------------------- |
|
|
416
|
+
| `--dry-run` | `-d` | Print old → new version only; no file writes. |
|
|
417
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
418
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
419
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
420
|
+
|
|
405
421
|
|
|
406
422
|
---
|
|
407
423
|
|
|
@@ -418,11 +434,13 @@ gflows -t
|
|
|
418
434
|
|
|
419
435
|
**Flags:**
|
|
420
436
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
|
424
|
-
| `--
|
|
425
|
-
| `--
|
|
437
|
+
|
|
438
|
+
| Flag | Short | Description |
|
|
439
|
+
| -------------- | ----- | --------------------- |
|
|
440
|
+
| `--path <dir>` | `-C` | Run as if in `<dir>`. |
|
|
441
|
+
| `--verbose` | `-v` | Verbose output. |
|
|
442
|
+
| `--quiet` | `-q` | Minimal output. |
|
|
443
|
+
|
|
426
444
|
|
|
427
445
|
---
|
|
428
446
|
|
|
@@ -476,7 +494,7 @@ Configuration is **optional**. Override branch names, remote, and branch **prefi
|
|
|
476
494
|
**Resolution order** (later overrides earlier):
|
|
477
495
|
|
|
478
496
|
1. Built-in defaults (`main`, `dev`, `origin`, and default prefixes).
|
|
479
|
-
2. Repo config file: `**.gflows.json`** in repo root, or `**gflows`** key in `**package.json
|
|
497
|
+
2. Repo config file: `**.gflows.json`** in repo root, or `**gflows`** key in `**package.json`**.
|
|
480
498
|
3. CLI (e.g. `--main`, `--dev`, `-R`/`--remote`).
|
|
481
499
|
|
|
482
500
|
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/cli.ts
CHANGED
|
@@ -123,6 +123,45 @@ function resolveCwd(pathFlag: string | undefined): string {
|
|
|
123
123
|
return absolute;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Returns the command name closest to `input` by edit distance, or undefined if no close match.
|
|
128
|
+
* Used for "did you mean?" when the user mistypes a command.
|
|
129
|
+
*/
|
|
130
|
+
function closestCommand(input: string): Command | undefined {
|
|
131
|
+
if (!input || input.length < 2) return undefined;
|
|
132
|
+
const target = input.toLowerCase();
|
|
133
|
+
let best: Command | undefined;
|
|
134
|
+
let bestDistance = 3; // only suggest if within 2 edits
|
|
135
|
+
for (const cmd of COMMANDS) {
|
|
136
|
+
const d = editDistance(target, cmd);
|
|
137
|
+
if (d < bestDistance) {
|
|
138
|
+
bestDistance = d;
|
|
139
|
+
best = cmd as Command;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return best;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Levenshtein edit distance between two strings. */
|
|
146
|
+
function editDistance(a: string, b: string): number {
|
|
147
|
+
const m = a.length;
|
|
148
|
+
const n = b.length;
|
|
149
|
+
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
150
|
+
for (let i = 0; i <= m; i++) dp[i]![0] = i;
|
|
151
|
+
for (let j = 0; j <= n; j++) dp[0]![j] = j;
|
|
152
|
+
for (let i = 1; i <= m; i++) {
|
|
153
|
+
for (let j = 1; j <= n; j++) {
|
|
154
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
155
|
+
dp[i]![j] = Math.min(
|
|
156
|
+
dp[i - 1]![j]! + 1,
|
|
157
|
+
dp[i]![j - 1]! + 1,
|
|
158
|
+
dp[i - 1]![j - 1]! + cost
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return dp[m]![n]!;
|
|
163
|
+
}
|
|
164
|
+
|
|
126
165
|
/** Resolve command from positionals and short flags. Short wins if both present. */
|
|
127
166
|
function resolveCommand(
|
|
128
167
|
positionals: string[],
|
|
@@ -240,7 +279,13 @@ export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
|
|
|
240
279
|
|
|
241
280
|
const command = resolveCommand(positionals, v);
|
|
242
281
|
if (!command) {
|
|
243
|
-
|
|
282
|
+
const first = positionals[0];
|
|
283
|
+
const suggestion = typeof first === "string" ? closestCommand(first) : undefined;
|
|
284
|
+
if (suggestion) {
|
|
285
|
+
console.error(`gflows: unknown command '${first}'. Did you mean '${suggestion}'?`);
|
|
286
|
+
} else {
|
|
287
|
+
console.error("gflows: missing command. Use 'gflows help' for usage.");
|
|
288
|
+
}
|
|
244
289
|
process.exit(EXIT_USER);
|
|
245
290
|
}
|
|
246
291
|
|
package/src/commands/bump.ts
CHANGED
|
@@ -111,7 +111,7 @@ function readPackageVersion(dir: string): { raw: string; semver: Semver } {
|
|
|
111
111
|
const version = data.version;
|
|
112
112
|
if (typeof version !== "string" || version.trim() === "") {
|
|
113
113
|
throw new InvalidVersionError(
|
|
114
|
-
"package.json has no valid 'version' field."
|
|
114
|
+
"package.json has no valid 'version' field. Add a \"version\" field (e.g. \"0.0.0\") to package.json."
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
const semver = parseVersion(version);
|
package/src/commands/list.ts
CHANGED
|
@@ -82,17 +82,17 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
82
82
|
typeFilter
|
|
83
83
|
);
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
// Show main and dev first (when present), then workflow branches
|
|
86
|
+
const mainAndDev = [config.main, config.dev].filter((b) =>
|
|
87
|
+
allBranches.includes(b)
|
|
88
|
+
);
|
|
89
|
+
const sorted = [...mainAndDev, ...[...workflowBranches].sort()];
|
|
86
90
|
|
|
87
91
|
for (const b of sorted) {
|
|
88
92
|
console.log(b);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
if (!quiet && sorted.length
|
|
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) {
|
|
95
|
+
if (!quiet && sorted.length > 0) {
|
|
96
96
|
// Hint: suggest switching to a listed branch
|
|
97
97
|
hint("Use gflows switch <branch> to switch to a branch.");
|
|
98
98
|
}
|
package/src/commands/switch.ts
CHANGED
|
@@ -81,10 +81,15 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
81
81
|
|
|
82
82
|
const allLocal = await branchList(root, { dryRun, verbose: args.verbose });
|
|
83
83
|
const workflowBranches = getWorkflowBranches(allLocal, config.prefixes);
|
|
84
|
+
// Include main and dev so we always show whatever branches exist
|
|
85
|
+
const mainAndDev = [config.main, config.dev].filter((b) =>
|
|
86
|
+
allLocal.includes(b)
|
|
87
|
+
);
|
|
88
|
+
const choices = [...mainAndDev, ...workflowBranches];
|
|
84
89
|
|
|
85
|
-
if (
|
|
90
|
+
if (choices.length === 0) {
|
|
86
91
|
if (!quiet) {
|
|
87
|
-
console.error("No
|
|
92
|
+
console.error("No branches found.");
|
|
88
93
|
}
|
|
89
94
|
process.exit(EXIT_OK);
|
|
90
95
|
}
|
|
@@ -92,7 +97,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
92
97
|
const { select } = await import("@inquirer/prompts");
|
|
93
98
|
const chosen = await select({
|
|
94
99
|
message: "Switch to branch",
|
|
95
|
-
choices:
|
|
100
|
+
choices: choices.map((b) => ({ name: b, value: b })),
|
|
96
101
|
});
|
|
97
102
|
|
|
98
103
|
if (typeof chosen !== "string") {
|