polyrepo-cli 1.0.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/README.md ADDED
@@ -0,0 +1,686 @@
1
+ # **Polyrepo CLI**
2
+
3
+ ![Rest Pipeline JS](https://github.com/macrulezru/assets/blob/master/packages-images/polyrepo-cli.png?raw=true)
4
+
5
+ An interactive CLI for managing a folder of local npm package repos:
6
+ version bumps through a pull request, npm publishing, GitHub releases,
7
+ and cross-package dependency drift — all from one tool, all reviewable
8
+ with `--dry-run` before anything actually changes.
9
+
10
+ ## Features
11
+
12
+ - **`setup`** — add, edit, or remove the package directories the CLI
13
+ scans, right from the terminal — no hand-editing JSON.
14
+ - **`clone`** — diff a GitHub org/user's repo list against what's
15
+ already cloned under a root, and clone whatever's missing.
16
+ - **`list`** — one table per package: version, branch, git status, git
17
+ tag, GitHub Release, npm registry status, and cross-package
18
+ dependency drift. `list --quick` skips the network checks for an
19
+ instant version/branch/git-only view; `list --output <path>` also
20
+ saves the same table as Markdown, JSON, CSV, HTML, or plain text.
21
+ - **`outdated`** — one table across every package of what's outdated
22
+ (`npm outdated`), instead of running it in each repo by hand.
23
+ - **`prs`** — one table of every open pull request across every
24
+ package (`gh pr list`) — useful after an interrupted `bump` run to
25
+ see what's still waiting to be merged.
26
+ - **`doctor`** — a one-command health check: is the environment set up
27
+ correctly (Node/git/gh/npm, authentication), and does any local
28
+ package still depend on an incompatible version of another local
29
+ package.
30
+ - **`switch-master`** — fast-forward selected repos to an up-to-date
31
+ `master`.
32
+ - **`bump`** — bump a package's version (patch by default, or
33
+ `--minor`/`--major`) through a branch → PR → merge, then tag the
34
+ release. Safe to re-run if a previous attempt was interrupted
35
+ partway — it picks up from wherever it left off instead of failing
36
+ or duplicating work. Can wait for CI checks before merging
37
+ (`--wait-checks`), and drafts a `CHANGELOG.md` entry when the
38
+ package already has one.
39
+ - **`publish`** — run `npm publish` for the packages that are actually
40
+ ahead of the registry, after comparing each one automatically.
41
+ - **`tag`** — tag a package at its *current* version without bumping
42
+ again, for when the version was already moved forward some other
43
+ way. Offers to create a GitHub Release right after.
44
+ - **`release`** — create a GitHub Release from a tag, with notes
45
+ pulled from the matching `CHANGELOG.md` section when there is one.
46
+ - **`exec`** — run any command (`npm test`, `npm outdated`, a lint
47
+ script, anything) across every selected package, one at a time, with
48
+ a real terminal.
49
+ - Every command that touches multiple repos supports `--packages` and
50
+ `--yes` for fully non-interactive use in scripts.
51
+
52
+ Everywhere a checkbox list appears, columns line up consistently
53
+ across commands, and read-only checks across many packages (versions,
54
+ tags, releases, registry state) run in parallel instead of one at a
55
+ time. Mutating steps (commit, push, merge) always run one package at a
56
+ time, printing each step as it happens, so progress stays easy to
57
+ follow.
58
+
59
+ ## Requirements
60
+
61
+ - Node.js 20+
62
+ - `git` on `PATH`
63
+ - [`gh`](https://cli.github.com/) (GitHub CLI), authenticated
64
+ (`gh auth status`) — needed for opening/merging pull requests,
65
+ checking the state of a previous `bump` attempt, CI checks
66
+ (`bump --wait-checks`), and `tag`/`release`
67
+ - `npm` on `PATH`, authenticated (`npm whoami`) — needed only for
68
+ `publish`
69
+
70
+ Run `polyrepo doctor` to check all of this in one go.
71
+
72
+ ## Installation
73
+
74
+ ```bash
75
+ git clone <this repository's URL>
76
+ cd polyrepo-cli
77
+ npm install
78
+ ```
79
+
80
+ Optionally, make `polyrepo` available everywhere:
81
+
82
+ ```bash
83
+ npm link
84
+ ```
85
+
86
+ Without `npm link`, run commands as `node src/index.js <command>`.
87
+
88
+ ## Quick start
89
+
90
+ ```bash
91
+ polyrepo setup # tell it where your package repos live
92
+ polyrepo doctor # confirm the environment is set up correctly
93
+ polyrepo list # see version/branch/tag/release/npm status for everything
94
+ ```
95
+
96
+ ## Help
97
+
98
+ Every command has built-in `--help`:
99
+
100
+ ```bash
101
+ polyrepo --help
102
+ polyrepo bump --help
103
+ ```
104
+
105
+ `polyrepo --help` lists every command together with its flags, so you
106
+ rarely need to open a command's own `--help` just to remember an
107
+ option name.
108
+
109
+ ## Commands
110
+
111
+ ### `polyrepo setup`
112
+
113
+ An interactive menu for `polyrepo.config.json` itself — no need to open the
114
+ JSON by hand. Shows the current `roots` and `packages` lists with
115
+ `✓ exists` / `✗ not found` (and, for `packages`, `! no
116
+ package.json/.git here` when the folder exists but isn't a package),
117
+ and lets you:
118
+
119
+ - add a root or package directory
120
+ - edit an existing entry
121
+ - remove an entry (with confirmation)
122
+
123
+ A path isn't checked strictly — adding one that doesn't exist yet
124
+ just prints a warning, in case the folder shows up later. Changes are
125
+ only written to disk when you choose "Save and exit"; "Discard changes
126
+ and exit" throws away anything done in that session.
127
+
128
+ ```bash
129
+ polyrepo setup
130
+
131
+ # edit a non-default config
132
+ polyrepo setup --config "/path/to/polyrepo.config.json"
133
+ ```
134
+
135
+ ### `polyrepo clone`
136
+
137
+ Diffs a GitHub org/user's repo list against what's already cloned
138
+ under a root, and clones whatever's missing:
139
+
140
+ 1. `gh repo list <org>` — every repo under that org/user (archived
141
+ ones are skipped by default, `--include-archived` to include them).
142
+ 2. Compares the names against the directories already found under the
143
+ target root (`--root`, or the first root in your config if not
144
+ given).
145
+ 3. Shows a checkbox of the missing ones (all checked by default), then
146
+ `git clone`s each selected one into the target root, one at a time.
147
+
148
+ `--org` is required and isn't stored in the config — the config's
149
+ `roots`/`packages` describe *where local repos live*, not which
150
+ GitHub account they came from. Cloning into a root that isn't in the
151
+ config yet still works — you'll just be reminded to run
152
+ `polyrepo setup` afterward so `list`/`doctor`/etc. pick the new repos
153
+ up too.
154
+
155
+ **Options:**
156
+
157
+ | Flag | What it does |
158
+ | --- | --- |
159
+ | `--org <name>` | GitHub org or user to list repos from (required). |
160
+ | `--root <path>` | Root directory to clone into. Defaults to the config's first `roots` entry. |
161
+ | `--include-archived` | Also offer archived repos. |
162
+ | `--packages <a,b,c>` | Only offer these repo names instead of the interactive checkbox. |
163
+ | `--yes` | Skip the "proceed?" confirmation. |
164
+ | `--dry-run` | Print what would be cloned; clone nothing. |
165
+
166
+ ```bash
167
+ polyrepo clone --org my-github-org
168
+
169
+ # a specific root, no prompts
170
+ polyrepo clone --org my-github-org --root "C:\work\NPM" --yes
171
+ ```
172
+
173
+ ### `polyrepo list` (alias `ls`)
174
+
175
+ A table of every discovered package. Full summary by default:
176
+
177
+ | Column | Shows |
178
+ | --- | --- |
179
+ | Package / Version / Branch | directory name, `package.json` version, current branch |
180
+ | Git | whether the working tree is clean or dirty |
181
+ | Tag | the `v<version>` tag for the current version, if it exists, else `—` |
182
+ | Release | whether that tag has a GitHub Release (`✓`/`✗`/`—` if untagged) |
183
+ | npm | `✓` if the registry matches the local version, else the registry version or `unpublished` |
184
+ | Deps | `✓`, or `⚠ N` — how many other local packages reference this one with a version range that no longer matches (same check as `doctor` and the end of `bump`) |
185
+
186
+ Read-only. The git/tag/release/npm checks run in parallel across
187
+ packages, but they're still real network calls (tag, release, and
188
+ registry — three per package), so a full `polyrepo list` across many
189
+ packages takes a few seconds rather than being instant. Use `--quick`
190
+ for just version/branch/git status when that's all you need. Add
191
+ `--path` for a **Path** column (right after Package) showing where
192
+ each one actually lives on disk — handy once packages come from a mix
193
+ of `roots` and one-off `packages` entries and the directory name alone
194
+ doesn't say where to find it.
195
+
196
+ `--output <path>` additionally saves the exact same table (same
197
+ columns as printed — respects `--quick`/`--path`) to a file, for
198
+ sending the data somewhere. `--format` picks the file format —
199
+ `md` (Markdown table), `json` (array of objects), `csv` (RFC
200
+ 4180-quoted), `html` (a standalone page, openable directly in a
201
+ browser), or `txt` (plain aligned columns, no ANSI colors); without
202
+ it, the format is guessed from `--output`'s extension (`.json` →
203
+ json, `.md`/`.markdown` → markdown, `.csv` → csv, `.html`/`.htm` →
204
+ html, anything else → plain text). In JSON, a cell that's just the
205
+ terminal's ✓/✗ checkmark (e.g. Release, or npm when it's up to date)
206
+ becomes a real `true`/`false` — anything carrying more information
207
+ than a plain yes/no (an outdated registry version, `⚠ N` stale deps,
208
+ `clean`/`dirty`) stays text.
209
+
210
+ **Options:**
211
+
212
+ | Flag | What it does |
213
+ | --- | --- |
214
+ | `--quick` | Skip the tag/release/npm/dependency checks — just version, branch, git status. |
215
+ | `--path` | Add a Path column showing each package's location on disk. |
216
+ | `--output <path>` | Also save the table to this file. |
217
+ | `--format <md\|json\|csv\|html\|txt>` | File format for `--output`. Guessed from the file extension if omitted. |
218
+
219
+ ```bash
220
+ polyrepo list
221
+
222
+ # version/branch/git only — no network calls
223
+ polyrepo list --quick
224
+
225
+ # also show each package's directory
226
+ polyrepo list --path
227
+
228
+ # also save as JSON (format guessed from the .json extension)
229
+ polyrepo list --output packages.json
230
+
231
+ # save as Markdown regardless of the file's own extension
232
+ polyrepo list --output report.txt --format md
233
+ ```
234
+
235
+ ### `polyrepo outdated`
236
+
237
+ Read-only. Runs `npm outdated --json` for every package in parallel
238
+ and prints one table (Package, Dependency, Current, Wanted, Latest)
239
+ instead of running it in each repo by hand — with a blank line
240
+ between each package's rows for readability, and a summary line above
241
+ the table (`N dependencies outdated across M package(s), K of them
242
+ major version behind`). The **Latest** column is colored by how far
243
+ behind it is — red for a major bump, yellow for minor, dim for patch
244
+ — so what actually needs a look stands out from routine bumps.
245
+ Packages with nothing outdated just don't add any rows.
246
+
247
+ ```bash
248
+ polyrepo outdated
249
+
250
+ # only these packages
251
+ polyrepo outdated --packages vue-toast-kit,os-detect
252
+ ```
253
+
254
+ ### `polyrepo prs`
255
+
256
+ Read-only. Runs `gh pr list` for every package in parallel and prints
257
+ one flat table (Package, PR, Title, Branch, Status). Useful after an
258
+ interrupted `polyrepo bump` run, to see at a glance which packages
259
+ still have a PR open that needs merging by hand.
260
+
261
+ ```bash
262
+ polyrepo prs
263
+
264
+ # only these packages
265
+ polyrepo prs --packages vue-toast-kit,os-detect
266
+ ```
267
+
268
+ ### `polyrepo doctor`
269
+
270
+ A read-only health check in three sections:
271
+
272
+ 1. **Environment** — Node.js version (20+ required), whether
273
+ `git`/`gh`/`npm` are on `PATH`, and whether `gh`/`npm` are
274
+ authenticated (npm auth is only a warning — it's only needed for
275
+ `publish`).
276
+ 2. **Config** — how many packages the current config actually
277
+ resolves to, and which repos are dirty or off `master`.
278
+ 3. **Cross-package dependencies** — the same dependency-drift check
279
+ that runs at the end of `bump`, available on demand without
280
+ bumping anything.
281
+
282
+ Worth running first if any other command is behaving unexpectedly.
283
+
284
+ ```bash
285
+ polyrepo doctor
286
+ ```
287
+
288
+ ### `polyrepo switch-master` (alias `sm`)
289
+
290
+ 1. Shows a checkbox list of every repo with its current branch; repos
291
+ not currently on `master` are pre-selected.
292
+ 2. After confirming, for each selected repo, one at a time (each
293
+ step's result prints immediately, not after the whole batch):
294
+ - a dirty working tree is skipped with a warning, untouched;
295
+ - otherwise: `git fetch origin` → `git checkout master` →
296
+ `git merge --ff-only origin/master`.
297
+ 3. If local `master` has diverged from `origin/master` (fast-forward
298
+ isn't possible), that repo is reported and left alone to resolve by
299
+ hand — no `--force`/`reset --hard` is ever used.
300
+
301
+ ```bash
302
+ polyrepo switch-master
303
+
304
+ # no checkbox, specific repos, no confirmation — for scripts
305
+ polyrepo switch-master --packages vue-toast-kit,os-detect --yes
306
+ ```
307
+
308
+ ### `polyrepo bump [options]`
309
+
310
+ 1. Shows a checkbox of packages with their current version and the
311
+ version they'd bump to (`1.2.9 → 1.2.10` for a patch bump, the
312
+ default — `--minor`/`--major` bump that part instead, resetting
313
+ the parts below it to `0`, same as any semver tool). Packages
314
+ with a dirty working tree are marked — they'll be skipped. The
315
+ highlighted package's description shows what's actually changed
316
+ since the last git tag (`git log <tag>..master`) — if that's empty,
317
+ there's probably nothing worth bumping. These previews are computed
318
+ for every package in parallel, not one at a time.
319
+ 2. After confirming, for each selected package, one at a time, with
320
+ live progress:
321
+ 1. `git fetch origin` → `git checkout master` →
322
+ `git merge --ff-only origin/master` (the bump branch is always
323
+ created from an up-to-date master, not whatever branch the repo
324
+ happened to be on);
325
+ 2. **checks the state of a previous attempt** — is there already a
326
+ merged PR, an open PR, or just a pushed branch named
327
+ `<new-version>-version-bump` (e.g. `1.2.10-version-bump` — the
328
+ version number at the start of the branch name guarantees two
329
+ different bumps never collide). Depending on what's found, it
330
+ resumes from the right place instead of failing on "branch
331
+ already exists" or opening a duplicate PR:
332
+ - **already merged** — nothing to do (master was already synced
333
+ in step 1), go straight to tagging;
334
+ - **open PR exists** — merge that one, don't open a new one;
335
+ - **branch pushed, no PR** — reuse the branch, open a PR;
336
+ - **nothing exists** — the full flow from scratch.
337
+ 3. if `package.json` on the branch isn't bumped yet, its
338
+ `"version"` field is updated (a text-level replace, not
339
+ `JSON.parse`/`stringify` — formatting and field order are left
340
+ alone). If the package already has a `CHANGELOG.md`, a draft
341
+ `## [x.y.z] - YYYY-MM-DD` entry (Keep a Changelog style) is
342
+ added too, with a `### Changed` section listing commits since
343
+ the last tag (merge commits filtered out) — a starting draft to
344
+ review, not a finished changelog. Packages without a
345
+ `CHANGELOG.md` don't get one created. Both files are committed
346
+ together;
347
+ 4. `gh pr create` against `master` (if there isn't one already);
348
+ 5. with `--wait-checks`: wait for the PR's CI checks via
349
+ `gh pr checks --watch` (with a real terminal, live-updating). No
350
+ checks configured isn't an error — there's just nothing to wait
351
+ for. Failing checks stop that package's bump with an error and
352
+ skip the merge;
353
+ 6. `gh pr merge --merge` — through a PR, not a direct push, since
354
+ these repos require it;
355
+ 7. `git checkout master` → `git fetch origin` →
356
+ `git merge --ff-only origin/master` — local master is synced to
357
+ the just-merged PR;
358
+ 8. **git tag** `v<new-version>` (e.g. `v1.2.10`) is created and
359
+ pushed if it doesn't already exist (idempotent, like everything
360
+ else here — a re-run won't try to create it twice).
361
+ 3. Any failure along the way (fetch/push/PR/CI/merge) marks that
362
+ package ✗ with a clear message and moves on to the next one in the
363
+ queue, without aborting the whole run.
364
+ 4. **Once every selected package is processed** — a separate check
365
+ across *all* packages (not just the ones just bumped): does any
366
+ local package's `dependencies`/`devDependencies`/`peerDependencies`
367
+ reference another local package with a range that no longer
368
+ matches (e.g. package A declares `"pkg-b": "^1.2.0"` but the local
369
+ version of `pkg-b` is `1.1.12`). Nothing is changed automatically —
370
+ just a warning that it's worth checking and bumping/adjusting that
371
+ dependency separately.
372
+
373
+ Publishing to npm and creating a GitHub Release are deliberately
374
+ separate steps — see `polyrepo publish` and `polyrepo release` below. Bumping a
375
+ batch of packages and then publishing or releasing only some of them
376
+ are different decisions that don't always happen at the same time.
377
+
378
+ **Options:**
379
+
380
+ | Flag | What it does |
381
+ | --- | --- |
382
+ | `--dry-run` | Prints the plan for each package, changes and pushes nothing — including the `CHANGELOG.md` entry, CI wait, and git tag. |
383
+ | `--minor` | Bump the minor version instead of patch (e.g. `1.2.9 → 1.3.0`). |
384
+ | `--major` | Bump the major version instead of patch (e.g. `1.2.9 → 2.0.0`). |
385
+ | `--packages <a,b,c>` | Comma-separated package dir names instead of the interactive checkbox — for scripts. Unknown names are printed as a warning and skipped. |
386
+ | `--yes` | Skip the "proceed?" confirmation. |
387
+ | `--wait-checks` | Wait for the PR's CI checks (if any are configured) before merging; don't merge if they fail. |
388
+
389
+ ```bash
390
+ # dry run first — nothing is pushed, committed, merged, or tagged,
391
+ # it just shows what would happen
392
+ polyrepo bump --dry-run
393
+
394
+ # normal interactive run (patch bump)
395
+ polyrepo bump
396
+
397
+ # minor bump instead
398
+ polyrepo bump --minor
399
+
400
+ # wait for CI before merging
401
+ polyrepo bump --wait-checks
402
+
403
+ # fully non-interactive, for a script/CI
404
+ polyrepo bump --packages vue-toast-kit,os-detect --yes
405
+ ```
406
+
407
+ ### `polyrepo publish [options]`
408
+
409
+ 1. Checks each package's registry version (`npm view <pkg> version`)
410
+ against its local `package.json` version — in parallel, printing
411
+ each result as it arrives (so the order reflects registry response
412
+ time, not the package list order).
413
+ 2. Shows a checkbox: registry version → local version. Packages where
414
+ they differ (genuinely unpublished) are pre-selected; already
415
+ published ones are unchecked but still selectable (e.g. to
416
+ republish after an unpublish).
417
+ 3. After confirming, for each selected package, one at a time:
418
+ `npm publish` (or `npm publish --dry-run` with the `--dry-run`
419
+ flag — npm's own dry run, including the real build and pack step,
420
+ not just printing a plan). Runs with a real terminal, not captured
421
+ — an npm 2FA/OTP prompt works normally.
422
+
423
+ **Options:**
424
+
425
+ | Flag | What it does |
426
+ | --- | --- |
427
+ | `--dry-run` | `npm publish --dry-run` instead of a real publish. |
428
+ | `--packages <a,b,c>` | Package list instead of the interactive checkbox. |
429
+ | `--yes` | Skip the "proceed?" confirmation. |
430
+
431
+ ```bash
432
+ # see what's unpublished, then publish what you pick
433
+ polyrepo publish
434
+
435
+ # same, but nothing is actually published — just build and pack
436
+ polyrepo publish --dry-run
437
+
438
+ # specific packages, no prompts
439
+ polyrepo publish --packages vue-toast-kit,os-detect --yes
440
+ ```
441
+
442
+ ### `polyrepo tag [options]`
443
+
444
+ For a package whose version was already bumped some other way (not
445
+ through `polyrepo bump`, or before it started tagging), `polyrepo release` has
446
+ nothing to work with — there's no tag for the current version yet.
447
+ `polyrepo tag` puts the missing tag on the current version without bumping
448
+ it again or opening a PR:
449
+
450
+ 1. Checks each package (in parallel) for whether a tag
451
+ `v<local version>` already exists — printing progress per package.
452
+ 2. Shows a checkbox: package, version, tag. Untagged packages are
453
+ pre-selected; already-tagged ones can still be picked manually
454
+ (harmless — it just confirms the tag is there).
455
+ 3. After confirming, for each selected package, one at a time:
456
+ `git fetch`/`checkout master`/`merge --ff-only` (tags an up-to-date
457
+ master, same as `bump`), then creates and pushes the tag if it's
458
+ missing.
459
+ 4. If at least one package was actually tagged (and it wasn't a
460
+ `--dry-run`), it asks: "Create a GitHub Release for the N
461
+ package(s) just tagged?" — answering yes runs the same process as
462
+ `polyrepo release` for exactly those packages (notes from `CHANGELOG.md`
463
+ when available, otherwise `--generate-notes`).
464
+
465
+ **Options:**
466
+
467
+ | Flag | What it does |
468
+ | --- | --- |
469
+ | `--dry-run` | Prints the plan; tags, pushes, and releases nothing. |
470
+ | `--packages <a,b,c>` | Package list instead of the interactive checkbox. |
471
+ | `--yes` | Skip the "proceed?" confirmation (the release question is skipped too — no release is created unless `--release` is also given). |
472
+ | `--release` | Create a release right after tagging, without asking — for scripts. |
473
+
474
+ ```bash
475
+ # see what needs tagging, tag it, get offered a release
476
+ polyrepo tag
477
+
478
+ # fully non-interactive: tag and release
479
+ polyrepo tag --packages vue-toast-kit,os-detect --yes --release
480
+ ```
481
+
482
+ ### `polyrepo release [options]`
483
+
484
+ 1. Checks each package (in parallel) for a `v<local version>` tag on
485
+ origin (the one `bump` or `tag` creates) and whether that tag
486
+ already has a GitHub Release — printing progress per package.
487
+ 2. Shows a checkbox: package and its tag. Packages with no tag for
488
+ their current version are shown disabled ("no tag yet — run
489
+ `polyrepo bump` first") — they can't be selected until tagged. Already
490
+ released ones are shown as "(already released)" — selectable but
491
+ not required.
492
+ 3. After confirming, for each selected package, one at a time:
493
+ `gh release create <tag> --verify-tag --title "<pkg>@<version>"`.
494
+ `--verify-tag` guarantees this never invents a new tag — only ever
495
+ uses one that already exists. Release notes come from the matching
496
+ `CHANGELOG.md` section when there is one, otherwise
497
+ `--generate-notes` (gh's own summary of merged PRs/commits).
498
+
499
+ **Options:**
500
+
501
+ | Flag | What it does |
502
+ | --- | --- |
503
+ | `--dry-run` | Prints what would be created; publishes nothing. |
504
+ | `--packages <a,b,c>` | Package list instead of the interactive checkbox. |
505
+ | `--yes` | Skip the "proceed?" confirmation. |
506
+
507
+ ```bash
508
+ # see what's tagged but not released, then release it
509
+ polyrepo release
510
+
511
+ # specific packages, no prompts
512
+ polyrepo release --packages vue-toast-kit,os-detect --yes
513
+ ```
514
+
515
+ ### `polyrepo exec -- <command...>`
516
+
517
+ Runs any command in each selected package, one at a time, with a real
518
+ terminal (its output, colors, and any prompts show up normally —
519
+ useful for things like `npm test` that might want a TTY). Shows a
520
+ checkbox of every discovered package first (all checked by default —
521
+ "run everywhere" is the common case).
522
+
523
+ The command itself must come after a literal `--`, same as
524
+ `npm run <script> --` — anything before it is parsed as `polyrepo`'s
525
+ own options. A package that exits non-zero is reported and, by
526
+ default, the run continues through the rest; `--bail` stops
527
+ immediately instead. A summary of failed packages (if any) prints at
528
+ the end, and the process exits non-zero if anything failed.
529
+
530
+ **Options:**
531
+
532
+ | Flag | What it does |
533
+ | --- | --- |
534
+ | `--packages <a,b,c>` | Package list instead of the interactive checkbox. |
535
+ | `--yes` | Skip the "proceed?" confirmation. |
536
+ | `--bail` | Stop at the first package that exits non-zero. |
537
+
538
+ ```bash
539
+ # run tests everywhere
540
+ polyrepo exec -- npm test
541
+
542
+ # specific packages, no prompts
543
+ polyrepo exec --packages vue-toast-kit,os-detect --yes -- npm outdated
544
+
545
+ # stop at the first failure
546
+ polyrepo exec --bail -- npm run lint
547
+ ```
548
+
549
+ ## Example output
550
+
551
+ ```
552
+ [1/1] vue-toast-kit 1.0.7 → 1.0.8
553
+
554
+ $ git fetch origin
555
+ $ git checkout master
556
+ $ git merge --ff-only origin/master
557
+ ✓ master is up to date.
558
+ ✓ Created branch 1.0.8-version-bump.
559
+ ✓ Drafted a CHANGELOG.md entry — review it before merging if you want it polished.
560
+ $ git commit -m chore: bump version to 1.0.8
561
+ ✓ package.json version set to 1.0.8 and committed.
562
+ $ git push -u origin 1.0.8-version-bump
563
+ ✓ Branch pushed (or already up to date on origin).
564
+ $ gh pr create --base master --head 1.0.8-version-bump ...
565
+ ✓ Opened PR #12.
566
+ $ gh pr merge 12 --merge --delete-branch=false
567
+ ✓ Merged PR #12.
568
+ ✓ Local master synced to origin at 1.0.8.
569
+ $ git tag -a v1.0.8 -m v1.0.8
570
+ $ git push origin v1.0.8
571
+ ✓ Tagged and pushed v1.0.8.
572
+ ```
573
+
574
+ Re-running `polyrepo bump` on the same package (say, a previous run was
575
+ interrupted at the CI or network step) is shorter — anything already
576
+ done is just confirmed, not redone:
577
+
578
+ ```
579
+ [1/1] vue-toast-kit 1.0.7 → 1.0.8
580
+
581
+ ✓ master is up to date.
582
+ ✓ Already merged as PR #12 — master already has it.
583
+ ✓ Tag v1.0.8 already exists on origin.
584
+ ```
585
+
586
+ `polyrepo publish` on its own:
587
+
588
+ ```
589
+ Checking 2 package(s) against the registry...
590
+ vue-toast-kit: registry 1.0.7 ≠ local 1.0.8
591
+ os-detect: registry 2.1.5 = local 2.1.5
592
+
593
+ ? Pick packages to publish:
594
+ vue-toast-kit 1.0.7 → 1.0.8
595
+
596
+ [1/1] vue-toast-kit@1.0.8
597
+ $ npm publish
598
+ ✓ Published vue-toast-kit@1.0.8.
599
+ ```
600
+
601
+ `polyrepo release` on its own:
602
+
603
+ ```
604
+ Checking 2 package(s) for a tag and an existing release...
605
+ vue-toast-kit: v1.0.8 — ready
606
+ os-detect: v2.1.5 — already released
607
+
608
+ ? Pick packages to create a GitHub Release for:
609
+ vue-toast-kit v1.0.8
610
+
611
+ [1/1] vue-toast-kit v1.0.8
612
+ ✓ Using the matching CHANGELOG.md section as release notes.
613
+ $ gh release create v1.0.8 --verify-tag --title vue-toast-kit@1.0.8 --notes-file ...
614
+ ✓ Created release vue-toast-kit@1.0.8.
615
+ ```
616
+
617
+ ## Configuration
618
+
619
+ The list of directories to scan lives in `polyrepo.config.json` (next to
620
+ this project's own `package.json`) — edit it through `polyrepo setup`
621
+ (recommended) or by hand. Two independent arrays:
622
+
623
+ - `roots` — directories whose **subfolders** are scanned: each
624
+ subfolder containing both `package.json` and `.git` counts as a
625
+ package. Convenient when all your packages live next to each other
626
+ in one shared folder.
627
+ - `packages` — directories that are themselves a package (not their
628
+ subfolders) — for a single repo that doesn't live next to the rest.
629
+
630
+ Both arrays are optional and additive; you can list several `roots`
631
+ and any number of `packages`. A package found through both `roots` and
632
+ `packages` (e.g. a path that happens to overlap) is only counted once.
633
+ Relative paths in the config are resolved against the config file's
634
+ own location, not the current working directory. With no config file
635
+ at all, the CLI finds nothing and tells you to run `polyrepo setup` — there
636
+ is no built-in default path.
637
+
638
+ ```json
639
+ {
640
+ "roots": ["/path/to/folder-of-repos"],
641
+ "packages": ["/path/to/a-single-repo"]
642
+ }
643
+ ```
644
+
645
+ A ready-to-copy template is at `polyrepo.config.example.json` in the
646
+ project root — copy it to `polyrepo.config.json` and edit by hand, or fill
647
+ it in through `polyrepo setup`.
648
+
649
+ If a directory in `roots`/`packages` doesn't exist, or (for
650
+ `packages`) doesn't contain `package.json`/`.git`, the CLI prints a
651
+ warning and skips it without stopping the rest of the run.
652
+
653
+ A different config file can be pointed to with `--config` (works
654
+ before or after the subcommand) or the `POLYREPO_CONFIG` environment
655
+ variable:
656
+
657
+ ```bash
658
+ polyrepo --config "/path/to/polyrepo.config.json" list
659
+ polyrepo list --config "/path/to/polyrepo.config.json"
660
+ ```
661
+
662
+ For a one-off override without editing the file, `POLYREPO_ROOT` replaces
663
+ the configured `roots` entirely (`packages` is left as-is):
664
+
665
+ ```bash
666
+ POLYREPO_ROOT="/other/path" polyrepo list
667
+ ```
668
+
669
+ ## Development
670
+
671
+ ```bash
672
+ npm test
673
+ ```
674
+
675
+ Unit tests cover the pure logic that doesn't need a real
676
+ `git`/`gh`/`npm` — version bumping, `CHANGELOG.md` entry insertion,
677
+ and cross-package dependency drift detection — using real temporary
678
+ files on disk rather than mocks.
679
+
680
+ ## Author
681
+
682
+ Danil Lisin Vladimirovich, aka Macrulez — [macrulez.ru/en](https://macrulez.ru/en)
683
+
684
+ ## License
685
+
686
+ MIT — see [LICENSE](./LICENSE).