rman 1.0.12 → 1.2.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 +90 -70
- package/cli.js +226 -14
- package/commands/build.command.js +1 -0
- package/commands/changed.command.js +2 -2
- package/commands/changelog.command.js +13 -15
- package/commands/config.command.js +61 -0
- package/commands/diff.command.js +9 -4
- package/commands/exec.command.js +2 -8
- package/commands/github-release.command.js +1 -0
- package/commands/info.command.d.ts +9 -0
- package/commands/info.command.js +12 -2
- package/commands/run.command.js +5 -8
- package/commands/test.command.js +1 -0
- package/commands/version.command.js +53 -14
- package/constants.js +1 -1
- package/core/config.d.ts +265 -17
- package/core/config.js +651 -76
- package/core/custom-command.d.ts +133 -0
- package/core/custom-command.js +99 -0
- package/core/extends-config.d.ts +27 -0
- package/core/extends-config.js +89 -0
- package/core/manifest.d.ts +222 -0
- package/core/manifest.js +150 -0
- package/core/merge-config.d.ts +70 -0
- package/core/merge-config.js +193 -0
- package/core/package.d.ts +73 -7
- package/core/package.js +86 -24
- package/core/plugin.d.ts +112 -0
- package/core/plugin.js +189 -0
- package/core/repository.d.ts +91 -1
- package/core/repository.js +277 -132
- package/core/resolve-target.d.ts +12 -0
- package/core/resolve-target.js +33 -0
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/core/version-scheme.d.ts +134 -0
- package/core/version-scheme.js +148 -0
- package/core/workspace.d.ts +68 -0
- package/core/workspace.js +83 -0
- package/index.d.ts +55 -8
- package/index.js +42 -7
- package/interfaces/rman-config.interface.d.ts +222 -46
- package/package.json +16 -7
- package/services/change-hash.service.d.ts +88 -0
- package/services/change-hash.service.js +112 -0
- package/services/changelog.service.d.ts +8 -13
- package/services/changelog.service.js +12 -11
- package/services/conventional-commits.service.d.ts +73 -0
- package/services/conventional-commits.service.js +116 -0
- package/services/docker-publish.service.js +1 -1
- package/services/exec.service.js +1 -1
- package/services/github-release.service.d.ts +2 -2
- package/services/github-release.service.js +10 -5
- package/services/list.service.js +5 -2
- package/services/run.service.d.ts +112 -6
- package/services/run.service.js +265 -89
- package/services/system-info.d.ts +22 -7
- package/services/system-info.js +8 -23
- package/services/version-plan.service.d.ts +244 -0
- package/services/version-plan.service.js +414 -0
- package/services/version.service.d.ts +102 -82
- package/services/version.service.js +226 -434
- package/services.d.ts +5 -3
- package/services.js +5 -3
- package/utils/bin-path.d.ts +59 -0
- package/utils/bin-path.js +82 -0
- package/utils/child-tracker.d.ts +16 -0
- package/utils/child-tracker.js +30 -0
- package/utils/exec.d.ts +13 -2
- package/utils/exec.js +17 -17
- package/utils/git.d.ts +9 -3
- package/utils/git.js +10 -2
- package/utils/package-filter.d.ts +33 -2
- package/utils/package-filter.js +47 -7
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
- package/utils/release-version.js +3 -3
- package/utils/run-bin.d.ts +46 -0
- package/utils/run-bin.js +63 -0
- package/utils/version-stamp.d.ts +14 -6
- package/utils/version-stamp.js +25 -13
- package/commands/ci.command.js +0 -30
- package/commands/clean.command.d.ts +0 -3
- package/commands/clean.command.js +0 -36
- package/commands/publish.command.d.ts +0 -3
- package/commands/publish.command.js +0 -225
- package/rmanrc.schema.json +0 -392
- package/services/ci.service.d.ts +0 -40
- package/services/ci.service.js +0 -204
- package/services/clean.service.d.ts +0 -42
- package/services/clean.service.js +0 -226
- package/services/publish.service.d.ts +0 -79
- package/services/publish.service.js +0 -273
- package/utils/change-hash.d.ts +0 -68
- package/utils/change-hash.js +0 -98
- package/utils/conventional-commits.d.ts +0 -52
- package/utils/conventional-commits.js +0 -90
- package/utils/npm-run-path.d.ts +0 -67
- package/utils/npm-run-path.js +0 -63
- package/utils/workspace-range.d.ts +0 -17
- package/utils/workspace-range.js +0 -28
- /package/commands/{ci.command.d.ts → config.command.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ packages, computing semantic version bumps from your commit history, publishing,
|
|
|
11
11
|
importing external repos with history intact, and more - all driven by a single, cascading
|
|
12
12
|
`.rmanrc`/`.rmanrc.yml` config.
|
|
13
13
|
|
|
14
|
-
Every command is also available as a **programmatic API** - see [docs/
|
|
14
|
+
Every command is also available as a **programmatic API** - see [docs/rman.md](https://github.com/panates/rman/blob/main/docs/rman.md) if you
|
|
15
15
|
want to call `rman`'s logic directly from a Node.js script instead of shelling out to the CLI.
|
|
16
16
|
|
|
17
17
|
## Table of contents
|
|
@@ -66,7 +66,7 @@ rman version
|
|
|
66
66
|
# Apply it: bump versions, write CHANGELOG.md, commit, tag
|
|
67
67
|
rman version --changelog
|
|
68
68
|
|
|
69
|
-
# Publish everything that isn't already on the registry
|
|
69
|
+
# Publish everything that isn't already on the registry (needs rman-node)
|
|
70
70
|
rman publish
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -75,7 +75,7 @@ Run any command with `--help` for its full option list (`rman version --help`, `
|
|
|
75
75
|
## Commands
|
|
76
76
|
|
|
77
77
|
The summaries and examples below cover the common cases. For the full option list, defaults, and
|
|
78
|
-
worked examples of every single command, see **[docs/cli.md](docs/cli.md)**.
|
|
78
|
+
worked examples of every single command, see **[docs/cli-rman.md](https://github.com/panates/rman/blob/main/docs/cli-rman.md)**.
|
|
79
79
|
|
|
80
80
|
| Command | Purpose |
|
|
81
81
|
| --- | --- |
|
|
@@ -85,23 +85,34 @@ worked examples of every single command, see **[docs/cli.md](docs/cli.md)**.
|
|
|
85
85
|
| [`build`](#rman-build) | Alias for `run build`. |
|
|
86
86
|
| [`test`](#rman-test) | Alias for `run test`. |
|
|
87
87
|
| [`exec <command..>`](#rman-exec-command) | Runs an arbitrary shell command in each package. |
|
|
88
|
-
| [`
|
|
89
|
-
| [`clean`](#rman-clean) | Removes compiled TypeScript output and configured extra files/dirs. |
|
|
88
|
+
| [`config`](#rman-config) | Prints the effective `.rmanrc` config for the current directory's package. |
|
|
90
89
|
| [`changed`](#rman-changed) | Shows which packages the next `version` run would bump. |
|
|
91
90
|
| [`diff [package]`](#rman-diff-package) | Shows the git diff since a package's (or the repo's) last release tag. |
|
|
92
91
|
| [`changelog`](#rman-changelog) | Generates a changelog per package from unreleased commits. |
|
|
93
92
|
| [`version [bump]`](#rman-version-bump) | Bumps versions of changed packages (and their dependents). |
|
|
94
|
-
| [`
|
|
93
|
+
| [`github-release`](#rman-github-release) | Creates the repository's GitHub Release for its release tag. |
|
|
95
94
|
| [`import <path>`](#rman-import-path) | Imports an external git repository as a new package, with history. |
|
|
96
95
|
|
|
96
|
+
**`publish`, `ci` and `clean` come from [`rman-node`](https://www.npmjs.com/package/rman-node)**,
|
|
97
|
+
not from this package - each is about npm or TypeScript rather than about repositories. Name the
|
|
98
|
+
plugin in `.rmanrc` to get them:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
plugins: ['rman-node']
|
|
102
|
+
```
|
|
103
|
+
|
|
97
104
|
Options shared across several commands:
|
|
98
105
|
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
- **`skip`** (`.rmanrc`, per package): leave this package alone - honoured by every command that
|
|
107
|
+
*acts* on packages, ignored by `list`, which reports on them.
|
|
108
|
+
- **Package filtering** (`list`, `run`/`build`/`test`, `exec`, `version`, `changelog`, and a
|
|
109
|
+
plugin's own commands): `--scope <glob>`, `--ignore <glob>`, `--deps`, `--dependents` - see
|
|
110
|
+
[Package filtering](https://github.com/panates/rman/blob/main/docs/rman.md#package-filtering-scopeignoredepsdependents)
|
|
111
|
+
for the full semantics.
|
|
112
|
+
- **`--root`/`-r`** (every command that narrows to the package you are standing in -
|
|
113
|
+
`run`/`build`/`test`, `exec`, `changelog`, `diff`): run against the whole repository instead.
|
|
103
114
|
- **Branch guard** (every command that mutates state or runs scripts - `run`/`build`/`test`,
|
|
104
|
-
`exec`, `
|
|
115
|
+
`exec`, `version`): `--allow-branch <glob>`, `--ignore-branch <glob>` -
|
|
105
116
|
refuses to run unless (or if) the current git branch matches, the same idea as GitHub Actions'
|
|
106
117
|
own `branches`/`branches-ignore` workflow filters.
|
|
107
118
|
|
|
@@ -123,8 +134,9 @@ rman list --scope '@myorg/*' --ignore '*-internal'
|
|
|
123
134
|
|
|
124
135
|
### `rman info`
|
|
125
136
|
|
|
126
|
-
Prints local environment (OS/CPU/memory, Node
|
|
127
|
-
|
|
137
|
+
Prints local environment (OS/CPU/memory, Node, git) and repository information. A plugin adds its
|
|
138
|
+
own ecosystem's part - `rman-node` reports whichever package manager `.rmanrc "packageManager"`
|
|
139
|
+
names, plus the installed `rman` packages.
|
|
128
140
|
|
|
129
141
|
```bash
|
|
130
142
|
rman info
|
|
@@ -148,7 +160,7 @@ rman run build --scope pkg-a --deps # pkg-a plus everything it depends on
|
|
|
148
160
|
|
|
149
161
|
Per-package/script behavior (pre/post hooks, `if` conditions, skip, concurrency, ...) is
|
|
150
162
|
configurable via `.rmanrc run.<script>.*` - see [Configuration](#configuration) below and the full
|
|
151
|
-
writeup in [docs/
|
|
163
|
+
writeup in [docs/rman.md](https://github.com/panates/rman/blob/main/docs/rman.md#runservice).
|
|
152
164
|
|
|
153
165
|
### `rman build`
|
|
154
166
|
|
|
@@ -177,25 +189,16 @@ rman exec --scope pkg-a -- ls -la
|
|
|
177
189
|
rman exec --topo=false pwd # every package independently, alphabetical order
|
|
178
190
|
```
|
|
179
191
|
|
|
180
|
-
### `rman
|
|
181
|
-
|
|
182
|
-
Deletes `node_modules` and any lockfile in every package (or runs the package's own `"ci"` script
|
|
183
|
-
instead, if it defines one), then installs once at the root.
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
rman ci
|
|
187
|
-
rman ci --package-manager pnpm
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### `rman clean`
|
|
192
|
+
### `rman config`
|
|
191
193
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
Prints the **effective** config for the package of the current directory - after the directory
|
|
195
|
+
cascade, `"[selector]"` blocks, `extends`, `+key` appends and `${{ ... }}` expressions have all been
|
|
196
|
+
applied. What rman actually sees there, which no single file shows.
|
|
195
197
|
|
|
196
198
|
```bash
|
|
197
|
-
rman
|
|
198
|
-
rman
|
|
199
|
+
rman config # the package you are standing in
|
|
200
|
+
rman config --root # the repository root's own config instead
|
|
201
|
+
rman config --json | jq .run
|
|
199
202
|
```
|
|
200
203
|
|
|
201
204
|
### `rman changed`
|
|
@@ -260,36 +263,9 @@ feat: needs to ship right now
|
|
|
260
263
|
Release-As: patch
|
|
261
264
|
```
|
|
262
265
|
|
|
263
|
-
See [docs/
|
|
266
|
+
See [docs/rman.md#versionservice](https://github.com/panates/rman/blob/main/docs/rman.md#versionservice) for the full grouping/propagation
|
|
264
267
|
algorithm, prerelease semantics, and `"workspace:"` dependency-range handling.
|
|
265
268
|
|
|
266
|
-
### `rman publish`
|
|
267
|
-
|
|
268
|
-
Publishes every package to its configured registry - `npm` by default, or whatever each package's
|
|
269
|
-
own `.rmanrc "publish.target"` says (`"npm"`, `"docker"`, or both). Each target decides for itself
|
|
270
|
-
whether the current version is already out there: `npm view` on the npm side, `docker manifest
|
|
271
|
-
inspect` on the docker side.
|
|
272
|
-
|
|
273
|
-
```bash
|
|
274
|
-
rman publish # show the plan, then ask for confirmation
|
|
275
|
-
rman publish --yes # publish immediately, no confirmation
|
|
276
|
-
rman publish --dry-run # only show the plan, never publish
|
|
277
|
-
rman publish --access public # required for a new scoped package
|
|
278
|
-
rman publish --tag next
|
|
279
|
-
rman publish --otp 123456
|
|
280
|
-
rman publish --registry https://registry.example.com --userconfig ./ci.npmrc
|
|
281
|
-
rman publish --package-manager pnpm
|
|
282
|
-
rman publish --target docker # only the packages configured for the "docker" target
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
A `"workspace:*"`/`"workspace:^"`/`"workspace:~"` dependency range is automatically rewritten to a
|
|
286
|
-
real, registry-consumable range immediately before each package's publish, and restored right
|
|
287
|
-
after - see [docs/api.md#publishservice](docs/api.md#publishservice).
|
|
288
|
-
|
|
289
|
-
A package opts into building/pushing a Docker image via `.rmanrc "publish.target": ["docker"]` plus
|
|
290
|
-
a `"publish.docker"` block (`image`, `platforms`, `buildContexts`, `buildArgs`, ...) - see
|
|
291
|
-
[docs/cli/publish.md#docker-publishing-publishdocker](docs/cli/publish.md#docker-publishing-publishdocker).
|
|
292
|
-
|
|
293
269
|
### `rman github-release`
|
|
294
270
|
|
|
295
271
|
Creates the repository's GitHub Release for the version that just shipped - one per run, named after
|
|
@@ -302,7 +278,7 @@ rman github-release --yes
|
|
|
302
278
|
It is deliberately neither a `publish.target` nor opt-in: a release isn't a registry a package ships
|
|
303
279
|
to, it's the repository's own record that a version shipped, and every repository wants that record.
|
|
304
280
|
It needs no configuration at all - see
|
|
305
|
-
[docs/cli/github-release.md](docs/cli/github-release.md).
|
|
281
|
+
[docs/cli/github-release.md](https://github.com/panates/rman/blob/main/docs/cli/github-release.md).
|
|
306
282
|
|
|
307
283
|
### `rman import <path>`
|
|
308
284
|
|
|
@@ -318,6 +294,51 @@ rman import ../my-old-standalone-repo --dest libs # under libs/ instead of pac
|
|
|
318
294
|
already. After importing, add the new directory to your `workspaces` glob if it isn't already
|
|
319
295
|
covered, then run `rman ci` to install it.
|
|
320
296
|
|
|
297
|
+
## Shared config (`extends`) and appending (`+key`)
|
|
298
|
+
|
|
299
|
+
House rules live in one package, and a repository names it:
|
|
300
|
+
|
|
301
|
+
```yaml
|
|
302
|
+
# .rmanrc.yml
|
|
303
|
+
extends: '@panates/rman-monorepo'
|
|
304
|
+
|
|
305
|
+
'[*]':
|
|
306
|
+
run:
|
|
307
|
+
build:
|
|
308
|
+
+before: 'rm -rf ./cache' # adds to the base's step, rather than replacing it
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
`extends` merges underneath the file naming it (a package, a path, or a list), and may itself be
|
|
312
|
+
chained. `+key` appends to whatever the key already resolved to - from the base, a parent directory,
|
|
313
|
+
or a selector - which is what lets a repository add one step without restating a list it doesn't
|
|
314
|
+
own. See [docs/rman.md](https://github.com/panates/rman/blob/main/docs/rman.md#inheriting-a-shared-config-extends).
|
|
315
|
+
|
|
316
|
+
## Your own commands
|
|
317
|
+
|
|
318
|
+
A module in `.rman/` at the repository root becomes an `rman` command:
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
// .rman/deploy.mjs
|
|
322
|
+
import { defineCommand, PublishService } from 'rman';
|
|
323
|
+
|
|
324
|
+
export default defineCommand({
|
|
325
|
+
describe: 'Ships what was just published to the staging cluster',
|
|
326
|
+
builder: y => y.option('stage', { choices: ['dev', 'prod'], demandOption: true }),
|
|
327
|
+
async handler({ repository }, args) {
|
|
328
|
+
const plan = await PublishService.getPlan(repository);
|
|
329
|
+
console.log(plan.filter(e => e.status === 'publish').length, '->', args.stage);
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
rman deploy --stage prod
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
It gets its own `--help` entry, its own options, and the `Repository` handed to it. For a shell step
|
|
339
|
+
across every package, reach for `.rmanrc "run.<script>"` instead - see
|
|
340
|
+
[docs/cli/custom-commands.md](https://github.com/panates/rman/blob/main/docs/cli/custom-commands.md) for where the line falls.
|
|
341
|
+
|
|
321
342
|
## Configuration
|
|
322
343
|
|
|
323
344
|
`rman` reads config cascaded from the repository root down to each package's own directory (the
|
|
@@ -373,25 +394,24 @@ version:
|
|
|
373
394
|
{ "group": "plugins" }
|
|
374
395
|
```
|
|
375
396
|
|
|
376
|
-
See [docs/
|
|
397
|
+
See [docs/rman.md#configuration-rmanrc--rmanrcyml](https://github.com/panates/rman/blob/main/docs/rman.md#configuration-rmanrc--rmanrcyml) for the
|
|
377
398
|
full key reference (every `run.<script>.*` sub-key, `clean.*`, `changelog.*`, selector precedence,
|
|
378
399
|
and which keys are root-level-only today).
|
|
379
400
|
|
|
380
|
-
**Editor autocomplete
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
autocomplete and validation in VS Code/WebStorm. For a `.rmanrc.cjs`/`.mjs`/`.js` config, wrap it in
|
|
384
|
-
the exported `defineConfig()` helper instead for the same autocomplete via the `RmanConfig` type:
|
|
401
|
+
**Editor autocomplete** comes from the `RmanConfig` type, so it applies to the JS forms of the
|
|
402
|
+
config - wrap a `.rmanrc.cjs`/`.mjs`/`.js` in the exported `defineConfig()` helper (or annotate it
|
|
403
|
+
with `/** @type {import('rman').RmanConfig} */`):
|
|
385
404
|
|
|
386
405
|
```js
|
|
387
406
|
// .rmanrc.mjs
|
|
388
407
|
import { defineConfig } from 'rman';
|
|
389
|
-
export default defineConfig({
|
|
408
|
+
export default defineConfig({ allowBranch: ['main'] });
|
|
390
409
|
```
|
|
391
410
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
411
|
+
With a plugin, import `defineConfig` from the plugin instead (`rman-node`'s carries its own keys
|
|
412
|
+
into the type). The `.rmanrc`/`.rmanrc.yml` forms get no checking - see
|
|
413
|
+
[docs/rman.md#editor-support-types](https://github.com/panates/rman/blob/main/docs/rman.md#editor-support-types) for why the JSON Schema that
|
|
414
|
+
used to cover them was removed.
|
|
395
415
|
|
|
396
416
|
## Programmatic API
|
|
397
417
|
|
|
@@ -408,7 +428,7 @@ await VersionService.applyPlan(repository, plan, { changelog: true, push: true }
|
|
|
408
428
|
|
|
409
429
|
Full reference, with detailed examples for every service (`VersionService`, `PublishService`,
|
|
410
430
|
`ChangelogService`, `RunService`, `CiService`, `CleanService`, `ExecService`, `ListService`,
|
|
411
|
-
`ImportService`, `SystemInfo`) and the `Repository`/`Package` core classes: **[docs/
|
|
431
|
+
`ImportService`, `SystemInfo`) and the `Repository`/`Package` core classes: **[docs/rman.md](https://github.com/panates/rman/blob/main/docs/rman.md)**.
|
|
412
432
|
|
|
413
433
|
## Node compatibility
|
|
414
434
|
|
package/cli.js
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import colors from 'ansi-colors';
|
|
5
|
-
import
|
|
6
|
+
import * as yaml from 'js-yaml';
|
|
7
|
+
import yargs, {} from 'yargs';
|
|
6
8
|
import { hideBin } from 'yargs/helpers';
|
|
7
9
|
import * as buildCommand from './commands/build.command.js';
|
|
8
10
|
import * as changedCommand from './commands/changed.command.js';
|
|
9
11
|
import * as changelogCommand from './commands/changelog.command.js';
|
|
10
|
-
import * as
|
|
11
|
-
import * as cleanCommand from './commands/clean.command.js';
|
|
12
|
+
import * as configCommand from './commands/config.command.js';
|
|
12
13
|
import * as diffCommand from './commands/diff.command.js';
|
|
13
14
|
import * as execCommand from './commands/exec.command.js';
|
|
14
15
|
import * as githubReleaseCommand from './commands/github-release.command.js';
|
|
15
16
|
import * as importCommand from './commands/import.command.js';
|
|
16
17
|
import * as infoCommand from './commands/info.command.js';
|
|
17
18
|
import * as listCommand from './commands/list.command.js';
|
|
18
|
-
import * as publishCommand from './commands/publish.command.js';
|
|
19
19
|
import * as runCommand from './commands/run.command.js';
|
|
20
20
|
import * as testCommand from './commands/test.command.js';
|
|
21
21
|
import * as versionCommand from './commands/version.command.js';
|
|
22
22
|
import { version } from './constants.js';
|
|
23
|
+
import { assertNoBuiltinShadowing, loadCustomCommands } from './core/custom-command.js';
|
|
23
24
|
import { Repository } from './core/repository.js';
|
|
24
|
-
import { LOG_LEVELS } from './utils/logger.js';
|
|
25
|
+
import { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
|
26
|
+
import { filterPackages, readPackageFilterOptions, readRootOption } from './utils/package-filter.js';
|
|
27
|
+
import { printableConfig } from './utils/printable-config.js';
|
|
28
|
+
import { runBin } from './utils/run-bin.js';
|
|
25
29
|
export async function runCli(options) {
|
|
26
30
|
try {
|
|
27
31
|
const repository = await Repository.create(options?.cwd);
|
|
@@ -37,6 +41,12 @@ export async function runCli(options) {
|
|
|
37
41
|
describe: 'Default verbosity of the per-step log for run/build/ci (default: info, or .rmanrc "logLevel"; ' +
|
|
38
42
|
'overridable per-package via .rmanrc run.<script>.logLevel)',
|
|
39
43
|
choices: LOG_LEVELS,
|
|
44
|
+
})
|
|
45
|
+
.option('config', {
|
|
46
|
+
describe: 'Show what this command would run with - its resolved options, the packages it would act ' +
|
|
47
|
+
'on, and the .rmanrc keys it reads - and run nothing',
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
default: false,
|
|
40
50
|
})
|
|
41
51
|
.showHelpOnFail(false, 'Run with --help for available options')
|
|
42
52
|
.fail((msg, err) => {
|
|
@@ -47,36 +57,218 @@ export async function runCli(options) {
|
|
|
47
57
|
? err.message
|
|
48
58
|
: '';
|
|
49
59
|
console.log('\n' + colors.red(text));
|
|
50
|
-
|
|
60
|
+
/** A real `Error`, marked `logged` since the text above just went out: yargs hands the
|
|
61
|
+
* reason over as a bare string, and rethrowing that raw made bad argv indistinguishable
|
|
62
|
+
* from success to anything holding the promise - or the shell. */
|
|
63
|
+
const failure = new Error(typeof msg === 'string' && msg ? msg : typeof err?.message === 'string' ? err.message : 'invalid arguments');
|
|
64
|
+
failure.logged = true;
|
|
65
|
+
throw failure;
|
|
51
66
|
}
|
|
52
|
-
|
|
53
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Already printed by whoever threw it (the `logged` convention), so there is nothing to say
|
|
69
|
+
* - but it still has to **throw**, not exit.
|
|
70
|
+
*
|
|
71
|
+
* This branch called `process.exit(1)`, and `runCli` is a library entry point: rman's own
|
|
72
|
+
* bin calls it, so do `rman-node`'s fixtures and every spec. Exiting from in here took the
|
|
73
|
+
* whole process down before any caller could see the rejection - which in mocha meant the
|
|
74
|
+
* first command that failed killed the run and the suite could not report a single result.
|
|
75
|
+
* The exit belongs to the bin entry alone (see `isMain()` at the bottom), which already does
|
|
76
|
+
* it; the shell sees the same code either way.
|
|
77
|
+
*/
|
|
78
|
+
const failure = new Error(err?.message || 'command failed');
|
|
79
|
+
failure.logged = true;
|
|
80
|
+
throw failure;
|
|
54
81
|
});
|
|
82
|
+
/**
|
|
83
|
+
* **`--config` is applied here, once, rather than in every command.** Every command - built-in,
|
|
84
|
+
* a plugin's, a `.rman/*.mjs` one - reaches yargs through `program.command`, so wrapping that
|
|
85
|
+
* one method is what makes the flag genuinely global. Adding an option to each command instead
|
|
86
|
+
* would have meant twelve edits plus a rule for plugin authors to remember, and a flag that
|
|
87
|
+
* quietly does nothing on whichever command forgot it is worse than no flag.
|
|
88
|
+
*/
|
|
89
|
+
interceptConfigFlag(repository, program);
|
|
55
90
|
infoCommand.initCli(repository, program);
|
|
56
91
|
listCommand.initCli(repository, program);
|
|
57
92
|
runCommand.initCli(repository, program);
|
|
58
93
|
buildCommand.initCli(repository, program);
|
|
59
|
-
ciCommand.initCli(repository, program);
|
|
60
|
-
cleanCommand.initCli(repository, program);
|
|
61
94
|
changelogCommand.initCli(repository, program);
|
|
62
95
|
testCommand.initCli(repository, program);
|
|
63
96
|
versionCommand.initCli(repository, program);
|
|
64
|
-
publishCommand.initCli(repository, program);
|
|
65
97
|
githubReleaseCommand.initCli(repository, program);
|
|
66
98
|
execCommand.initCli(repository, program);
|
|
67
99
|
changedCommand.initCli(repository, program);
|
|
68
100
|
diffCommand.initCli(repository, program);
|
|
101
|
+
configCommand.initCli(repository, program);
|
|
69
102
|
importCommand.initCli(repository, program);
|
|
103
|
+
/**
|
|
104
|
+
* Commands that are not built in, from two places, registered after the built-ins so the clash
|
|
105
|
+
* check below has the full list to compare against:
|
|
106
|
+
*
|
|
107
|
+
* - **plugins** (`.rmanrc "plugins"`) - a *package* contributing commands, which is how
|
|
108
|
+
* everything Node-specific lives outside rman's core;
|
|
109
|
+
* - **`.rman/*.mjs`** - this one repository's own commands.
|
|
110
|
+
*
|
|
111
|
+
* The repository wins a name clash with a plugin, and silently: it is the more specific
|
|
112
|
+
* statement, the same way its own `.rmanrc` overrides an `extends` base. A plugin taking a
|
|
113
|
+
* *built-in's* name is still refused outright.
|
|
114
|
+
*/
|
|
115
|
+
/** Already loaded: `Repository.create` had to, because a plugin's workspace provider is what
|
|
116
|
+
* finds the packages. This is just what it brought back. */
|
|
117
|
+
const pluginCommands = repository.pluginCommands;
|
|
118
|
+
const { commands: localCommands, errors } = await loadCustomCommands(repository.dirname);
|
|
119
|
+
const localNames = new Set(localCommands.map(c => c.name));
|
|
120
|
+
const commands = [...pluginCommands.filter(c => !localNames.has(c.name)), ...localCommands];
|
|
121
|
+
assertNoBuiltinShadowing(commands, BUILT_IN_COMMANDS);
|
|
122
|
+
for (const custom of commands) {
|
|
123
|
+
program.command({
|
|
124
|
+
command: custom.command,
|
|
125
|
+
describe: custom.describe,
|
|
126
|
+
/** Forwarded, not dropped: this loop builds a *new* spec object, so anything the command
|
|
127
|
+
* declared and is not copied here is silently lost - `--config` printed the whole config
|
|
128
|
+
* for every plugin command until this line existed (measured, on `clean` and `ci`). */
|
|
129
|
+
configKeys: custom.configKeys,
|
|
130
|
+
builder: custom.builder ?? (y => y),
|
|
131
|
+
handler: args => {
|
|
132
|
+
/** Resolved per invocation, not once at registration: `--log-level` is only known now. */
|
|
133
|
+
const logLevel = args.logLevel ?? resolveRootLogLevel(repository);
|
|
134
|
+
const context = {
|
|
135
|
+
repository,
|
|
136
|
+
package: repository.currentPackage,
|
|
137
|
+
runBin: (bin, argv, opts) => runBin(bin, argv, { cwd: repository.dirname, logLevel, ...opts }),
|
|
138
|
+
logger: new Logger(logLevel),
|
|
139
|
+
};
|
|
140
|
+
return custom.handler(context, args);
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/** Warned about, not thrown: one unparseable file must not take the other commands with it.
|
|
145
|
+
* Loud enough not to be mistaken for success, and it names the file and the reason - "my
|
|
146
|
+
* command isn't there" is otherwise a long afternoon. */
|
|
147
|
+
for (const { file, reason } of errors) {
|
|
148
|
+
console.error(colors.yellow(`Skipped "${path.relative(process.cwd(), file)}": ${reason}`));
|
|
149
|
+
}
|
|
70
150
|
program.demandCommand(1).strict().recommendCommands().completion();
|
|
71
151
|
if (!_argv.length)
|
|
72
152
|
program.showHelp();
|
|
153
|
+
/** Rejects rather than exiting, for the reason the `fail` handler above does - the bin entry
|
|
154
|
+
* turns the rejection into an exit code. */
|
|
73
155
|
else
|
|
74
|
-
await program.parseAsync()
|
|
156
|
+
await program.parseAsync();
|
|
75
157
|
}
|
|
76
158
|
catch (e) {
|
|
77
|
-
|
|
159
|
+
/** Setup failures - no `package.json` to be found, a `.rman` command shadowing a built-in -
|
|
160
|
+
* used to be printed and then swallowed, so the shell saw success: `rman info` in the wrong
|
|
161
|
+
* directory reported failure on stdout and 0 to whatever called it. Printed once (unless the
|
|
162
|
+
* thrower already did, per the `logged` convention) and rethrown, so the exit code agrees
|
|
163
|
+
* with the message. */
|
|
164
|
+
if (!e?.logged)
|
|
165
|
+
console.error(colors.red(e.message));
|
|
166
|
+
throw e;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Replaces `program.command` with a version that wraps every handler: with `--config` it prints
|
|
171
|
+
* what the command would run with and returns, instead of running it.
|
|
172
|
+
*
|
|
173
|
+
* One interception point rather than an option per command - see the call site. It has to go in
|
|
174
|
+
* **before** any command is registered, since it only affects registrations that pass through it.
|
|
175
|
+
*/
|
|
176
|
+
function interceptConfigFlag(repository, program) {
|
|
177
|
+
const register = program.command.bind(program);
|
|
178
|
+
program.command = (spec) => {
|
|
179
|
+
if (!spec || typeof spec !== 'object' || typeof spec.handler !== 'function')
|
|
180
|
+
return register(spec);
|
|
181
|
+
const run = spec.handler;
|
|
182
|
+
return register({
|
|
183
|
+
...spec,
|
|
184
|
+
handler: (args) => (args.config ? printCommandConfig(repository, spec, args) : run(args)),
|
|
185
|
+
});
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* What a command would run with: its resolved options, the packages it would act on, and the
|
|
190
|
+
* `.rmanrc` those packages carry.
|
|
191
|
+
*
|
|
192
|
+
* The three parts answer the three ways a run surprises someone. **Options** is the parsed argv -
|
|
193
|
+
* what *this invocation* asked for, plus any default yargs itself declares. Most of rman's own
|
|
194
|
+
* defaults are not yargs defaults (`run`'s `bail`/`topo`/`progress` are resolved per package inside
|
|
195
|
+
* `RunService`, from `.rmanrc run.<script>.*`), so an option missing here means "not stated on the
|
|
196
|
+
* command line", and the `.rmanrc` section below is where its value comes from. **Packages** is the
|
|
197
|
+
* set after `--scope`/`--ignore`/
|
|
198
|
+
* `--deps`/`skip`, computed the way the command itself computes it, because a config that is
|
|
199
|
+
* perfect for a package the command never reaches explains nothing. **Config** is narrowed to the
|
|
200
|
+
* keys the command declares it reads (`configKeys` on the command object, which yargs ignores) and
|
|
201
|
+
* is the whole effective config otherwise - the honest answer when nobody has said which half
|
|
202
|
+
* matters.
|
|
203
|
+
*/
|
|
204
|
+
function printCommandConfig(repository, spec, args) {
|
|
205
|
+
const name = String(spec.command).split(/\s+/)[0];
|
|
206
|
+
const comment = (text) => (process.stdout.isTTY ? colors.gray(text) : text);
|
|
207
|
+
console.log(comment(`# ${name} --config: nothing was run.`));
|
|
208
|
+
console.log(`command: ${name}`);
|
|
209
|
+
const options = readOptions(args);
|
|
210
|
+
console.log(Object.keys(options).length
|
|
211
|
+
? `options:\n${indent(yaml.dump(printableConfig(options), { noRefs: true }).trimEnd())}`
|
|
212
|
+
: `options: {}${comment(' # nothing but defaults')}`);
|
|
213
|
+
const targets = commandTargets(repository, args);
|
|
214
|
+
console.log(`packages: ${targets.length ? `[${targets.map(p => p.name).join(', ')}]` : '[]'}`);
|
|
215
|
+
const keys = typeof spec.configKeys === 'function' ? spec.configKeys(args) : spec.configKeys;
|
|
216
|
+
console.log(comment(`# .rmanrc${keys?.length ? `, the keys ${name} reads: ${keys.join(', ')}` : ', in full'}`));
|
|
217
|
+
/**
|
|
218
|
+
* **The root package is always listed, even when it is not a target.** A repo-wide setting is
|
|
219
|
+
* read off the root - `packageManager`, `allowBranch`, `version.*`, `githubRelease.*` - so
|
|
220
|
+
* showing only the targets answered `rman ci --config` with `pkg-a: {}`, which reads as "nothing
|
|
221
|
+
* is configured" about the one key `ci` actually reads (measured).
|
|
222
|
+
*/
|
|
223
|
+
const shown = [...targets];
|
|
224
|
+
if (!shown.some(p => p === repository.rootPackage))
|
|
225
|
+
shown.push(repository.rootPackage);
|
|
226
|
+
const config = {};
|
|
227
|
+
for (const pkg of shown)
|
|
228
|
+
config[pkg.name] = keys?.length ? pick(pkg.config, keys) : pkg.config;
|
|
229
|
+
console.log(indent(yaml.dump(printableConfig(config), { noRefs: true, lineWidth: 100 }).trimEnd()));
|
|
230
|
+
if (!targets.includes(repository.rootPackage)) {
|
|
231
|
+
console.log(comment(`# "${repository.rootPackage.name}" is the root - listed because repo-wide keys are read there.`));
|
|
78
232
|
}
|
|
79
233
|
}
|
|
234
|
+
/** The parsed argv, minus what yargs adds and minus the camelCase twin of every dashed option -
|
|
235
|
+
* both spellings are the same option, and printing both reads as two settings. */
|
|
236
|
+
function readOptions(args) {
|
|
237
|
+
const dashed = new Set(Object.keys(args).filter(k => k.includes('-')));
|
|
238
|
+
const camel = new Set([...dashed].map(k => k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())));
|
|
239
|
+
const result = {};
|
|
240
|
+
for (const [key, value] of Object.entries(args)) {
|
|
241
|
+
if (key === '_' || key === '$0' || key === 'config' || camel.has(key))
|
|
242
|
+
continue;
|
|
243
|
+
result[key] = value;
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
/** The packages the command would act on - `filterPackages` with the command's own options, so
|
|
248
|
+
* this is the same set the command will compute, `skip` included. Narrowed to the current package
|
|
249
|
+
* for a command that scopes by directory, unless `--root` says otherwise. */
|
|
250
|
+
function commandTargets(repository, args) {
|
|
251
|
+
const current = repository.currentPackage;
|
|
252
|
+
if (current && !readRootOption(args))
|
|
253
|
+
return [current];
|
|
254
|
+
return filterPackages(repository.getPackages(), readPackageFilterOptions(args));
|
|
255
|
+
}
|
|
256
|
+
/** The named config paths only - `"run.build"` keeps just that subtree, under that path. */
|
|
257
|
+
function pick(config, keys) {
|
|
258
|
+
const result = {};
|
|
259
|
+
for (const key of keys) {
|
|
260
|
+
const value = key.split('.').reduce((node, part) => (node == null ? undefined : node[part]), config);
|
|
261
|
+
if (value !== undefined)
|
|
262
|
+
result[key] = value;
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
}
|
|
266
|
+
function indent(text) {
|
|
267
|
+
return text
|
|
268
|
+
.split('\n')
|
|
269
|
+
.map(line => (line ? ` ${line}` : line))
|
|
270
|
+
.join('\n');
|
|
271
|
+
}
|
|
80
272
|
function isMain() {
|
|
81
273
|
try {
|
|
82
274
|
return !!process.argv[1] && realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
|
|
@@ -86,4 +278,24 @@ function isMain() {
|
|
|
86
278
|
}
|
|
87
279
|
}
|
|
88
280
|
if (isMain())
|
|
89
|
-
runCli().catch(() =>
|
|
281
|
+
runCli().catch(() => process.exit(1));
|
|
282
|
+
/** Every name a built-in command answers to - what a `.rman/*.mjs` command may not take. Kept here
|
|
283
|
+
* rather than read back out of yargs (which exposes no such list) and pinned by a test against the
|
|
284
|
+
* `command:` strings in `src/commands/*.command.ts`, so adding a command can't quietly leave a
|
|
285
|
+
* repository's own able to shadow it. */
|
|
286
|
+
const BUILT_IN_COMMANDS = [
|
|
287
|
+
'build',
|
|
288
|
+
'changed',
|
|
289
|
+
'changelog',
|
|
290
|
+
'completion',
|
|
291
|
+
'config',
|
|
292
|
+
'diff',
|
|
293
|
+
'exec',
|
|
294
|
+
'github-release',
|
|
295
|
+
'import',
|
|
296
|
+
'info',
|
|
297
|
+
'list',
|
|
298
|
+
'run',
|
|
299
|
+
'test',
|
|
300
|
+
'version',
|
|
301
|
+
];
|
|
@@ -4,6 +4,7 @@ import { applyRunOptions, readRunOptions } from './run.command.js';
|
|
|
4
4
|
export function initCli(repository, program) {
|
|
5
5
|
program.command({
|
|
6
6
|
command: 'build',
|
|
7
|
+
configKeys: ['run.build'],
|
|
7
8
|
describe: 'Alias for "run build"',
|
|
8
9
|
builder: cmd => applyRunOptions(cmd).example('$0 build', '# Builds packages'),
|
|
9
10
|
handler: async (args) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import colors from 'ansi-colors';
|
|
2
|
-
import {
|
|
2
|
+
import { VersionPlanService } from '../services/version-plan.service.js';
|
|
3
3
|
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
4
4
|
export function initCli(repository, program) {
|
|
5
5
|
program.command({
|
|
@@ -11,7 +11,7 @@ export function initCli(repository, program) {
|
|
|
11
11
|
type: 'boolean',
|
|
12
12
|
}),
|
|
13
13
|
handler: async (args) => {
|
|
14
|
-
const plan = await
|
|
14
|
+
const plan = await VersionPlanService.getPlanner().getPlan(repository, readPackageFilterOptions(args));
|
|
15
15
|
const changed = plan.filter(e => e.status === 'bump');
|
|
16
16
|
if (args.json) {
|
|
17
17
|
console.log(JSON.stringify(changed.map(e => ({ name: e.package.name, group: e.group, from: e.from, to: e.to, reason: e.reason })), undefined, 2));
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
import colors from 'ansi-colors';
|
|
2
|
+
import { ChangeHashService } from '../services/change-hash.service.js';
|
|
2
3
|
import { ChangelogService } from '../services/changelog.service.js';
|
|
3
4
|
import { Logger, resolveRootLogLevel } from '../utils/logger.js';
|
|
4
|
-
import { applyPackageFilterOptions, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
5
|
+
import { applyPackageFilterOptions, applyRootOption, readPackageFilterOptions } from '../utils/package-filter.js';
|
|
5
6
|
export function initCli(repository, program) {
|
|
6
7
|
program.command({
|
|
7
8
|
command: 'changelog',
|
|
9
|
+
configKeys: ['changelog', 'publish.skip'],
|
|
8
10
|
describe: 'Generates a changelog per package from unreleased commits',
|
|
9
|
-
builder: cmd => applyPackageFilterOptions(cmd)
|
|
11
|
+
builder: cmd => applyRootOption(applyPackageFilterOptions(cmd), 'Generate')
|
|
10
12
|
.example('$0 changelog', "# Auto-detects each package's own last release tag (or npm version)")
|
|
11
13
|
.example('$0 changelog --from <hash> --write', '# Since a specific commit, written to file')
|
|
12
14
|
.option('from', {
|
|
13
15
|
describe: 'Generate the changelog since this commit/hash, applied the same way to every package. ' +
|
|
14
|
-
'Default (also "
|
|
15
|
-
'tag - same as "version"/"changed" - falling back to
|
|
16
|
-
"then to its whole history for a package that's never been
|
|
16
|
+
'Default (also "auto" explicitly): auto-detect per package from its own most recent release ' +
|
|
17
|
+
'tag - same as "version"/"changed" - falling back to the version its own ecosystem\'s ' +
|
|
18
|
+
"registry reports (no tag yet), then to its whole history for a package that's never been " +
|
|
19
|
+
'released at all',
|
|
17
20
|
type: 'string',
|
|
18
21
|
})
|
|
19
22
|
.option('write', {
|
|
@@ -24,12 +27,6 @@ export function initCli(repository, program) {
|
|
|
24
27
|
describe: "With --write, the file to prepend into, relative to each package's own directory " +
|
|
25
28
|
'(default: "CHANGELOG.md", or .rmanrc "changelog.filePath")',
|
|
26
29
|
type: 'string',
|
|
27
|
-
})
|
|
28
|
-
.option('root', {
|
|
29
|
-
alias: 'r',
|
|
30
|
-
describe: 'Generate for the whole repository even when the current directory is inside a single ' +
|
|
31
|
-
'package (which otherwise scopes it to just that package). No effect elsewhere.',
|
|
32
|
-
type: 'boolean',
|
|
33
30
|
})
|
|
34
31
|
.option('include-skipped', {
|
|
35
32
|
describe: 'Also generate for a package with .rmanrc "publish.skip" - excluded by default',
|
|
@@ -46,10 +43,11 @@ export function initCli(repository, program) {
|
|
|
46
43
|
const from = args.from;
|
|
47
44
|
const write = args.write;
|
|
48
45
|
const logger = new Logger(args.logLevel ?? resolveRootLogLevel(repository));
|
|
49
|
-
if (!from || from ===
|
|
50
|
-
// Auto-detection is mostly local git work, but the
|
|
51
|
-
// when a package has no tag at all
|
|
52
|
-
// the command looks hung for that
|
|
46
|
+
if (!from || from === ChangeHashService.AUTO) {
|
|
47
|
+
// Auto-detection is mostly local git work, but the registry fallback it can reach for
|
|
48
|
+
// (only when a package has no tag at all, and only if the package's own ecosystem provides
|
|
49
|
+
// one) is a network round trip per package - without this, the command looks hung for that
|
|
50
|
+
// stretch instead of just busy.
|
|
53
51
|
logger.info(colors.gray("Detecting each package's last release..."));
|
|
54
52
|
}
|
|
55
53
|
const options = {
|