gflows 0.1.8 → 0.1.10

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 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**
@@ -110,12 +115,17 @@ You can override branch names and prefixes via [configuration](#configuration).
110
115
 
111
116
  ## Quick start
112
117
 
113
- **1. One-time setup** in your repo (ensure `main` exists and create `dev`):
118
+ **1. One-time setup** In your repo, ensure `main` exists and create `dev`:
114
119
 
115
120
  ```bash
116
121
  gflows init
117
- gflows init --push # also push dev to origin
118
- gflows init --dry-run # show what would be done, no writes
122
+ ```
123
+
124
+ Optional: use `--push` to push `dev` to the remote, or `--dry-run` to preview. To use different branch or remote names, pass `--main`, `--dev`, and `--remote`; those values are written to `.gflows.json` for future runs.
125
+
126
+ ```bash
127
+ gflows init --push # create dev, then push to origin
128
+ gflows init --main main --dev develop --remote origin # custom names, persisted to .gflows.json
119
129
  ```
120
130
 
121
131
  **2. Daily development** (feature → dev):
@@ -151,19 +161,21 @@ gflows finish hotfix --push # merge to main, then dev; tag v1.3.1;
151
161
 
152
162
  ### Summary table
153
163
 
154
- | Command | Short | Description |
155
- |-------------|-------|--------------|
156
- | `init` | `-I` | Ensure main exists; create dev from main. |
157
- | `start` | `-S` | Create a workflow branch (requires type + name). |
158
- | `finish` | `-F` | Merge branch into target(s), optional tag (release/hotfix), delete, push. |
159
- | `switch` | `-W` | Switch to a workflow branch (picker or name). |
160
- | `delete` | `-L` | Delete local workflow branch(es). Never main/dev. |
161
- | `list` | `-l` | List workflow branches; optional type filter and remote. |
162
- | `bump` | | Bump or rollback package version (patch/minor/major). |
163
- | `completion` | — | Print shell completion script (bash \| zsh \| fish). |
164
- | `status` | `-t` | Show current branch, type, base, merge target(s), ahead/behind. |
165
- | `help` | `-h` | Show usage and quick reference. |
166
- | `version` | `-V` | Show version. |
164
+
165
+ | Command | Short | Description |
166
+ | ------------ | ----- | ------------------------------------------------------------------------- |
167
+ | `init` | `-I` | Ensure main exists; create dev from main. |
168
+ | `start` | `-S` | Create a workflow branch (requires type + name). |
169
+ | `finish` | `-F` | Merge branch into target(s), optional tag (release/hotfix), delete, push. |
170
+ | `switch` | `-W` | Switch to a workflow branch (picker or name). |
171
+ | `delete` | `-L` | Delete local workflow branch(es). Never main/dev. |
172
+ | `list` | `-l` | List workflow branches; optional type filter and remote. |
173
+ | `bump` | — | Bump or rollback package version (patch/minor/major). |
174
+ | `completion` | | Print shell completion script (bash | zsh | fish). |
175
+ | `status` | `-t` | Show current branch, type, base, merge target(s), ahead/behind. |
176
+ | `help` | `-h` | Show usage and quick reference. |
177
+ | `version` | `-V` | Show version. |
178
+
167
179
 
168
180
  **Branch types (for start/finish/list):** `feature` (`-f`), `bugfix` (`-b`), `chore` (`-c`), `release` (`-r`), `hotfix` (`-x`), `spike` (`-e`).
169
181
 
@@ -173,16 +185,19 @@ gflows finish hotfix --push # merge to main, then dev; tag v1.3.1;
173
185
 
174
186
  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.
175
187
 
188
+ 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
+
176
190
  **Examples:**
177
191
 
178
192
  ```bash
179
193
  gflows init
180
194
  gflows init --push # push dev to remote after creating
195
+ gflows init --main main --dev develop --remote origin # create dev branch "develop", persist to .gflows.json
181
196
  gflows init -C ../other-repo # run in another directory
182
197
  gflows init --dry-run # log intended actions only
183
198
  ```
184
199
 
185
- **Flags:** `--push`, `-C`/`--path <dir>`, `--dry-run`, `-v`/`--verbose`, `-q`/`--quiet`.
200
+ **Flags:** `--push`, `--main <name>`, `--dev <name>`, `-R`/`--remote <name>` (main/dev/remote are persisted to `.gflows.json` when provided), `-C`/`--path <dir>`, `--dry-run`, `-v`/`--verbose`, `-q`/`--quiet`.
186
201
 
187
202
  ---
188
203
 
@@ -342,14 +357,16 @@ gflows -V
342
357
 
343
358
  ## Branch types in detail
344
359
 
345
- | Type | Short | Base (default) | With `-o main` | Merge target(s) | Tag |
346
- |----------|-------|------------------|-----------------|------------------------|-------|
347
- | feature | `-f` | dev | | dev | no |
348
- | bugfix | `-b` | dev | main | dev (or main if from main) | no |
349
- | chore | `-c` | dev | | dev | no |
350
- | release | `-r` | dev | — | main, then dev | yes |
351
- | hotfix | `-x` | main | — | main, then dev | yes |
352
- | spike | `-e` | dev | — | dev | no |
360
+
361
+ | Type | Short | Base (default) | With `-o main` | Merge target(s) | Tag |
362
+ | ------- | ----- | -------------- | -------------- | -------------------------- | --- |
363
+ | feature | `-f` | dev | | dev | no |
364
+ | bugfix | `-b` | dev | main | dev (or main if from main) | no |
365
+ | chore | `-c` | dev | — | dev | no |
366
+ | release | `-r` | dev | — | main, then dev | yes |
367
+ | hotfix | `-x` | main | — | main, then dev | yes |
368
+ | spike | `-e` | dev | — | dev | no |
369
+
353
370
 
354
371
  - **feature** — New functionality; branches from dev, merges to dev.
355
372
  - **bugfix** — Bug fixes. Usually from dev → dev. Use `-o main` when fixing a bug on production (branch from main, merge to main then dev).
@@ -369,9 +386,8 @@ Configuration is **optional**. Override branch names, remote, and branch **prefi
369
386
  **Resolution order** (later overrides earlier):
370
387
 
371
388
  1. Built-in defaults (`main`, `dev`, `origin`, and default prefixes).
372
- 2. Repo config file: **`.gflows.json`** in repo root, or **`gflows`** key in **`package.json`**.
373
- 3. Environment: **`GFLOWS_MAIN`**, **`GFLOWS_DEV`**, **`GFLOWS_REMOTE`**.
374
- 4. CLI (e.g. `-R`/`--remote` for push).
389
+ 2. Repo config file: `**.gflows.json`** in repo root, or `**gflows**` key in `**package.json**`.
390
+ 3. CLI (e.g. `--main`, `--dev`, `-R`/`--remote`).
375
391
 
376
392
  Only include keys you want to override; the rest stay default. Invalid or malformed config is ignored (with an optional warning when using `-v`).
377
393
 
@@ -427,21 +443,12 @@ Only include keys you want to override; the rest stay default. Invalid or malfor
427
443
  }
428
444
  ```
429
445
 
430
- ### Environment variables
431
-
432
- ```bash
433
- export GFLOWS_MAIN=main
434
- export GFLOWS_DEV=develop
435
- export GFLOWS_REMOTE=origin
436
- gflows init
437
- ```
438
-
439
446
  ---
440
447
 
441
448
  ## Scripting and CI
442
449
 
443
450
  - **Non-interactive:** When stdin is **not** a TTY, gflows does **not** show pickers (e.g. for switch, delete, or finish `-B`). You must pass branch names explicitly or the command exits with a clear message (exit code 1).
444
- - **Skip confirmations:** Use **`-y`/`--yes`** to accept defaults (e.g. "Delete branch after finish?" → no delete unless you also passed `-D`).
451
+ - **Skip confirmations:** Use `**-y`/`--yes`** to accept defaults (e.g. "Delete branch after finish?" → no delete unless you also passed `-D`).
445
452
  - **Exit codes:** Use them in scripts: `0` success, `1` usage/validation, `2` Git/system error.
446
453
 
447
454
  **Examples:**
@@ -471,11 +478,13 @@ gflows -C ./packages/api list
471
478
 
472
479
  ## Exit codes
473
480
 
474
- | Code | Meaning | Typical causes |
475
- |------|---------|-----------------|
476
- | **0** | Success | Command completed without error. |
477
- | **1** | Usage / validation | Missing type or name for `start`; invalid branch name or version format; wrong/missing positionals for non-TTY. |
478
- | **2** | Git / system | Not a Git repo; branch not found; dirty working tree (start without `--force`); merge conflict on finish; rebase/merge in progress; detached HEAD; finish on main/dev; tag already exists; push failed after merge. |
481
+
482
+ | Code | Meaning | Typical causes |
483
+ | ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
484
+ | **0** | Success | Command completed without error. |
485
+ | **1** | Usage / validation | Missing type or name for `start`; invalid branch name or version format; wrong/missing positionals for non-TTY. |
486
+ | **2** | Git / system | Not a Git repo; branch not found; dirty working tree (start without `--force`); merge conflict on finish; rebase/merge in progress; detached HEAD; finish on main/dev; tag already exists; push failed after merge. |
487
+
479
488
 
480
489
  **Validation (exit 1):** Invalid version (e.g. `start release foo`), invalid branch name (e.g. `feature/bad..name`), or missing required args in non-interactive mode.
481
490
 
@@ -485,19 +494,21 @@ gflows -C ./packages/api list
485
494
 
486
495
  ## Troubleshooting
487
496
 
488
- | Situation | What to do |
489
- |-----------|------------|
490
- | **"Not a Git repository"** | Run from a directory that contains `.git`, or use `-C <path>` to point to the repo root. |
491
- | **"Working tree has uncommitted changes"** | Commit or stash changes before `start`, or use `--force` (only when you intend to carry uncommitted work). |
492
- | **"Merge conflict while merging into …"** | Resolve conflicts in your working tree, then run `git add` and `git merge --continue` (or `git merge --abort` to cancel). Re-run `gflows finish` after resolving if needed. |
493
- | **"Tag v1.2.3 already exists"** | Use a new version for the release/hotfix, or delete/move the tag if you know what you’re doing. gflows does not overwrite tags. |
494
- | **"Cannot finish the long-lived branch main/dev"** | You’re on main or dev. Checkout a workflow branch first, or use `-B <branch>` to finish another branch. |
495
- | **"HEAD is detached"** | Checkout a branch (e.g. `git checkout dev`) before running `start` or `finish`. |
496
- | **"A rebase or merge is in progress"** | Run `git rebase --abort` or `git merge --abort`, or complete the operation, then retry gflows. |
497
- | **Picker not showing / "requires branch name"** | Without a TTY, gflows does not show interactive pickers. Pass the branch name explicitly (e.g. `-B feature/xyz` or `gflows switch feature/xyz`). |
498
- | **Wrong remote or branch names** | Set `GFLOWS_MAIN`, `GFLOWS_DEV`, `GFLOWS_REMOTE` or use `.gflows.json` / `package.json` "gflows" key. Use `-R` for one-off remote override. |
499
497
 
500
- Use **`-v`/`--verbose`** to see git commands and extra diagnostics; combine with the error message to pinpoint the cause.
498
+ | Situation | What to do |
499
+ | -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
500
+ | **"Not a Git repository"** | Run from a directory that contains `.git`, or use `-C <path>` to point to the repo root. |
501
+ | **"Working tree has uncommitted changes"** | Commit or stash changes before `start`, or use `--force` (only when you intend to carry uncommitted work). |
502
+ | **"Merge conflict while merging into …"** | Resolve conflicts in your working tree, then run `git add` and `git merge --continue` (or `git merge --abort` to cancel). Re-run `gflows finish` after resolving if needed. |
503
+ | **"Tag v1.2.3 already exists"** | Use a new version for the release/hotfix, or delete/move the tag if you know what you’re doing. gflows does not overwrite tags. |
504
+ | **"Cannot finish the long-lived branch main/dev"** | You’re on main or dev. Checkout a workflow branch first, or use `-B <branch>` to finish another branch. |
505
+ | **"HEAD is detached"** | Checkout a branch (e.g. `git checkout dev`) before running `start` or `finish`. |
506
+ | **"A rebase or merge is in progress"** | Run `git rebase --abort` or `git merge --abort`, or complete the operation, then retry gflows. |
507
+ | **Picker not showing / "requires branch name"** | Without a TTY, gflows does not show interactive pickers. Pass the branch name explicitly (e.g. `-B feature/xyz` or `gflows switch feature/xyz`). |
508
+ | **Wrong remote or branch names** | Use `.gflows.json` or `package.json` "gflows" key, or `gflows init --main … --dev … --remote …`. Use `-R` for one-off remote override. |
509
+
510
+
511
+ Use `**-v`/`--verbose`** to see git commands and extra diagnostics; combine with the error message to pinpoint the cause.
501
512
 
502
513
  ---
503
514
 
@@ -551,22 +562,22 @@ bun run publish:all -- --force # skip pre-publish checks (clean tree, branch m
551
562
 
552
563
  1. Ensure you’re on **main** with a clean working tree (or use `--force` when intentional).
553
564
  2. Bump version and tag in Git yourself, or use gflows bump + your own commit:
554
- ```bash
565
+ ```bash
555
566
  gflows bump up minor --dry-run # confirm
556
567
  gflows bump up minor
557
568
  git add package.json jsr.json && git commit -m "chore: bump to 1.4.0"
558
- ```
569
+ ```
559
570
  3. Run the publish script:
560
- ```bash
571
+ ```bash
561
572
  bun run publish:all -- --dry-run # verify
562
573
  bun run publish:all
563
- ```
574
+ ```
564
575
  4. Optionally push main and tags:
565
- ```bash
576
+ ```bash
566
577
  git push origin main --tags
567
- ```
578
+ ```
568
579
 
569
- **Version sync:** The script reads `version` from **package.json** and writes it to **jsr.json** before publishing so the two registries never drift. Use **`gflows bump`** to change the version; the script does not bump for you.
580
+ **Version sync:** The script reads `version` from **package.json** and writes it to **jsr.json** before publishing so the two registries never drift. Use `**gflows bump`** to change the version; the script does not bump for you.
570
581
 
571
582
  ### JSR score (100%)
572
583
 
@@ -574,10 +585,10 @@ To get a 100% score on [JSR](https://jsr.io/@alialnaghmoush/gflows/score):
574
585
 
575
586
  1. **Description** — Set in `jsr.json` (already added). If the score still shows 0/1, set the description in [package settings](https://jsr.io/@alialnaghmoush/gflows/settings) on JSR.
576
587
  2. **Runtime compatibility** — In [package settings](https://jsr.io/@alialnaghmoush/gflows/settings), open “Runtime compatibility” and mark at least **Bun** and **Node.js** (or others) as **Supported**.
577
- 3. **Provenance** — The repo includes [`.github/workflows/publish.yml`](.github/workflows/publish.yml) (test, lint, then publish to npm and JSR). In JSR package settings, **link** the package to this GitHub repository. Add `NPM_TOKEN` in repo Secrets for npm. After that, pushes to `main` run CI and publish both registries; JSR records provenance.
588
+ 3. **Provenance** — The repo includes `[.github/workflows/publish.yml](.github/workflows/publish.yml)` (test, lint, then publish to npm and JSR). In JSR package settings, **link** the package to this GitHub repository. Add `NPM_TOKEN` in repo Secrets for npm. After that, pushes to `main` run CI and publish both registries; JSR records provenance.
578
589
 
579
590
  ---
580
591
 
581
592
  ## License
582
593
 
583
- See [LICENSE](LICENSE) in this repository.
594
+ See [LICENSE](LICENSE) in this repository.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gflows",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",
package/src/cli.ts CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env bun
2
+
1
3
  /**
2
4
  * CLI entrypoint for gflows. Parses argv, resolves -C/path, dispatches to commands,
3
5
  * and ensures exit codes and unhandled rejections are handled.
@@ -78,6 +80,8 @@ function buildParseArgsOptions() {
78
80
  // Common
79
81
  push: { type: "boolean" as const, short: "p" },
80
82
  noPush: { type: "boolean" as const, short: "P" },
83
+ main: { type: "string" as const },
84
+ dev: { type: "string" as const },
81
85
  remote: { type: "string" as const, short: "R" },
82
86
  from: { type: "string" as const, short: "o" },
83
87
  branch: { type: "string" as const, short: "B" },
@@ -276,6 +280,8 @@ export function parse(argv: string[] = Bun.argv.slice(2)): ParsedArgs {
276
280
  bumpType,
277
281
  push: v.push === true,
278
282
  noPush: v.noPush === true,
283
+ main: typeof v.main === "string" && v.main.trim() !== "" ? v.main.trim() : undefined,
284
+ dev: typeof v.dev === "string" && v.dev.trim() !== "" ? v.dev.trim() : undefined,
279
285
  remote: typeof v.remote === "string" ? v.remote : undefined,
280
286
  branch: typeof v.branch === "string" ? v.branch : undefined,
281
287
  yes: v.yes === true,
@@ -50,7 +50,11 @@ export async function run(args: ParsedArgs): Promise<void> {
50
50
  if (err instanceof NotRepoError) throw err;
51
51
  throw err;
52
52
  });
53
- const config = resolveConfig(root);
53
+ const config = resolveConfig(root, {
54
+ main: args.main,
55
+ dev: args.dev,
56
+ remote: args.remote,
57
+ });
54
58
  const { main, dev, prefixes } = config;
55
59
 
56
60
  const fromPositionals = (rawBranchNames ?? [])
@@ -91,7 +91,7 @@ export async function run(args: ParsedArgs): Promise<void> {
91
91
  const repoRoot = await resolveRepoRoot(args.cwd);
92
92
  const config = resolveConfig(
93
93
  repoRoot,
94
- { remote: args.remote },
94
+ { main: args.main, dev: args.dev, remote: args.remote },
95
95
  { verbose: args.verbose }
96
96
  );
97
97
 
@@ -33,7 +33,9 @@ Types: feature (-f), bugfix (-b), chore (-c), release (-r), hotfix (-x), spike (
33
33
  Common flags:
34
34
  -p, --push Push after init/start/finish
35
35
  -P, --no-push Do not push
36
- -R, --remote <name> Remote name for push
36
+ --main <name> Main branch (init: persist to .gflows.json)
37
+ --dev <name> Dev branch (init: persist to .gflows.json)
38
+ -R, --remote <name> Remote for push (init: persist to .gflows.json)
37
39
  -o, --from <branch> Base branch override (e.g. -o main for bugfix)
38
40
  -B, --branch <name> Branch name (finish: branch to finish)
39
41
  -y, --yes Skip confirmations
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Init command: ensure main exists, create dev from main, optional push and dry-run.
3
+ * Can persist main, dev, and remote to .gflows.json via --main, --dev, --remote.
3
4
  * @module commands/init
4
5
  */
5
6
 
6
- import { resolveConfig } from "../config.js";
7
+ import { resolveConfig, writeConfigFile } from "../config.js";
7
8
  import { BranchNotFoundError, NotRepoError } from "../errors.js";
8
9
  import {
9
10
  branchList,
@@ -18,14 +19,21 @@ import type { ParsedArgs } from "../types.js";
18
19
  * Runs the init command: ensure main exists, create dev from main if missing, optional push.
19
20
  * Pre-check: cwd (or -C) must be a git repo; main branch must exist (exit 2 otherwise).
20
21
  * Skips creating dev if it already exists. Supports --dry-run and --push.
22
+ * When --main, --dev, or --remote are passed, writes or updates .gflows.json with those values.
21
23
  *
22
- * @param args - Parsed CLI args (cwd, dryRun, push, noPush, remote, verbose, quiet).
24
+ * @param args - Parsed CLI args (cwd, dryRun, push, noPush, main, dev, remote, verbose, quiet).
23
25
  */
24
26
  export async function run(args: ParsedArgs): Promise<void> {
25
27
  const repoRoot = await resolveRepoRoot(args.cwd);
26
- const config = resolveConfig(repoRoot, {
27
- remote: args.remote,
28
- }, { verbose: args.verbose });
28
+ const config = resolveConfig(
29
+ repoRoot,
30
+ {
31
+ main: args.main,
32
+ dev: args.dev,
33
+ remote: args.remote,
34
+ },
35
+ { verbose: args.verbose }
36
+ );
29
37
 
30
38
  const opts = {
31
39
  dryRun: args.dryRun,
@@ -67,4 +75,16 @@ export async function run(args: ParsedArgs): Promise<void> {
67
75
  console.error(`gflows: pushed '${config.dev}' to '${config.remote}'.`);
68
76
  }
69
77
  }
78
+
79
+ const hasConfigFlags = args.main !== undefined || args.dev !== undefined || args.remote !== undefined;
80
+ if (!args.dryRun && hasConfigFlags) {
81
+ writeConfigFile(repoRoot, {
82
+ ...(args.main !== undefined && { main: args.main }),
83
+ ...(args.dev !== undefined && { dev: args.dev }),
84
+ ...(args.remote !== undefined && { remote: args.remote }),
85
+ });
86
+ if (!args.quiet) {
87
+ console.error("gflows: updated .gflows.json with provided options.");
88
+ }
89
+ }
70
90
  }
@@ -57,7 +57,11 @@ export async function run(args: ParsedArgs): Promise<void> {
57
57
  throw err;
58
58
  });
59
59
 
60
- const config = resolveConfig(root, undefined, { verbose: !!verbose });
60
+ const config = resolveConfig(
61
+ root,
62
+ { main: args.main, dev: args.dev, remote: args.remote },
63
+ { verbose: !!verbose }
64
+ );
61
65
 
62
66
  if (includeRemote && !dryRun) {
63
67
  await fetch(root, config.remote, {
@@ -53,7 +53,7 @@ export async function run(args: ParsedArgs): Promise<void> {
53
53
  const repoRoot = await resolveRepoRoot(args.cwd);
54
54
  const config = resolveConfig(
55
55
  repoRoot,
56
- { remote: args.remote },
56
+ { main: args.main, dev: args.dev, remote: args.remote },
57
57
  { verbose: args.verbose }
58
58
  );
59
59
 
@@ -74,7 +74,11 @@ export async function run(args: ParsedArgs): Promise<void> {
74
74
  throw err;
75
75
  });
76
76
 
77
- const config = resolveConfig(root, undefined, { verbose: !!verbose });
77
+ const config = resolveConfig(
78
+ root,
79
+ { main: args.main, dev: args.dev, remote: args.remote },
80
+ { verbose: !!verbose }
81
+ );
78
82
  const current = await getCurrentBranch(root, {
79
83
  dryRun: !!dryRun,
80
84
  verbose: !!verbose,
@@ -49,7 +49,11 @@ export async function run(args: ParsedArgs): Promise<void> {
49
49
  if (err instanceof NotRepoError) throw err;
50
50
  throw err;
51
51
  });
52
- const config = resolveConfig(root);
52
+ const config = resolveConfig(root, {
53
+ main: args.main,
54
+ dev: args.dev,
55
+ remote: args.remote,
56
+ });
53
57
 
54
58
  const branchName = (branch?.trim() || name?.trim() || "").trim() || undefined;
55
59
 
package/src/config.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Config resolution for gflows: defaults → repo config file → env → CLI overrides.
2
+ * Config resolution for gflows: defaults → repo config file → CLI overrides.
3
3
  * Exposes resolved main, dev, remote, and branch type prefixes for use by commands.
4
4
  * @module config
5
5
  */
6
6
 
7
- import { existsSync, readFileSync } from "node:fs";
7
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { join } from "node:path";
9
9
  import type {
10
10
  BranchPrefixes,
@@ -24,11 +24,7 @@ const CONFIG_FILE = ".gflows.json";
24
24
  const PACKAGE_JSON = "package.json";
25
25
  const GFLOWS_KEY = "gflows";
26
26
 
27
- const ENV_MAIN = "GFLOWS_MAIN";
28
- const ENV_DEV = "GFLOWS_DEV";
29
- const ENV_REMOTE = "GFLOWS_REMOTE";
30
-
31
- /** CLI overrides for main, dev, and remote (e.g. from -R/--remote or future flags). */
27
+ /** CLI overrides for main, dev, and remote (e.g. --main, --dev, -R/--remote). */
32
28
  export interface ConfigCliOverrides {
33
29
  main?: string;
34
30
  dev?: string;
@@ -121,26 +117,6 @@ function normalizeConfigFile(data: unknown): GflowsConfigFile | null {
121
117
  return out;
122
118
  }
123
119
 
124
- /**
125
- * Returns config overrides from environment (GFLOWS_MAIN, GFLOWS_DEV, GFLOWS_REMOTE).
126
- */
127
- export function getEnvConfigOverrides(): ConfigCliOverrides {
128
- const overrides: ConfigCliOverrides = {};
129
- const main = process.env[ENV_MAIN];
130
- if (typeof main === "string" && main.trim() !== "") {
131
- overrides.main = main.trim();
132
- }
133
- const dev = process.env[ENV_DEV];
134
- if (typeof dev === "string" && dev.trim() !== "") {
135
- overrides.dev = dev.trim();
136
- }
137
- const remote = process.env[ENV_REMOTE];
138
- if (typeof remote === "string" && remote.trim() !== "") {
139
- overrides.remote = remote.trim();
140
- }
141
- return overrides;
142
- }
143
-
144
120
  /**
145
121
  * Merges prefix overrides into a full Required<BranchPrefixes> (defaults + overrides).
146
122
  */
@@ -157,11 +133,11 @@ function mergePrefixes(overrides?: BranchPrefixes): Required<BranchPrefixes> {
157
133
  }
158
134
 
159
135
  /**
160
- * Resolves full config for the given directory: defaults → file → env → CLI.
136
+ * Resolves full config for the given directory: defaults → file → CLI.
161
137
  * Uses dir as the repo root for locating .gflows.json and package.json.
162
138
  *
163
139
  * @param dir - Directory to read config from (e.g. cwd or resolved -C path).
164
- * @param cliOverrides - Optional overrides from CLI (e.g. --remote).
140
+ * @param cliOverrides - Optional overrides from CLI (e.g. --main, --dev, --remote).
165
141
  * @param options - Optional { verbose } to warn when config file is missing or invalid.
166
142
  * @returns Resolved config with main, dev, remote, and full prefixes.
167
143
  */
@@ -189,11 +165,6 @@ export function resolveConfig(
189
165
  if (file.prefixes !== undefined) prefixes = mergePrefixes(file.prefixes);
190
166
  }
191
167
 
192
- const envOverrides = getEnvConfigOverrides();
193
- if (envOverrides.main !== undefined) main = envOverrides.main;
194
- if (envOverrides.dev !== undefined) dev = envOverrides.dev;
195
- if (envOverrides.remote !== undefined) remote = envOverrides.remote;
196
-
197
168
  if (cliOverrides?.main !== undefined) main = cliOverrides.main;
198
169
  if (cliOverrides?.dev !== undefined) dev = cliOverrides.dev;
199
170
  if (cliOverrides?.remote !== undefined) remote = cliOverrides.remote;
@@ -201,6 +172,55 @@ export function resolveConfig(
201
172
  return { main, dev, remote, prefixes };
202
173
  }
203
174
 
175
+ /**
176
+ * Writes or updates .gflows.json in dir with the given partial config.
177
+ * Merges with existing .gflows.json if present; only provided keys are updated.
178
+ * Skips keys with empty string values.
179
+ *
180
+ * @param dir - Repo root directory.
181
+ * @param partial - Keys to set (main, dev, remote, prefixes); omitted keys are left unchanged.
182
+ */
183
+ export function writeConfigFile(
184
+ dir: string,
185
+ partial: Partial<GflowsConfigFile>
186
+ ): void {
187
+ const path = join(dir, CONFIG_FILE);
188
+ let existing: GflowsConfigFile = {};
189
+ if (existsSync(path)) {
190
+ try {
191
+ const raw = readFileSync(path, "utf-8");
192
+ const data = JSON.parse(raw) as unknown;
193
+ const normalized = normalizeConfigFile(data);
194
+ if (normalized) existing = normalized;
195
+ } catch {
196
+ // overwrite invalid file
197
+ }
198
+ }
199
+ const merged: GflowsConfigFile = { ...existing };
200
+ if (typeof partial.main === "string" && partial.main.trim() !== "") {
201
+ merged.main = partial.main.trim();
202
+ }
203
+ if (typeof partial.dev === "string" && partial.dev.trim() !== "") {
204
+ merged.dev = partial.dev.trim();
205
+ }
206
+ if (typeof partial.remote === "string" && partial.remote.trim() !== "") {
207
+ merged.remote = partial.remote.trim();
208
+ }
209
+ if (partial.prefixes !== undefined && partial.prefixes !== null && typeof partial.prefixes === "object" && !Array.isArray(partial.prefixes)) {
210
+ const prefs = partial.prefixes as Record<string, unknown>;
211
+ const prefixes: BranchPrefixes = { ...(merged.prefixes ?? {}) };
212
+ const keys: (keyof BranchPrefixes)[] = ["feature", "bugfix", "chore", "release", "hotfix", "spike"];
213
+ for (const k of keys) {
214
+ const v = prefs[k];
215
+ if (typeof v === "string" && v.trim() !== "") {
216
+ prefixes[k] = v.trim();
217
+ }
218
+ }
219
+ merged.prefixes = prefixes;
220
+ }
221
+ writeFileSync(path, JSON.stringify(merged, null, 2) + "\n", "utf-8");
222
+ }
223
+
204
224
  /**
205
225
  * Returns the branch name prefix for a given branch type from resolved config.
206
226
  */
package/src/index.ts CHANGED
@@ -22,9 +22,9 @@ export { BRANCH_TYPE_SHORTS } from "./types.js";
22
22
  export {
23
23
  readConfigFile,
24
24
  resolveConfig,
25
+ writeConfigFile,
25
26
  getPrefixForType,
26
27
  getBranchTypeMeta,
27
- getEnvConfigOverrides,
28
28
  } from "./config.js";
29
29
  export type {
30
30
  ConfigCliOverrides,
package/src/types.ts CHANGED
@@ -101,6 +101,10 @@ export interface ParsedArgs {
101
101
  // Common flags
102
102
  push: boolean;
103
103
  noPush: boolean;
104
+ /** Main branch override (e.g. from --main; init persists to .gflows.json). */
105
+ main: string | undefined;
106
+ /** Dev branch override (e.g. from --dev; init persists to .gflows.json). */
107
+ dev: string | undefined;
104
108
  remote: string | undefined;
105
109
  branch: string | undefined;
106
110
  yes: boolean;