rman 1.2.5 → 2.0.0-beta.2

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 (153) hide show
  1. package/README.md +63 -19
  2. package/cli.d.ts +5 -0
  3. package/cli.js +214 -88
  4. package/commands/build.command.d.ts +177 -3
  5. package/commands/build.command.js +20 -10
  6. package/commands/changed.command.d.ts +80 -3
  7. package/commands/changed.command.js +19 -12
  8. package/commands/changelog.command.d.ts +192 -3
  9. package/commands/changelog.command.js +87 -43
  10. package/commands/config.command.d.ts +44 -3
  11. package/commands/config.command.js +30 -19
  12. package/commands/diff.command.d.ts +37 -3
  13. package/commands/diff.command.js +25 -16
  14. package/commands/exec.command.d.ts +193 -3
  15. package/commands/exec.command.js +60 -58
  16. package/commands/github-release.command.d.ts +154 -3
  17. package/commands/github-release.command.js +67 -37
  18. package/commands/import.command.d.ts +36 -3
  19. package/commands/import.command.js +28 -20
  20. package/commands/info.command.d.ts +35 -7
  21. package/commands/info.command.js +36 -30
  22. package/commands/list.command.d.ts +163 -3
  23. package/commands/list.command.js +109 -71
  24. package/commands/publish.command.d.ts +231 -0
  25. package/commands/publish.command.js +304 -0
  26. package/commands/run.command.d.ts +186 -6
  27. package/commands/run.command.js +26 -72
  28. package/commands/test.command.d.ts +173 -3
  29. package/commands/test.command.js +16 -10
  30. package/commands/version.command.d.ts +317 -3
  31. package/commands/version.command.js +149 -69
  32. package/commands.d.ts +32 -0
  33. package/commands.js +28 -0
  34. package/constants.js +1 -1
  35. package/core/application.d.ts +116 -0
  36. package/core/application.js +143 -0
  37. package/core/command-builder.d.ts +14 -0
  38. package/core/command-builder.js +78 -0
  39. package/core/config.d.ts +180 -50
  40. package/core/config.js +332 -153
  41. package/core/core-services.d.ts +14 -0
  42. package/core/core-services.js +30 -0
  43. package/core/core-targets.d.ts +14 -0
  44. package/core/core-targets.js +16 -0
  45. package/core/custom-command.d.ts +42 -6
  46. package/core/custom-command.js +44 -17
  47. package/core/extends-config.d.ts +13 -5
  48. package/core/extends-config.js +52 -12
  49. package/core/load-config-module.d.ts +28 -0
  50. package/core/load-config-module.js +42 -0
  51. package/core/manifest.d.ts +47 -25
  52. package/core/manifest.js +51 -69
  53. package/core/merge-config.d.ts +33 -34
  54. package/core/merge-config.js +138 -93
  55. package/core/package.d.ts +145 -17
  56. package/core/package.js +128 -36
  57. package/core/plugin-loader.d.ts +65 -0
  58. package/core/plugin-loader.js +234 -0
  59. package/core/plugin.d.ts +135 -90
  60. package/core/plugin.js +70 -173
  61. package/core/publish-target.d.ts +124 -0
  62. package/core/publish-target.js +30 -0
  63. package/core/registry.d.ts +30 -0
  64. package/core/registry.js +47 -0
  65. package/core/repository.d.ts +72 -9
  66. package/core/repository.js +293 -43
  67. package/core/resolve-target.d.ts +1 -1
  68. package/core/resolve-target.js +1 -1
  69. package/core/service.d.ts +49 -0
  70. package/core/service.js +40 -0
  71. package/core/version-scheme.d.ts +23 -1
  72. package/core/version-scheme.js +29 -1
  73. package/core/workspace.d.ts +84 -33
  74. package/core/workspace.js +63 -22
  75. package/index.d.ts +111 -14
  76. package/index.js +85 -9
  77. package/interfaces/rman-config.interface.d.ts +739 -212
  78. package/interfaces/rman-config.interface.js +61 -1
  79. package/package.json +2 -1
  80. package/plugins/builtins.d.ts +44 -0
  81. package/plugins/builtins.js +33 -0
  82. package/plugins/detect.d.ts +78 -0
  83. package/plugins/detect.js +70 -0
  84. package/plugins/node/augmentation/rman.augmentation.d.ts +84 -0
  85. package/plugins/node/augmentation/rman.augmentation.js +1 -0
  86. package/plugins/node/augmentation/system-info.augmentation.d.ts +26 -0
  87. package/plugins/node/augmentation/system-info.augmentation.js +79 -0
  88. package/plugins/node/commands/ci.command.d.ts +131 -0
  89. package/plugins/node/commands/ci.command.js +59 -0
  90. package/plugins/node/commands/clean.command.d.ts +183 -0
  91. package/plugins/node/commands/clean.command.js +73 -0
  92. package/plugins/node/index.d.ts +29 -0
  93. package/plugins/node/index.js +40 -0
  94. package/plugins/node/node-config.interface.d.ts +77 -0
  95. package/plugins/node/node-config.interface.js +7 -0
  96. package/plugins/node/node-manifest.provider.d.ts +68 -0
  97. package/plugins/node/node-manifest.provider.js +125 -0
  98. package/plugins/node/node.platform.d.ts +53 -0
  99. package/plugins/node/node.platform.js +134 -0
  100. package/plugins/node/npm-publish-target.d.ts +73 -0
  101. package/plugins/node/npm-publish-target.js +96 -0
  102. package/plugins/node/services/ci.service.d.ts +47 -0
  103. package/plugins/node/services/ci.service.js +213 -0
  104. package/plugins/node/services/clean.service.d.ts +53 -0
  105. package/plugins/node/services/clean.service.js +237 -0
  106. package/plugins/node/services/publish.service.d.ts +114 -0
  107. package/plugins/node/services/publish.service.js +371 -0
  108. package/plugins/node/services/version-plan.service.d.ts +44 -0
  109. package/plugins/node/services/version-plan.service.js +58 -0
  110. package/plugins/node/utils/npm-view.d.ts +48 -0
  111. package/plugins/node/utils/npm-view.js +71 -0
  112. package/plugins/node/utils/workspace-range.d.ts +26 -0
  113. package/plugins/node/utils/workspace-range.js +28 -0
  114. package/services/change-hash.service.d.ts +2 -2
  115. package/services/change-hash.service.js +2 -2
  116. package/services/changelog.service.d.ts +62 -51
  117. package/services/changelog.service.js +14 -11
  118. package/services/docker-publish.service.d.ts +50 -29
  119. package/services/docker-publish.service.js +43 -20
  120. package/services/exec.service.d.ts +23 -12
  121. package/services/exec.service.js +14 -9
  122. package/services/github-release.service.d.ts +44 -33
  123. package/services/github-release.service.js +13 -10
  124. package/services/import.service.d.ts +25 -14
  125. package/services/import.service.js +9 -5
  126. package/services/list.service.d.ts +62 -10
  127. package/services/list.service.js +62 -15
  128. package/services/run.service.d.ts +22 -13
  129. package/services/run.service.js +262 -223
  130. package/services/version-plan.service.d.ts +27 -4
  131. package/services/version-plan.service.js +42 -15
  132. package/services/version.service.d.ts +31 -11
  133. package/services/version.service.js +29 -13
  134. package/targets/docker.target.d.ts +53 -0
  135. package/targets/docker.target.js +40 -0
  136. package/utils/bin-path.d.ts +6 -7
  137. package/utils/bin-path.js +7 -18
  138. package/utils/branch-guard.d.ts +29 -0
  139. package/utils/branch-guard.js +31 -0
  140. package/utils/exec.d.ts +10 -0
  141. package/utils/exec.js +1 -1
  142. package/utils/logger.d.ts +1 -1
  143. package/utils/logger.js +1 -1
  144. package/utils/package-filter.d.ts +127 -7
  145. package/utils/package-filter.js +197 -16
  146. package/utils/printable-config.d.ts +1 -1
  147. package/utils/printable-config.js +1 -1
  148. package/utils/run-bin.d.ts +10 -0
  149. package/utils/run-bin.js +1 -1
  150. package/utils/run-options.d.ts +97 -0
  151. package/utils/run-options.js +81 -0
  152. package/utils/version-stamp.d.ts +1 -1
  153. package/utils/version-stamp.js +1 -1
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Turns a command's **declaration** into the yargs registration it describes.
3
+ *
4
+ * This is the one place that knows how the two relate, which is the point of declaring commands
5
+ * instead of building them: a command says what it has, and only this function says what yargs is
6
+ * told. Everything a `builder` used to do by hand - options, positionals, examples, parser
7
+ * switches - arrives as data now, so a typo in any of it is a compile error at the command rather
8
+ * than a flag that silently never existed.
9
+ */
10
+ export function toYargsCommand(meta) {
11
+ return {
12
+ command: meta.command,
13
+ aliases: meta.aliases,
14
+ describe: meta.describe,
15
+ configKeys: configKeysOf(meta),
16
+ builder: (cmd) => {
17
+ if (meta.parserConfiguration)
18
+ cmd.parserConfiguration(meta.parserConfiguration);
19
+ for (const [name, spec] of Object.entries(meta.positionals ?? {}))
20
+ cmd.positional(name, spec);
21
+ for (const [key, option] of Object.entries(meta.config ?? {})) {
22
+ /**
23
+ * **`target: 'config'` means "a `.rmanrc` key, and not a flag"** - `githubRelease.draft` is
24
+ * the first of those. Skipping it here is what makes the field mean something rather than
25
+ * being documentation.
26
+ */
27
+ if (option.target === 'config')
28
+ continue;
29
+ /** `target` and `cliName` are rman's, not yargs' - dropped rather than passed through. */
30
+ const { cliName, ...rest } = option;
31
+ delete rest.target;
32
+ /**
33
+ * Registered under `cliName` when the flag and the config key are spelled differently
34
+ * (`--ignore-dirty` for `ignoreDirty`). yargs expands the hyphenated form back to camelCase
35
+ * in argv, so the handler reads the key it declared either way.
36
+ */
37
+ cmd.option(cliName ?? key, rest);
38
+ }
39
+ for (const example of meta.examples ?? [])
40
+ cmd.example(example.command, example.description ?? '');
41
+ return cmd;
42
+ },
43
+ /**
44
+ * Cast because the two descriptions of argv disagree on purpose: yargs types it as an index
45
+ * signature of `unknown`, and a command annotates it with the options it actually declared. The
46
+ * narrower one is the useful one, and this is the single place the widening is admitted -
47
+ * previously it was an `as` per option read, in every handler.
48
+ */
49
+ handler: meta.handler,
50
+ };
51
+ }
52
+ /** A command's own name - the first word of its `command` string, before any positional. */
53
+ export function commandName(command) {
54
+ return command.split(/\s+/)[0];
55
+ }
56
+ /**
57
+ * Which `.rmanrc` keys `--config` shows for a command: the ones it declared reading, plus **its own
58
+ * key when it has one**.
59
+ *
60
+ * `configKeys` is the read-only list on purpose - a command should not have to repeat the key it
61
+ * already owns, and `version` listing `'version'` beside `'changelog'` said nothing about which of
62
+ * the two it defines. Measured before this existed: `rman version --config` reported
63
+ * `changelog, group, allowBranch, ignoreBranch` and left out `version` itself.
64
+ *
65
+ * **Own key only when the command actually declares a config option.** `build` and `test` own
66
+ * nothing - their settings live in `run.build`/`run.test`, which they merely read - so prepending
67
+ * their names would print an empty `build: {}` that reads as "nothing is configured" about a key
68
+ * that does not exist. A `configKeys` given as a *function* is left alone entirely: `run` computes
69
+ * `run.<script>` from argv, which is narrower than its own key and deliberately so.
70
+ */
71
+ function configKeysOf(meta) {
72
+ if (typeof meta.configKeys === 'function')
73
+ return meta.configKeys;
74
+ const ownsConfig = Object.values(meta.config ?? {}).some(o => o.target !== 'cli');
75
+ const own = ownsConfig ? [meta.configKey ?? commandName(meta.command)] : [];
76
+ const keys = [...own, ...(meta.configKeys ?? [])];
77
+ return keys.length ? keys : undefined;
78
+ }
package/core/config.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import path from 'path';
2
2
  import semver from 'semver';
3
3
  import type { RmanConfig } from '../interfaces/rman-config.interface.js';
4
+ import { type DetectedBuiltin } from '../plugins/detect.js';
5
+ import type { RunConditionFn, RunStepFn } from './run-step.js';
4
6
  /**
5
7
  * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
6
8
  * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
@@ -20,59 +22,93 @@ export declare function defineConfig(config: RmanConfig): RmanConfig;
20
22
  * Reads the rman configuration defined at a single directory level, merging
21
23
  * (in increasing precedence): `package.json#rman`, `.rmanrc.yml`, `.rmanrc`,
22
24
  * then `.rmanrc.cjs`/`.rmanrc.mjs`/`.rmanrc.js` (whichever exist, in that order).
25
+ *
26
+ * `options.inject` supplies a built-in for a repository that declared no technology - **already
27
+ * decided**, rather than a "please detect" flag. The decision needs the application (a programmatic
28
+ * caller or a spec may have registered a technology without writing it in a config), which this
29
+ * function has no business knowing about; `Repository.create` makes it once and hands the answer
30
+ * to every read that has to agree with it. See `detectBuiltin`.
23
31
  */
24
- export declare function readDirConfig(dirname: string): Promise<RmanConfig>;
32
+ export declare function readDirConfig(dirname: string, options?: {
33
+ inject?: DetectedBuiltin;
34
+ }): Promise<RmanConfig>;
25
35
  /**
26
36
  * Resolves the effective config for the package at `targetDir`, cascading from `rootDir` down to
27
37
  * it (inclusive) - each directory level overrides the ones above it, the way tsconfig's `extends`
28
38
  * chain does.
29
39
  *
30
- * Every level contributes in two ways, and the difference is the whole model:
31
- *
32
- * - **Unmarked keys configure the package of the directory that declares them.** The root's own
33
- * `.rmanrc` therefore configures the *root package* - which is where repo-wide settings
34
- * (`packageManager`, `allowBranch`, `version.*`, `githubRelease.*`) are read from anyway - and
35
- * not, silently, every package under it.
36
- * - **A `"[selector]"` block configures the packages it names** - `"[*]"` for all of them (the root
37
- * included), `"[ws:*]"` for every one but the root, `"[/]"` for the root alone, `"[*-dialect]"`
38
- * for a glob over package names. See `parseSelector`. This is the only way a directory speaks
39
- * about anything but its own package.
40
- *
41
- * Splitting the two matters because the same key means different things to the two audiences. The
42
- * clearest case is `run.<script>.postScript`: on a package it's that package's build hook, run in
43
- * its own directory; on the root it's a repo-wide bookend run once at the repository root. A
44
- * cascade that fed one declaration to both ran a package-relative command (`node
45
- * ../../support/postbuild.cjs`) at the root, where it cannot resolve.
46
- *
47
- * `packageName` is what selectors match against; without it, selector blocks contribute nothing at
48
- * all. The root package passes its own, since `"[/]"` and `"[*]"` speak to it.
40
+ * Every level contributes in two ways:
41
+ *
42
+ * - **An unmarked key configures that directory and every package under it.** What a parent says
43
+ * reaches the children, which is what every directory-scoped config in the ecosystem does and
44
+ * what a reader expects without being told.
45
+ * - **A `"[selector]"` block narrows the audience** - `"[/]"` to the root package alone, `"[*]"` or
46
+ * a glob to the packages below (never the root, which is nobody's child). See `parseSelector`.
47
+ *
48
+ * **The root used to be the one directory whose unmarked config did *not* cascade**, on the
49
+ * reasoning that a setting means different things to a package and to the repository - and the
50
+ * reasoning is sound, but the rule it produced was not readable: an intermediate `packages/`
51
+ * cascaded while the root did not, so what a file meant depended on whether a `package.json` sat
52
+ * beside it. `vars` then had to be carved out as an exception, which is what a rule fighting itself
53
+ * looks like. One sentence now covers both: what is written above reaches below, and `"[/]"` is how
54
+ * a statement stays at the root.
55
+ *
56
+ * **The cost is real and lands on one subtree.** `run.<script>`'s hooks on the root are a repo-wide
57
+ * bookend, run once at the repository root; on a package they are that package's own hook, run in
58
+ * its directory. Cascaded, one declaration is both - once at the root and once per package. A
59
+ * repo-wide bookend therefore belongs under `"[/]"`, where its audience is visible; that is the
60
+ * migration this change asks for, and the only one that is not mechanical.
61
+ *
62
+ * **`selector` is what a `"[glob]"` block matches** - `Package.selector`, which is the package's
63
+ * `.rmanrc "name"` if it declares one and its platform's answer otherwise. Without it, glob blocks
64
+ * contribute nothing: the walk resolves config for a directory *before* the package exists, since
65
+ * that is where `platform` and `name` are read from, and a glob has nothing to match against yet.
66
+ *
67
+ * **`"[/]"` needs no selector, and that is the documented rule rather than an exception.** The root
68
+ * is addressed structurally - its directory *is* the repository root - which is the whole reason it
69
+ * is `/` and not a name. So a root block applies whenever the target is the root, named or not, and
70
+ * `platform` under `"[/]"` therefore works during the walk. It did not until this was noticed: the
71
+ * gate was `if (packageName)`, so the walk skipped every selector block including that one, and the
72
+ * key documented as "keeps it on the root package alone" silently did nothing.
73
+ *
74
+ * It was called `packageName`, which was wrong twice over: a package is not guaranteed to have a
75
+ * name (that is an ecosystem's promise, not rman's), and what this matches is the selector, which a
76
+ * repository can assign itself.
49
77
  */
50
- export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, RmanConfig>, packageName?: string): Promise<RmanConfig>;
51
- /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[ws:*]"`, `"[pkg-a]"`. The
78
+ export declare function resolveConfig(rootDir: string, targetDir: string, cache?: Map<string, RmanConfig>, selector?: string,
79
+ /** The built-in `Repository.create` decided on, for a repository that declared no technology.
80
+ * Applied at the **root level only** - `plugins` is read nowhere else, and this is the read whose
81
+ * result becomes `pkg.config`, which is where `cli.ts` finds a built-in's `commands`. */
82
+ inject?: DetectedBuiltin): Promise<RmanConfig>;
83
+ /** A config key naming packages rather than settings: `"[*]"`, `"[/]"`, `"[pkg-a]"`. The
52
84
  * brackets are what keep this space from colliding with real config keys - no setting starts with
53
85
  * one - and in YAML they also mean the key always needs quoting (`"[*]":`), since a bare `[*]`
54
86
  * parses as a flow sequence. */
55
87
  export declare function isSelectorKey(key: string): boolean;
56
88
  /**
57
- * **Which packages a selector speaks for.** Three audiences, because a repository has three:
89
+ * **Which packages a selector speaks for.** Two audiences, and the second is a glob:
58
90
  *
59
91
  * | | |
60
92
  * | --- | --- |
61
- * | `"[/]"` | the **root package** only |
62
- * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | **every** package the glob matches, root included |
63
- * | `"[ws:*]"`, `"[workspace:pkg-*]"` | every **non-root** package the glob matches |
93
+ * | `"[/]"` | the **root package** alone |
94
+ * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | the packages **below** this directory that the glob matches |
64
95
  *
65
96
  * `/` for the root because that is what a repository root is called everywhere else, and it cannot
66
- * collide with a package name. `ws:` is a qualifier on the glob rather than a separate spelling of
67
- * `*`, so `"[ws:pkg-*]"` means what it looks like.
68
- *
69
- * **`"[*]"` includes the root, and that is a change from how it used to read.** Before, selectors
70
- * were not applied to the root at all, so `"[*]"` silently meant "the workspace packages" - a
71
- * catch-all with an exception nothing in the syntax mentioned. The three names above say which
72
- * audience is meant; `"[ws:*]"` is the old behaviour, now spelled.
97
+ * collide with a package name.
98
+ *
99
+ * **The root is never selected by name, and that one rule removes two traps.** A glob matches
100
+ * package names, and the root is nobody's child - so `"[my-*]"` cannot quietly pick up a repository
101
+ * whose root package happens to be called `my-repo`, and `"[*]"` cannot hand a package-shaped
102
+ * setting to a root that has no build directory to apply it to. The root is addressed structurally
103
+ * or not at all.
104
+ *
105
+ * **`"[ws:*]"` / `"[workspace:*]"` is accepted and means exactly `"[*]"`.** The qualifier existed to
106
+ * say "not the root" back when a bare glob included it; the shape of the set says that now, so it
107
+ * has nothing left to add. Accepted rather than rejected because the two spellings resolve to the
108
+ * same packages - an error would be friction with no reader to protect.
73
109
  */
74
110
  export declare function parseSelector(key: string): {
75
- scope: 'root' | 'all' | 'workspace';
111
+ scope: 'root' | 'package';
76
112
  test: (name: string) => boolean;
77
113
  };
78
114
  /** The glob inside a selector key, as a `RegExp` anchored at both ends - so `"[*-dialect]"` matches
@@ -153,7 +189,7 @@ export interface PackageScope {
153
189
  * addressing another package from the root usually needs. Empty string for the root itself. */
154
190
  relativeDir: string;
155
191
  /**
156
- * Which ecosystem this package belongs to - `'node'` for one read by `rman-node`, empty when no
192
+ * Which ecosystem this package belongs to - `'node'` for one the `node` built-in read, empty when no
157
193
  * plugin claimed it. The same `Package.provider`, so one declaration can address a single
158
194
  * ecosystem in a polyglot repository (`if: "${{ pkg.provider === 'node' }}"`).
159
195
  */
@@ -163,7 +199,7 @@ export interface PackageScope {
163
199
  * about (`${{ pkg.manifest.engines.node }}`).
164
200
  *
165
201
  * Named `manifest`, not `json`: which file a package's identity lives in is the ecosystem's
166
- * business now (see `ManifestProvider`), and `json` was that assumption showing through the one
202
+ * business now (see `Plugin`'s manifest members), and `json` was that assumption showing through the one
167
203
  * remaining user-facing name. A config written against `${{ pkg.json... }}` needs the rename.
168
204
  */
169
205
  manifest: Record<string, unknown>;
@@ -194,12 +230,90 @@ export interface GitScope {
194
230
  /** Whether the working tree has uncommitted changes. */
195
231
  dirty: boolean | undefined;
196
232
  }
233
+ /**
234
+ * What a **value function** is handed - `clean: { include: ({ value, pkg }) => [...] }`.
235
+ *
236
+ * The same scope a `${{ }}` expression sees, plus `value`. There is no asymmetry between the two
237
+ * spellings and that is an invariant with a spec on it: the argument *is* the expression context
238
+ * with `value` on it, so a member defined straight onto the argument would split them silently and
239
+ * a config author would meet a name that works in one spelling and not the other.
240
+ *
241
+ * **The config's own top-level keys are bound bare too**, and they cannot be typed here - they come
242
+ * from the config being interpolated rather than from this object. Reach them through `pkg.config`
243
+ * when a type matters, or accept `any` from the bare name.
244
+ *
245
+ * **Not what a *step* function is handed.** `run.<script>.exec` and `version.<slot>` take a
246
+ * `RunStepFn`, which gets a `RunStepContext` (`pkg`, `repository`, `cwd`, `runBin`, `logger`) when
247
+ * its turn comes - a different object at a different time, which is the whole distinction the two
248
+ * forms exist to draw. Never widen a step key to accept this one.
249
+ */
250
+ export interface ConfigValueContext extends ConfigScope {
251
+ /**
252
+ * What this key resolved to in the layers **below** this one - the list form of it, so
253
+ * `[...value, 'x']` needs no guard. `any` rather than a generic: the key's own type is what the
254
+ * function must return, while `value` is whatever the layers underneath happened to produce, and
255
+ * a scalar underneath arrives as a one-element list. See `previousValue`.
256
+ */
257
+ value: any;
258
+ /** The config's own top-level keys, bound bare - `vars`, `publish`, `clean`, … */
259
+ [key: string]: any;
260
+ }
261
+ /**
262
+ * A config value that may be **written as a function instead**, computed per package at the moment
263
+ * the config resolves.
264
+ *
265
+ * `T` is what the function has to return, so the same checking applies either way - measured, with
266
+ * a control: a typo inside a wrapped object is still caught, and so is one inside an object a value
267
+ * function *returns*.
268
+ *
269
+ * **Not for a step key.** `run.<script>.before`/`.exec`/`.after`, `run.<script>.if` and
270
+ * `version.<slot>` already take a function, and it means something else there - code for `run` to
271
+ * call in its own time, with its own context. Wrapping one of those would produce a type that
272
+ * accepts a value function where a step is what actually runs. The key path decides which a
273
+ * function is (`STEP_PATHS`, `CODE_SUBTREES`), and the type can only follow that split by hand.
274
+ */
275
+ export type ConfigValue<T> = T | ((ctx: ConfigValueContext) => T);
276
+ /**
277
+ * The same config **after** it resolves: every `ConfigValue<T>` is just `T`, because
278
+ * `interpolateConfig` has already called it.
279
+ *
280
+ * **This is the half that lets `RmanConfig` be the author's type.** One type cannot answer both
281
+ * "what may I write?" (a function is fine - rman calls it) and "what do I get?" (never a function -
282
+ * it was already called), so it used to answer only the second, and writing a value function was a
283
+ * compile error the docs themselves committed. Widening `RmanConfig` alone just moves the problem:
284
+ * measured, six read sites needed a cast. The author's type widens and the *reader's* is computed
285
+ * from it - one derived type, applied at `Package.config`, rather than a second one to keep in step
286
+ * by hand.
287
+ *
288
+ * **Two guards, and each was measured by leaving it out.**
289
+ *
290
+ * - **Steps are named first.** A value function is recognised by its parameter, and
291
+ * `ConfigValueContext` carries an index signature - so `RunStepFn` is assignable to it and a
292
+ * `run.build.exec` function collapsed to its *return type*, leaving `RunService` nothing to call.
293
+ * - **`CODE_SUBTREES` is skipped, at every level.** `plugins`/`commands`/`publishTargets` hold code
294
+ * all the way down, and the selector index (`[selector]: RmanConfig`) re-enters the config, so a
295
+ * top-level-only guard misses the copy inside a `"[*]"` block. Left out, the walk reached
296
+ * `Plugin.manifestProvider.versionScheme` and rewrote its **methods**: `smallestBump(): string`
297
+ * became `string`, `bumpFor`/`isValid`/`compare`/`next` became `{}`. A function with *fewer*
298
+ * parameters is assignable to one with more, so a zero-argument method matches the value-function
299
+ * pattern - which makes this transform unsafe over any object carrying methods, and the guard the
300
+ * only thing keeping one out of its way.
301
+ *
302
+ * Both lists are the runtime's own (`STEP_PATHS`' function types, `CODE_SUBTREES` itself), so the
303
+ * type follows the rule rather than restating it - the drift `ScopedVars` already demonstrated is
304
+ * not available here.
305
+ */
306
+ export type Resolved<T> = T extends RunStepFn | RunConditionFn ? T : T extends (ctx: ConfigValueContext) => infer R ? R : T extends object ? {
307
+ [K in keyof T]: K extends CodeSubtree ? T[K] : Resolved<T[K]>;
308
+ } : T;
309
+ /** `pkg.config`'s type: what every command reads, with the value functions already called. */
310
+ export type ResolvedConfig = Resolved<RmanConfig>;
197
311
  /**
198
312
  * What a `${{ ... }}` expression can see - the bindings of the fresh global it is evaluated in.
199
313
  * Namespaced rather than a flat bag of loose names: one obvious place per fact, and room to add
200
314
  * helpers to `pkg`/`repository` later without crowding the global.
201
315
  *
202
- * Alongside these, **the config's own top-level keys are bound bare** (`${{ publish.directory }}`,
316
+ * Alongside these, **the config's own top-level keys are bound bare** (`${{ changelog.filePath }}`,
203
317
  * `${{ clean.include }}`) - see `interpolateConfig`. They are not listed here because they come
204
318
  * from the config being interpolated, not from this object; a name here wins over a config key of
205
319
  * the same name.
@@ -278,7 +392,7 @@ export interface ConfigScope {
278
392
  * run:
279
393
  * build:
280
394
  * # the config's own keys are in scope, so this is not a second copy of "build"
281
- * after: "cp README.md ${{ publish.directory }}/"
395
+ * after: "cp README.md ${{ changelog.filePath }}/"
282
396
  * ```
283
397
  *
284
398
  * Every string, with no list of "interpolated keys" to memorize - a rule with exceptions is a rule
@@ -311,7 +425,12 @@ export interface InterpolateOptions {
311
425
  /** Where `config` sits in the whole config, for a caller interpolating a fragment. */
312
426
  at?: string[];
313
427
  }
314
- export declare function interpolateConfig<T>(config: T, scope: ConfigScope, options?: InterpolateOptions): T;
428
+ /**
429
+ * **Returns `Resolved<T>`, not `T`, because resolving is what it does.** Calling every value
430
+ * function is half this function's job, so the type it hands back is the one where they are gone -
431
+ * which is what makes `pkg.config` a `ResolvedConfig` without a cast anywhere between.
432
+ */
433
+ export declare function interpolateConfig<T>(config: T, scope: ConfigScope, options?: InterpolateOptions): Resolved<T>;
315
434
  /**
316
435
  * Config paths left untouched when a repository's config is first resolved, and evaluated only by
317
436
  * the command that runs them.
@@ -330,14 +449,14 @@ export declare const DEFERRED_PATHS: string[];
330
449
  * config:
331
450
  *
332
451
  * ```js
333
- * '[ws:*]': {
452
+ * '[*]': {
334
453
  * clean: { include: ({ vars, value }) => [...value, vars.buildDir] }, // a value: called here
335
454
  * run: { build: { after: ({ pkg }) => copyDocs(pkg) } }, // a step: called by `run`
336
455
  * }
337
456
  * ```
338
457
  *
339
458
  * **The key decides, and it already did.** `run.build.exec: 'tsc -b'` is a shell command and
340
- * `publish.directory: 'build'` is a path - not because of anything about the strings, but because of
459
+ * `publish.npm.directory: 'build'` is a path - not because of anything about the strings, but because of
341
460
  * where they sit. A function inherits the same rule, so nothing new has to be learned and no marker
342
461
  * has to be remembered. The alternative was inspecting the function (arity, parameter names), which
343
462
  * is the kind of guess `loadPlugins` refuses to make about a module's export for the same reason:
@@ -350,16 +469,23 @@ export declare const DEFERRED_PATHS: string[];
350
469
  export declare const STEP_PATHS: string[];
351
470
  /**
352
471
  * Keys whose **whole subtree** is code rather than config, so no function under them is a value to
353
- * compute. `plugins` is the only one, and it has to be here: an entry may be the plugin *object*
354
- * itself, and an `RmanPlugin` is almost entirely functions - `manifest.read`, `workspace.resolve`,
355
- * `versionPlanner`, `binPaths`, and every command's `builder` and `handler`.
356
- *
357
- * Measured, and it is why this exists: with `plugins` walked like any other key, resolving the
358
- * config of a repository that named a plugin called that plugin's yargs builder with the config
359
- * scope - `Config function in "plugins[0].commands[0].builder" failed: cmd.option is not a
360
- * function`. A `plugins` entry is loaded by `loadPlugins`, never read as a setting.
472
+ * compute.
473
+ *
474
+ * The three contribution keys, and each has to be here: an entry may be the *instance* itself, and
475
+ * a `Plugin` is almost entirely functions - `manifestProvider.read`, `getWorkspace`,
476
+ * `getBinPaths`, `versionPlanner` - while a command is often a bare factory and a publish target
477
+ * carries `getPlan`/`applyPlan`.
478
+ *
479
+ * Measured twice, once per shape. With `plugins` walked like any other key, resolving the config
480
+ * of a repository that named a plugin called that plugin's yargs builder with the config scope:
481
+ * `Config function in "plugins[0].commands[0].builder" failed: cmd.option is not a function`. And
482
+ * with `commands` left out of this list, a declarative command - which *is* a function - was
483
+ * invoked with the interpolation scope instead of the application, so its handler closed over a
484
+ * repository that was not one: `repository.getPackages is not a function`, from inside `clean`.
485
+ *
486
+ * These entries are loaded by `loadPlugins` and `cli.ts`, never read as settings.
361
487
  */
362
- export declare const CODE_SUBTREES: string[];
488
+ export declare const CODE_SUBTREES: readonly ["plugins", "commands", "publishTargets"];
363
489
  /** The `file` namespace for one package's directory - see `FileScope`. */
364
490
  /**
365
491
  * `read` in a `${{ ... }}` expression (and in a value function): a structured file's **contents**,
@@ -398,3 +524,7 @@ export interface CachedFile {
398
524
  value: unknown;
399
525
  }
400
526
  export declare function createFileScope(dirname: string): FileScope;
527
+ /** The `CODE_SUBTREES` entries as a type, so the runtime list and `Resolved`'s guard cannot name
528
+ * different keys. */
529
+ type CodeSubtree = (typeof CODE_SUBTREES)[number];
530
+ export {};