rman 1.3.0 → 2.0.0-beta.3

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.
Files changed (187) hide show
  1. package/README.md +65 -31
  2. package/builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts +83 -0
  3. package/builtins/platforms/node/augmentation/system-info.augmentation.d.ts +26 -0
  4. package/builtins/platforms/node/augmentation/system-info.augmentation.js +79 -0
  5. package/builtins/platforms/node/commands/ci.command.d.ts +133 -0
  6. package/builtins/platforms/node/commands/ci.command.js +60 -0
  7. package/builtins/platforms/node/commands/clean.command.d.ts +185 -0
  8. package/builtins/platforms/node/commands/clean.command.js +74 -0
  9. package/builtins/platforms/node/index.d.ts +8 -0
  10. package/builtins/platforms/node/index.js +8 -0
  11. package/builtins/platforms/node/node-config.interface.d.ts +76 -0
  12. package/builtins/platforms/node/node-config.interface.js +7 -0
  13. package/builtins/platforms/node/node-manifest.provider.d.ts +68 -0
  14. package/builtins/platforms/node/node-manifest.provider.js +125 -0
  15. package/builtins/platforms/node/node.platform.d.ts +53 -0
  16. package/builtins/platforms/node/node.platform.js +134 -0
  17. package/builtins/platforms/node/services/ci.service.d.ts +47 -0
  18. package/builtins/platforms/node/services/ci.service.js +213 -0
  19. package/builtins/platforms/node/services/clean.service.d.ts +53 -0
  20. package/builtins/platforms/node/services/clean.service.js +237 -0
  21. package/builtins/platforms/node/services/publish.service.d.ts +114 -0
  22. package/builtins/platforms/node/services/publish.service.js +371 -0
  23. package/builtins/platforms/node/services/version-plan.service.d.ts +44 -0
  24. package/builtins/platforms/node/services/version-plan.service.js +58 -0
  25. package/builtins/platforms/node/utils/workspace-range.d.ts +26 -0
  26. package/builtins/platforms/node/utils/workspace-range.js +28 -0
  27. package/builtins/presets/index.d.ts +15 -0
  28. package/builtins/presets/index.js +57 -0
  29. package/builtins/presets/node.d.ts +16 -0
  30. package/builtins/presets/node.js +57 -0
  31. package/{services → builtins/publish-targets/docker}/docker-publish.service.d.ts +52 -31
  32. package/{services → builtins/publish-targets/docker}/docker-publish.service.js +49 -26
  33. package/builtins/publish-targets/docker/docker.target.d.ts +54 -0
  34. package/builtins/publish-targets/docker/docker.target.js +40 -0
  35. package/builtins/publish-targets/docker/index.d.ts +2 -0
  36. package/builtins/publish-targets/docker/index.js +2 -0
  37. package/builtins/publish-targets/npm/index.d.ts +2 -0
  38. package/builtins/publish-targets/npm/index.js +2 -0
  39. package/builtins/publish-targets/npm/npm-publish-target.d.ts +73 -0
  40. package/builtins/publish-targets/npm/npm-publish-target.js +95 -0
  41. package/builtins/publish-targets/npm/npm-view.d.ts +48 -0
  42. package/builtins/publish-targets/npm/npm-view.js +71 -0
  43. package/cli.d.ts +8 -0
  44. package/cli.js +228 -93
  45. package/commands/build.command.d.ts +177 -3
  46. package/commands/build.command.js +20 -10
  47. package/commands/changelog.command.d.ts +296 -3
  48. package/commands/changelog.command.js +127 -43
  49. package/commands/config.command.d.ts +44 -3
  50. package/commands/config.command.js +53 -24
  51. package/commands/diff.command.d.ts +37 -3
  52. package/commands/diff.command.js +25 -16
  53. package/commands/exec.command.d.ts +193 -3
  54. package/commands/exec.command.js +60 -58
  55. package/commands/github-release.command.d.ts +154 -3
  56. package/commands/github-release.command.js +67 -37
  57. package/commands/import.command.d.ts +36 -3
  58. package/commands/import.command.js +28 -20
  59. package/commands/info.command.d.ts +35 -7
  60. package/commands/info.command.js +36 -30
  61. package/commands/lint.command.d.ts +176 -0
  62. package/commands/lint.command.js +34 -0
  63. package/commands/list.command.d.ts +163 -3
  64. package/commands/list.command.js +109 -71
  65. package/commands/publish.command.d.ts +231 -0
  66. package/commands/publish.command.js +304 -0
  67. package/commands/run.command.d.ts +186 -6
  68. package/commands/run.command.js +26 -72
  69. package/commands/test.command.d.ts +173 -3
  70. package/commands/test.command.js +16 -10
  71. package/commands/version.command.d.ts +330 -3
  72. package/commands/version.command.js +292 -78
  73. package/commands.d.ts +32 -0
  74. package/commands.js +28 -0
  75. package/constants.js +1 -1
  76. package/core/application.d.ts +116 -0
  77. package/core/application.js +160 -0
  78. package/core/classes/package.d.ts +219 -0
  79. package/core/classes/package.js +214 -0
  80. package/core/classes/registry.d.ts +30 -0
  81. package/core/classes/registry.js +47 -0
  82. package/core/{repository.d.ts → classes/repository.d.ts} +69 -20
  83. package/core/{repository.js → classes/repository.js} +195 -75
  84. package/core/classes/service.d.ts +49 -0
  85. package/core/classes/service.js +40 -0
  86. package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +23 -1
  87. package/core/{version-scheme.js → classes/version-scheme.js} +29 -1
  88. package/core/classes/workspace.d.ts +275 -0
  89. package/core/classes/workspace.js +537 -0
  90. package/core/command-builder.d.ts +14 -0
  91. package/core/command-builder.js +94 -0
  92. package/core/config/config-file-scope.d.ts +107 -0
  93. package/core/config/config-file-scope.js +226 -0
  94. package/core/config/config-interpolator.d.ts +164 -0
  95. package/core/config/config-interpolator.js +577 -0
  96. package/core/config/config-paths.d.ts +65 -0
  97. package/core/config/config-paths.js +78 -0
  98. package/core/config/config-reader.d.ts +192 -0
  99. package/core/config/config-reader.js +727 -0
  100. package/core/{merge-config.d.ts → config/merge-config.d.ts} +33 -34
  101. package/core/config/merge-config.js +260 -0
  102. package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +1 -1
  103. package/core/{resolve-target.js → config/resolve-config-target.js} +1 -1
  104. package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +46 -10
  105. package/core/{custom-command.js → interfaces/custom-command.js} +44 -17
  106. package/core/{manifest.d.ts → interfaces/manifest.d.ts} +49 -27
  107. package/core/interfaces/manifest.js +132 -0
  108. package/core/interfaces/plugin.d.ts +174 -0
  109. package/core/interfaces/plugin.js +124 -0
  110. package/core/interfaces/publish-target.d.ts +136 -0
  111. package/core/interfaces/publish-target.js +43 -0
  112. package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
  113. package/core/interfaces/run-step.js +1 -0
  114. package/core/plugin-loader.d.ts +25 -0
  115. package/core/plugin-loader.js +53 -0
  116. package/index.d.ts +122 -24
  117. package/index.js +66 -16
  118. package/interfaces/config-scope.interface.d.ts +223 -0
  119. package/interfaces/config-scope.interface.js +1 -0
  120. package/interfaces/rman-config.interface.d.ts +821 -203
  121. package/interfaces/rman-config.interface.js +78 -1
  122. package/package.json +3 -1
  123. package/services/change-hash.service.d.ts +42 -8
  124. package/services/change-hash.service.js +92 -13
  125. package/services/changelog.service.d.ts +91 -52
  126. package/services/changelog.service.js +460 -74
  127. package/services/config-service.d.ts +8 -0
  128. package/services/config-service.js +3 -0
  129. package/services/exec.service.d.ts +23 -12
  130. package/services/exec.service.js +14 -9
  131. package/services/github-release.service.d.ts +45 -34
  132. package/services/github-release.service.js +15 -12
  133. package/services/import.service.d.ts +25 -14
  134. package/services/import.service.js +9 -5
  135. package/services/list.service.d.ts +67 -13
  136. package/services/list.service.js +62 -15
  137. package/services/run.service.d.ts +35 -22
  138. package/services/run.service.js +316 -233
  139. package/services/system-info.d.ts +1 -1
  140. package/services/version-plan.service.d.ts +35 -8
  141. package/services/version-plan.service.js +96 -36
  142. package/services/version.service.d.ts +34 -14
  143. package/services/version.service.js +35 -16
  144. package/services.d.ts +1 -1
  145. package/services.js +1 -1
  146. package/utils/bin-path.d.ts +6 -7
  147. package/utils/bin-path.js +7 -18
  148. package/utils/branch-guard.d.ts +30 -1
  149. package/utils/branch-guard.js +31 -0
  150. package/utils/color-yaml.d.ts +8 -0
  151. package/utils/color-yaml.js +86 -0
  152. package/utils/exec.d.ts +10 -0
  153. package/utils/exec.js +1 -1
  154. package/utils/git.d.ts +26 -0
  155. package/utils/git.js +65 -0
  156. package/utils/logger.d.ts +2 -2
  157. package/utils/logger.js +1 -1
  158. package/utils/package-filter.d.ts +135 -8
  159. package/utils/package-filter.js +204 -16
  160. package/utils/printable-config.d.ts +9 -1
  161. package/utils/printable-config.js +46 -1
  162. package/utils/progress-panel.d.ts +9 -0
  163. package/utils/progress-panel.js +23 -1
  164. package/utils/release-version.d.ts +1 -1
  165. package/utils/run-bin.d.ts +10 -0
  166. package/utils/run-bin.js +1 -1
  167. package/utils/run-options.d.ts +97 -0
  168. package/utils/run-options.js +81 -0
  169. package/utils/version-group.d.ts +31 -0
  170. package/utils/version-group.js +43 -0
  171. package/utils/version-stamp.d.ts +1 -1
  172. package/utils/version-stamp.js +1 -1
  173. package/commands/changed.command.d.ts +0 -3
  174. package/commands/changed.command.js +0 -29
  175. package/core/config.d.ts +0 -409
  176. package/core/config.js +0 -939
  177. package/core/extends-config.d.ts +0 -27
  178. package/core/extends-config.js +0 -89
  179. package/core/manifest.js +0 -150
  180. package/core/merge-config.js +0 -219
  181. package/core/package.d.ts +0 -83
  182. package/core/package.js +0 -106
  183. package/core/plugin.d.ts +0 -112
  184. package/core/plugin.js +0 -189
  185. package/core/workspace.d.ts +0 -68
  186. package/core/workspace.js +0 -83
  187. /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
@@ -1,98 +1,292 @@
1
- import type { RmanPlugin } from '../core/plugin.js';
2
- import type { RunConditionFn, RunStepValue } from '../core/run-step.js';
3
- /**
4
- * Adds a `+key` alongside every key of `T`, which **appends** to whatever that key already resolved
5
- * to instead of replacing it - see `mergeConfig`.
6
- *
7
- * Generated by key remapping rather than written out, so a key added to the interface gets its
8
- * append form automatically and the two can never drift apart.
9
- */
10
- export type WithAppend<T> = {
11
- [K in keyof T as `+${K & string}`]?: T[K];
12
- };
13
- /**
14
- * The one key every nested config node may carry: `vars` scoping that node's subtree - a fresh copy
15
- * per level, merged per key over the level above. See `withScopedVars` in `core/config.ts` for what
16
- * it does at resolution time, and docs/rman.md#scoped-vars for how it reads.
17
- *
18
- * **Every nested options interface extends this**, and a new one has to remember to - which is the
19
- * cost of the runtime rule being general (any object node scopes) while a type can only say it one
20
- * interface at a time. TypeScript has no way to state "and every object below this may also carry
21
- * `vars`" without a recursive remap that would wreck the error messages.
22
- *
23
- * Extended by the `XOptions` interface rather than declared on `XOptionsKeys`, so `WithAppend` does
24
- * not generate a `+vars`: appending to `vars` means nothing, since objects merge either way.
25
- */
26
- export interface ScopedVars {
27
- /** Values for `${{ vars.* }}` to read, for this node and everything under it. */
28
- vars?: Record<string, unknown>;
29
- }
1
+ import type * as yargs from 'yargs';
2
+ import type { RmanApplication } from '../core/application.js';
3
+ import type { CodeSubtree } from '../core/config/config-paths.js';
4
+ import type { CustomCommand } from '../core/interfaces/custom-command.js';
5
+ import type { Platform, Plugin } from '../core/interfaces/plugin.js';
6
+ import type { PublishTarget } from '../core/interfaces/publish-target.js';
7
+ import type { RunConditionFn, RunStepFn, RunStepValue } from '../core/interfaces/run-step.js';
8
+ import type { ConfigValueContext } from './config-scope.interface.js';
30
9
  /**
31
10
  * The shape of `.rmanrc`/`.rmanrc.yml`/`.rmanrc.cjs`/`.mjs`/`.js` (and `package.json`'s own
32
11
  * `"rman"` key) - see docs/rman.md#configuration-rmanrc-rmanrcyml for the full reference. Every
33
12
  * field is optional and cascades from the repository root down to each package's own directory.
34
- * Purely a typing aid (used by `defineConfig` below, and importable on its own for a `.rmanrc.ts`/
35
- * `.mts` authored config, or a plain `: RmanConfig` annotation) - never read by rman itself, which
36
- * only ever sees the plain JS object a JS config file exports.
13
+ * Purely a typing aid - never read by rman itself, which only ever sees the plain JS object a
14
+ * config file exports.
15
+ *
16
+ * **Two halves, and the split is who declares a key.** `RmanConfigKeys` holds the ones no command
17
+ * owns; `CommandConfigs` is what every command contributes its own into, from beside the command
18
+ * (see `CommandContribution`). This used to be two files and two types both called `RmanConfig` -
19
+ * one for the config shape, one for the command declarations - which is why the package could not
20
+ * export the second at all.
21
+ *
22
+ * **This is the type an *author* writes**, and `ResolvedConfig` - derived from it - is what a
23
+ * *reader* gets. One type could not be both: a value may be written as a **function** here, because
24
+ * rman calls it while the config resolves, and by the time anything reads `pkg.config` it has
25
+ * already been called. Answering only the reader's question is what this type used to do, and it
26
+ * made `docs/rman.md`'s own `changelog: { filePath: ({ vars }) => ... }` a compile error.
27
+ *
28
+ * **The direction matters, and it was measured the wrong way round first.** Widening this type
29
+ * while `Package.config` still used it moved the cast to every **read** - six sites broke
30
+ * (`version.command.ts`, `publish-target.ts`, `changelog.service.ts`, three in
31
+ * `github-release.service.ts`). The widening was right; leaving the reader on the same name was not.
32
+ * So the author's view is the declared one and the reader's is computed: see `Resolved`, which also
33
+ * records the two guards that make a mechanical transform safe here.
34
+ *
35
+ * **Deriving the other direction is what cannot work, and the reason is this name.** `RmanConfig` is
36
+ * also a **namespace** (`RmanConfig.CommandConfigs`, `CommandContribution`, …) that `rman-node`
37
+ * augments; a `type` alias cannot merge with a namespace, and the resulting circular reference
38
+ * resolves to `{}` **silently** - measured, every key then read as "does not exist", `vars` and
39
+ * `run` included. A derived *reader* view needs no such merge, which is why it is the half that is
40
+ * derived.
41
+ *
42
+ * Which keys may be written as a function is not a judgement made key by key: every option a
43
+ * command declares is one, because an option's value is by construction a value rather than a step
44
+ * (`CommandConfigFromMetadata`). Only the hand-written `Extra` keys are decided individually, since
45
+ * that is where a step key lives.
37
46
  */
38
- export interface RmanConfig extends RmanConfigKeys, WithAppend<RmanConfigKeys> {
47
+ export interface RmanConfig extends RmanConfigKeys, RmanConfig.CommandConfigs {
39
48
  /**
40
49
  * Configs to inherit from, merged **underneath** this one - a shared package
41
50
  * (`"@panates/rman-monorepo"`), a relative path, or an array applied in declaration order.
42
51
  *
43
- * A bare name resolves through *this* file's own `node_modules`, so a subpath works too
44
- * (`"@panates/rman-monorepo/strict"`). The target may be YAML, JSON, or a module exporting a
45
- * config via `defineConfig`, and may itself `extends` another.
52
+ * A bare name resolves through *this* file's own `node_modules`, so a subpath works too. The
53
+ * target may be YAML, JSON, or a module exporting a config via `defineConfig`, and may itself
54
+ * `extends` another.
46
55
  *
47
56
  * Top level only: a `"[selector]"` block naming one is an error rather than a no-op, since
48
57
  * inheritance is a statement about this config and not about the packages a selector names.
58
+ *
59
+ * Resolved away long before a reader sees one, and kept on this type because `rman config` prints
60
+ * the object it was resolved from.
49
61
  */
50
62
  extends?: string | string[];
51
63
  }
52
- /** Every setting a config may carry, without the `+key` append forms or `extends` - the shape
53
- * `RmanConfig` is built from, kept separate only so `WithAppend` has something to map over. */
64
+ /**
65
+ * One entry of `.rmanrc "commands"` - a command, or a **glob** naming `.js` modules that
66
+ * `export default` one.
67
+ *
68
+ * Both authoring forms count as "a command": the declarative `app => ({ ... })` factory, which is
69
+ * the one to write, and the older `defineCommand({ ... })` object a `.rman/*.mjs` uses.
70
+ */
71
+ export type CommandEntry = string | CustomCommand | RmanConfig.CommandRegisterFunction;
72
+ /** One entry of `.rmanrc "publishTargets"` - a target, or a **glob** naming `.js` modules that
73
+ * `export default` one. */
74
+ export type PublishTargetEntry = string | PublishTarget;
75
+ /**
76
+ * **The `.rmanrc` keys no command owns**, without `extends` - kept
77
+ * separate from `RmanConfig` only so the two halves stay legible. A key a *command* owns is
78
+ * declared beside that command and arrives through `RmanConfig.CommandConfigs` instead.
79
+ *
80
+ * **A plugin adds its own keys here, by declaration merging** - `rman-node` contributes `clean`
81
+ * from its own `interfaces/rman-config.interface.ts`, so `pkg.config.clean` stays typed wherever it
82
+ * is read without the core having to know npm has a `node_modules` or that TypeScript has build
83
+ * output. (A *target's* block is different again: `publish.npm.*` goes through the
84
+ * `PublishTargetConfigs` slot, beside the core's own `publish.docker.*`.)
85
+ *
86
+ * A config author annotates with the plugin's own name for the union - `RmanNodeConfig` - which is
87
+ * what makes the import that carries the augmentation explicit rather than incidental.
88
+ */
54
89
  export interface RmanConfigKeys {
55
90
  /**
56
- * **A plugin adds its own keys here, by declaration merging** - `rman-node` contributes
57
- * `clean` and `publish.directory` from its own
58
- * `interfaces/rman-config.interface.ts`, so `pkg.config.clean` stays typed wherever it is read
59
- * without the core having to know npm has a `node_modules` or that TypeScript has build output.
60
- * `WithAppend` is a mapped type evaluated at use, so an augmented key gets its `+key` form too.
61
- *
62
- * A config author annotates with the plugin's own name for the union - `RmanNodeConfig` - which
63
- * is what makes the import that carries the augmentation explicit rather than incidental.
91
+ * The technologies this repository holds, in declaration order - how its packages are
92
+ * recognized, where they are, how their versions are planned. One per ecosystem: `rman-node`
93
+ * contributes the `node` one, a Cargo plugin would contribute `cargo`, and a polyglot repository
94
+ * names both.
95
+ *
96
+ * Each entry is **a plugin, or a glob naming `.js` modules that `export default` one** - the same
97
+ * two forms `commands` and `publishTargets` take:
98
+ *
99
+ * ```js
100
+ * // .rmanrc.mjs - the instance, which a JS config can hand over and a YAML one cannot
101
+ * import { defineConfig, definePlugin } from 'rman';
102
+ * export default defineConfig({ plugins: [definePlugin({ name: 'cargo', manifestProvider })] });
103
+ * ```
104
+ *
105
+ * A *published* plugin is not usually named here at all: its package exports a config carrying
106
+ * it, so the repository writes `extends` and the plugin arrives with everything else the package
107
+ * declares. `rman-node` deliberately exports no `NodePlugin` for this reason - there is nothing a
108
+ * consumer needs to do with it by hand.
109
+ *
110
+ * ```yaml
111
+ * # .rmanrc.yml - a glob, anchored to this file's own directory
112
+ * plugins: './plugins/*.js'
113
+ * ```
114
+ *
115
+ * **A package name is not one of the forms, and `plugins: ['rman-node']` is never valid.** That
116
+ * package's entry point exports an rman *config* - `{ plugins, commands, publishTargets }` - and
117
+ * a config's way into a repository is `extends`, the key that means "merge this underneath
118
+ * mine". The two are different statements: `extends: 'rman-node'` inherits everything the
119
+ * package declares, while `plugins` names the technologies themselves. Naming the package here
120
+ * is refused with a message saying so, rather than half-read.
121
+ *
122
+ * A plugin that cannot be loaded is an error, not a skip - silently losing a manifest reader
123
+ * means every package reads as nameless at `0.0.0`, which looks like a working repository.
124
+ *
125
+ * **Root level only, and unlike `commands` this is forced rather than chosen**: `loadPlugins`
126
+ * runs inside `Repository.create` *before* the packages are known, because a plugin's
127
+ * `getWorkspace` is what finds them - so there are no package configs to read. An entry in a
128
+ * package's own `.rmanrc` is never seen.
64
129
  */
130
+ plugins?: string | Plugin | (string | Plugin)[];
65
131
  /**
66
- * Plugins to load, in declaration order - a *package* contributing commands, where `.rman/*.mjs`
67
- * contributes one repository's own.
132
+ * **The technologies available here** - how a directory's packages are recognized, where they
133
+ * are, how their versions are planned.
134
+ *
135
+ * Each entry is a `Platform`, or a **glob** naming `.js` modules that `export default` one - the
136
+ * same two forms `plugins`, `commands` and `publishTargets` take. It **appends**: naming one
137
+ * never means "and drop the ones my preset brought", so a polyglot repository writes
138
+ * `extends: ['node', 'cargo']` and holds both.
139
+ *
140
+ * ```js
141
+ * // presets/node.js
142
+ * export default defineConfig({ platforms: [new NodePlatform()], commands, publishTargets });
143
+ * ```
68
144
  *
69
- * Each entry is **either a package name (or path) to import, or a plugin object itself**:
145
+ * **Not the same key as `platform`** (singular), which *selects* one for a directory rather than
146
+ * contributing it: this says what is available, that says which one a package is. A package-level
147
+ * setting meant for one technology goes under `"[platform:node]"`.
148
+ */
149
+ platforms?: string | Platform | (string | Platform)[];
150
+ /**
151
+ * **Which technology a package belongs to**, by `Platform.name`.
70
152
  *
71
153
  * ```yaml
72
- * # .rmanrc.yml - imported by name, resolved through the repository's own node_modules
73
- * plugins: ['rman-node']
154
+ * plugins: ['node']
155
+ * "[/]":
156
+ * platform: node
74
157
  * ```
75
158
  *
76
- * ```js
77
- * // .rmanrc.mjs - or handed over directly, which a JS config can do and a YAML one cannot
78
- * import { defineConfig, definePlugin } from 'rman';
79
- * export default defineConfig({ plugins: [definePlugin({ name: 'mine', commands: [...] })] });
159
+ * **`plugins` loads; this selects.** They were one key for a while and that conflated two
160
+ * statements: `plugins` says which technologies this repository has available, which is a fact
161
+ * about the *repository* and is read once at its root. Which technology a given directory's
162
+ * package belongs to is a fact about the *package*, and a repository may hold more than one - so
163
+ * it is an ordinary cascading key, declared wherever the answer changes.
164
+ *
165
+ * - **A declaration wins over the guess.** Without it, a directory belongs to the first
166
+ * registered platform whose manifest provider recognizes it (`app.platformFor`) - which is a
167
+ * reasonable default and is registration order deciding a question about someone's code. Naming
168
+ * it takes the answer away from load order.
169
+ * - **The named platform validates it, and a mismatch is an error.** If it does not recognize the
170
+ * directory the declaration is simply untrue, and the alternative is a package reading as
171
+ * nameless at `0.0.0` - which looks like a working repository. The message names the file the
172
+ * platform looked for.
173
+ * - **It cascades like any unmarked key**, so one line at the root covers a single-technology
174
+ * repository and a package's own `.rmanrc` overrides it for that subtree. `"[/]"` keeps it on
175
+ * the root package alone.
176
+ * - **At the root it also means the repository said something**, so detection does not run - the
177
+ * same rule `plugins: []` follows. A repository that states its technology is not a repository
178
+ * that stated nothing.
179
+ *
180
+ * **At the root, naming a built-in is the same statement as `plugins: ['node']`** - it is put at
181
+ * the *front* of `plugins`, so the whole built-in arrives: the technology, its commands and its
182
+ * publish targets. Saying which technology this repository is *is* saying it has it, and making
183
+ * an author write both was a distinction only rman could see. At the front rather than appended,
184
+ * because `platformFor` takes the first registered platform that recognizes a directory - the one
185
+ * the repository says it *is* should win over whatever a shared config brought along. Read from
186
+ * the unmarked key or from `"[/]"`, since both are the root speaking.
187
+ *
188
+ * **Below the root it loads the technology alone**, and that is structural rather than chosen:
189
+ * `plugins` is read once, at the root, before any package exists, so a nested declaration cannot
190
+ * contribute commands even in principle. It is also what you want there - a Node package inside a
191
+ * Cargo repository wants npm's manifest read, not a repo-wide `rman clean` sweeping the tree.
192
+ *
193
+ * Only a built-in is promoted. A platform rman does not ship is loaded by the `plugins` entry
194
+ * that brings it, and naming one with no such entry is an error listing what the repository has -
195
+ * putting the bare name into `plugins` would hand the loader a glob matching no file, so the
196
+ * failure would read as the plugin being missing while it is registered perfectly well.
197
+ *
198
+ * **A plain string, never a `${{ }}` expression.** This is read while the packages are still
199
+ * being found, so there is no `pkg` for an expression to be about - it is what decides what a
200
+ * package *is*. An expression here is refused rather than passed through as a literal, which is
201
+ * what silently happened until it was.
202
+ */
203
+ platform?: string;
204
+ /**
205
+ * **The selector this package answers to** - what a `"[glob]"` block and `--scope`/`--ignore`
206
+ * match against, overriding whatever its platform said.
207
+ *
208
+ * ```yaml
209
+ * # packages/web/.rmanrc
210
+ * name: web
211
+ * ```
212
+ *
213
+ * **The escape hatch for an ecosystem that cannot name a package, and only that.** npm can:
214
+ * `package.json#name` exists and identifies the package, so a Node repository never writes this.
215
+ * An ecosystem where a package need not be named, or where the name is an import path nobody
216
+ * would type after `--scope`, leaves `ManifestProvider.selector` unanswered and the repository
217
+ * fills it in here.
218
+ *
219
+ * **It does not rename the package.** `pkg.name` is still what the manifest says, so tags,
220
+ * changelogs and the registry are untouched - this is an address, and `Package.selector` is where
221
+ * it lands.
222
+ *
223
+ * **Unique within the repository, and checked.** Two packages answering to one selector make
224
+ * `"[that]"` and `--scope that` ambiguous, and the ambiguity is silent: the config reaches both
225
+ * and `getPackage` returns whichever came first. The error names both directories.
226
+ *
227
+ * **Cascades like any unmarked key, which for this one is almost always a mistake** - one
228
+ * declaration above two packages gives them the same address, so the uniqueness check fires and
229
+ * says so. Declare it in the package's own `.rmanrc`.
230
+ *
231
+ * **Not inside a `"[glob]"` block**, which is refused rather than ignored: the glob matches the
232
+ * selector, so a block that sets it would need its own answer to find itself. Read from the
233
+ * unmarked cascade for the same reason `platform` is - before the package exists.
234
+ */
235
+ name?: string;
236
+ /**
237
+ * Where this repository keeps command modules of its own - a glob, or a list of them.
238
+ *
239
+ * ```yaml
240
+ * commands: ['tools/commands/*.mjs']
80
241
  * ```
81
242
  *
82
- * The object form is what lets a **plugin package export a config** rather than a single plugin:
83
- * `rman-node`'s entry point is `export default defineConfig({ plugins: [ ... ] })`, so it is an
84
- * `.rmanrc` like any other and is free to grow a second plugin without changing its shape. When an
85
- * imported module exports a config this way, **only its `plugins` are read** - a config's other
86
- * keys reach a repository through `extends`, which is the key that means "merge this underneath
87
- * mine".
243
+ * **Where a command comes from, whoever ships it.** A repository writing one of its own, and a
244
+ * package contributing three, reach yargs by this one key - so there is one source of
245
+ * non-built-in commands and one precedence slot. `.rman/*.{js,mjs,cjs}` is simply this key's
246
+ * default value rather than a second mechanism beside it, and a package needs no plugin to
247
+ * contribute a command: a plugin is a *technology*, and a command is not one.
248
+ *
249
+ * **A relative glob is anchored to the file that declared it**, not to the repository root (see
250
+ * `anchorContributions`), so a shared config can ship commands with `commands: './commands/*.js'`
251
+ * and have it mean its own directory.
252
+ *
253
+ * **Always appends** (`ALWAYS_APPEND`), like the other two contribution keys and for the same
254
+ * reason: naming a directory of your own never means "and stop loading the ones my shared config
255
+ * ships". It follows that a closer layer cannot *un*-say one.
256
+ *
257
+ * **Trap: a glob replaces the `.rman/` default and an instance does not.** The default applies
258
+ * only when *no* glob was declared anywhere - the key appends onto other layers, not onto a
259
+ * built-in fallback - so a shared config shipping its commands by glob silently takes the
260
+ * `.rman/` directory away from every repository inheriting it. Measured both ways on the same
261
+ * pair of repositories: with the shared config declaring instances, the consumer's own
262
+ * `.rman/hello.mjs` is in `rman --help`; with it declaring `'./commands/*.js'`, that command is
263
+ * simply gone. **A config meant to be inherited should therefore list its commands
264
+ * individually.**
88
265
  *
89
- * This is how everything that only means something in a Node repository lives outside rman's
90
- * core. A plugin that cannot be loaded is an error, not a skip: silently losing `rman publish` is
91
- * worse than not starting.
266
+ * Declared at any level, unlike `plugins` and `publishTargets`, because by the time this is read
267
+ * the packages exist - so a package's own `.rmanrc` may contribute one. The commands themselves
268
+ * are still repository-wide, because there is one command list; a package declaring one is
269
+ * contributing it to the repository.
92
270
  *
93
- * Root level only - which commands exist is a property of the repository, not of a package.
271
+ * A module exports either form: the declarative `app => ({ ... })` factory, which is the one to
272
+ * write, or the `defineCommand({ ... })` object. **`.ts` is not loadable** - rman imports these in
273
+ * its own process, with no loader registered - so a TypeScript repository compiles them first or
274
+ * writes them as `.mjs`.
94
275
  */
95
- plugins?: string | RmanPlugin | (string | RmanPlugin)[];
276
+ commands?: CommandEntry | CommandEntry[];
277
+ /**
278
+ * Publish targets this repository has - where a package's artifact ships. An instance, or a glob
279
+ * naming `.js` modules that `export default` one.
280
+ *
281
+ * The same two forms as `plugins` and `commands`, and for the same reason: a target is a
282
+ * contribution, so a config declares it rather than a plugin registering it by hand. rman's own
283
+ * `docker` target is built in; `rman-node` contributes `npm` from its own config.
284
+ *
285
+ * Always appends. **Root level only, like `plugins` and for the same mechanical reason** - both
286
+ * are read by `loadPlugins` inside `Repository.create`, before there is a package whose config
287
+ * could be consulted. `commands` is the one of the three that is read at any level.
288
+ */
289
+ publishTargets?: PublishTargetEntry | PublishTargetEntry[];
96
290
  /**
97
291
  * Values for `${{ vars.* }}` to read - a name for something the config would otherwise repeat:
98
292
  *
@@ -132,10 +326,6 @@ export interface RmanConfigKeys {
132
326
  */
133
327
  skip?: boolean;
134
328
  group?: boolean | string;
135
- version?: RmanConfig.VersionOptions;
136
- changelog?: RmanConfig.ChangelogOptions;
137
- publish?: RmanConfig.PublishOptions;
138
- githubRelease?: RmanConfig.GithubReleaseOptions;
139
329
  /** Keyed by npm script name (e.g. `"build"`, `"lint"`, `"test"`). A bare string (or array of
140
330
  * them) is shorthand for `{ exec: ... }` - `test: "mocha"` and `test: { exec: "mocha" }` mean
141
331
  * exactly the same thing, and a bare function is the same shorthand for a function step. */
@@ -155,11 +345,11 @@ export interface RmanConfigKeys {
155
345
  * **A list, and only a list.** It used to accept a `Record<string, string>` too, documented as "an
156
346
  * explicit name -> range map" - and the ranges went nowhere: the one reader took `Object.keys` and
157
347
  * dropped the values. Nor could they ever mean anything here, since the cascade works from groups
158
- * and severities, and `ManifestProvider.updateDependencyVersions` rewrites ranges in the
348
+ * and severities, and `Plugin.updateDependencyVersions` rewrites ranges in the
159
349
  * *manifest* - a range declared only in `.rmanrc` has no file to be written to. What this key
160
350
  * states is an **edge**, and an edge needs two ends and nothing else.
161
351
  *
162
- * **Core, and it has to be**: it is layered on top of whatever `ManifestProvider.dependencies`
352
+ * **Core, and it has to be**: it is layered on top of whatever `Plugin.readDependencies`
163
353
  * read, and it is the *only* way a repository with no provider at all has a graph - a repo whose
164
354
  * manifests rman cannot read can still state its edges by hand. Moving it to an ecosystem plugin
165
355
  * would take that away from exactly the repositories that need it.
@@ -198,73 +388,365 @@ export interface RmanConfigKeys {
198
388
  */
199
389
  [selector: `[${string}]`]: RmanConfig;
200
390
  }
391
+ export declare const commandRegistry: RmanConfig.CommandRegisterFunction[];
392
+ /**
393
+ * The one key every nested config node may carry: `vars` scoping that node's subtree - a fresh copy
394
+ * per level, merged per key over the level above. See `withScopedVars` in `core/config.ts` for what
395
+ * it does at resolution time, and docs/rman.md#scoped-vars for how it reads.
396
+ *
397
+ * **Every nested options interface has to carry this**, and a hand-written one had to remember to -
398
+ * which is the cost of the runtime rule being general (any object node scopes) while a type can only
399
+ * say it one interface at a time. TypeScript has no way to state "and every object below this may
400
+ * also carry `vars`" without a recursive remap that would wreck the error messages. A command's
401
+ * contributed block gets it from `RmanConfig.ConfigBlock` and cannot forget.
402
+ */
403
+ export interface ScopedVars {
404
+ /** Values for `${{ vars.* }}` to read, for this node and everything under it. */
405
+ vars?: Record<string, unknown>;
406
+ }
201
407
  export declare namespace RmanConfig {
202
408
  /**
203
- * One `version.stamp` entry: a path, or a path plus the identifier that holds the version.
409
+ * **The slot every command merges its own keys into.** Empty here on purpose: the core declares
410
+ * no command's config, each command declares its own beside the command itself, and a plugin's
411
+ * commands do the same from their own package.
412
+ *
413
+ * A command augments it with its derived contribution rather than a hand-written interface, so
414
+ * the option list and the config type cannot drift apart:
415
+ *
416
+ * ```ts
417
+ * const versionCommand = registerCommand(...);
418
+ * export default versionCommand;
204
419
  *
205
- * The object form exists because the identifier was unnameable - only the exact lowercase word
206
- * `version` was ever matched, so a Go `const Version`, a Python `__version__` and a plain
207
- * `appVersion` were all silently skipped. What the identifier *means* is the ecosystem's
208
- * (`ManifestProvider.stampVersion`); which file holds one is the repository's, which is why it is
209
- * here.
420
+ * declare module '../interfaces/rman-config.interface.js' {
421
+ * namespace RmanConfig {
422
+ * interface CommandConfigs extends CommandContribution<ReturnType<typeof versionCommand>> {}
423
+ * }
424
+ * }
425
+ * ```
426
+ *
427
+ * **Two commands may not contribute under the same top-level key.** Interface merging is not a
428
+ * deep merge: `{run: {build: ...}}` and `{run: {test: ...}}` is
429
+ * `Interface 'CommandConfigs' cannot simultaneously extend types ...` - measured. That is the
430
+ * mechanical reason `build` and `test` own nothing and only *read* `run.build`/`run.test`; `run`
431
+ * is the single owner of that subtree.
210
432
  */
211
- type VersionStampEntry = string | {
212
- file: string;
213
- constant?: string;
214
- };
215
- interface VersionOptions extends VersionOptionsKeys, WithAppend<VersionOptionsKeys>, ScopedVars {
433
+ interface CommandConfigs {
216
434
  }
217
- interface VersionOptionsKeys {
218
- commitMessage?: string;
219
- /** Default for `version --changelog` when the CLI flag isn't given - a standing "always fold
220
- * the changelog into the version-bump commit" policy, rather than something that behaves
221
- * differently on the one run someone forgets to pass `--changelog`. An explicit `--changelog`/
222
- * `--no-changelog` on the command line still wins either way. Root-level only. Default `false`. */
223
- changelog?: boolean;
224
- /** Tag naming the repository's own release, as opposed to the per-package/group tags
225
- * `changelog.tagPattern` names - only created when the root is on a calendar version (a repo
226
- * with more than one version line). Root-level only. Default `"release-*"`. Must **not** match
227
- * any package's own `changelog.tagPattern`, or that package's changelog boundary will resolve
228
- * to the repository release instead of its own last release. */
229
- releaseTagPattern?: string;
230
- /** Keep this package's Dockerfile `org.opencontainers.image.version` label in step with the
231
- * version being written. Per-package cascaded. Default `true` - the label's value is, by
232
- * specification, the version of the packaged software, so there is only ever one correct
233
- * value for it, and `version` is what knows it. Only ever *rewrites* a label the Dockerfile
234
- * already declares (never inserts one), and reads the same path `publish --target docker`
235
- * builds from (`publish.docker.dockerfile`), so a package without one is a no-op. */
236
- stampDockerfile?: boolean;
237
- /** Files whose hard-coded version is rewritten to the version being written, in the same
238
- * commit as the bump - paths relative to the package's own directory (e.g.
239
- * `["src/constants.ts"]`). Per-package cascaded; a listed file a package doesn't have is a
240
- * silent no-op, so one `"[*]"` declaration covers a repo where only some packages carry one.
241
- *
242
- * Stamping the source, not the build output: a build-time rewrite leaves the checked-in file
243
- * claiming a placeholder, so anything running from source reports that placeholder, git never
244
- * records the released version, and the rewrite has to be redone on every build. */
245
- stamp?: VersionStampEntry | VersionStampEntry[];
246
- /** Command(s) run at the version write itself, when the package does not declare a hook for
247
- * that slot of its own (`version` in a Node repository's `package.json#scripts`, whatever a
248
- * plugin's step source answers elsewhere - the package's own declaration wins, as in `run`).
249
- * An array runs them in sequence. `${{ pkg.targetVersion }}` is bound here and in the two
250
- * below, and nowhere else.
251
- *
252
- * A `RunStepFn` runs in place of a shell command - but note that `${{ pkg.targetVersion }}` is
253
- * a *string* substitution, so a function reads the written version off `pkg` instead. */
254
- exec?: RunStepValue | RunStepValue[];
255
- /** Same, before the write (`preversion` in a Node repository). */
256
- before?: RunStepValue | RunStepValue[];
257
- /** Same, after it (`postversion` in a Node repository). */
258
- after?: RunStepValue | RunStepValue[];
259
- }
260
- interface ChangelogOptions extends ChangelogOptionsKeys, WithAppend<ChangelogOptionsKeys>, ScopedVars {
435
+ interface CommandConfig {
436
+ skip?: boolean;
437
+ logLevel?: 'silent' | 'error' | 'info' | 'verbose';
438
+ vars?: RmanConfig.VarsMap;
439
+ [index: string]: any;
261
440
  }
262
- interface ChangelogOptionsKeys {
263
- ignoreTypes?: string[];
264
- template?: string;
265
- filePath?: string;
266
- tagPattern?: string;
441
+ interface VarsMap {
442
+ /** Values for `${{ vars.* }}` to read, for this node and everything under it. */
443
+ vars?: Record<string, unknown>;
267
444
  }
445
+ /**
446
+ * Called during init, with the application - not at import, and not with just the repository.
447
+ *
448
+ * The application is what a command reaches services through (`app.getService('changelog')`), and
449
+ * `app.repository` is still there for the metadata that reads it: `version`'s help names its own
450
+ * scheme's bump words, which cannot be known before a repository exists.
451
+ */
452
+ type CommandRegisterFunction = (app: RmanApplication) => CommandMetadata;
453
+ /** One declared option: everything yargs takes, plus where it may be set from. */
454
+ type CommandOption = yargs.Options & {
455
+ array?: boolean;
456
+ conflicts?: string | string[];
457
+ target: 'cli' | 'config' | 'both';
458
+ /** The flag's spelling on the command line when it differs from the config key - `ignoreDirty`
459
+ * is `--ignore-dirty`. The key stays the camelCase one, since that is what a `.rmanrc` writes. */
460
+ cliName?: string;
461
+ };
462
+ /**
463
+ * One declared positional, as `CommandMetadata.positionals` holds them - yargs' own type, named
464
+ * here so a command's author can reach it.
465
+ *
466
+ * **An alias with no additions, and it earns its place anyway.** `positionals` was the one part
467
+ * of a declaration that could only be typed by importing yargs, and a plugin does not depend on
468
+ * yargs - rman does. Nothing enforced that, because a returned object literal is checked
469
+ * structurally: measured on a JavaScript plugin, `positionals: { paths: { type: 'string' } }`
470
+ * widens `'string'` to `string` and fails `PositionalOptionsType`, with the error arriving on
471
+ * the whole `commands` key several levels of "is not assignable" away from the word that caused
472
+ * it. A `satisfies Record<string, PositionalOption>` beside the options' own puts it back on the
473
+ * line. Same reasoning as the flat `CommandOption` in `index.ts`.
474
+ */
475
+ type PositionalOption = yargs.PositionalOptions;
476
+ type CommandMetadata = {
477
+ command: string;
478
+ /** Other names the command answers to - `list` is also `ls`. */
479
+ aliases?: string[];
480
+ /**
481
+ * The technologies this command is for, by `Platform.name` - `['node']` for `clean` and `ci`,
482
+ * which only mean anything in an npm package. Absent means **every** platform, which is what a
483
+ * command that speaks for no one in particular says.
484
+ *
485
+ * It narrows what the command acts on: in a polyglot repository `rman clean` sweeps the node
486
+ * packages and leaves the Cargo ones alone, with no `--platform` and no check inside the
487
+ * command.
488
+ */
489
+ platforms?: string[];
490
+ /**
491
+ * yargs parser switches this command needs. `exec` is the only one so far and needs two:
492
+ * `populate--` to keep everything after `--` out of its own options, and
493
+ * `unknown-options-as-args` so the flags of the command *being run* pass through untouched.
494
+ */
495
+ parserConfiguration?: Partial<yargs.ParserConfigurationOptions>;
496
+ describe?: string;
497
+ examples?: {
498
+ command: string;
499
+ description?: string;
500
+ }[];
501
+ /**
502
+ * The options this command **owns** - the ones it contributes to `RmanConfig`, under
503
+ * `configKey`. An option a command merely *reads* belongs to whoever declares it; name it in
504
+ * `configKeys` instead. One key, one owner: two commands declaring the same key differently
505
+ * would collide silently in the augmentation's intersection.
506
+ */
507
+ config?: Record<string, CommandOption>;
508
+ /**
509
+ * Where `config` lands in a `.rmanrc`. Defaults to the first word of `command`, and a dotted
510
+ * path nests (`'run.build'` -> `{ run: { build: ... } }`).
511
+ *
512
+ * **Needed exactly once across the nine commands today**, which is why it is optional rather
513
+ * than required: `github-release` writes `githubRelease`, since a config key is camelCase and a
514
+ * command name is hyphenated. Deriving that with a kebab-to-camel rule was the alternative and
515
+ * was refused - it is one more rule to remember, and when it goes wrong the key lands somewhere
516
+ * plausible and nobody notices.
517
+ */
518
+ configKey?: string;
519
+ /**
520
+ * Config subtrees this command reads but does not own - what `--config` should show beside its
521
+ * own. A function of argv where the answer depends on it (`run <script>` reads `run.<script>`).
522
+ *
523
+ * `version` is the clearest case: it owns `version.*`, but `group` decides which packages move
524
+ * together and `changelog.*` is consulted when `--changelog` folds one into the bump commit.
525
+ * Neither is `version`'s to declare.
526
+ */
527
+ configKeys?: string[] | ((argv: yargs.Arguments) => string[]);
528
+ /**
529
+ * The positionals `command` declares, described - `'version [bump]'` names `bump`, this says
530
+ * what it is. Keyed by name, and **the names are checked against the command string**: a typo
531
+ * or a positional the command never declared fails on its own key and names the string it was
532
+ * checked against, which the `.positional('bump', ...)` builder call could never do.
533
+ *
534
+ * Only the description belongs here. Whether one is required (`<x>`) or variadic (`[x..]`) is
535
+ * part of the command string, which is the only place it can be, since that is what yargs
536
+ * parses.
537
+ */
538
+ positionals?: Record<string, PositionalOption>;
539
+ /**
540
+ * **The parameter is the command's own business, so this states no type for it.**
541
+ * A handler annotates itself with `ArgsOf<typeof config, typeof COMMAND>`, which this interface
542
+ * cannot compute for it - `M` is inferred from the literal the handler sits in, so naming it
543
+ * here is the circularity `ArgsOf` documents.
544
+ *
545
+ * It was `(argv: yargs.Arguments) => void`, and that was **not** a neutral placeholder: a
546
+ * parameter is checked contravariantly, so every command's own `Args` had to be a *supertype* of
547
+ * yargs' `{ [argName: string]: unknown }`. An index signature does not make a property present,
548
+ * so any required key put the declaration out of assignment - which is what forced `ArgsOf` to
549
+ * mark **everything** optional, required positionals included. Measured when it stopped:
550
+ * `Property 'script' is missing in type '{ [argName: string]: unknown; ... }' but required in
551
+ * type '{ script: string; }'`, on `run` and `import` alone.
552
+ *
553
+ * `never` is the spelling for "any parameter type is acceptable" - it constrains the *shape*
554
+ * (one argument, returning nothing a caller reads) and leaves the type to the annotation. It is
555
+ * not an `any` that could leak: nothing ever reads argv through this field, since
556
+ * `toYargsCommand` casts the handler to yargs' own and that cast is the single place the two
557
+ * descriptions of argv are admitted to disagree.
558
+ */
559
+ handler: (argv: never) => void;
560
+ };
561
+ /**
562
+ * The names a command string declares as positionals: `'run <script> [args..]'` is
563
+ * `'script' | 'args'`.
564
+ *
565
+ * Handles all four spellings in use - `<required>`, `[optional]`, `[variadic..]`, and the
566
+ * `[three...]` that `list` writes (yargs' own marker is two dots, so that one is a latent bug
567
+ * worth fixing separately; the parser tolerates it rather than silently producing a name with a
568
+ * dot in it).
569
+ */
570
+ type PositionalsOf<C extends string> = C extends `${string}<${infer N}>${infer Rest}` ? StripDots<N> | PositionalsOf<Rest> : C extends `${string}[${infer N}]${infer Rest}` ? StripDots<N> | PositionalsOf<Rest> : never;
571
+ /**
572
+ * `argv` as a command's own declarations describe it - every option by its config key, every
573
+ * positional the command string names, plus yargs' own two.
574
+ *
575
+ * **It is written as an explicit annotation on the handler, not inferred**, and that is a
576
+ * measured limitation rather than a preference. Inferring it from the sibling `config` works on
577
+ * its own, but not beside the `M & ValidMeta<M>` check: `M` is inferred from the whole literal,
578
+ * `handler` is part of that literal, so `ArgsOf<M>` is circular and resolves to
579
+ * `ArgsOf<CommandMetadata>` - every option "does not exist". Splitting the inference sites gets
580
+ * the names back and loses the values instead, because `M` and `C` then infer from the same
581
+ * `config` property and the literals widen, so every option arrives as `unknown`.
582
+ *
583
+ * So the declarations are hoisted out of the literal and the handler is annotated:
584
+ *
585
+ * ```ts
586
+ * const COMMAND = 'version [bump]' as const;
587
+ * const config = { ... } satisfies Record<string, RmanConfig.CommandOption>;
588
+ *
589
+ * handler: (args: RmanConfig.ArgsOf<typeof config, typeof COMMAND>) => { ... }
590
+ * ```
591
+ *
592
+ * Two lines, and in exchange the typo checking stays and a renamed option can no longer survive
593
+ * silently in the handler behind an `as`.
594
+ *
595
+ * **A `<required>` positional is required here too, and that is the one thing argv genuinely
596
+ * guarantees.** yargs refuses the call before the handler runs - measured on both commands that
597
+ * declare one: `rman run` and `rman import` each exit 1 with `Not enough non-option arguments:
598
+ * got 0, need at least 1`. Typed optional, the two handlers had to say so themselves
599
+ * (`args.script as string`, `args.path!`), and an `as` in a handler is exactly what declaring
600
+ * commands exists to remove - it is also how a renamed positional would survive silently. The
601
+ * rule is purely syntactic (`<x>` vs `[x]`), so it needs nothing but the command string, which
602
+ * is already the only place that says whether a positional is variadic.
603
+ *
604
+ * **What had made everything optional was not a judgement about argv - it was `handler`'s own
605
+ * parameter type**, which is where that story is written down. Worth knowing before adding a
606
+ * required key here: the constraint was three types away from the one that looked responsible.
607
+ * The claim and the parsing are pinned together in `cli.spec.ts` ("cli: a required positional"),
608
+ * since a type asserting a key is present is only honest while yargs refuses the call without it.
609
+ *
610
+ * **Every option stays optional, `default:` included, and that asymmetry is deliberate.**
611
+ * Narrowing on `default` needs *two* conditions, not one: the default must be present **and**
612
+ * `target !== 'config'`, because `toYargsCommand` never registers a `target: 'config'` option, so
613
+ * yargs never applies its default and the key is simply absent from argv. Written with the one
614
+ * obvious condition the type would claim a key is present where the runtime has `undefined` -
615
+ * failing in the direction that crashes. The gain does not pay for that risk yet: rman declares
616
+ * exactly **one** real option default (`config --json`), read as `if (args.json)`, so `undefined`
617
+ * and `false` already behave alike. The day a contributed command wants it, both conditions are
618
+ * written down here. `demandOption` would take the same pair of conditions and no declared
619
+ * command uses it - the only mention in the tree is a `CustomCommand`'s hand-written `builder`,
620
+ * which does not go through `ArgsOf` at all.
621
+ */
622
+ type ArgsOf<C, Cmd extends string> = {
623
+ [K in keyof C]?: OptionValue<C[K]>;
624
+ } & {
625
+ [K in RequiredPositionalsOf<Cmd>]: PositionalValue<Cmd, K>;
626
+ } & {
627
+ [K in Exclude<PositionalsOf<Cmd>, RequiredPositionalsOf<Cmd>>]?: PositionalValue<Cmd, K>;
628
+ } & GlobalArgs;
629
+ /**
630
+ * What every command's argv carries whatever it declares - yargs' own two, and rman's global
631
+ * options, which are registered on the program rather than per command.
632
+ *
633
+ * **Measured the moment `exec` was converted**: its handler read `args.logLevel`, which compiled
634
+ * only because `yargs.Arguments` is an index signature. With a real type it is
635
+ * `Property 'logLevel' does not exist` - correct, and exactly the kind of thing that was invisible
636
+ * before. Global options have to be stated once, here, or every handler that reads one is wrong.
637
+ */
638
+ type GlobalArgs = {
639
+ _: (string | number)[];
640
+ $0: string;
641
+ /** `--log-level`, declared on the program in `cli.ts`. */
642
+ logLevel?: 'silent' | 'error' | 'info' | 'verbose';
643
+ /** `--config`: print what this command would run with, and run nothing. */
644
+ config?: boolean;
645
+ /** Everything after `--`, present when the command sets `populate--` - `exec` reads it to tell
646
+ * its own flags from the ones belonging to the command it runs. */
647
+ '--'?: (string | number)[];
648
+ };
649
+ /**
650
+ * The config object a command's `config` declaration describes - `{ type: 'string' }` becomes
651
+ * `string`, `{ type: 'string', array: true }` becomes `string[]`.
652
+ *
653
+ * **The literal survives inference, which is the whole reason this works.** `{ type: 'string' }`
654
+ * would normally widen to `string`, but `yargs.Options['type']` is a union of string literals and
655
+ * the generic's constraint types the object literal contextually - so `ReturnType<T>` still holds
656
+ * `'string'` by the time this maps over it. Measured, with negative controls: reading a mapped
657
+ * value as the wrong type errors, and so does naming an option that was not declared.
658
+ *
659
+ * `choices` narrows further, and is the one place the caller has to help: an array literal widens
660
+ * to `string[]` on its own, so `choices: ['a', 'b'] as const` is what yields `'a' | 'b'`. Without
661
+ * the assertion it falls back to the declared `type`.
662
+ *
663
+ * **Every one of them is a `ConfigValue`, and that needs no per-key judgement.** An option
664
+ * declares a `type` yargs can parse, so what it contributes to `.rmanrc` is by construction a
665
+ * *value* - never a step, which is why steps arrive through `Extra` instead and have to be decided
666
+ * one at a time. So the whole derived half widens here, in one place, and a config author may
667
+ * write `changelog: { filePath: ({ vars }) => ... }` for any of them. `Resolved` takes it back off
668
+ * again for the reader.
669
+ */
670
+ type CommandConfigFromMetadata<T extends CommandMetadata> = T['config'] extends infer C ? C extends Record<string, CommandOption> ? {
671
+ [K in keyof ConfigOnly<C>]?: ConfigValue<OptionValue<ConfigOnly<C>[K]>>;
672
+ } : {} : {};
673
+ /**
674
+ * What the command adds to `RmanConfig`: its config options, under the key they live at -
675
+ * `{ version: { changelog: boolean } }`, or `{ run: { build: { exec: string } } }` for a dotted
676
+ * `configKey`.
677
+ *
678
+ * **`Extra` is for what an option cannot describe**, and only that. A `CommandOption` says
679
+ * `type: 'string'` or `type: 'boolean'`; it has no way to say `{ file: string; constant?: string }`
680
+ * or "a shell command or a function". So `version.stamp` and `version.before`/`.exec`/`.after`
681
+ * arrive through `Extra`, hand-written beside the command, while `version.commitMessage` and
682
+ * `version.releaseTagPattern` are ordinary `target: 'config'` options and are derived like the
683
+ * rest. Reach for `Extra` when the shape genuinely resists, never to avoid declaring an option.
684
+ *
685
+ * **One key, one declaration - which is what makes `Extra` necessary rather than convenient.**
686
+ * Interface merging is not a deep merge, so a key that arrives from two places is
687
+ * `Interface 'RmanConfig' cannot simultaneously extend types ... Named property 'publish' of
688
+ * types ... are not identical` (measured). A command therefore contributes its key *whole*: the
689
+ * derived half intersected with the hand-written one, never one half here and the other in some
690
+ * central interface.
691
+ *
692
+ * **`run` is the one command this cannot describe**, and it is worth knowing before relying on
693
+ * this: `run.<script>.*` is keyed by script name, so there is no flat option map to derive from
694
+ * and `Extra` would be the entire block. Its contribution stays hand-written in `RmanConfig`.
695
+ *
696
+ * **A widened key is refused rather than used**, because the damage otherwise is not local:
697
+ * `Nest<string, V>` is an index signature, so a `configKey` that lost its literal (a missing
698
+ * `as const`) turns `CommandConfigs` into "every string key has this command's shape" and every
699
+ * other key in `RmanConfig` then fails to satisfy it. Measured, on `github-release`: four errors
700
+ * pointing at `extends`, `logLevel`, `vars` and `changelog`, none of them anywhere near the
701
+ * mistake. The named property fails at the augmentation instead, and says what to add.
702
+ */
703
+ type CommandContribution<T extends CommandMetadata, Extra = {}> = string extends ConfigKeyOf<T> ? {
704
+ __configKeyMustBeALiteral: 'add `as const` to `command` (or to `configKey`)';
705
+ } : Nest<ConfigKeyOf<T>, ConfigBlock<CommandConfigFromMetadata<T> & Extra>>;
706
+ /**
707
+ * One config block as a `.rmanrc` may actually write it: the keys themselves, their `+key` append
708
+ * forms, and the `vars` that scopes the subtree.
709
+ *
710
+ * This is what a hand-written options interface said by extending
711
+ * `XOptionsKeys, ScopedVars` - clauses every new one had to remember. Derived, it cannot be
712
+ * forgotten. `ScopedVars` is folded in **before**
713
+ * `ScopedVars` is added, so no `+vars` is generated: appending to `vars` means nothing, since
714
+ * objects merge either way.
715
+ */
716
+ type ConfigBlock<K> = K & ScopedVars;
717
+ /** The declared `configKey`, or the first word of `command`. */
718
+ type ConfigKeyOf<T> = T extends {
719
+ configKey: infer K extends string;
720
+ } ? K : T extends {
721
+ command: infer C extends string;
722
+ } ? C extends `${infer N} ${string}` ? N : C : never;
723
+ /** One option's value type: the scalar its `type` denotes, in a list when it is one. */
724
+ type OptionValue<O> = O extends {
725
+ array: true;
726
+ } ? OptionScalar<O>[] : O extends {
727
+ type: 'array';
728
+ } ? OptionScalar<O>[] : OptionScalar<O>;
729
+ /**
730
+ * The scalar behind a yargs `type`, in decreasing order of how much each says.
731
+ *
732
+ * `coerce` first: it is a function that *produces* the value, so its return type is the answer and
733
+ * whatever `type` claims is a description of the input. `run --parallel` is the case - it takes a
734
+ * flag, a number or `false` and coerces them into `boolean | number | undefined`, which no `type`
735
+ * can state. Then `choices`, which narrows a `type` to the values it allows; then the `type`
736
+ * itself. An option declaring none of the three is `unknown` rather than `any` - a value nothing
737
+ * described should not silently type-check against everything.
738
+ */
739
+ type OptionScalar<O> = O extends {
740
+ coerce: (...args: never[]) => infer R;
741
+ } ? R : O extends {
742
+ choices: readonly (infer C)[];
743
+ } ? C : O extends {
744
+ type: 'string';
745
+ } ? string : O extends {
746
+ type: 'number' | 'count';
747
+ } ? number : O extends {
748
+ type: 'boolean';
749
+ } ? boolean : unknown;
268
750
  /**
269
751
  * The `run` block: scripts by name.
270
752
  *
@@ -281,14 +763,29 @@ export declare namespace RmanConfig {
281
763
  * remap still produces an index signature that claims `vars`.
282
764
  */
283
765
  type RunConfig = Record<string, RunStepValue | RunStepValue[] | RunScriptOptions>;
284
- interface RunScriptOptions extends RunScriptOptionsKeys, WithAppend<RunScriptOptionsKeys>, ScopedVars {
766
+ interface RunScriptOptions extends RunScriptOptionsKeys, ScopedVars {
285
767
  }
768
+ /**
769
+ * **Which level a key is read at is not uniform, and it follows what the key decides.**
770
+ * `RunService` reads `concurrency`, `progress`, `changed` and `changedSince` off the **root
771
+ * package only** - one scheduler, one answer for the whole batch - so those belong under
772
+ * `"[/]"`, and a `"[*]"` block declaring them is silently ignored (measured: `concurrency: 1`
773
+ * under `"[*]"` still ran two packages at once). `logLevel`, `skip`, `if`, `override` and the
774
+ * step slots are per package. `topo` and `bail` are read **both** ways and mean different things
775
+ * at each: the root's `topo` picks the sort (topological vs alphabetical), a package's own
776
+ * decides whether *it* waits for its dependencies; the root's `bail` is the default, a package's
777
+ * own outranks even an explicit CLI flag.
778
+ */
286
779
  interface RunScriptOptionsKeys {
287
780
  concurrency?: number;
288
781
  topo?: boolean;
289
782
  bail?: boolean;
290
783
  progress?: boolean;
291
784
  logLevel?: 'silent' | 'error' | 'info' | 'verbose';
785
+ /** Only run in packages that have changed since their last release - the config twin of
786
+ * `--changed`, read off the root. It was read at runtime long before it was declared here, so
787
+ * a typed config could not say the thing that already worked. */
788
+ changed?: boolean;
292
789
  changedSince?: string;
293
790
  skip?: boolean;
294
791
  /** Whether this script runs for a package at all - the small `changed and not private` grammar,
@@ -305,74 +802,195 @@ export declare namespace RmanConfig {
305
802
  after?: RunStepValue | RunStepValue[];
306
803
  override?: boolean;
307
804
  }
308
- interface PublishOptions extends PublishOptionsKeys, WithAppend<PublishOptionsKeys>, ScopedVars {
309
- }
310
- interface PublishOptionsKeys {
311
- /** Which **registry** `publish` ships this package to - default `['npm']` (every existing repo
312
- * keeps working unchanged). A package that only ever wants Docker images (typically also
313
- * `"private": true`, since it's not meant for npm at all) sets `['docker']`; both works too.
314
- * Each target answers "is this version already out there?" against its own registry, so a
315
- * package is never left without one: npm via `npm view`, docker via `docker manifest inspect`.
316
- *
317
- * Note this is strictly about *package distribution*. The repository's GitHub Release is not
318
- * a target here - it isn't a place a package ships to, it's the repository's own record that
319
- * a release happened, and it is never opted into: see `githubRelease` and the
320
- * `github-release` command. */
321
- target?: PublishTarget | PublishTarget[];
322
- docker?: DockerPublishOptions;
323
- /** Excludes this package from `publish` entirely (every target), regardless of
324
- * `target`/`"private"` - a single, explicit "never published" statement, e.g. for a package
325
- * released through some separate, unrelated process. `changelog` also skips it by default
326
- * (see its own `--include-skipped`) - there's little point changelogging something that's
327
- * never actually released. Independent of `version`, which never consults this at all - a
328
- * package can still be meaningfully versioned without ever being published. */
329
- skip?: boolean;
330
- }
331
- type PublishTarget = 'npm' | 'docker';
332
- /** Required once `"docker"` is one of this package's `publish.target`s - `publish --target
333
- * docker` errors clearly on a package that opts in here but leaves this out. */
334
- interface DockerPublishOptions extends DockerPublishOptionsKeys, WithAppend<DockerPublishOptionsKeys>, ScopedVars {
335
- }
336
- interface DockerPublishOptionsKeys {
337
- /** DockerHub image name/repository - bare (e.g. `"my-app"`) to be prefixed with
338
- * `--docker-namespace`/`DOCKERHUB_NAMESPACE`, or already-namespaced (contains a `/`) to use
339
- * verbatim. */
340
- image: string;
341
- /** Relative to the package's own directory. Default `"Dockerfile"`. */
342
- dockerfile?: string;
343
- /** Default `["linux/amd64"]`. */
344
- platforms?: string[];
345
- /** Build `cwd` override, relative to the repository root - only needed when the Dockerfile's
346
- * own `COPY`/`ADD` paths expect something other than the package's own directory (rare). */
347
- cwd?: string;
348
- /** Named `docker buildx build --build-context <name>=<path>` entries, keyed by name - each
349
- * path is relative to the package's own directory (or absolute). */
350
- buildContexts?: Record<string, string>;
351
- /** `docker buildx build --build-arg <name>=<value>` entries - a value of exactly `"$NAME"`
352
- * expands to `process.env.NAME` at build time (e.g. to pass a CI secret through). */
353
- buildArgs?: Record<string, string>;
354
- /** A file (relative to the package's own directory) whose contents become the DockerHub repo's
355
- * full description, if present. Default `"DOCKER_README.md"`. */
356
- readme?: string;
357
- }
358
- /** Entirely optional - `github-release` needs no configuration at all, since every required fact
359
- * (which tag, which repository, what the notes say) already has a sensible source. Nothing here
360
- * decides *whether* a release is cut: a release records that the repository shipped, so it is
361
- * always cut, and these are only details about how. */
362
- interface GithubReleaseOptions extends GithubReleaseOptionsKeys, WithAppend<GithubReleaseOptionsKeys>, ScopedVars {
363
- }
364
- interface GithubReleaseOptionsKeys {
365
- /** Files to attach to the release, as glob patterns relative to the package's own directory
366
- * (e.g. `["dist/*.tar.gz"]`). Read from **every** package, since one release covers the whole
367
- * source tree. A release with no assets at all is still perfectly valid - it records that the
368
- * version shipped, which is all a deploy-elsewhere package needs. */
369
- assets?: string[];
370
- /** `owner/repo`. Default: parsed from the `origin` remote's URL. Root-level only. */
371
- repository?: string;
372
- /** Create the release as an unpublished draft. Default `false`. Root-level only. */
373
- draft?: boolean;
374
- /** Default: whether the version being released is itself a prerelease by the root's own version
375
- * scheme (`1.3.0-beta.0` under semver) - see `VersionScheme.isPrerelease`. Root-level only. */
376
- prerelease?: boolean;
377
- }
378
805
  }
806
+ /**
807
+ * Registers a command and hands the register function straight back.
808
+ *
809
+ * **It returns the function, not the command's config, and that is the point.** The function has
810
+ * not run yet - it runs during init, once there is a `Repository` - so at registration time no
811
+ * config object exists to return. Declaring one anyway is what made the old body
812
+ * (`return def['config']`) uncompilable: `def` is a function, and `config` belongs to what it
813
+ * *returns*.
814
+ *
815
+ * Nothing is lost by being honest, because the metadata type rides along on the return type:
816
+ * `ReturnType<typeof someCommand>` is the `CommandMetadata`, and `CommandConfigFromMetadata` maps
817
+ * it to the config object the command contributes. A phantom property carrying the config type was
818
+ * tried and is unnecessary for exactly this reason - and it would not have survived being stored in
819
+ * `commandRegistry`, whose element type erases it.
820
+ *
821
+ * **`M` is inferred from the metadata rather than from the function, so that the same position can
822
+ * check it.** With the plainer `<T extends CommandRegisterFunction>(def: T)`, `T` is inferred from
823
+ * the literal itself, so the constraint is a subtype check - and a subtype check does no
824
+ * excess-property checking. Measured: `anotherBogusTopLevelKey: true` and a `totallyBogusKey`
825
+ * inside an option both compiled silently, which is how `cliName` and `examples` went unnoticed in
826
+ * `version.command.ts`. `M & ValidMeta<M>` keeps the bare `M` as the inference site and puts the
827
+ * check beside it, so a misspelled key now errors on its own line and names itself.
828
+ *
829
+ * **A command's `command` string needs `as const`** for the config key to be derivable from it
830
+ * (`'version [bump]' as const` -> `'version'`). `type: 'string'` survives inference on its own,
831
+ * since `yargs.Options['type']` is a union of literals and a union suppresses widening; `command`
832
+ * is a plain `string`, so it widens without the assertion. Spelling it `` `${string}` `` does not
833
+ * help - measured; that trick only applies where the constraint sits on the generic parameter.
834
+ */
835
+ export declare function registerCommand<M extends RmanConfig.CommandMetadata>(def: (app: RmanApplication) => M & ValidMeta<M>): (app: RmanApplication) => M;
836
+ /**
837
+ * The same declaration, **without** the registration - for a command that must exist only when
838
+ * something asks for it.
839
+ *
840
+ * That is exactly a contributed command's situation: `commandRegistry` is a module-level array
841
+ * walked by every `runCli`, so a package pushing onto it would give its commands to repositories
842
+ * that never named it - the module is imported as soon as anything imports the package. A package
843
+ * puts the function in its config's `commands` instead, and `cli.ts` calls it once the repository
844
+ * exists.
845
+ *
846
+ * **Why a factory rather than the metadata itself**: the config is resolved *inside*
847
+ * `Repository.create`, before the packages are known (plugins are what find them), so
848
+ * `app.repository` throws at that point. The function is stored unrun and called later, in
849
+ * `cli.ts`, where the built-ins' own factories run.
850
+ *
851
+ * Everything `registerCommand` documents about inference - `M & ValidMeta<M>`, the `as const` on
852
+ * `command`, the metadata riding on the return type - applies here unchanged; the two differ in one
853
+ * line.
854
+ */
855
+ export declare function declareCommand<M extends RmanConfig.CommandMetadata>(def: (app: RmanApplication) => M & ValidMeta<M>): (app: RmanApplication) => M;
856
+ /**
857
+ * Only the options a `.rmanrc` may actually set. `target: 'cli'` says the flag exists on the
858
+ * command line and nowhere else - `--interactive`, `--yes`, `--show` - and putting those in the
859
+ * config type would invite writing them where nothing reads them.
860
+ */
861
+ type ConfigOnly<C> = {
862
+ [K in keyof C as C[K] extends {
863
+ target: 'config' | 'both';
864
+ } ? K : never]: C[K];
865
+ };
866
+ /**
867
+ * A dotted path into nested objects: `'run.build'` + shape -> `{ run?: { build?: shape } }`.
868
+ *
869
+ * **Optional at every level**, and that is not a detail: a required key would make the whole
870
+ * contribution mandatory, so `defineConfig({})` stopped compiling with
871
+ * `Property 'version' is missing in type '{}'` (measured). A `.rmanrc` may say nothing about a
872
+ * command, and usually does.
873
+ */
874
+ type Nest<P extends string, V> = P extends `${infer H}.${infer R}` ? {
875
+ [K in H]?: Nest<R, V>;
876
+ } : {
877
+ [K in P]?: V;
878
+ };
879
+ /** Keys `T` declares that `Shape` does not know about. */
880
+ type Excess<T, Shape> = Exclude<keyof T, keyof Shape>;
881
+ /**
882
+ * `T` itself when it declares nothing extra; otherwise a shape it cannot satisfy, so the assignment
883
+ * fails on the offending key and says which one it is.
884
+ *
885
+ * The failure payload is an **object** rather than a tuple or a string literal. Intersecting a
886
+ * property's real type with an object leaves a real object type, so the error reads `Property
887
+ * '__unknownKey' is missing`; with a string or a tuple the intersection collapses to `never` and the
888
+ * message stops naming anything.
889
+ */
890
+ type Exact<T, Shape> = [Excess<T, Shape>] extends [never] ? T : {
891
+ [K in Excess<T, Shape>]: {
892
+ __unknownKey: K;
893
+ };
894
+ };
895
+ /** Every option checked against `CommandOption`, so a typo inside one is caught too. */
896
+ type ValidConfig<C> = {
897
+ [K in keyof C]: Exact<C[K], RmanConfig.CommandOption>;
898
+ };
899
+ type ValidMeta<M> = Exact<M, RmanConfig.CommandMetadata> & {
900
+ config?: M extends {
901
+ config: infer C;
902
+ } ? ValidConfig<C> : unknown;
903
+ positionals?: ValidPositionals<M>;
904
+ };
905
+ /** A positional the `command` string never declared fails on its own key, naming the string it was
906
+ * checked against. */
907
+ type ValidPositionals<M> = M extends {
908
+ command: infer C extends string;
909
+ positionals: infer P;
910
+ } ? {
911
+ [K in keyof P]: K extends RmanConfig.PositionalsOf<C> ? P[K] : {
912
+ __notDeclaredIn: C;
913
+ };
914
+ } : unknown;
915
+ /** A trailing `..`/`...` is yargs' variadic marker, not part of the positional's name. */
916
+ type StripDots<S extends string> = S extends `${infer N}...` ? N : S extends `${infer N}..` ? N : S;
917
+ /**
918
+ * The names a command string declares as **required** positionals - `'run <script> [args..]'` is
919
+ * `'script'` alone. `ArgsOf`'s only caller, which is why it is private: whether a positional is
920
+ * required is `ArgsOf`'s question, while `PositionalsOf` answers the different one `ValidPositionals`
921
+ * asks - which names the string declares at all.
922
+ *
923
+ * Unlike `PositionalsOf` it recurses through **one** branch, so a `[bracketed]` name is never
924
+ * emitted; the recursion still steps past one, since `${string}` matches whatever precedes the next
925
+ * `<`.
926
+ */
927
+ type RequiredPositionalsOf<C extends string> = C extends `${string}<${infer N}>${infer Rest}` ? StripDots<N> | RequiredPositionalsOf<Rest> : never;
928
+ /** A variadic positional (`[command..]`) collects a list; every other one is a single value. The
929
+ * command string is the only place that says which, since it is what yargs parses. */
930
+ type PositionalValue<Cmd extends string, N extends string> = Cmd extends `${string}${'<' | '['}${N}..${string}` ? string[] : string;
931
+ /**
932
+ * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
933
+ * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
934
+ * unchanged; this exists purely so TypeScript can infer/check against `RmanConfig`, not for any
935
+ * runtime behavior:
936
+ *
937
+ * // .rmanrc.mjs
938
+ * import { defineConfig } from 'rman';
939
+ * export default defineConfig({ packageManager: 'pnpm' });
940
+ *
941
+ * // .rmanrc.cjs
942
+ * const { defineConfig } = require('rman');
943
+ * module.exports = defineConfig({ packageManager: 'pnpm' });
944
+ */
945
+ export declare function defineConfig(config: RmanConfig): RmanConfig;
946
+ /**
947
+ * A config value that may be **written as a function instead**, computed per package at the moment
948
+ * the config resolves.
949
+ *
950
+ * `T` is what the function has to return, so the same checking applies either way - measured, with
951
+ * a control: a typo inside a wrapped object is still caught, and so is one inside an object a value
952
+ * function *returns*.
953
+ *
954
+ * **Not for a step key.** `run.<script>.before`/`.exec`/`.after`, `run.<script>.if` and
955
+ * `version.<slot>` already take a function, and it means something else there - code for `run` to
956
+ * call in its own time, with its own context. Wrapping one of those would produce a type that
957
+ * accepts a value function where a step is what actually runs. The key path decides which a
958
+ * function is (`STEP_PATHS`, `CODE_SUBTREES`), and the type can only follow that split by hand.
959
+ */
960
+ export type ConfigValue<T> = T | ((ctx: ConfigValueContext) => T);
961
+ /**
962
+ * The same config **after** it resolves: every `ConfigValue<T>` is just `T`, because
963
+ * `interpolateConfig` has already called it.
964
+ *
965
+ * **This is the half that lets `RmanConfig` be the author's type.** One type cannot answer both
966
+ * "what may I write?" (a function is fine - rman calls it) and "what do I get?" (never a function -
967
+ * it was already called), so it used to answer only the second, and writing a value function was a
968
+ * compile error the docs themselves committed. Widening `RmanConfig` alone just moves the problem:
969
+ * measured, six read sites needed a cast. The author's type widens and the *reader's* is computed
970
+ * from it - one derived type, applied at `Package.config`, rather than a second one to keep in step
971
+ * by hand.
972
+ *
973
+ * **Two guards, and each was measured by leaving it out.**
974
+ *
975
+ * - **Steps are named first.** A value function is recognised by its parameter, and
976
+ * `ConfigValueContext` carries an index signature - so `RunStepFn` is assignable to it and a
977
+ * `run.build.exec` function collapsed to its *return type*, leaving `RunService` nothing to call.
978
+ * - **`CODE_SUBTREES` is skipped, at every level.** `plugins`/`commands`/`publishTargets` hold code
979
+ * all the way down, and the selector index (`[selector]: RmanConfig`) re-enters the config, so a
980
+ * top-level-only guard misses the copy inside a `"[*]"` block. Left out, the walk reached
981
+ * `Plugin.manifestProvider.versionScheme` and rewrote its **methods**: `smallestBump(): string`
982
+ * became `string`, `bumpFor`/`isValid`/`compare`/`next` became `{}`. A function with *fewer*
983
+ * parameters is assignable to one with more, so a zero-argument method matches the value-function
984
+ * pattern - which makes this transform unsafe over any object carrying methods, and the guard the
985
+ * only thing keeping one out of its way.
986
+ *
987
+ * Both lists are the runtime's own (`STEP_PATHS`' function types, `CODE_SUBTREES` itself), so the
988
+ * type follows the rule rather than restating it - the drift `ScopedVars` already demonstrated is
989
+ * not available here.
990
+ */
991
+ export type Resolved<T> = T extends RunStepFn | RunConditionFn ? T : T extends (ctx: ConfigValueContext) => infer R ? R : T extends object ? {
992
+ [K in keyof T]: K extends CodeSubtree ? T[K] : Resolved<T[K]>;
993
+ } : T;
994
+ /** `pkg.config`'s type: what every command reads, with the value functions already called. */
995
+ export type ResolvedConfig = Resolved<RmanConfig>;
996
+ export {};