gflows 0.1.13 → 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.
- package/README.md +232 -263
- package/package.json +6 -2
- package/src/cli.ts +26 -28
- package/src/commands/bump.ts +81 -31
- package/src/commands/completion.ts +21 -30
- package/src/commands/delete.ts +10 -30
- package/src/commands/finish.ts +34 -58
- package/src/commands/help.ts +1 -1
- package/src/commands/init.ts +8 -13
- package/src/commands/list.ts +7 -24
- package/src/commands/start.ts +15 -18
- package/src/commands/status.ts +11 -28
- package/src/commands/switch.ts +7 -23
- package/src/config.ts +43 -23
- package/src/constants.ts +1 -1
- package/src/errors.ts +4 -2
- package/src/git.ts +20 -28
- package/src/index.ts +59 -63
- package/src/out.ts +10 -2
- package/src/types.ts +1 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gflows",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "A lightweight CLI for consistent Git branching workflows (main + dev, feature/bugfix/chore/release/hotfix).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
12
|
"test": "bun test",
|
|
13
|
-
"
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"lint": "biome check .",
|
|
15
|
+
"lint:fix": "biome check . --write",
|
|
16
|
+
"format": "biome format . --write",
|
|
14
17
|
"gflows": "bun run src/cli.ts",
|
|
15
18
|
"publish:all": "bun run scripts/publish.ts",
|
|
16
19
|
"publish:npm": "bun run scripts/publish.ts -- --npm-only",
|
|
@@ -33,6 +36,7 @@
|
|
|
33
36
|
"@inquirer/prompts": "^8"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
39
|
+
"@biomejs/biome": "^2.4.4",
|
|
36
40
|
"@types/bun": "latest",
|
|
37
41
|
"typescript": "^5"
|
|
38
42
|
}
|
package/src/cli.ts
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
import { existsSync, statSync } from "node:fs";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
import { parseArgs } from "node:util";
|
|
12
|
-
import
|
|
13
|
-
import { EXIT_OK, EXIT_USER, EXIT_GIT } from "./constants.js";
|
|
12
|
+
import { EXIT_GIT, EXIT_OK, EXIT_USER } from "./constants.js";
|
|
14
13
|
import { exitCodeForError } from "./errors.js";
|
|
14
|
+
import type { BranchType, Command, ParsedArgs } from "./types.js";
|
|
15
15
|
|
|
16
16
|
/** Last parsed args, set at start of run(); used by catch/rejection to respect -v for stack trace. */
|
|
17
17
|
let lastParsedArgs: ParsedArgs | null = null;
|
|
@@ -30,14 +30,7 @@ const COMMANDS: Command[] = [
|
|
|
30
30
|
"version",
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
-
const BRANCH_TYPES: BranchType[] = [
|
|
34
|
-
"feature",
|
|
35
|
-
"bugfix",
|
|
36
|
-
"chore",
|
|
37
|
-
"release",
|
|
38
|
-
"hotfix",
|
|
39
|
-
"spike",
|
|
40
|
-
];
|
|
33
|
+
const BRANCH_TYPES: BranchType[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
|
|
41
34
|
|
|
42
35
|
/** Short flag → command (when used as main command). */
|
|
43
36
|
const SHORT_TO_COMMAND: Record<string, Command> = {
|
|
@@ -80,6 +73,7 @@ function buildParseArgsOptions() {
|
|
|
80
73
|
// Common
|
|
81
74
|
push: { type: "boolean" as const, short: "p" },
|
|
82
75
|
noPush: { type: "boolean" as const, short: "P" },
|
|
76
|
+
"no-push": { type: "boolean" as const },
|
|
83
77
|
main: { type: "string" as const },
|
|
84
78
|
dev: { type: "string" as const },
|
|
85
79
|
remote: { type: "string" as const, short: "R" },
|
|
@@ -147,27 +141,33 @@ function editDistance(a: string, b: string): number {
|
|
|
147
141
|
const m = a.length;
|
|
148
142
|
const n = b.length;
|
|
149
143
|
const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
150
|
-
for (let i = 0; i <= m; i++)
|
|
151
|
-
|
|
144
|
+
for (let i = 0; i <= m; i++) {
|
|
145
|
+
const row = dp[i];
|
|
146
|
+
if (row) row[0] = i;
|
|
147
|
+
}
|
|
148
|
+
for (let j = 0; j <= n; j++) {
|
|
149
|
+
const row = dp[0];
|
|
150
|
+
if (row) row[j] = j;
|
|
151
|
+
}
|
|
152
152
|
for (let i = 1; i <= m; i++) {
|
|
153
153
|
for (let j = 1; j <= n; j++) {
|
|
154
154
|
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
155
|
-
dp[i]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
);
|
|
155
|
+
const v1 = dp[i - 1]?.[j] ?? 0;
|
|
156
|
+
const v2 = dp[i]?.[j - 1] ?? 0;
|
|
157
|
+
const v3 = dp[i - 1]?.[j - 1] ?? 0;
|
|
158
|
+
const rowI = dp[i];
|
|
159
|
+
if (rowI) rowI[j] = Math.min(v1 + 1, v2 + 1, v3 + cost);
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
return dp[m]
|
|
162
|
+
return dp[m]?.[n] ?? 0;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/** Resolve command from positionals and short flags. Short wins if both present. */
|
|
166
166
|
function resolveCommand(
|
|
167
167
|
positionals: string[],
|
|
168
|
-
values: Record<string, string | boolean | undefined
|
|
168
|
+
values: Record<string, string | boolean | undefined>,
|
|
169
169
|
): Command | undefined {
|
|
170
|
-
for (const [
|
|
170
|
+
for (const [_short, cmd] of Object.entries(SHORT_TO_COMMAND)) {
|
|
171
171
|
const key = cmd === "delete" ? "delete" : cmd;
|
|
172
172
|
if (values[key] === true) return cmd as Command;
|
|
173
173
|
}
|
|
@@ -182,7 +182,7 @@ function resolveCommand(
|
|
|
182
182
|
function resolveType(
|
|
183
183
|
command: Command,
|
|
184
184
|
positionals: string[],
|
|
185
|
-
values: Record<string, string | boolean | undefined
|
|
185
|
+
values: Record<string, string | boolean | undefined>,
|
|
186
186
|
): BranchType | undefined {
|
|
187
187
|
if (command !== "start" && command !== "finish" && command !== "list") {
|
|
188
188
|
return undefined;
|
|
@@ -218,7 +218,7 @@ function resolveType(
|
|
|
218
218
|
function resolveName(
|
|
219
219
|
command: Command,
|
|
220
220
|
positionals: string[],
|
|
221
|
-
values: Record<string, string | boolean | undefined
|
|
221
|
+
values: Record<string, string | boolean | undefined>,
|
|
222
222
|
): string | undefined {
|
|
223
223
|
const branch = values.branch;
|
|
224
224
|
if (typeof branch === "string" && branch.trim() !== "") {
|
|
@@ -239,7 +239,7 @@ function resolveName(
|
|
|
239
239
|
}
|
|
240
240
|
if (command === "bump") {
|
|
241
241
|
const dir = positionals[skip];
|
|
242
|
-
const
|
|
242
|
+
const _typ = positionals[skip + 1];
|
|
243
243
|
if (dir === "up" || dir === "down") {
|
|
244
244
|
return dir;
|
|
245
245
|
}
|
|
@@ -254,7 +254,7 @@ function resolveName(
|
|
|
254
254
|
/** Resolve bump direction and type from positionals (bump [up|down] [patch|minor|major]). */
|
|
255
255
|
function resolveBump(
|
|
256
256
|
positionals: string[],
|
|
257
|
-
|
|
257
|
+
_values: Record<string, string | boolean | undefined>,
|
|
258
258
|
): { direction?: "up" | "down"; type?: "patch" | "minor" | "major" } {
|
|
259
259
|
const skip = positionals[0] === "bump" ? 1 : 0;
|
|
260
260
|
const a = positionals[skip];
|
|
@@ -304,9 +304,7 @@ export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
|
|
|
304
304
|
// -r context: for list → includeRemote; for start/finish → already used as type release
|
|
305
305
|
const includeRemote =
|
|
306
306
|
command === "list"
|
|
307
|
-
?
|
|
308
|
-
v["include-remote"] === true ||
|
|
309
|
-
v.release === true)
|
|
307
|
+
? v.includeRemote === true || v["include-remote"] === true || v.release === true
|
|
310
308
|
: false;
|
|
311
309
|
|
|
312
310
|
let completionShell: "bash" | "zsh" | "fish" | undefined;
|
|
@@ -324,7 +322,7 @@ export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
|
|
|
324
322
|
bumpDirection,
|
|
325
323
|
bumpType,
|
|
326
324
|
push: v.push === true,
|
|
327
|
-
noPush: v.noPush === true,
|
|
325
|
+
noPush: v.noPush === true || v["no-push"] === true,
|
|
328
326
|
main: typeof v.main === "string" && v.main.trim() !== "" ? v.main.trim() : undefined,
|
|
329
327
|
dev: typeof v.dev === "string" && v.dev.trim() !== "" ? v.dev.trim() : undefined,
|
|
330
328
|
remote: typeof v.remote === "string" ? v.remote : undefined,
|
package/src/commands/bump.ts
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bump command: bump or rollback
|
|
2
|
+
* Bump command: bump or rollback package version (patch/minor/major).
|
|
3
|
+
* Supports monorepos: discovers all package.json and jsr.json under cwd and bumps them to the same version.
|
|
3
4
|
* Keeps package.json and jsr.json in sync; no git operations.
|
|
4
5
|
* @module commands/bump
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import type { ParsedArgs } from "../types.js";
|
|
10
|
-
import type { BumpDirection, BumpType } from "../types.js";
|
|
8
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join, relative } from "node:path";
|
|
11
10
|
import { EXIT_OK, EXIT_USER } from "../constants.js";
|
|
12
11
|
import { InvalidVersionError } from "../errors.js";
|
|
13
12
|
import { hint, success } from "../out.js";
|
|
13
|
+
import type { BumpDirection, BumpType, ParsedArgs } from "../types.js";
|
|
14
14
|
|
|
15
15
|
const PACKAGE_JSON = "package.json";
|
|
16
16
|
const JSR_JSON = "jsr.json";
|
|
17
17
|
|
|
18
|
+
/** Directory names to skip when discovering package roots (monorepo). */
|
|
19
|
+
const SKIP_DIRS = new Set(["node_modules", ".git"]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Recursively finds all directories under `root` that contain a package.json.
|
|
23
|
+
* Skips node_modules and .git.
|
|
24
|
+
*/
|
|
25
|
+
function findPackageRoots(root: string): string[] {
|
|
26
|
+
const acc: string[] = [];
|
|
27
|
+
if (!existsSync(root) || !statSync(root, { throwIfNoEntry: false })?.isDirectory()) {
|
|
28
|
+
return acc;
|
|
29
|
+
}
|
|
30
|
+
if (existsSync(join(root, PACKAGE_JSON))) {
|
|
31
|
+
acc.push(root);
|
|
32
|
+
}
|
|
33
|
+
let entries: Array<{ isDirectory(): boolean; name: string }>;
|
|
34
|
+
try {
|
|
35
|
+
entries = readdirSync(root, { withFileTypes: true }) as Array<{
|
|
36
|
+
isDirectory(): boolean;
|
|
37
|
+
name: string;
|
|
38
|
+
}>;
|
|
39
|
+
} catch {
|
|
40
|
+
return acc;
|
|
41
|
+
}
|
|
42
|
+
for (const e of entries) {
|
|
43
|
+
if (!e.isDirectory() || SKIP_DIRS.has(e.name)) continue;
|
|
44
|
+
acc.push(...findPackageRoots(join(root, e.name)));
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}
|
|
48
|
+
|
|
18
49
|
/** Semver triplet. */
|
|
19
50
|
interface Semver {
|
|
20
51
|
major: number;
|
|
@@ -30,18 +61,16 @@ function parseVersion(version: string): Semver {
|
|
|
30
61
|
const trimmed = version.trim();
|
|
31
62
|
const normalized = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
|
|
32
63
|
const parts = normalized.split(".");
|
|
33
|
-
if (
|
|
34
|
-
parts.length !== 3 ||
|
|
35
|
-
parts.some((p) => !/^\d+$/.test(p))
|
|
36
|
-
) {
|
|
64
|
+
if (parts.length !== 3 || parts.some((p) => !/^\d+$/.test(p))) {
|
|
37
65
|
throw new InvalidVersionError(
|
|
38
|
-
`Invalid version '${version}'. Expected format: X.Y.Z or vX.Y.Z (e.g. 1.2.3 or v1.2.3)
|
|
66
|
+
`Invalid version '${version}'. Expected format: X.Y.Z or vX.Y.Z (e.g. 1.2.3 or v1.2.3).`,
|
|
39
67
|
);
|
|
40
68
|
}
|
|
69
|
+
const [p0, p1, p2] = parts;
|
|
41
70
|
return {
|
|
42
|
-
major: parseInt(
|
|
43
|
-
minor: parseInt(
|
|
44
|
-
patch: parseInt(
|
|
71
|
+
major: parseInt(p0 ?? "0", 10),
|
|
72
|
+
minor: parseInt(p1 ?? "0", 10),
|
|
73
|
+
patch: parseInt(p2 ?? "0", 10),
|
|
45
74
|
};
|
|
46
75
|
}
|
|
47
76
|
|
|
@@ -103,7 +132,7 @@ function readPackageVersion(dir: string): { raw: string; semver: Semver } {
|
|
|
103
132
|
const path = join(dir, PACKAGE_JSON);
|
|
104
133
|
if (!existsSync(path)) {
|
|
105
134
|
throw new InvalidVersionError(
|
|
106
|
-
`No package.json found at ${path}. Run from project root or use -C <dir
|
|
135
|
+
`No package.json found at ${path}. Run from project root or use -C <dir>.`,
|
|
107
136
|
);
|
|
108
137
|
}
|
|
109
138
|
const raw = readFileSync(path, "utf-8");
|
|
@@ -111,7 +140,7 @@ function readPackageVersion(dir: string): { raw: string; semver: Semver } {
|
|
|
111
140
|
const version = data.version;
|
|
112
141
|
if (typeof version !== "string" || version.trim() === "") {
|
|
113
142
|
throw new InvalidVersionError(
|
|
114
|
-
|
|
143
|
+
'package.json has no valid \'version\' field. Add a "version" field (e.g. "0.0.0") to package.json.',
|
|
115
144
|
);
|
|
116
145
|
}
|
|
117
146
|
const semver = parseVersion(version);
|
|
@@ -127,7 +156,7 @@ function writePackageVersion(dir: string, newVersion: string): void {
|
|
|
127
156
|
const raw = readFileSync(path, "utf-8");
|
|
128
157
|
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
129
158
|
data.version = newVersion;
|
|
130
|
-
writeFileSync(path, JSON.stringify(data, null, 2)
|
|
159
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
131
160
|
}
|
|
132
161
|
|
|
133
162
|
/**
|
|
@@ -139,7 +168,7 @@ function syncJsrVersion(dir: string, newVersion: string): boolean {
|
|
|
139
168
|
const raw = readFileSync(path, "utf-8");
|
|
140
169
|
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
141
170
|
data.version = newVersion;
|
|
142
|
-
writeFileSync(path, JSON.stringify(data, null, 2)
|
|
171
|
+
writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
143
172
|
return true;
|
|
144
173
|
}
|
|
145
174
|
|
|
@@ -154,15 +183,14 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
154
183
|
let direction: BumpDirection;
|
|
155
184
|
let type: BumpType;
|
|
156
185
|
|
|
157
|
-
const isTTY =
|
|
158
|
-
typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY;
|
|
186
|
+
const isTTY = typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY;
|
|
159
187
|
|
|
160
188
|
if (bumpDirection && bumpType) {
|
|
161
189
|
direction = bumpDirection;
|
|
162
190
|
type = bumpType;
|
|
163
191
|
} else if (!isTTY) {
|
|
164
192
|
console.error(
|
|
165
|
-
"gflows bump: when not in a TTY, both direction and type are required. Example: gflows bump up patch"
|
|
193
|
+
"gflows bump: when not in a TTY, both direction and type are required. Example: gflows bump up patch",
|
|
166
194
|
);
|
|
167
195
|
process.exit(EXIT_USER);
|
|
168
196
|
} else {
|
|
@@ -184,14 +212,33 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
184
212
|
});
|
|
185
213
|
}
|
|
186
214
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
215
|
+
let roots = findPackageRoots(cwd);
|
|
216
|
+
roots = [...roots].sort((a, b) => {
|
|
217
|
+
if (a === cwd && b !== cwd) return -1;
|
|
218
|
+
if (a !== cwd && b === cwd) return 1;
|
|
219
|
+
return a.localeCompare(b);
|
|
220
|
+
});
|
|
221
|
+
if (roots.length === 0) {
|
|
222
|
+
throw new InvalidVersionError(
|
|
223
|
+
`No package.json found under ${cwd}. Run from project root or use -C <dir>.`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
const primaryRoot = roots[0];
|
|
227
|
+
if (primaryRoot === undefined) {
|
|
228
|
+
throw new InvalidVersionError(
|
|
229
|
+
`No package.json found under ${cwd}. Run from project root or use -C <dir>.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const { raw: oldVersion, semver } = readPackageVersion(primaryRoot);
|
|
233
|
+
const newSemver = direction === "up" ? bumpUp(semver, type) : bumpDown(semver, type);
|
|
190
234
|
const newVersion = formatVersion(newSemver);
|
|
191
235
|
|
|
192
|
-
const filesToUpdate: string[] = [
|
|
193
|
-
|
|
194
|
-
filesToUpdate.push(
|
|
236
|
+
const filesToUpdate: string[] = [];
|
|
237
|
+
for (const dir of roots) {
|
|
238
|
+
filesToUpdate.push(relative(cwd, join(dir, PACKAGE_JSON)) || PACKAGE_JSON);
|
|
239
|
+
if (existsSync(join(dir, JSR_JSON))) {
|
|
240
|
+
filesToUpdate.push(relative(cwd, join(dir, JSR_JSON)) || JSR_JSON);
|
|
241
|
+
}
|
|
195
242
|
}
|
|
196
243
|
|
|
197
244
|
if (dryRun) {
|
|
@@ -202,15 +249,18 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
202
249
|
process.exit(EXIT_OK);
|
|
203
250
|
}
|
|
204
251
|
|
|
205
|
-
|
|
206
|
-
const
|
|
252
|
+
const updated: string[] = [];
|
|
253
|
+
for (const dir of roots) {
|
|
254
|
+
writePackageVersion(dir, newVersion);
|
|
255
|
+
updated.push(relative(cwd, join(dir, PACKAGE_JSON)) || PACKAGE_JSON);
|
|
256
|
+
if (syncJsrVersion(dir, newVersion)) {
|
|
257
|
+
updated.push(relative(cwd, join(dir, JSR_JSON)) || JSR_JSON);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
207
260
|
|
|
208
261
|
if (!quiet) {
|
|
209
262
|
success(`Bumped version: ${oldVersion} → ${newVersion}`);
|
|
210
|
-
const updated = [PACKAGE_JSON];
|
|
211
|
-
if (jsrUpdated) updated.push(JSR_JSON);
|
|
212
263
|
success(`Updated: ${updated.join(", ")}`);
|
|
213
|
-
// Hint: suggest next step — commit and start release branch
|
|
214
264
|
hint("Commit the change, then run gflows start release vX.Y.Z to release.");
|
|
215
265
|
}
|
|
216
266
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @module commands/completion
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ParsedArgs } from "../types.js";
|
|
9
8
|
import { EXIT_USER } from "../constants.js";
|
|
9
|
+
import type { ParsedArgs } from "../types.js";
|
|
10
10
|
|
|
11
11
|
const COMMANDS = [
|
|
12
12
|
"init",
|
|
@@ -22,14 +22,7 @@ const COMMANDS = [
|
|
|
22
22
|
"version",
|
|
23
23
|
];
|
|
24
24
|
|
|
25
|
-
const BRANCH_TYPES = [
|
|
26
|
-
"feature",
|
|
27
|
-
"bugfix",
|
|
28
|
-
"chore",
|
|
29
|
-
"release",
|
|
30
|
-
"hotfix",
|
|
31
|
-
"spike",
|
|
32
|
-
];
|
|
25
|
+
const BRANCH_TYPES = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
|
|
33
26
|
|
|
34
27
|
const COMPLETION_SHELLS = ["bash", "zsh", "fish"] as const;
|
|
35
28
|
|
|
@@ -46,7 +39,7 @@ function bashScript(): string {
|
|
|
46
39
|
_gflows() {
|
|
47
40
|
local cur prev words cword cmd_idx cmd
|
|
48
41
|
words=(${D}COMP_WORDS[@]})
|
|
49
|
-
cword
|
|
42
|
+
cword=$COMP_CWORD
|
|
50
43
|
cur="${D}words[cword]:-}"
|
|
51
44
|
prev="${D}words[cword-1]:-}"
|
|
52
45
|
|
|
@@ -72,58 +65,58 @@ _gflows() {
|
|
|
72
65
|
break
|
|
73
66
|
fi
|
|
74
67
|
done
|
|
75
|
-
if [[ -n "
|
|
76
|
-
gflows -C "
|
|
68
|
+
if [[ -n "$path" ]]; then
|
|
69
|
+
gflows -C "$path" list 2>/dev/null
|
|
77
70
|
else
|
|
78
71
|
gflows list 2>/dev/null
|
|
79
72
|
fi
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
# Completing -C/--path value: suggest directories
|
|
83
|
-
if [[ "
|
|
76
|
+
if [[ "$prev" == "-C" ]] || [[ "$prev" == "--path" ]]; then
|
|
84
77
|
compopt -o dirnames 2>/dev/null
|
|
85
|
-
COMPREPLY=($(compgen -d -S / -- "
|
|
78
|
+
COMPREPLY=($(compgen -d -S / -- "$cur"))
|
|
86
79
|
return
|
|
87
80
|
fi
|
|
88
81
|
|
|
89
82
|
# First positional: command
|
|
90
83
|
if (( cword == cmd_idx )); then
|
|
91
|
-
COMPREPLY=($(compgen -W "${COMMANDS.join(" ")}" -- "
|
|
84
|
+
COMPREPLY=($(compgen -W "${COMMANDS.join(" ")}" -- "$cur"))
|
|
92
85
|
return
|
|
93
86
|
fi
|
|
94
87
|
|
|
95
88
|
# After command: type/name/branches/shell/bump by command
|
|
96
89
|
case "$cmd" in
|
|
97
90
|
completion)
|
|
98
|
-
COMPREPLY=($(compgen -W "${COMPLETION_SHELLS.join(" ")}" -- "
|
|
91
|
+
COMPREPLY=($(compgen -W "${COMPLETION_SHELLS.join(" ")}" -- "$cur"))
|
|
99
92
|
;;
|
|
100
93
|
start)
|
|
101
94
|
if (( cword == cmd_idx + 1 )); then
|
|
102
|
-
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "
|
|
95
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "$cur"))
|
|
103
96
|
else
|
|
104
97
|
COMPREPLY=()
|
|
105
98
|
fi
|
|
106
99
|
;;
|
|
107
100
|
finish)
|
|
108
|
-
if [[ "
|
|
109
|
-
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "
|
|
101
|
+
if [[ "$prev" == "-B" ]] || [[ "$prev" == "--branch" ]]; then
|
|
102
|
+
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "$cur"))
|
|
110
103
|
elif (( cword == cmd_idx + 1 )); then
|
|
111
|
-
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "
|
|
104
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "$cur"))
|
|
112
105
|
else
|
|
113
106
|
COMPREPLY=()
|
|
114
107
|
fi
|
|
115
108
|
;;
|
|
116
109
|
list)
|
|
117
|
-
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "
|
|
110
|
+
COMPREPLY=($(compgen -W "${BRANCH_TYPES.join(" ")}" -- "$cur"))
|
|
118
111
|
;;
|
|
119
112
|
switch|delete)
|
|
120
|
-
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "
|
|
113
|
+
COMPREPLY=($(compgen -W "$(_gflows_path)" -- "$cur"))
|
|
121
114
|
;;
|
|
122
115
|
bump)
|
|
123
116
|
if (( cword == cmd_idx + 1 )); then
|
|
124
|
-
COMPREPLY=($(compgen -W "${BUMP_DIRECTIONS.join(" ")}" -- "
|
|
117
|
+
COMPREPLY=($(compgen -W "${BUMP_DIRECTIONS.join(" ")}" -- "$cur"))
|
|
125
118
|
elif (( cword == cmd_idx + 2 )); then
|
|
126
|
-
COMPREPLY=($(compgen -W "${BUMP_TYPES.join(" ")}" -- "
|
|
119
|
+
COMPREPLY=($(compgen -W "${BUMP_TYPES.join(" ")}" -- "$cur"))
|
|
127
120
|
else
|
|
128
121
|
COMPREPLY=()
|
|
129
122
|
fi
|
|
@@ -169,7 +162,7 @@ _gflows() {
|
|
|
169
162
|
|
|
170
163
|
case $state in
|
|
171
164
|
args)
|
|
172
|
-
cur
|
|
165
|
+
cur=$words[CURRENT]
|
|
173
166
|
cmd_idx=1
|
|
174
167
|
while (( cmd_idx < CURRENT )); do
|
|
175
168
|
if [[ "${D}words[cmd_idx]}" == "-C" ]] || [[ "${D}words[cmd_idx]}" == "--path" ]]; then
|
|
@@ -190,7 +183,7 @@ _gflows() {
|
|
|
190
183
|
;;
|
|
191
184
|
finish)
|
|
192
185
|
if [[ "${D}words[CURRENT-1]}" == "-B" ]] || [[ "${D}words[CURRENT-1]}" == "--branch" ]]; then
|
|
193
|
-
_values "branch"
|
|
186
|
+
_values "branch" $(_gflows_list_branches)
|
|
194
187
|
else
|
|
195
188
|
_values "type" ${BRANCH_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
196
189
|
fi
|
|
@@ -199,7 +192,7 @@ _gflows() {
|
|
|
199
192
|
_values "type" ${BRANCH_TYPES.map((t) => `"${t}"`).join(" ")}
|
|
200
193
|
;;
|
|
201
194
|
switch|delete)
|
|
202
|
-
_values "branch"
|
|
195
|
+
_values "branch" $(_gflows_list_branches)
|
|
203
196
|
;;
|
|
204
197
|
bump)
|
|
205
198
|
if [[ "${D}words[CURRENT-1]}" == "up" ]] || [[ "${D}words[CURRENT-1]}" == "down" ]]; then
|
|
@@ -326,9 +319,7 @@ complete -c gflows -f -n "__fish_seen_subcommand_from ${COMMANDS.join(" ")}" -l
|
|
|
326
319
|
export async function run(args: ParsedArgs): Promise<void> {
|
|
327
320
|
const shell = args.completionShell;
|
|
328
321
|
if (!shell) {
|
|
329
|
-
console.error(
|
|
330
|
-
"gflows: completion requires a shell. Use: gflows completion bash | zsh | fish"
|
|
331
|
-
);
|
|
322
|
+
console.error("gflows: completion requires a shell. Use: gflows completion bash | zsh | fish");
|
|
332
323
|
process.exit(EXIT_USER);
|
|
333
324
|
}
|
|
334
325
|
|
package/src/commands/delete.ts
CHANGED
|
@@ -3,38 +3,24 @@
|
|
|
3
3
|
* @module commands/delete
|
|
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 { CannotDeleteMainOrDevError, NotRepoError } from "../errors.js";
|
|
11
|
-
import {
|
|
12
|
-
branchList,
|
|
13
|
-
deleteBranch,
|
|
14
|
-
resolveRepoRoot,
|
|
15
|
-
} from "../git.js";
|
|
9
|
+
import { branchList, deleteBranch, 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
|
/**
|
|
@@ -58,16 +44,12 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
58
44
|
});
|
|
59
45
|
const { main, dev, prefixes } = config;
|
|
60
46
|
|
|
61
|
-
const fromPositionals = (rawBranchNames ?? [])
|
|
62
|
-
.map((s) => s.trim())
|
|
63
|
-
.filter(Boolean);
|
|
47
|
+
const fromPositionals = (rawBranchNames ?? []).map((s) => s.trim()).filter(Boolean);
|
|
64
48
|
|
|
65
49
|
if (fromPositionals.length > 0) {
|
|
66
50
|
for (const branch of fromPositionals) {
|
|
67
51
|
if (branch === main || branch === dev) {
|
|
68
|
-
throw new CannotDeleteMainOrDevError(
|
|
69
|
-
`Cannot delete the long-lived branch '${branch}'.`
|
|
70
|
-
);
|
|
52
|
+
throw new CannotDeleteMainOrDevError(`Cannot delete the long-lived branch '${branch}'.`);
|
|
71
53
|
}
|
|
72
54
|
}
|
|
73
55
|
for (const branch of fromPositionals) {
|
|
@@ -89,7 +71,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
89
71
|
const isTTY = typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY;
|
|
90
72
|
if (!isTTY) {
|
|
91
73
|
console.error(
|
|
92
|
-
"gflows delete: no branch name(s) given and stdin is not a TTY. Pass branch name(s) (e.g. gflows delete feature/my-branch) or run from an interactive terminal."
|
|
74
|
+
"gflows delete: no branch name(s) given and stdin is not a TTY. Pass branch name(s) (e.g. gflows delete feature/my-branch) or run from an interactive terminal.",
|
|
93
75
|
);
|
|
94
76
|
process.exit(EXIT_USER);
|
|
95
77
|
}
|
|
@@ -103,7 +85,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
103
85
|
if (workflowBranches.length === 0) {
|
|
104
86
|
if (!quiet) {
|
|
105
87
|
console.error(
|
|
106
|
-
"No workflow branches to delete. Create one with 'gflows start <type> <name>'."
|
|
88
|
+
"No workflow branches to delete. Create one with 'gflows start <type> <name>'.",
|
|
107
89
|
);
|
|
108
90
|
}
|
|
109
91
|
process.exit(EXIT_USER);
|
|
@@ -124,9 +106,7 @@ export async function run(args: ParsedArgs): Promise<void> {
|
|
|
124
106
|
|
|
125
107
|
for (const branch of chosen) {
|
|
126
108
|
if (branch === main || branch === dev) {
|
|
127
|
-
throw new CannotDeleteMainOrDevError(
|
|
128
|
-
`Cannot delete the long-lived branch '${branch}'.`
|
|
129
|
-
);
|
|
109
|
+
throw new CannotDeleteMainOrDevError(`Cannot delete the long-lived branch '${branch}'.`);
|
|
130
110
|
}
|
|
131
111
|
}
|
|
132
112
|
|