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/LICENSE +21 -0
- package/README.md +686 -0
- package/package.json +42 -0
- package/polyrepo.config.example.json +4 -0
- package/src/changelog.js +81 -0
- package/src/changes.js +55 -0
- package/src/ciChecks.js +26 -0
- package/src/commands/bump.js +245 -0
- package/src/commands/clone.js +102 -0
- package/src/commands/doctor.js +100 -0
- package/src/commands/exec.js +76 -0
- package/src/commands/list.js +107 -0
- package/src/commands/outdated.js +92 -0
- package/src/commands/prs.js +65 -0
- package/src/commands/publish.js +82 -0
- package/src/commands/release.js +121 -0
- package/src/commands/setup.js +121 -0
- package/src/commands/switchMaster.js +76 -0
- package/src/commands/tag.js +120 -0
- package/src/config.js +5 -0
- package/src/configFile.js +37 -0
- package/src/crossDeps.js +31 -0
- package/src/exec.js +132 -0
- package/src/export.js +136 -0
- package/src/filterByNames.js +16 -0
- package/src/github.js +57 -0
- package/src/index.js +533 -0
- package/src/loadConfig.js +63 -0
- package/src/masterSync.js +18 -0
- package/src/pMap.js +20 -0
- package/src/registry.js +10 -0
- package/src/release.js +32 -0
- package/src/repos.js +114 -0
- package/src/selectPackages.js +38 -0
- package/src/spinner.js +38 -0
- package/src/tags.js +30 -0
- package/src/ui.js +94 -0
- package/src/version.js +29 -0
- package/test/changelog.test.js +101 -0
- package/test/crossDeps.test.js +51 -0
- package/test/version.test.js +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
# **Polyrepo CLI**
|
|
2
|
+
|
|
3
|
+

|
|
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).
|