rman 2.0.0-beta.2 → 2.0.0-beta.4

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 (162) hide show
  1. package/README.md +2 -12
  2. package/{plugins/node/augmentation/rman.augmentation.d.ts → builtins/platforms/node/augmentation/rmanrc.augmentation.d.ts} +4 -5
  3. package/{plugins → builtins/platforms}/node/augmentation/system-info.augmentation.js +1 -1
  4. package/{plugins → builtins/platforms}/node/commands/ci.command.d.ts +5 -3
  5. package/{plugins → builtins/platforms}/node/commands/ci.command.js +6 -5
  6. package/{plugins → builtins/platforms}/node/commands/clean.command.d.ts +6 -4
  7. package/{plugins → builtins/platforms}/node/commands/clean.command.js +6 -5
  8. package/builtins/platforms/node/index.d.ts +8 -0
  9. package/builtins/platforms/node/index.js +8 -0
  10. package/{plugins → builtins/platforms}/node/node-config.interface.d.ts +1 -2
  11. package/{plugins → builtins/platforms}/node/node-manifest.provider.d.ts +3 -3
  12. package/{plugins → builtins/platforms}/node/node-manifest.provider.js +3 -3
  13. package/{plugins → builtins/platforms}/node/node.platform.d.ts +3 -3
  14. package/{plugins → builtins/platforms}/node/services/ci.service.d.ts +3 -3
  15. package/{plugins → builtins/platforms}/node/services/ci.service.js +4 -4
  16. package/{plugins → builtins/platforms}/node/services/clean.service.d.ts +3 -3
  17. package/{plugins → builtins/platforms}/node/services/clean.service.js +3 -3
  18. package/{plugins → builtins/platforms}/node/services/publish.service.d.ts +4 -4
  19. package/{plugins → builtins/platforms}/node/services/publish.service.js +13 -7
  20. package/{plugins → builtins/platforms}/node/services/version-plan.service.d.ts +3 -3
  21. package/{plugins → builtins/platforms}/node/services/version-plan.service.js +2 -2
  22. package/builtins/presets/index.d.ts +15 -0
  23. package/builtins/presets/index.js +57 -0
  24. package/builtins/presets/node.d.ts +16 -0
  25. package/builtins/presets/node.js +57 -0
  26. package/{services → builtins/publish-targets/docker}/docker-publish.service.d.ts +6 -6
  27. package/{services → builtins/publish-targets/docker}/docker-publish.service.js +9 -9
  28. package/{targets → builtins/publish-targets/docker}/docker.target.d.ts +6 -5
  29. package/{targets → builtins/publish-targets/docker}/docker.target.js +1 -1
  30. package/builtins/publish-targets/docker/index.d.ts +2 -0
  31. package/builtins/publish-targets/docker/index.js +2 -0
  32. package/builtins/publish-targets/npm/index.d.ts +2 -0
  33. package/builtins/publish-targets/npm/index.js +2 -0
  34. package/{plugins/node → builtins/publish-targets/npm}/npm-publish-target.d.ts +4 -4
  35. package/{plugins/node → builtins/publish-targets/npm}/npm-publish-target.js +4 -5
  36. package/cli.d.ts +3 -0
  37. package/cli.js +16 -7
  38. package/commands/changelog.command.d.ts +106 -2
  39. package/commands/changelog.command.js +40 -0
  40. package/commands/config.command.js +23 -5
  41. package/commands/list.command.js +1 -1
  42. package/commands/publish.command.d.ts +1 -1
  43. package/commands/publish.command.js +1 -1
  44. package/commands/run.command.js +1 -1
  45. package/commands/version.command.d.ts +16 -3
  46. package/commands/version.command.js +143 -9
  47. package/commands.d.ts +0 -1
  48. package/commands.js +0 -1
  49. package/constants.js +1 -1
  50. package/core/application.d.ts +10 -10
  51. package/core/application.js +28 -11
  52. package/core/{package.d.ts → classes/package.d.ts} +12 -4
  53. package/core/{package.js → classes/package.js} +17 -1
  54. package/core/{repository.d.ts → classes/repository.d.ts} +22 -36
  55. package/core/{repository.js → classes/repository.js} +112 -241
  56. package/core/{service.d.ts → classes/service.d.ts} +2 -2
  57. package/core/classes/workspace.d.ts +277 -0
  58. package/core/classes/workspace.js +585 -0
  59. package/core/command-builder.js +17 -1
  60. package/core/config/config-file-scope.d.ts +107 -0
  61. package/core/config/config-file-scope.js +226 -0
  62. package/core/config/config-interpolator.d.ts +164 -0
  63. package/core/config/config-interpolator.js +577 -0
  64. package/core/config/config-paths.d.ts +65 -0
  65. package/core/config/config-paths.js +78 -0
  66. package/core/config/config-reader.d.ts +215 -0
  67. package/core/config/config-reader.js +851 -0
  68. package/core/{merge-config.js → config/merge-config.js} +4 -8
  69. package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +5 -5
  70. package/core/{manifest.d.ts → interfaces/manifest.d.ts} +3 -3
  71. package/core/{manifest.js → interfaces/manifest.js} +2 -2
  72. package/core/{plugin.d.ts → interfaces/plugin.d.ts} +44 -27
  73. package/core/{plugin.js → interfaces/plugin.js} +48 -10
  74. package/core/{publish-target.d.ts → interfaces/publish-target.d.ts} +24 -12
  75. package/core/{publish-target.js → interfaces/publish-target.js} +16 -3
  76. package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
  77. package/core/plugin-loader.d.ts +11 -51
  78. package/core/plugin-loader.js +21 -202
  79. package/index.d.ts +29 -28
  80. package/index.js +20 -46
  81. package/interfaces/config-scope.interface.d.ts +223 -0
  82. package/interfaces/config-scope.interface.js +1 -0
  83. package/interfaces/rman-config.interface.d.ts +100 -5
  84. package/interfaces/rman-config.interface.js +17 -0
  85. package/package.json +3 -2
  86. package/services/change-hash.service.d.ts +40 -6
  87. package/services/change-hash.service.js +90 -11
  88. package/services/changelog.service.d.ts +31 -3
  89. package/services/changelog.service.js +448 -65
  90. package/services/config-service.d.ts +8 -0
  91. package/services/config-service.js +3 -0
  92. package/services/exec.service.d.ts +2 -2
  93. package/services/exec.service.js +1 -1
  94. package/services/github-release.service.d.ts +3 -3
  95. package/services/github-release.service.js +3 -3
  96. package/services/import.service.d.ts +2 -2
  97. package/services/import.service.js +1 -1
  98. package/services/list.service.d.ts +9 -7
  99. package/services/list.service.js +2 -2
  100. package/services/run.service.d.ts +15 -11
  101. package/services/run.service.js +62 -18
  102. package/services/system-info.d.ts +1 -1
  103. package/services/version-plan.service.d.ts +8 -4
  104. package/services/version-plan.service.js +54 -21
  105. package/services/version.service.d.ts +5 -5
  106. package/services/version.service.js +7 -4
  107. package/services.d.ts +1 -1
  108. package/services.js +1 -1
  109. package/utils/branch-guard.d.ts +1 -1
  110. package/utils/color-yaml.d.ts +8 -0
  111. package/utils/color-yaml.js +86 -0
  112. package/utils/git.d.ts +26 -0
  113. package/utils/git.js +65 -0
  114. package/utils/logger.d.ts +1 -1
  115. package/utils/package-filter.d.ts +8 -1
  116. package/utils/package-filter.js +7 -0
  117. package/utils/printable-config.d.ts +8 -0
  118. package/utils/printable-config.js +45 -0
  119. package/utils/progress-panel.d.ts +9 -0
  120. package/utils/progress-panel.js +23 -1
  121. package/utils/release-version.d.ts +1 -1
  122. package/utils/run-options.js +1 -1
  123. package/utils/version-group.d.ts +31 -0
  124. package/utils/version-group.js +43 -0
  125. package/commands/changed.command.d.ts +0 -80
  126. package/commands/changed.command.js +0 -36
  127. package/core/config.d.ts +0 -530
  128. package/core/config.js +0 -1123
  129. package/core/core-services.d.ts +0 -14
  130. package/core/core-services.js +0 -30
  131. package/core/core-targets.d.ts +0 -14
  132. package/core/core-targets.js +0 -16
  133. package/core/extends-config.d.ts +0 -35
  134. package/core/extends-config.js +0 -129
  135. package/core/load-config-module.d.ts +0 -28
  136. package/core/load-config-module.js +0 -42
  137. package/core/workspace.d.ts +0 -119
  138. package/core/workspace.js +0 -124
  139. package/plugins/builtins.d.ts +0 -44
  140. package/plugins/builtins.js +0 -33
  141. package/plugins/detect.d.ts +0 -78
  142. package/plugins/detect.js +0 -70
  143. package/plugins/node/index.d.ts +0 -29
  144. package/plugins/node/index.js +0 -40
  145. /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
  146. /package/{plugins → builtins/platforms}/node/augmentation/system-info.augmentation.d.ts +0 -0
  147. /package/{plugins → builtins/platforms}/node/node-config.interface.js +0 -0
  148. /package/{plugins → builtins/platforms}/node/node.platform.js +0 -0
  149. /package/{plugins → builtins/platforms}/node/utils/workspace-range.d.ts +0 -0
  150. /package/{plugins → builtins/platforms}/node/utils/workspace-range.js +0 -0
  151. /package/{plugins/node/utils → builtins/publish-targets/npm}/npm-view.d.ts +0 -0
  152. /package/{plugins/node/utils → builtins/publish-targets/npm}/npm-view.js +0 -0
  153. /package/core/{registry.d.ts → classes/registry.d.ts} +0 -0
  154. /package/core/{registry.js → classes/registry.js} +0 -0
  155. /package/core/{service.js → classes/service.js} +0 -0
  156. /package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +0 -0
  157. /package/core/{version-scheme.js → classes/version-scheme.js} +0 -0
  158. /package/core/{merge-config.d.ts → config/merge-config.d.ts} +0 -0
  159. /package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +0 -0
  160. /package/core/{resolve-target.js → config/resolve-config-target.js} +0 -0
  161. /package/core/{custom-command.js → interfaces/custom-command.js} +0 -0
  162. /package/{plugins/node/augmentation/rman.augmentation.js → core/interfaces/run-step.js} +0 -0
package/core/config.d.ts DELETED
@@ -1,530 +0,0 @@
1
- import path from 'path';
2
- import semver from 'semver';
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';
6
- /**
7
- * Identity helper for authoring a `.rmanrc.cjs`/`.mjs`/`.js` config with full type-checking and
8
- * autocomplete - the same `defineConfig` pattern Vite/Vitest use. Returns `config` completely
9
- * unchanged; this exists purely so TypeScript can infer/check against `RmanConfig`, not for any
10
- * runtime behavior:
11
- *
12
- * // .rmanrc.mjs
13
- * import { defineConfig } from 'rman';
14
- * export default defineConfig({ packageManager: 'pnpm' });
15
- *
16
- * // .rmanrc.cjs
17
- * const { defineConfig } = require('rman');
18
- * module.exports = defineConfig({ packageManager: 'pnpm' });
19
- */
20
- export declare function defineConfig(config: RmanConfig): RmanConfig;
21
- /**
22
- * Reads the rman configuration defined at a single directory level, merging
23
- * (in increasing precedence): `package.json#rman`, `.rmanrc.yml`, `.rmanrc`,
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`.
31
- */
32
- export declare function readDirConfig(dirname: string, options?: {
33
- inject?: DetectedBuiltin;
34
- }): Promise<RmanConfig>;
35
- /**
36
- * Resolves the effective config for the package at `targetDir`, cascading from `rootDir` down to
37
- * it (inclusive) - each directory level overrides the ones above it, the way tsconfig's `extends`
38
- * chain does.
39
- *
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.
77
- */
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
84
- * brackets are what keep this space from colliding with real config keys - no setting starts with
85
- * one - and in YAML they also mean the key always needs quoting (`"[*]":`), since a bare `[*]`
86
- * parses as a flow sequence. */
87
- export declare function isSelectorKey(key: string): boolean;
88
- /**
89
- * **Which packages a selector speaks for.** Two audiences, and the second is a glob:
90
- *
91
- * | | |
92
- * | --- | --- |
93
- * | `"[/]"` | the **root package** alone |
94
- * | `"[*]"`, `"[pkg-a]"`, `"[*-dialect]"` | the packages **below** this directory that the glob matches |
95
- *
96
- * `/` for the root because that is what a repository root is called everywhere else, and it cannot
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.
109
- */
110
- export declare function parseSelector(key: string): {
111
- scope: 'root' | 'package';
112
- test: (name: string) => boolean;
113
- };
114
- /** The glob inside a selector key, as a `RegExp` anchored at both ends - so `"[*-dialect]"` matches
115
- * `mysql-dialect` but not `my-dialect-helper`. Glob rather than regex, to match every other
116
- * pattern in rman (`allowBranch`, `changelog.tagPattern`, `clean.include`). */
117
- export declare function selectorToRegExp(key: string): RegExp;
118
- /** One package, as an expression sees it - the same shape for the package the config belongs to
119
- * and for the repository itself, so `${{ repository.basename }}` reads the way `${{ pkg.basename }}` does. */
120
- /**
121
- * `file` in a `${{ ... }}` expression: what is actually on disk, resolved against **the package
122
- * the config was resolved for** - so one declaration in a `"[*]"` block asks each package about
123
- * its own directory.
124
- *
125
- * The pair exists because a config has two different questions about a path, and answering both
126
- * with one function would mean picking a wrong default for the other:
127
- *
128
- * ```yaml
129
- * "[*]":
130
- * run:
131
- * build:
132
- * # first of these that exists, and an error naming the config path if none do
133
- * exec: 'tsc -b ${{ file.exists("tsconfig-build.json") || file.resolve("tsconfig.json") }}'
134
- * ```
135
- *
136
- * **Every member asks a question, and none may ever change anything - no `copy`, no `write`, no
137
- * `mkdir`.** Not a matter of taste: this is evaluated when the config *resolves*, which every
138
- * command does, so a member that acted would act on `rman list`, `rman info` and `rman config`.
139
- *
140
- * That has been tried, in the only way a missing function can be: a shared config reaching for a
141
- * `file.copyMany(...)` that does not exist made **every** rman command exit 1 - and had it existed,
142
- * the quieter outcome would have been files copied by `rman list`. Work belongs in a step
143
- * (`run.<script>`'s slots, `version`'s hooks), which is the one place rman runs anything, and a
144
- * step can now be a function - so there is nothing this would enable that is not already possible
145
- * at the right moment.
146
- */
147
- export interface FileScope {
148
- /**
149
- * The absolute path if it exists, **`''` if it does not** - so `a || b || c` picks the first one
150
- * present, and so a miss never reaches the "nullish inside a string" guard that `undefined` would
151
- * trip. Accepts a relative path (against the package directory) or an absolute one.
152
- *
153
- * It returns a path rather than a boolean on purpose: the caller almost always wants the path,
154
- * and a separate `file.path()` to fetch it after a boolean test would read the disk twice and
155
- * invite the two calls to disagree.
156
- */
157
- exists(target: string): string;
158
- /** The absolute path, or **throws** - for a file whose absence is a mistake rather than a case to
159
- * handle. The error names the config path holding the expression, like any other. */
160
- resolve(target: string): string;
161
- /**
162
- * The first of several that exists, or **throws** naming every candidate it tried:
163
- *
164
- * ```yaml
165
- * exec: 'tsc -b ${{ file.resolveFirst("tsconfig-build.json", "tsconfig.build.json", "tsconfig.json") }}'
166
- * ```
167
- *
168
- * The same thing an `exists() || exists() || resolve()` chain does, said once - and it cannot be
169
- * got subtly wrong the way that chain can: ending it in `exists()` leaves `tsc -b ` with no
170
- * argument when nothing matches, and tsc then silently falls back to the directory's default
171
- * rather than reporting that the package has no build config.
172
- */
173
- resolveFirst(...targets: string[]): string;
174
- }
175
- export interface PackageScope {
176
- /** The package's own name, scope included (`@sqb/builder`). */
177
- name: string;
178
- /** Just the scope (`@sqb`), or `undefined` for an unscoped package. */
179
- scope: string | undefined;
180
- /** The name with its scope stripped (`builder`). */
181
- unscopedName: string;
182
- version: string;
183
- /** The package directory's last segment (`builder`) - not always the same as `unscopedName`,
184
- * which is why both exist, and usually what a sibling path (`../../coverage/builder`) is keyed on. */
185
- basename: string;
186
- /** Absolute path to the package's own directory - named as rman's own `Package.dirname` is. */
187
- dirname: string;
188
- /** That directory relative to the repository root (`packages/builder`), which is what a command
189
- * addressing another package from the root usually needs. Empty string for the root itself. */
190
- relativeDir: string;
191
- /**
192
- * Which ecosystem this package belongs to - `'node'` for one the `node` built-in read, empty when no
193
- * plugin claimed it. The same `Package.provider`, so one declaration can address a single
194
- * ecosystem in a polyglot repository (`if: "${{ pkg.provider === 'node' }}"`).
195
- */
196
- provider: string;
197
- /**
198
- * The whole manifest, as a copy - so an expression can reach a field rman itself has no opinion
199
- * about (`${{ pkg.manifest.engines.node }}`).
200
- *
201
- * Named `manifest`, not `json`: which file a package's identity lives in is the ecosystem's
202
- * business now (see `Plugin`'s manifest members), and `json` was that assumption showing through the one
203
- * remaining user-facing name. A config written against `${{ pkg.json... }}` needs the rename.
204
- */
205
- manifest: Record<string, unknown>;
206
- /**
207
- * The version this run is about to write - **bound only during `version`**, and only once its
208
- * plan is computed. Reading it anywhere else throws rather than yielding `undefined`: no other
209
- * command has a target version, so an expression asking for one has been put in the wrong place,
210
- * and a config that quietly evaluates to "undefined" is the failure this evaluator exists to
211
- * prevent.
212
- */
213
- targetVersion: string;
214
- }
215
- /** Facts about the repository, on top of the root package's own - because the repository root *is*
216
- * a package (`repository.name` is what its `package.json` says, `repository.basename` the directory
217
- * it sits in, and the two genuinely differ). Sharing `PackageScope`'s shape is what makes
218
- * `repository.version` read the way `pkg.version` does. */
219
- export interface RepositoryScope extends PackageScope {
220
- monorepo: boolean;
221
- /** Every package in the repository - the root included only when it *is* the one package. */
222
- packages: PackageScope[];
223
- /** One package by name, or `undefined` - for reaching a sibling's directory. */
224
- package(name: string): PackageScope | undefined;
225
- }
226
- export interface GitScope {
227
- branch: string | undefined;
228
- sha: string | undefined;
229
- shortSha: string | undefined;
230
- /** Whether the working tree has uncommitted changes. */
231
- dirty: boolean | undefined;
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>;
311
- /**
312
- * What a `${{ ... }}` expression can see - the bindings of the fresh global it is evaluated in.
313
- * Namespaced rather than a flat bag of loose names: one obvious place per fact, and room to add
314
- * helpers to `pkg`/`repository` later without crowding the global.
315
- *
316
- * Alongside these, **the config's own top-level keys are bound bare** (`${{ changelog.filePath }}`,
317
- * `${{ clean.include }}`) - see `interpolateConfig`. They are not listed here because they come
318
- * from the config being interpolated, not from this object; a name here wins over a config key of
319
- * the same name.
320
- *
321
- * **Trap: bare `${{ version }}` is the `version` *options block*, not the package's version
322
- * string** - that is `${{ pkg.version }}`. Same word, two different things, and the plain one
323
- * belongs to the config because every other config key is reachable that way.
324
- */
325
- export interface ConfigScope {
326
- /** The package the config was resolved for - which is what lets one declaration at the root
327
- * still say something package-specific. */
328
- pkg: PackageScope;
329
- repository: RepositoryScope;
330
- /** Paths, resolved against the package the config was resolved for. */
331
- file: FileScope;
332
- /**
333
- * The **contents** of a structured file - `${{ read('tsconfig.json').compilerOptions.outDir }}` -
334
- * where `file` answers only where one is. Resolved against `pkg.dirname` like `file`, so a
335
- * `"[*]"` block asks each package about its own; a repository-level file is reached through
336
- * `read(path.join(repository.dirname, ...))`.
337
- *
338
- * `.json`, `.yml`/`.yaml` and `.ini` by extension, or name it for a file that does not say
339
- * (`read('.npmrc', 'ini')`). **Throws** when the file is absent, as `file.resolve` does - compose
340
- * with `file.exists` when its absence is a case to handle.
341
- *
342
- * **A manifest is `pkg.manifest`, not this.** `read('package.json')` works and is the wrong
343
- * answer: which file a package's identity lives in belongs to the ecosystem, so that expression
344
- * is already wrong in a Cargo package sitting beside a Node one.
345
- *
346
- * The result is **deeply frozen and shared** - see `readStructuredFile`. Spread it to change it.
347
- */
348
- read: ReadFile;
349
- env: Record<string, string | undefined>;
350
- /** rman's own `semver`, for the arithmetic every release config eventually wants
351
- * (`semver.major(pkg.version)`). */
352
- semver: typeof semver;
353
- /**
354
- * Node's own `node:path` - `${{ path.join(pkg.dirname, 'LICENSE') }}`, rather than gluing
355
- * strings with `+ "/" +` and getting a double separator or none.
356
- *
357
- * The platform's flavour, not `path.posix`, so a joined path is the one the shell on *this*
358
- * machine understands; `path.posix` and `path.win32` are reachable through it when a config
359
- * genuinely needs one of them (a Docker image path, say, which is always posix).
360
- */
361
- path: typeof path;
362
- /**
363
- * The checkout: branch, sha, whether the tree is dirty.
364
- *
365
- * **Top level, not `repository.git`** - which is where it used to be, and the move is the point.
366
- * `repository` shares its shape with `pkg` because the repository root *is* a package, and its
367
- * only other members (`monorepo`, `packages`, `package()`) say something about the repository as
368
- * a container of packages. A branch name says nothing about any package; it describes the
369
- * working tree every one of them happens to be sitting in - the same kind of ambient fact as
370
- * `env`, and it belongs beside it.
371
- *
372
- * **Read from git only if an expression actually asks**, then remembered for the whole run: every
373
- * command resolves config, and a repository that never mentions git must not pay for one. See
374
- * `Repository.configScope` for the getter, and `interpolateConfig` for why the context is built
375
- * from property descriptors rather than a spread - a spread would fire this getter on every
376
- * command, which is exactly what moving it up here risked.
377
- */
378
- git: GitScope;
379
- }
380
- /**
381
- * Evaluates every `${{ ... }}` expression in **every** string value of a resolved config, against
382
- * the package it was resolved for:
383
- *
384
- * ```yaml
385
- * "[*]":
386
- * clean:
387
- * include: ["build", "../../coverage/${{ pkg.basename }}"]
388
- * publish:
389
- * directory: build
390
- * docker:
391
- * image: "panates/${{ pkg.basename }}:${{ semver.major(pkg.version) }}"
392
- * run:
393
- * build:
394
- * # the config's own keys are in scope, so this is not a second copy of "build"
395
- * after: "cp README.md ${{ changelog.filePath }}/"
396
- * ```
397
- *
398
- * Every string, with no list of "interpolated keys" to memorize - a rule with exceptions is a rule
399
- * nobody remembers.
400
- *
401
- * The contents are **real JavaScript**, not a template mini-language, so there is no growing list
402
- * of substitutions to keep adding (`{{major}}`, `{{scope}}`, ...) - see `ConfigScope` for what is
403
- * in scope.
404
- *
405
- * **`${{ }}`, deliberately not `{{ }}`.** A config value may legitimately carry `{{...}}` meant for
406
- * something else entirely (`helm template --set tag={{.Values.tag}}`); with the plainer delimiter
407
- * rman would try to evaluate it. To emit a literal, let an expression produce it, the way GitHub
408
- * Actions does: `${{ '${{' }}`.
409
- *
410
- * A string that is *nothing but* one expression keeps the value's own type (`"${{ pkg.private }}"`
411
- * -> a boolean), since otherwise this could only ever produce strings and settings like
412
- * `run.<script>.skip` would be unreachable. Embedded in surrounding text it is stringified.
413
- *
414
- * Evaluation happens in a fresh V8 context holding only the scope's bindings. That is a clean
415
- * scope, **not a sandbox** - `node:vm` is explicitly not a security mechanism, and no sandbox is
416
- * called for here anyway: a `.rmanrc` that can say `exec: "..."` already runs arbitrary shell, so
417
- * the expression evaluator adds no trust boundary that wasn't already wide open.
418
- *
419
- * A failing expression throws with the config path that holds it, rather than being left in place:
420
- * silently passing through a mistake is how a config ends up quietly doing nothing.
421
- */
422
- export interface InterpolateOptions {
423
- /** Config paths to leave entirely untouched - `DEFERRED_PATHS`, when the whole config is walked. */
424
- skip?: string[];
425
- /** Where `config` sits in the whole config, for a caller interpolating a fragment. */
426
- at?: string[];
427
- }
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>;
434
- /**
435
- * Config paths left untouched when a repository's config is first resolved, and evaluated only by
436
- * the command that runs them.
437
- *
438
- * `version`'s own hooks are the one place `${{ pkg.targetVersion }}` makes sense, and the version
439
- * being written is not known until `version` has computed its plan - long after the config was
440
- * resolved. Evaluating these eagerly would throw while merely *loading* the repository, so any
441
- * command at all would fail on a config that mentions it.
442
- */
443
- export declare const DEFERRED_PATHS: string[];
444
- /**
445
- * Paths whose value is a **step** - something to run later - rather than a setting to compute now.
446
- * `*` matches one path segment (`run.<script>.exec`).
447
- *
448
- * This is what tells a step function from a value function, and the two live side by side in one
449
- * config:
450
- *
451
- * ```js
452
- * '[*]': {
453
- * clean: { include: ({ vars, value }) => [...value, vars.buildDir] }, // a value: called here
454
- * run: { build: { after: ({ pkg }) => copyDocs(pkg) } }, // a step: called by `run`
455
- * }
456
- * ```
457
- *
458
- * **The key decides, and it already did.** `run.build.exec: 'tsc -b'` is a shell command and
459
- * `publish.npm.directory: 'build'` is a path - not because of anything about the strings, but because of
460
- * where they sit. A function inherits the same rule, so nothing new has to be learned and no marker
461
- * has to be remembered. The alternative was inspecting the function (arity, parameter names), which
462
- * is the kind of guess `loadPlugins` refuses to make about a module's export for the same reason:
463
- * guessing wrong here means running build-time code while merely loading the repository, or
464
- * silently never running it.
465
- *
466
- * A **string** at one of these paths is still interpolated - `exec: 'tsc -b ${{ file.resolve(...) }}'`
467
- * has to keep working - so this is narrower than `DEFERRED_PATHS`, which skips its paths entirely.
468
- */
469
- export declare const STEP_PATHS: string[];
470
- /**
471
- * Keys whose **whole subtree** is code rather than config, so no function under them is a value to
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.
487
- */
488
- export declare const CODE_SUBTREES: readonly ["plugins", "commands", "publishTargets"];
489
- /** The `file` namespace for one package's directory - see `FileScope`. */
490
- /**
491
- * `read` in a `${{ ... }}` expression (and in a value function): a structured file's **contents**,
492
- * parsed - where `file` answers only where a path is.
493
- *
494
- * ```yaml
495
- * "[*]":
496
- * run:
497
- * build:
498
- * exec: 'tsc --outDir ${{ read("tsconfig.json").compilerOptions.outDir }}'
499
- * ```
500
- *
501
- * `cache` is shared across every package (see `Repository.configScope`) and keyed by what the file
502
- * *is*, not merely where - so the same file read by twenty packages is parsed once, and a file rman
503
- * itself rewrites mid-run is re-read rather than remembered. See `readStructuredFile`.
504
- */
505
- export declare function createReadScope(dirname: string, cache: Map<string, CachedFile>): ReadFile;
506
- /**
507
- * What `read` can parse.
508
- *
509
- * **`.env` is deliberately absent, and that is the durable part of this list**: `env` is already in
510
- * scope, and a `.env` file exists to be loaded *into* an environment by something else - a config
511
- * reading one as data would mean two different things called the environment.
512
- *
513
- * Nothing else is excluded on principle. `xml` arrived because a `pom.xml` or a `.csproj` holds a
514
- * version exactly the way a `package.json` does, and rman is language-agnostic; the earlier line
515
- * ("no new parsers") did not survive it, since xmldom *is* a new one.
516
- */
517
- export type FileFormat = 'json' | 'yaml' | 'ini' | 'xml';
518
- /** `read(path)`, or `read(path, 'ini')` for a file whose name does not say what it is (`.npmrc`). */
519
- export type ReadFile = (target: string, format?: FileFormat) => unknown;
520
- /** One parsed file, kept against the identity of the bytes it came from - see `readStructuredFile`. */
521
- export interface CachedFile {
522
- /** `mtimeNs:size`. */
523
- stamp: string;
524
- value: unknown;
525
- }
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 {};