gflows 1.1.1 → 1.2.0
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/AGENTS.md +5 -2
- package/CHANGELOG.md +11 -0
- package/README.md +10 -2
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/commands/bump.ts +6 -170
- package/src/commands/completion.ts +25 -0
- package/src/commands/finish.ts +113 -74
- package/src/commands/help.ts +3 -1
- package/src/commands/mcp.ts +27 -1
- package/src/commands/release.ts +209 -0
- package/src/commands/schema.ts +10 -0
- package/src/interactive.ts +18 -0
- package/src/parse.ts +6 -4
- package/src/recommend.ts +2 -1
- package/src/tui/HubHome.tsx +2 -1
- package/src/tui/HubShell.tsx +16 -0
- package/src/tui/flows.tsx +47 -0
- package/src/tui/slash.ts +1 -0
- package/src/types.ts +2 -0
- package/src/version-bump.ts +236 -0
package/src/commands/mcp.ts
CHANGED
|
@@ -93,6 +93,24 @@ const TOOLS = [
|
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
+
{
|
|
97
|
+
name: "gflows_release",
|
|
98
|
+
description:
|
|
99
|
+
"Quick release from dev: bump package version, merge into main, tag, sync main→dev. Requires bumpType.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
path: { type: "string" },
|
|
104
|
+
bumpType: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Semver segment to bump: patch, minor, or major",
|
|
107
|
+
},
|
|
108
|
+
push: { type: "boolean" },
|
|
109
|
+
preview: { type: "boolean" },
|
|
110
|
+
},
|
|
111
|
+
required: ["bumpType"],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
96
114
|
{
|
|
97
115
|
name: "gflows_schema",
|
|
98
116
|
description: "Return the gflows command schema JSON.",
|
|
@@ -159,6 +177,14 @@ async function runTool(
|
|
|
159
177
|
if (args.push) argv.push("-p");
|
|
160
178
|
else argv.push("-P");
|
|
161
179
|
break;
|
|
180
|
+
case "gflows_release": {
|
|
181
|
+
const bumpType = String(args.bumpType ?? "patch");
|
|
182
|
+
argv.push("release", "up", bumpType);
|
|
183
|
+
if (args.preview) argv.push("--preview");
|
|
184
|
+
if (args.push) argv.push("-p");
|
|
185
|
+
else argv.push("-P");
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
162
188
|
case "gflows_schema":
|
|
163
189
|
argv.push("schema");
|
|
164
190
|
break;
|
|
@@ -185,7 +211,7 @@ async function runTool(
|
|
|
185
211
|
*/
|
|
186
212
|
export async function run(_args: ParsedArgs): Promise<void> {
|
|
187
213
|
console.error(
|
|
188
|
-
"gflows mcp: listening on stdio (JSON-RPC). Tools: status, doctor, info, list, start, sync, finish, schema.",
|
|
214
|
+
"gflows mcp: listening on stdio (JSON-RPC). Tools: status, doctor, info, list, start, sync, finish, release, schema.",
|
|
189
215
|
);
|
|
190
216
|
|
|
191
217
|
const decoder = new TextDecoder();
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick release from the long-lived `dev` branch: bump, merge into main, tag, sync main→dev.
|
|
3
|
+
* @module commands/release
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { resolveConfig } from "../config.js";
|
|
8
|
+
import { EXIT_USER } from "../constants.js";
|
|
9
|
+
import { DirtyWorkingTreeError, NothingToFinishError } from "../errors.js";
|
|
10
|
+
import { normalizeTagVersion } from "../flow.js";
|
|
11
|
+
import {
|
|
12
|
+
assertNoRebaseOrMerge,
|
|
13
|
+
assertNotDetached,
|
|
14
|
+
getAheadBehind,
|
|
15
|
+
getCurrentBranch,
|
|
16
|
+
isClean,
|
|
17
|
+
resolveRepoRoot,
|
|
18
|
+
runGit,
|
|
19
|
+
tagExists,
|
|
20
|
+
} from "../git.js";
|
|
21
|
+
import { hint, success } from "../out.js";
|
|
22
|
+
import { PACKAGE_JSON } from "../packages.js";
|
|
23
|
+
import type { BumpType, ParsedArgs } from "../types.js";
|
|
24
|
+
import { applyVersionToPackages, computeBump, JSR_JSON } from "../version-bump.js";
|
|
25
|
+
import { type FinishContext, runFinishPlan } from "./finish.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Runs quick release: only valid when HEAD is the configured `dev` branch.
|
|
29
|
+
*/
|
|
30
|
+
export async function run(args: ParsedArgs): Promise<void> {
|
|
31
|
+
const repoRoot = await resolveRepoRoot(args.cwd);
|
|
32
|
+
const config = resolveConfig(
|
|
33
|
+
repoRoot,
|
|
34
|
+
{ main: args.main, dev: args.dev, remote: args.remote },
|
|
35
|
+
{ verbose: args.verbose },
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const opts = { dryRun: args.dryRun, verbose: args.verbose, quiet: args.quiet };
|
|
39
|
+
const isTTY = Boolean(process.stdin.isTTY);
|
|
40
|
+
|
|
41
|
+
await assertNotDetached(repoRoot);
|
|
42
|
+
assertNoRebaseOrMerge(repoRoot);
|
|
43
|
+
|
|
44
|
+
const current = await getCurrentBranch(repoRoot, opts);
|
|
45
|
+
if (!current || current !== config.dev) {
|
|
46
|
+
console.error(
|
|
47
|
+
`gflows release: must be on '${config.dev}' (currently ${current ? `'${current}'` : "detached"}).`,
|
|
48
|
+
);
|
|
49
|
+
hint("Checkout dev, or use: gflows start release vX.Y.Z && gflows finish release -y -P");
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!args.force) {
|
|
54
|
+
const clean = await isClean(repoRoot, opts);
|
|
55
|
+
if (!clean) {
|
|
56
|
+
throw new DirtyWorkingTreeError(
|
|
57
|
+
"Working tree has uncommitted changes. Commit or stash them before release, or use --force.",
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { ahead } = await getAheadBehind(repoRoot, config.main, config.dev, opts);
|
|
63
|
+
if (ahead === 0) {
|
|
64
|
+
throw new NothingToFinishError(
|
|
65
|
+
`Nothing to release: '${config.dev}' has no commits beyond '${config.main}'.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let bumpType: BumpType | undefined = args.bumpType;
|
|
70
|
+
if (args.bumpDirection === "down") {
|
|
71
|
+
console.error(
|
|
72
|
+
"gflows release: only 'up' is supported (not 'down'). Example: gflows release up patch",
|
|
73
|
+
);
|
|
74
|
+
process.exit(EXIT_USER);
|
|
75
|
+
}
|
|
76
|
+
if (args.bumpDirection && args.bumpDirection !== "up") {
|
|
77
|
+
console.error(
|
|
78
|
+
"gflows release: expected 'up <patch|minor|major>'. Example: gflows release up patch",
|
|
79
|
+
);
|
|
80
|
+
process.exit(EXIT_USER);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!bumpType) {
|
|
84
|
+
if (!isTTY) {
|
|
85
|
+
console.error(
|
|
86
|
+
"gflows release: when not in a TTY, bump type is required. Example: gflows release up patch -y -P",
|
|
87
|
+
);
|
|
88
|
+
process.exit(EXIT_USER);
|
|
89
|
+
}
|
|
90
|
+
const { selectPrompt } = await import("../prompts.js");
|
|
91
|
+
const computedPreview = (() => {
|
|
92
|
+
try {
|
|
93
|
+
return computeBump(repoRoot, "up", "patch");
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
})();
|
|
98
|
+
const currentVer = computedPreview?.oldVersion ?? "?";
|
|
99
|
+
bumpType = await selectPrompt<"patch" | "minor" | "major">({
|
|
100
|
+
message: `Bump version (current ${currentVer})`,
|
|
101
|
+
options: [
|
|
102
|
+
{ label: "patch (x.y.Z)", value: "patch" },
|
|
103
|
+
{ label: "minor (x.Y.0)", value: "minor" },
|
|
104
|
+
{ label: "major (X.0.0)", value: "major" },
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let doPush = false;
|
|
110
|
+
if (args.push && !args.noPush) doPush = true;
|
|
111
|
+
else if (args.noPush) doPush = false;
|
|
112
|
+
else if (!isTTY) {
|
|
113
|
+
console.error("gflows release: specify --push (-p) or --no-push (-P) when not interactive.");
|
|
114
|
+
process.exit(EXIT_USER);
|
|
115
|
+
} else if (!args.yes) {
|
|
116
|
+
const { confirmPrompt } = await import("../prompts.js");
|
|
117
|
+
doPush = await confirmPrompt({
|
|
118
|
+
message: "Push after release?",
|
|
119
|
+
initialValue: false,
|
|
120
|
+
});
|
|
121
|
+
} else {
|
|
122
|
+
// -y without push polarity in TTY: default no-push (same as finish when -y alone)
|
|
123
|
+
doPush = false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const { oldVersion, newVersion, roots } = computeBump(repoRoot, "up", bumpType);
|
|
127
|
+
const tagName = normalizeTagVersion(newVersion);
|
|
128
|
+
|
|
129
|
+
if (await tagExists(repoRoot, tagName, opts)) {
|
|
130
|
+
console.error(`gflows release: tag '${tagName}' already exists.`);
|
|
131
|
+
hint("Use a different bump (minor/major), or delete the tag if you intend to recreate it.");
|
|
132
|
+
process.exit(2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.error("gflows release plan:");
|
|
136
|
+
console.error(` from: ${config.dev}`);
|
|
137
|
+
console.error(` bump: ${oldVersion} → ${newVersion}`);
|
|
138
|
+
console.error(` merge: → ${config.main}, then ${config.main} → ${config.dev}`);
|
|
139
|
+
console.error(` tag: ${tagName}`);
|
|
140
|
+
console.error(` push: ${doPush ? "yes" : "no"}`);
|
|
141
|
+
|
|
142
|
+
if (args.preview) {
|
|
143
|
+
success("gflows: preview only (no changes).");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!args.yes && isTTY) {
|
|
148
|
+
const { confirmPrompt } = await import("../prompts.js");
|
|
149
|
+
const ok = await confirmPrompt({
|
|
150
|
+
message: "Proceed with quick release?",
|
|
151
|
+
initialValue: true,
|
|
152
|
+
});
|
|
153
|
+
if (!ok) {
|
|
154
|
+
success("gflows: release cancelled.");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
} else if (!args.yes && !isTTY) {
|
|
158
|
+
console.error("gflows release: pass -y to accept the plan when not interactive.");
|
|
159
|
+
process.exit(EXIT_USER);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!opts.dryRun) {
|
|
163
|
+
applyVersionToPackages(repoRoot, roots, newVersion);
|
|
164
|
+
for (const dir of roots) {
|
|
165
|
+
await runGit(["add", "--", join(dir, PACKAGE_JSON)], {
|
|
166
|
+
cwd: repoRoot,
|
|
167
|
+
dryRun: false,
|
|
168
|
+
verbose: args.verbose,
|
|
169
|
+
}).catch(() => undefined);
|
|
170
|
+
await runGit(["add", "--", join(dir, JSR_JSON)], {
|
|
171
|
+
cwd: repoRoot,
|
|
172
|
+
dryRun: false,
|
|
173
|
+
verbose: args.verbose,
|
|
174
|
+
}).catch(() => undefined);
|
|
175
|
+
}
|
|
176
|
+
await runGit(["commit", "-m", `chore: bump to ${newVersion}`], {
|
|
177
|
+
cwd: repoRoot,
|
|
178
|
+
dryRun: false,
|
|
179
|
+
verbose: args.verbose,
|
|
180
|
+
});
|
|
181
|
+
if (!args.quiet) {
|
|
182
|
+
success(`gflows: bumped version ${oldVersion} → ${newVersion} and committed.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const ctx: FinishContext = {
|
|
187
|
+
branchToFinish: config.dev,
|
|
188
|
+
type: "release",
|
|
189
|
+
version: newVersion,
|
|
190
|
+
mergeTarget: "main-then-dev",
|
|
191
|
+
shouldDelete: false,
|
|
192
|
+
doPush,
|
|
193
|
+
noFf: args.noFf,
|
|
194
|
+
squash: false,
|
|
195
|
+
message: args.message,
|
|
196
|
+
signTag: args.signTag,
|
|
197
|
+
noTag: false,
|
|
198
|
+
tagMessage: args.tagMessage,
|
|
199
|
+
remote: args.remote ?? config.remote,
|
|
200
|
+
main: config.main,
|
|
201
|
+
dev: config.dev,
|
|
202
|
+
bumpOnFinish: false,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
await runFinishPlan(repoRoot, ctx, {
|
|
206
|
+
...opts,
|
|
207
|
+
printPlan: false,
|
|
208
|
+
});
|
|
209
|
+
}
|
package/src/commands/schema.ts
CHANGED
|
@@ -56,6 +56,14 @@ export async function run(_args: ParsedArgs): Promise<void> {
|
|
|
56
56
|
"-M/--tag-message",
|
|
57
57
|
"-m/--message",
|
|
58
58
|
],
|
|
59
|
+
release: [
|
|
60
|
+
"up patch|minor|major",
|
|
61
|
+
"-y",
|
|
62
|
+
"-p/--push",
|
|
63
|
+
"-P/--no-push",
|
|
64
|
+
"--preview",
|
|
65
|
+
"-s/--sign",
|
|
66
|
+
],
|
|
59
67
|
start: ["-o/--from", "--force", "-p/--push"],
|
|
60
68
|
sync: ["--rebase", "--force"],
|
|
61
69
|
list: ["-r/--include-remote", "--json"],
|
|
@@ -66,6 +74,7 @@ export async function run(_args: ParsedArgs): Promise<void> {
|
|
|
66
74
|
agentNotes: [
|
|
67
75
|
"Always pass explicit flags in non-TTY (never assume interactive hub).",
|
|
68
76
|
"Prefer -y to accept finish plan (delete defaults on); pass -P or -p for push.",
|
|
77
|
+
"Quick release from dev: gflows release up patch -y -p (or -P).",
|
|
69
78
|
"On conflict: gflows continue | gflows abort | gflows undo.",
|
|
70
79
|
"Use gflows schema and AGENTS.md as source of truth.",
|
|
71
80
|
],
|
|
@@ -78,6 +87,7 @@ function commandMeta(command: string): { summary: string; interactiveOk: boolean
|
|
|
78
87
|
init: { summary: "Ensure main; create dev", interactiveOk: true },
|
|
79
88
|
start: { summary: "Create workflow branch", interactiveOk: true },
|
|
80
89
|
finish: { summary: "Merge and close workflow branch", interactiveOk: true },
|
|
90
|
+
release: { summary: "Quick release from dev (bump, merge main, tag)", interactiveOk: true },
|
|
81
91
|
switch: { summary: "Switch workflow branch", interactiveOk: true },
|
|
82
92
|
delete: { summary: "Delete local workflow branches", interactiveOk: true },
|
|
83
93
|
list: { summary: "List workflow branches", interactiveOk: false },
|
package/src/interactive.ts
CHANGED
|
@@ -82,6 +82,11 @@ function buildLegacyOptions(
|
|
|
82
82
|
{ value: "sync", label: "Sync this branch with base" },
|
|
83
83
|
{ value: "pr", label: "Open a pull request" },
|
|
84
84
|
{ value: "finish", label: "Finish / merge this branch" },
|
|
85
|
+
);
|
|
86
|
+
if (snap?.current === snap?.dev) {
|
|
87
|
+
items.push({ value: "release", label: "Quick release to main" });
|
|
88
|
+
}
|
|
89
|
+
items.push(
|
|
85
90
|
{ value: "switch", label: "Switch branch" },
|
|
86
91
|
{ value: "list", label: "List branches" },
|
|
87
92
|
{ value: "viz", label: "Refresh map" },
|
|
@@ -121,6 +126,19 @@ async function runHubAction(cwd: string, action: string): Promise<void> {
|
|
|
121
126
|
await dispatch(cwd, ["finish", "-y", push ? "-p" : "-P"]);
|
|
122
127
|
return;
|
|
123
128
|
}
|
|
129
|
+
if (action === "release") {
|
|
130
|
+
const bumpType = await selectPrompt<"patch" | "minor" | "major">({
|
|
131
|
+
message: "Bump version",
|
|
132
|
+
options: [
|
|
133
|
+
{ label: "patch (x.y.Z)", value: "patch" },
|
|
134
|
+
{ label: "minor (x.Y.0)", value: "minor" },
|
|
135
|
+
{ label: "major (X.0.0)", value: "major" },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
const push = await confirmPrompt({ message: "Push after release?", initialValue: false });
|
|
139
|
+
await dispatch(cwd, ["release", "up", bumpType, "-y", push ? "-p" : "-P"]);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
124
142
|
if (action === "sync") {
|
|
125
143
|
const rebase = await confirmPrompt({
|
|
126
144
|
message: "Rebase onto base (instead of merge)?",
|
package/src/parse.ts
CHANGED
|
@@ -234,7 +234,7 @@ function resolveName(
|
|
|
234
234
|
if (shell === "bash" || shell === "zsh" || shell === "fish") return shell;
|
|
235
235
|
return undefined;
|
|
236
236
|
}
|
|
237
|
-
if (command === "bump") {
|
|
237
|
+
if (command === "bump" || command === "release") {
|
|
238
238
|
const dir = positionals[skip];
|
|
239
239
|
if (dir === "up" || dir === "down") return dir;
|
|
240
240
|
return undefined;
|
|
@@ -249,8 +249,8 @@ function resolveBump(positionals: string[]): {
|
|
|
249
249
|
direction?: "up" | "down";
|
|
250
250
|
type?: "patch" | "minor" | "major";
|
|
251
251
|
} {
|
|
252
|
-
// Skip leading
|
|
253
|
-
const skip = positionals[0] === "bump" ? 1 : 0;
|
|
252
|
+
// Skip leading command when present; with -U, positionals start at up/down
|
|
253
|
+
const skip = positionals[0] === "bump" || positionals[0] === "release" ? 1 : 0;
|
|
254
254
|
const a = positionals[skip];
|
|
255
255
|
const b = positionals[skip + 1];
|
|
256
256
|
const direction = a === "up" || a === "down" ? a : undefined;
|
|
@@ -337,7 +337,9 @@ export function parse(
|
|
|
337
337
|
const type = resolveType(command, positionals, v);
|
|
338
338
|
const name = resolveName(command, positionals, v);
|
|
339
339
|
const { direction: bumpDirection, type: bumpType } =
|
|
340
|
-
command === "bump"
|
|
340
|
+
command === "bump" || command === "release"
|
|
341
|
+
? resolveBump(positionals)
|
|
342
|
+
: { direction: undefined, type: undefined };
|
|
341
343
|
const configArgs = command === "config" ? resolveConfigArgs(positionals) : {};
|
|
342
344
|
|
|
343
345
|
const branchNames =
|
package/src/recommend.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type RecommendAction =
|
|
|
11
11
|
| "continue"
|
|
12
12
|
| "sync"
|
|
13
13
|
| "finish"
|
|
14
|
+
| "release"
|
|
14
15
|
| "pr"
|
|
15
16
|
| "start"
|
|
16
17
|
| "commit"
|
|
@@ -79,7 +80,7 @@ export function recommend(snap: VizSnapshot): Recommendation {
|
|
|
79
80
|
return {
|
|
80
81
|
action: "start",
|
|
81
82
|
label: "Start a feature (or chore / bugfix)",
|
|
82
|
-
detail: "Integration line — short-lived branches start here",
|
|
83
|
+
detail: "Integration line — short-lived branches start here; /release for quick ship to main",
|
|
83
84
|
menuLabel: "Start new work",
|
|
84
85
|
};
|
|
85
86
|
}
|
package/src/tui/HubHome.tsx
CHANGED
|
@@ -289,7 +289,7 @@ export function HubHome({
|
|
|
289
289
|
) : (
|
|
290
290
|
<Text color={MUTED}>
|
|
291
291
|
{showHelp
|
|
292
|
-
? "/init /start /sync /pr /finish /doctor /info /help · esc clear · ctrl+c quit"
|
|
292
|
+
? "/init /start /sync /pr /finish /release /doctor /info /help · esc clear · ctrl+c quit"
|
|
293
293
|
: "? for shortcuts · / for command menu"}
|
|
294
294
|
</Text>
|
|
295
295
|
)}
|
|
@@ -372,6 +372,7 @@ function buildActions(snap: VizSnapshot | null, recommended: RecommendAction | n
|
|
|
372
372
|
push("sync", "Sync with base", "/sync");
|
|
373
373
|
push("pr", "Open pull request", "/pr");
|
|
374
374
|
push("finish", "Finish / merge branch", "/finish");
|
|
375
|
+
if (snap?.current === snap?.dev) push("release", "Quick release to main", "/release");
|
|
375
376
|
if (snap?.suspended) push("continue", "Continue suspended", "/continue");
|
|
376
377
|
push("switch", "Switch branch", "/switch");
|
|
377
378
|
push("list", "List branches", "/list");
|
package/src/tui/HubShell.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
FinishFlow,
|
|
12
12
|
InitFlow,
|
|
13
13
|
ListFlow,
|
|
14
|
+
ReleaseFlow,
|
|
14
15
|
StartFlow,
|
|
15
16
|
SwitchFlow,
|
|
16
17
|
SyncFlow,
|
|
@@ -31,6 +32,7 @@ type Screen =
|
|
|
31
32
|
| { id: "home"; flash?: string }
|
|
32
33
|
| { id: "start" }
|
|
33
34
|
| { id: "finish" }
|
|
35
|
+
| { id: "release" }
|
|
34
36
|
| { id: "sync" }
|
|
35
37
|
| { id: "list" }
|
|
36
38
|
| { id: "switch" }
|
|
@@ -87,6 +89,9 @@ export function HubShell({ cwd, onDone }: HubShellProps): React.ReactElement {
|
|
|
87
89
|
case "finish":
|
|
88
90
|
setScreen({ id: "finish" });
|
|
89
91
|
return true;
|
|
92
|
+
case "release":
|
|
93
|
+
setScreen({ id: "release" });
|
|
94
|
+
return true;
|
|
90
95
|
case "sync":
|
|
91
96
|
setScreen({ id: "sync" });
|
|
92
97
|
return true;
|
|
@@ -156,6 +161,14 @@ export function HubShell({ cwd, onDone }: HubShellProps): React.ReactElement {
|
|
|
156
161
|
setScreen({ id: "finish" });
|
|
157
162
|
return;
|
|
158
163
|
}
|
|
164
|
+
if (cmd === "release") {
|
|
165
|
+
if (rest.length > 0) {
|
|
166
|
+
runArgv(["release", "-y", "-P", ...rest]);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
setScreen({ id: "release" });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
159
172
|
if (cmd === "sync") {
|
|
160
173
|
if (rest.length > 0) {
|
|
161
174
|
runArgv(["sync", "--force", ...rest]);
|
|
@@ -217,6 +230,9 @@ export function HubShell({ cwd, onDone }: HubShellProps): React.ReactElement {
|
|
|
217
230
|
if (screen.id === "finish") {
|
|
218
231
|
return <FinishFlow onCancel={cancelWizard} onDone={runArgv} />;
|
|
219
232
|
}
|
|
233
|
+
if (screen.id === "release") {
|
|
234
|
+
return <ReleaseFlow onCancel={cancelWizard} onDone={runArgv} />;
|
|
235
|
+
}
|
|
220
236
|
if (screen.id === "sync") {
|
|
221
237
|
return <SyncFlow onCancel={cancelWizard} onDone={runArgv} />;
|
|
222
238
|
}
|
package/src/tui/flows.tsx
CHANGED
|
@@ -124,6 +124,53 @@ export function FinishFlow({
|
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Collects argv for `gflows release …` (quick release from dev) inside Ink.
|
|
129
|
+
*/
|
|
130
|
+
export function ReleaseFlow({
|
|
131
|
+
onDone,
|
|
132
|
+
onCancel,
|
|
133
|
+
}: {
|
|
134
|
+
onDone: (argv: string[]) => void;
|
|
135
|
+
onCancel: () => void;
|
|
136
|
+
}): React.ReactElement {
|
|
137
|
+
const [step, setStep] = useState<"type" | "push">("type");
|
|
138
|
+
const [bumpType, setBumpType] = useState<"patch" | "minor" | "major">("patch");
|
|
139
|
+
|
|
140
|
+
if (step === "type") {
|
|
141
|
+
return (
|
|
142
|
+
<WizardFrame title="Quick release from dev">
|
|
143
|
+
<InkSelect<"patch" | "minor" | "major">
|
|
144
|
+
message="Bump version"
|
|
145
|
+
options={[
|
|
146
|
+
{ value: "patch", label: "patch (x.y.Z)" },
|
|
147
|
+
{ value: "minor", label: "minor (x.Y.0)" },
|
|
148
|
+
{ value: "major", label: "major (X.0.0)" },
|
|
149
|
+
]}
|
|
150
|
+
onCancel={onCancel}
|
|
151
|
+
onSubmit={(t) => {
|
|
152
|
+
setBumpType(t);
|
|
153
|
+
setStep("push");
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
</WizardFrame>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<WizardFrame title={`Release · up ${bumpType}`}>
|
|
162
|
+
<InkConfirm
|
|
163
|
+
message="Push after release?"
|
|
164
|
+
initialValue={false}
|
|
165
|
+
onCancel={onCancel}
|
|
166
|
+
onSubmit={(push) => {
|
|
167
|
+
onDone(["release", "up", bumpType, "-y", push ? "-p" : "-P"]);
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
</WizardFrame>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
127
174
|
/**
|
|
128
175
|
* Collects argv for `gflows sync …` entirely inside Ink.
|
|
129
176
|
*/
|
package/src/tui/slash.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const SLASH_COMMANDS: readonly SlashCommand[] = [
|
|
|
20
20
|
{ name: "sync", hint: "Sync with base (wizard)" },
|
|
21
21
|
{ name: "pr", hint: "Open pull request" },
|
|
22
22
|
{ name: "finish", hint: "Finish / merge (wizard)" },
|
|
23
|
+
{ name: "release", hint: "Quick release from dev" },
|
|
23
24
|
{ name: "continue", hint: "Continue suspended run" },
|
|
24
25
|
{ name: "switch", hint: "Switch branch" },
|
|
25
26
|
{ name: "list", hint: "List branches" },
|
package/src/types.ts
CHANGED
|
@@ -27,6 +27,7 @@ export type Command =
|
|
|
27
27
|
| "init"
|
|
28
28
|
| "start"
|
|
29
29
|
| "finish"
|
|
30
|
+
| "release"
|
|
30
31
|
| "switch"
|
|
31
32
|
| "delete"
|
|
32
33
|
| "list"
|
|
@@ -52,6 +53,7 @@ export const ALL_COMMANDS: Command[] = [
|
|
|
52
53
|
"init",
|
|
53
54
|
"start",
|
|
54
55
|
"finish",
|
|
56
|
+
"release",
|
|
55
57
|
"switch",
|
|
56
58
|
"delete",
|
|
57
59
|
"list",
|