polyrepo-cli 1.0.0 → 1.0.1
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 +10 -5
- package/package.json +1 -1
- package/src/commands/publish.js +41 -4
- package/src/commands/release.js +16 -3
- package/src/commands/tag.js +16 -3
package/README.md
CHANGED
|
@@ -414,11 +414,16 @@ polyrepo bump --packages vue-toast-kit,os-detect --yes
|
|
|
414
414
|
they differ (genuinely unpublished) are pre-selected; already
|
|
415
415
|
published ones are unchecked but still selectable (e.g. to
|
|
416
416
|
republish after an unpublish).
|
|
417
|
-
3. After confirming,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
417
|
+
3. After confirming, checks `npm whoami` once for the whole batch
|
|
418
|
+
(skipped under `--dry-run`) — not logged in runs `npm login`
|
|
419
|
+
(a real terminal, so its browser-based OTP flow or credential
|
|
420
|
+
prompt works normally) before publishing anything, instead of
|
|
421
|
+
only finding out partway through the first package's `npm publish`.
|
|
422
|
+
4. For each selected package, one at a time: `npm publish` (or
|
|
423
|
+
`npm publish --dry-run` with the `--dry-run` flag — npm's own dry
|
|
424
|
+
run, including the real build and pack step, not just printing a
|
|
425
|
+
plan). Runs with a real terminal, not captured — an npm 2FA/OTP
|
|
426
|
+
prompt works normally.
|
|
422
427
|
|
|
423
428
|
**Options:**
|
|
424
429
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polyrepo-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Interactive CLI for managing a folder of local npm package repos: version bumps through a PR, npm publish, GitHub releases, and cross-package dependency drift checks — all from one tool.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/publish.js
CHANGED
|
@@ -5,19 +5,29 @@ import { loadConfig } from '../loadConfig.js'
|
|
|
5
5
|
import { npm } from '../exec.js'
|
|
6
6
|
import { fetchPublishedVersionAsync } from '../registry.js'
|
|
7
7
|
import { selectPackages } from '../selectPackages.js'
|
|
8
|
+
import { filterByNames } from '../filterByNames.js'
|
|
8
9
|
import { pMap } from '../pMap.js'
|
|
9
|
-
import { heading, stepHeading, ok, fail, columnWidths, formatRow } from '../ui.js'
|
|
10
|
+
import { heading, stepHeading, ok, fail, warn, columnWidths, formatRow } from '../ui.js'
|
|
10
11
|
|
|
11
12
|
export async function publishCommand({ configPath, packages, yes = false, dryRun = false } = {}) {
|
|
12
13
|
const config = loadConfig({ configPath })
|
|
13
|
-
const
|
|
14
|
-
if (
|
|
14
|
+
const allRepos = (await inspectRepos(discoverRepos(config))).filter((r) => r.version)
|
|
15
|
+
if (allRepos.length === 0) {
|
|
15
16
|
console.log(pc.yellow('No repos found.'))
|
|
16
17
|
return
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
heading('Publish to npm')
|
|
20
21
|
|
|
22
|
+
// Narrowed to --packages up front (a no-op when it wasn't given) — no
|
|
23
|
+
// reason to hit the registry for, or print the status of, packages
|
|
24
|
+
// nobody asked to publish.
|
|
25
|
+
const repos = filterByNames(allRepos, packages)
|
|
26
|
+
if (repos.length === 0) {
|
|
27
|
+
console.log(pc.dim('Nothing selected.'))
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
|
|
21
31
|
console.log(pc.dim(`Checking ${repos.length} package(s) against the registry...`))
|
|
22
32
|
// Each repo's registry lookup is a real network round trip — running them
|
|
23
33
|
// all at once instead of one at a time is where this actually pays off.
|
|
@@ -34,7 +44,11 @@ export async function publishCommand({ configPath, packages, yes = false, dryRun
|
|
|
34
44
|
|
|
35
45
|
const selected = await selectPackages({
|
|
36
46
|
items: withRegistry,
|
|
37
|
-
packages
|
|
47
|
+
// repos above is already the exact --packages match — passing those
|
|
48
|
+
// same dir names back through here just skips the checkbox (as
|
|
49
|
+
// before) without filterByNames re-warning about anything, since
|
|
50
|
+
// there's nothing left for it to not find.
|
|
51
|
+
packages: packages ? withRegistry.map((r) => r.dir) : undefined,
|
|
38
52
|
message: 'Pick packages to publish:',
|
|
39
53
|
buildChoice: (all) => {
|
|
40
54
|
const columns = [
|
|
@@ -68,6 +82,8 @@ export async function publishCommand({ configPath, packages, yes = false, dryRun
|
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
if (!dryRun && !(await ensureNpmLogin())) return
|
|
86
|
+
|
|
71
87
|
let index = 0
|
|
72
88
|
for (const repo of selected) {
|
|
73
89
|
index += 1
|
|
@@ -80,3 +96,24 @@ export async function publishCommand({ configPath, packages, yes = false, dryRun
|
|
|
80
96
|
else ok(`Published ${repo.name}@${repo.version}${dryRun ? ' (dry run).' : '.'}`)
|
|
81
97
|
}
|
|
82
98
|
}
|
|
99
|
+
|
|
100
|
+
// Without this, a batch of several packages would only find out about a
|
|
101
|
+
// missing/expired npm login at the very end of the first `npm publish` —
|
|
102
|
+
// after everything before it in the run (fetch, checkout, confirmation)
|
|
103
|
+
// already succeeded. Checked once for the whole batch, not per package,
|
|
104
|
+
// since npm auth isn't per-repo. `npm login` needs a real terminal — it
|
|
105
|
+
// can open a browser for npm's web-based OTP flow, or prompt directly.
|
|
106
|
+
async function ensureNpmLogin() {
|
|
107
|
+
if (npm('.', ['whoami'], { quiet: true }).ok) return true
|
|
108
|
+
|
|
109
|
+
warn('Not logged in to npm — running `npm login` first.')
|
|
110
|
+
npm('.', ['login'], { interactive: true })
|
|
111
|
+
|
|
112
|
+
if (npm('.', ['whoami'], { quiet: true }).ok) {
|
|
113
|
+
ok('Logged in to npm.')
|
|
114
|
+
return true
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fail('npm login did not succeed — aborting before publishing anything.')
|
|
118
|
+
return false
|
|
119
|
+
}
|
package/src/commands/release.js
CHANGED
|
@@ -6,19 +6,29 @@ import { tagName, tagExists } from '../tags.js'
|
|
|
6
6
|
import { releaseExistsAsync, createRelease } from '../release.js'
|
|
7
7
|
import { extractChangelogSection } from '../changelog.js'
|
|
8
8
|
import { selectPackages } from '../selectPackages.js'
|
|
9
|
+
import { filterByNames } from '../filterByNames.js'
|
|
9
10
|
import { pMap } from '../pMap.js'
|
|
10
11
|
import { heading, stepHeading, ok, fail, columnWidths, formatRow } from '../ui.js'
|
|
11
12
|
|
|
12
13
|
export async function releaseCommand({ configPath, packages, yes = false, dryRun = false } = {}) {
|
|
13
14
|
const config = loadConfig({ configPath })
|
|
14
|
-
const
|
|
15
|
-
if (
|
|
15
|
+
const allRepos = (await inspectRepos(discoverRepos(config))).filter((r) => r.version)
|
|
16
|
+
if (allRepos.length === 0) {
|
|
16
17
|
console.log(pc.yellow('No repos found.'))
|
|
17
18
|
return
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
heading('GitHub releases')
|
|
21
22
|
|
|
23
|
+
// Narrowed to --packages up front (a no-op when it wasn't given) — no
|
|
24
|
+
// reason to check, or print the tag/release status of, packages nobody
|
|
25
|
+
// asked about.
|
|
26
|
+
const repos = filterByNames(allRepos, packages)
|
|
27
|
+
if (repos.length === 0) {
|
|
28
|
+
console.log(pc.dim('Nothing selected.'))
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
console.log(pc.dim(`Checking ${repos.length} package(s) for a tag and an existing release...`))
|
|
23
33
|
// A release always targets `v<local version>` — the tag `polyrepo bump`
|
|
24
34
|
// creates. No tag for the current version means there's nothing to
|
|
@@ -48,7 +58,10 @@ export async function releaseCommand({ configPath, packages, yes = false, dryRun
|
|
|
48
58
|
|
|
49
59
|
const selected = await selectPackages({
|
|
50
60
|
items: withStatus,
|
|
51
|
-
packages
|
|
61
|
+
// withStatus is already the exact --packages match — passing those
|
|
62
|
+
// same dir names back here just skips the checkbox without
|
|
63
|
+
// re-warning about anything (see publish.js for the same pattern).
|
|
64
|
+
packages: packages ? withStatus.map((r) => r.dir) : undefined,
|
|
52
65
|
message: 'Pick packages to create a GitHub Release for:',
|
|
53
66
|
buildChoice: (all) => {
|
|
54
67
|
const columns = [
|
package/src/commands/tag.js
CHANGED
|
@@ -5,6 +5,7 @@ import { loadConfig } from '../loadConfig.js'
|
|
|
5
5
|
import { syncMaster } from '../masterSync.js'
|
|
6
6
|
import { tagName, tagExists, tagExistsAsync, createAndPushTag } from '../tags.js'
|
|
7
7
|
import { selectPackages } from '../selectPackages.js'
|
|
8
|
+
import { filterByNames } from '../filterByNames.js'
|
|
8
9
|
import { pMap } from '../pMap.js'
|
|
9
10
|
import { heading, stepHeading, ok, fail, columnWidths, formatRow } from '../ui.js'
|
|
10
11
|
import { releaseOne } from './release.js'
|
|
@@ -18,14 +19,23 @@ import { releaseOne } from './release.js'
|
|
|
18
19
|
// release for it" are almost always the same reason to run this.
|
|
19
20
|
export async function tagCommand({ configPath, packages, yes = false, dryRun = false, release = false } = {}) {
|
|
20
21
|
const config = loadConfig({ configPath })
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
22
|
+
const allRepos = (await inspectRepos(discoverRepos(config))).filter((r) => r.version)
|
|
23
|
+
if (allRepos.length === 0) {
|
|
23
24
|
console.log(pc.yellow('No repos found.'))
|
|
24
25
|
return
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
heading('Tag current versions')
|
|
28
29
|
|
|
30
|
+
// Narrowed to --packages up front (a no-op when it wasn't given) — no
|
|
31
|
+
// reason to check, or print the tag status of, packages nobody asked
|
|
32
|
+
// about.
|
|
33
|
+
const repos = filterByNames(allRepos, packages)
|
|
34
|
+
if (repos.length === 0) {
|
|
35
|
+
console.log(pc.dim('Nothing selected.'))
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
console.log(pc.dim(`Checking ${repos.length} package(s) for an existing tag...`))
|
|
30
40
|
const withTag = await pMap(repos, async (r) => {
|
|
31
41
|
const tag = tagName(r.version)
|
|
@@ -36,7 +46,10 @@ export async function tagCommand({ configPath, packages, yes = false, dryRun = f
|
|
|
36
46
|
|
|
37
47
|
const selected = await selectPackages({
|
|
38
48
|
items: withTag,
|
|
39
|
-
packages
|
|
49
|
+
// withTag is already the exact --packages match — passing those same
|
|
50
|
+
// dir names back here just skips the checkbox without re-warning
|
|
51
|
+
// about anything (see publish.js for the same pattern).
|
|
52
|
+
packages: packages ? withTag.map((r) => r.dir) : undefined,
|
|
40
53
|
message: 'Pick packages to tag at their current version:',
|
|
41
54
|
buildChoice: (all) => {
|
|
42
55
|
const columns = [
|