rman 2.0.0-beta.2 → 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 (164) 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 +5 -5
  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/lint.command.d.ts +176 -0
  42. package/commands/lint.command.js +34 -0
  43. package/commands/list.command.js +1 -1
  44. package/commands/publish.command.d.ts +1 -1
  45. package/commands/publish.command.js +1 -1
  46. package/commands/run.command.js +1 -1
  47. package/commands/version.command.d.ts +16 -3
  48. package/commands/version.command.js +143 -9
  49. package/commands.d.ts +1 -1
  50. package/commands.js +1 -1
  51. package/constants.js +1 -1
  52. package/core/application.d.ts +10 -10
  53. package/core/application.js +28 -11
  54. package/core/{package.d.ts → classes/package.d.ts} +12 -4
  55. package/core/{package.js → classes/package.js} +17 -1
  56. package/core/{repository.d.ts → classes/repository.d.ts} +22 -36
  57. package/core/{repository.js → classes/repository.js} +112 -241
  58. package/core/{service.d.ts → classes/service.d.ts} +2 -2
  59. package/core/classes/workspace.d.ts +275 -0
  60. package/core/classes/workspace.js +537 -0
  61. package/core/command-builder.js +17 -1
  62. package/core/config/config-file-scope.d.ts +107 -0
  63. package/core/config/config-file-scope.js +226 -0
  64. package/core/config/config-interpolator.d.ts +164 -0
  65. package/core/config/config-interpolator.js +577 -0
  66. package/core/config/config-paths.d.ts +65 -0
  67. package/core/config/config-paths.js +78 -0
  68. package/core/config/config-reader.d.ts +192 -0
  69. package/core/config/config-reader.js +727 -0
  70. package/core/{merge-config.js → config/merge-config.js} +4 -8
  71. package/core/{custom-command.d.ts → interfaces/custom-command.d.ts} +5 -5
  72. package/core/{manifest.d.ts → interfaces/manifest.d.ts} +3 -3
  73. package/core/{manifest.js → interfaces/manifest.js} +2 -2
  74. package/core/{plugin.d.ts → interfaces/plugin.d.ts} +44 -27
  75. package/core/{plugin.js → interfaces/plugin.js} +48 -10
  76. package/core/{publish-target.d.ts → interfaces/publish-target.d.ts} +24 -12
  77. package/core/{publish-target.js → interfaces/publish-target.js} +16 -3
  78. package/core/{run-step.d.ts → interfaces/run-step.d.ts} +4 -4
  79. package/core/plugin-loader.d.ts +11 -51
  80. package/core/plugin-loader.js +21 -202
  81. package/index.d.ts +29 -28
  82. package/index.js +20 -46
  83. package/interfaces/config-scope.interface.d.ts +223 -0
  84. package/interfaces/config-scope.interface.js +1 -0
  85. package/interfaces/rman-config.interface.d.ts +100 -5
  86. package/interfaces/rman-config.interface.js +17 -0
  87. package/package.json +2 -1
  88. package/services/change-hash.service.d.ts +40 -6
  89. package/services/change-hash.service.js +90 -11
  90. package/services/changelog.service.d.ts +31 -3
  91. package/services/changelog.service.js +448 -65
  92. package/services/config-service.d.ts +8 -0
  93. package/services/config-service.js +3 -0
  94. package/services/exec.service.d.ts +2 -2
  95. package/services/exec.service.js +1 -1
  96. package/services/github-release.service.d.ts +3 -3
  97. package/services/github-release.service.js +3 -3
  98. package/services/import.service.d.ts +2 -2
  99. package/services/import.service.js +1 -1
  100. package/services/list.service.d.ts +9 -7
  101. package/services/list.service.js +2 -2
  102. package/services/run.service.d.ts +15 -11
  103. package/services/run.service.js +62 -18
  104. package/services/system-info.d.ts +1 -1
  105. package/services/version-plan.service.d.ts +8 -4
  106. package/services/version-plan.service.js +54 -21
  107. package/services/version.service.d.ts +5 -5
  108. package/services/version.service.js +7 -4
  109. package/services.d.ts +1 -1
  110. package/services.js +1 -1
  111. package/utils/branch-guard.d.ts +1 -1
  112. package/utils/color-yaml.d.ts +8 -0
  113. package/utils/color-yaml.js +86 -0
  114. package/utils/git.d.ts +26 -0
  115. package/utils/git.js +65 -0
  116. package/utils/logger.d.ts +1 -1
  117. package/utils/package-filter.d.ts +8 -1
  118. package/utils/package-filter.js +7 -0
  119. package/utils/printable-config.d.ts +8 -0
  120. package/utils/printable-config.js +45 -0
  121. package/utils/progress-panel.d.ts +9 -0
  122. package/utils/progress-panel.js +23 -1
  123. package/utils/release-version.d.ts +1 -1
  124. package/utils/run-options.js +1 -1
  125. package/utils/version-group.d.ts +31 -0
  126. package/utils/version-group.js +43 -0
  127. package/commands/changed.command.d.ts +0 -80
  128. package/commands/changed.command.js +0 -36
  129. package/core/config.d.ts +0 -530
  130. package/core/config.js +0 -1123
  131. package/core/core-services.d.ts +0 -14
  132. package/core/core-services.js +0 -30
  133. package/core/core-targets.d.ts +0 -14
  134. package/core/core-targets.js +0 -16
  135. package/core/extends-config.d.ts +0 -35
  136. package/core/extends-config.js +0 -129
  137. package/core/load-config-module.d.ts +0 -28
  138. package/core/load-config-module.js +0 -42
  139. package/core/workspace.d.ts +0 -119
  140. package/core/workspace.js +0 -124
  141. package/plugins/builtins.d.ts +0 -44
  142. package/plugins/builtins.js +0 -33
  143. package/plugins/detect.d.ts +0 -78
  144. package/plugins/detect.js +0 -70
  145. package/plugins/node/index.d.ts +0 -29
  146. package/plugins/node/index.js +0 -40
  147. /package/{core/run-step.js → builtins/platforms/node/augmentation/rmanrc.augmentation.js} +0 -0
  148. /package/{plugins → builtins/platforms}/node/augmentation/system-info.augmentation.d.ts +0 -0
  149. /package/{plugins → builtins/platforms}/node/node-config.interface.js +0 -0
  150. /package/{plugins → builtins/platforms}/node/node.platform.js +0 -0
  151. /package/{plugins → builtins/platforms}/node/utils/workspace-range.d.ts +0 -0
  152. /package/{plugins → builtins/platforms}/node/utils/workspace-range.js +0 -0
  153. /package/{plugins/node/utils → builtins/publish-targets/npm}/npm-view.d.ts +0 -0
  154. /package/{plugins/node/utils → builtins/publish-targets/npm}/npm-view.js +0 -0
  155. /package/core/{registry.d.ts → classes/registry.d.ts} +0 -0
  156. /package/core/{registry.js → classes/registry.js} +0 -0
  157. /package/core/{service.js → classes/service.js} +0 -0
  158. /package/core/{version-scheme.d.ts → classes/version-scheme.d.ts} +0 -0
  159. /package/core/{version-scheme.js → classes/version-scheme.js} +0 -0
  160. /package/core/{merge-config.d.ts → config/merge-config.d.ts} +0 -0
  161. /package/core/{resolve-target.d.ts → config/resolve-config-target.d.ts} +0 -0
  162. /package/core/{resolve-target.js → config/resolve-config-target.js} +0 -0
  163. /package/core/{custom-command.js → interfaces/custom-command.js} +0 -0
  164. /package/{plugins/node/augmentation/rman.augmentation.js → core/interfaces/run-step.js} +0 -0
@@ -0,0 +1,226 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DOMParser } from '@xmldom/xmldom';
4
+ import ini from 'ini';
5
+ import * as yaml from 'js-yaml';
6
+ /**
7
+ * The two members of the expression scope that answer questions about the filesystem: `file`, which
8
+ * says **where** something is, and `read`, which says what is **in** it.
9
+ *
10
+ * Both are built per directory - a package's own - so one `"[*]"` declaration asks each package
11
+ * about its own files. The parse cache is per *instance* and shared across those directories, so
12
+ * twenty packages reading one file parse it once.
13
+ *
14
+ * ```yaml
15
+ * "[*]":
16
+ * run:
17
+ * build:
18
+ * exec: 'tsc -b ${{ file.resolveFirst("tsconfig-build.json", "tsconfig.json") }}'
19
+ * version:
20
+ * stamp: '${{ read("pom.xml").getElementsByTagName("version")[0].textContent }}'
21
+ * ```
22
+ *
23
+ * **Everything here is a query, and must stay one.** These run while a config resolves, which
24
+ * *every* command does - so anything that *did* something would do it on `rman list`, `rman info`
25
+ * and `rman config`, once per package, with nothing having asked.
26
+ */
27
+ /* **Do not add `copy`, `write` or `mkdir`, however reasonable the request sounds.** It has already
28
+ * been tried, in the only way a function that does not exist can be: a shared config reaching for
29
+ * `file.copyMany(...)` made **every** rman command exit 1. The loud failure was the lucky outcome -
30
+ * had the member existed, `rman list` would have quietly copied files. Work goes in a step, which
31
+ * is the one thing rman runs on purpose, and a step can be a function too. */
32
+ export class ConfigFileScope {
33
+ cache;
34
+ /**
35
+ * One parsed file per path, kept against the identity of the bytes it came from.
36
+ *
37
+ * Passed in to share one cache across several instances; left out, the instance owns its own.
38
+ */
39
+ /* **Per instance rather than per scope**, because the scope is built once *per package*: a cache
40
+ * living in one pass never helps across them, and twenty packages reading one shared file would
41
+ * parse it twenty times. */
42
+ constructor(cache = new Map()) {
43
+ this.cache = cache;
44
+ }
45
+ /**
46
+ * The `file` member, resolving against `dirname`.
47
+ *
48
+ * @param dirname the package directory a relative path is resolved against
49
+ */
50
+ fileScope(dirname) {
51
+ return {
52
+ exists: (target) => {
53
+ const { path: resolved, found } = this._locate(dirname, target);
54
+ return found ? resolved : '';
55
+ },
56
+ resolve: (target) => {
57
+ const { path: resolved, found } = this._locate(dirname, target);
58
+ if (!found) {
59
+ throw new Error(`file.resolve("${target}") found nothing at ${resolved}\n` +
60
+ ` Use file.exists() instead if its absence is a case to handle rather than a mistake.`);
61
+ }
62
+ return resolved;
63
+ },
64
+ resolveFirst: (...targets) => {
65
+ if (!targets.length)
66
+ throw new Error('file.resolveFirst() needs at least one path');
67
+ for (const target of targets) {
68
+ const { path: resolved, found } = this._locate(dirname, target);
69
+ if (found)
70
+ return resolved;
71
+ }
72
+ throw new Error(`file.resolveFirst() found none of: ${targets.map(t => `"${t}"`).join(', ')}\n Looked in ${dirname}.`);
73
+ },
74
+ };
75
+ }
76
+ /**
77
+ * The `read` member, resolving against `dirname`.
78
+ *
79
+ * @param dirname the package directory a relative path is resolved against
80
+ */
81
+ readScope(dirname) {
82
+ return (target, format) => {
83
+ if (typeof target !== 'string' || !target.trim()) {
84
+ throw new Error('read() needs a path - it was given ' + JSON.stringify(target));
85
+ }
86
+ return this.readStructured(path.resolve(dirname, target), format);
87
+ };
88
+ }
89
+ /**
90
+ * Reads and parses one file, deeply frozen and memoized.
91
+ *
92
+ * Throws when the file is absent - `file.exists(p) ? read(p) : fallback` is the optional form, so
93
+ * there is no second function for it.
94
+ */
95
+ /* **Memoized against the identity of the contents, not the path**: the key is `mtimeNs:size`.
96
+ * Both halves were measured.
97
+ *
98
+ * A stat rather than a re-read: `statSync` is 1.3µs where `readFileSync` + `JSON.parse` is 16.1µs
99
+ * on a 2KB manifest, so the check costs a thirteenth of what it saves.
100
+ *
101
+ * Keyed on the stat rather than held for the run, because **rman writes files while it runs** -
102
+ * `version` rewrites every bumped manifest and then re-interpolates its deferred hooks, and a
103
+ * path-keyed cache would hand those back as they were before the write. `mtimeNs` is nanoseconds,
104
+ * so a same-millisecond rewrite does not slip through.
105
+ *
106
+ * **Frozen once on the way in and shared**, rather than copied per caller: every package gets the
107
+ * same object, so a mutation would quietly change what the next one sees. A copy costs 5.6µs on
108
+ * every call where freezing costs ~1µs once, and it turns the mistake into a `TypeError` rather
109
+ * than an effect at a distance. */
110
+ readStructured(file, format) {
111
+ let stat;
112
+ try {
113
+ stat = fs.statSync(file, { bigint: true });
114
+ }
115
+ catch {
116
+ throw new Error(`read("${path.basename(file)}") found nothing at ${file}\n` +
117
+ ` Use file.exists() first if its absence is a case to handle rather than a mistake.`);
118
+ }
119
+ if (stat.isDirectory())
120
+ throw new Error(`read() was given a directory, not a file: ${file}`);
121
+ const stamp = `${stat.mtimeNs}:${stat.size}`;
122
+ const cached = this.cache.get(file);
123
+ if (cached?.stamp === stamp)
124
+ return cached.value;
125
+ const resolved = format ?? this.formatOf(file);
126
+ const text = fs.readFileSync(file, 'utf-8');
127
+ let value;
128
+ try {
129
+ value = this.parse(text, resolved);
130
+ }
131
+ catch (e) {
132
+ /** The parser's own message says what is wrong with the syntax but never which file it was
133
+ * reading - and an expression can name several. */
134
+ throw new Error(`read("${path.basename(file)}") could not parse ${file} as ${resolved}: ${e?.message}`, {
135
+ cause: e,
136
+ });
137
+ }
138
+ deepFreeze(value);
139
+ this.cache.set(file, { stamp, value });
140
+ return value;
141
+ }
142
+ /**
143
+ * What format a file name says it is. An unrecognized name is an error naming the four, rather
144
+ * than a guess at JSON.
145
+ */
146
+ /* The extension decides because the caller already wrote it - naming the parser as well would
147
+ * restate it and let the two disagree (`json("x.yml")`). A name that says nothing takes the
148
+ * explicit argument instead. */
149
+ formatOf(file) {
150
+ const ext = path.extname(file).toLowerCase();
151
+ if (ext === '.json')
152
+ return 'json';
153
+ if (ext === '.yml' || ext === '.yaml')
154
+ return 'yaml';
155
+ if (ext === '.ini')
156
+ return 'ini';
157
+ if (XML_EXTENSIONS.has(ext))
158
+ return 'xml';
159
+ throw new Error(`read() cannot tell what "${path.basename(file)}" is from its name.\n` +
160
+ ` Name the format: read("${path.basename(file)}", "json" | "yaml" | "ini" | "xml").`);
161
+ }
162
+ /** Parses text in one of the four formats. Override to add another. */
163
+ parse(text, format) {
164
+ if (format === 'json')
165
+ return JSON.parse(text);
166
+ /** `load`, not `loadAll`: a multi-document stream has no single value to be, and js-yaml says
167
+ * so clearly enough ("expected a single document in the stream") to leave alone. */
168
+ if (format === 'yaml')
169
+ return yaml.load(text);
170
+ if (format === 'xml')
171
+ return this.parseXml(text);
172
+ return ini.parse(text);
173
+ }
174
+ /**
175
+ * Parses XML into a **DOM**, not a plain object.
176
+ *
177
+ * ```yaml
178
+ * version: '${{ read("pom.xml").getElementsByTagName("version")[0].textContent }}'
179
+ * ```
180
+ */
181
+ /* **The asymmetry with the other three formats is the honest shape rather than an omission.** XML
182
+ * has no lossless object form: an element can repeat, carry attributes and hold text at the same
183
+ * time, so any flattening has to pick a convention (`$`? `_text`? array-or-not?) and be wrong for
184
+ * somebody. A DOM is the shape XML actually has.
185
+ *
186
+ * **Freezing a DOM is safe** - measured, not assumed: a frozen `@xmldom/xmldom` document still
187
+ * answers `getElementsByTagName` for a tag first asked about *after* the freeze (the
188
+ * live-collection case that would have broken it), reads attributes, resolves namespaces, walks
189
+ * `childNodes` and serialises back.
190
+ *
191
+ * **A malformed file must throw.** xmldom reports problems through a handler and otherwise
192
+ * carries on with whatever it salvaged - so without the check a truncated file came back as a
193
+ * half-parsed DOM and the expression reading it simply found nothing. `read()` throws for a
194
+ * broken JSON file; it has to throw for this one too. */
195
+ parseXml(text) {
196
+ const problems = [];
197
+ const doc = new DOMParser({
198
+ onError: (level, message) => {
199
+ if (level !== 'warning')
200
+ problems.push(message.split('\n')[0]);
201
+ },
202
+ }).parseFromString(text, 'text/xml');
203
+ if (problems.length)
204
+ throw new Error(problems[0]);
205
+ return doc;
206
+ }
207
+ /** Where a relative target lands, and whether anything is there. */
208
+ _locate(dirname, target) {
209
+ if (typeof target !== 'string' || !target.trim()) {
210
+ throw new Error('file.exists()/file.resolve() need a path - they were given ' + JSON.stringify(target));
211
+ }
212
+ const resolved = path.resolve(dirname, target);
213
+ return { path: resolved, found: fs.existsSync(resolved) };
214
+ }
215
+ }
216
+ /** The XML family worth recognizing by name: a project file is XML whatever its extension calls
217
+ * itself, and `.csproj`/`.pom` are what a .NET or Maven repository actually holds. Anything else
218
+ * still reads with an explicit `read(p, 'xml')`. */
219
+ const XML_EXTENSIONS = new Set(['.xml', '.csproj', '.vbproj', '.fsproj', '.props', '.targets', '.nuspec', '.plist']);
220
+ function deepFreeze(value) {
221
+ if (!value || typeof value !== 'object' || Object.isFrozen(value))
222
+ return;
223
+ Object.freeze(value);
224
+ for (const item of Object.values(value))
225
+ deepFreeze(item);
226
+ }
@@ -0,0 +1,164 @@
1
+ import vm from 'node:vm';
2
+ import type { ConfigScope } from '../../interfaces/config-scope.interface.js';
3
+ import type { Resolved } from '../../interfaces/rman-config.interface.js';
4
+ import { type PreviousValue } from './merge-config.js';
5
+ /**
6
+ * Resolves a config's values - every `${{ ... }}` expression and every value function - against a
7
+ * scope.
8
+ *
9
+ * Where `ConfigReader` answers *what the files say*, this answers *what those words evaluate to*.
10
+ * It takes a `ConfigScope` rather than a package, so whoever holds the repository builds the scope;
11
+ * `Repository.configScope` is that today.
12
+ *
13
+ * Two passes: {@link interpolate} resolves everything the scope can answer now, and
14
+ * {@link resolveDeferred} resolves what `defer(...)` left for later.
15
+ */
16
+ export declare class ConfigInterpolator {
17
+ /** How long one expression may run, in milliseconds. Raise it for a repository with genuinely
18
+ * slow `read()` calls. */
19
+ protected readonly expressionTimeout: number;
20
+ /** The file the key being walked was written in, for an error message to name. */
21
+ protected currentOrigin: string | undefined;
22
+ /** How many `defer(...)` calls have been rewritten so far. */
23
+ protected deferCount: number;
24
+ /**
25
+ * Pass one: resolves everything the scope can answer now, and leaves `defer(...)` for
26
+ * {@link resolveDeferred}.
27
+ *
28
+ * A string that is *nothing but* one expression keeps that value's own type -
29
+ * `"${{ pkg.private }}"` is a boolean, not the word `true`. Embedded in surrounding text it is
30
+ * stringified, and a nullish result there is an error rather than the word `undefined`.
31
+ *
32
+ * Returns `Resolved<T>`: every value function has been called, so they are gone from the type.
33
+ */
34
+ interpolate<T>(args: ConfigInterpolator.InterpolateArgs<T>): Resolved<T>;
35
+ /**
36
+ * Pass two: resolves what pass one deferred, against a scope that can now answer it.
37
+ *
38
+ * `version` is the case it exists for - `${{ defer(pkg.targetVersion) }}` in a `version` hook
39
+ * names a version that does not exist while the config is being read.
40
+ *
41
+ * Only the keys pass one marked are walked; a `${{` that arrived any other way is left exactly as
42
+ * it is. A string handed over on its own has no containing object to carry a mark, so it is
43
+ * interpolated outright - the caller passing one has already decided.
44
+ */
45
+ resolveDeferred<T>(args: ConfigInterpolator.ResolveDeferredArgs<T>): T;
46
+ /** Whether anything under `config` is waiting for {@link resolveDeferred} - `false` for every
47
+ * config that never writes `defer(...)`, so the second pass can be skipped. */
48
+ hasDeferred(config: unknown): boolean;
49
+ /** The V8 context an expression is evaluated in - a clean scope holding only `scope`'s bindings. */
50
+ protected _createContext(scope: ConfigScope): vm.Context;
51
+ protected _walk(value: unknown, scope: ConfigScope, context: vm.Context, at: Path, skip: string[]): any;
52
+ /**
53
+ * Runs `body` with `vars` scoped to this node: a fresh copy at every level, with the node's own
54
+ * `vars` block - if it declares one - merged **per key** over what the level above resolved to,
55
+ * so redeclaring one var keeps the rest.
56
+ *
57
+ * The node's own block is resolved against the outer scope before being installed, so
58
+ * `vars: { out: '${{ vars.x }}/dist' }` refines the `x` it inherits rather than reading itself.
59
+ */
60
+ protected _withScopedVars<T>(node: Record<string, unknown>, scope: ConfigScope, context: vm.Context, at: Path, skip: string[], body: () => T): T;
61
+ /**
62
+ * Walks one key with `value` bound to what the layers underneath resolved to.
63
+ *
64
+ * Always bound, even with nothing underneath - see {@link _previousValue} for what it is then.
65
+ * The chain resolves bottom-up, so a layer deriving from a layer that itself derived from
66
+ * something is handed the finished value rather than a half-resolved expression.
67
+ */
68
+ protected _walkWithPrevious(item: unknown, previous: PreviousValue | undefined, scope: ConfigScope, context: vm.Context, at: Path, skip: string[]): unknown;
69
+ /**
70
+ * Whether a function at `at` is **code** - a step to run later, or part of a plugin - rather than
71
+ * a value to compute now.
72
+ *
73
+ * Array indices are dropped before matching, so a function inside a *list* of steps is still a
74
+ * step; `*` in a `STEP_PATHS` entry matches any one segment (`run.<script>.exec`).
75
+ */
76
+ protected _isCodePath(at: Path): boolean;
77
+ /**
78
+ * Calls a **value** function: the JS spelling of a `${{ }}` expression, answering the same
79
+ * question at the same moment.
80
+ *
81
+ * It is handed one object carrying everything an expression can name, plus `value` - what this
82
+ * key resolved to in the layers underneath.
83
+ *
84
+ * A value function **computes and returns; it must never act.** This runs while the config
85
+ * resolves, which *every* command does - so one that writes a file writes it on `rman list`,
86
+ * `rman info` and `rman config` too, once per package, with nothing having asked. Work goes in a
87
+ * step.
88
+ */
89
+ protected _callValueFn(fn: (arg: unknown) => unknown, context: vm.Context, at: Path): unknown;
90
+ protected _interpolateString(value: string, context: vm.Context, at: Path): unknown;
91
+ /** Evaluates one expression, rewriting any `defer(...)` in it first. A failure names the config
92
+ * path and the expression. */
93
+ protected _evaluate(expr: string, source: string, context: vm.Context, at: Path): unknown;
94
+ /**
95
+ * Turns `defer(<inner>)` into a string literal holding `${{ <inner> }}`, so the expression around
96
+ * it resolves now and the deferred part survives as an expression for pass two.
97
+ *
98
+ * ```
99
+ * '_' + defer(pkg.targetVersion) + '-' + pkg.name
100
+ * '_' + "${{ pkg.targetVersion }}" + '-' + pkg.name // rewritten
101
+ * _${{ pkg.targetVersion }}-mypackage // evaluated
102
+ * ```
103
+ *
104
+ * `defer` is a marker the source is rewritten around, not a function in scope - so
105
+ * `defer(() => x)` is not a thing to write. A nested `defer` is refused.
106
+ */
107
+ protected _rewriteDefer(expr: string, source: string, at: Path): string;
108
+ /** The text between `defer(` and the parenthesis that closes it. */
109
+ protected _findDeferArgument(expr: string, open: number, source: string, at: Path): string;
110
+ /** Records which of `node`'s keys deferred, invisibly to anything but pass two. */
111
+ protected _markDeferred(node: Record<string, unknown>, keys: Record<string, true>): void;
112
+ /** Pass two's walk: only the keys pass one marked, so a `${{` that arrived from anywhere else is
113
+ * left exactly as it is. */
114
+ protected _walkDeferred(value: unknown, context: vm.Context, at: Path): unknown;
115
+ protected _withOrigin<T>(origin: string | undefined, body: () => T): T;
116
+ /** `"version.commitMessage" (.rmanrc.yml)`, or just the path when nothing recorded a file. The
117
+ * file is relative to the working directory when it sits inside it. */
118
+ protected _describeAt(at: Path): string;
119
+ /**
120
+ * What a layer deriving from the one below it reads as `value`.
121
+ *
122
+ * Spreads as empty when nothing below set the key, so `[...value, 'x']` needs no guard. Used as a
123
+ * string or a number it throws, naming the key and what to write instead.
124
+ */
125
+ protected _previousValue(raw: unknown, at: Path): unknown;
126
+ /** The one sentence both `value` refusals share: what was asked for, why it cannot be done, what
127
+ * to write instead. */
128
+ protected _unusable(at: Path, because: string, instead: string): Error;
129
+ }
130
+ /** Where a value sits in the config, as the walk builds it up - a string key or an array index.
131
+ * A module-level type rather than a member of the namespace below, so the class's own signatures
132
+ * can name it without shadowing the namespace's copy of the name. */
133
+ type Path = (string | number)[];
134
+ export declare namespace ConfigInterpolator {
135
+ interface InterpolateArgs<T = unknown> {
136
+ /** The config, or a fragment of it. */
137
+ config: T;
138
+ /** Everything an expression can name: `pkg`, `repository`, `file`, `env`, `semver`, `path`,
139
+ * `git`. Built by whoever holds the repository - see the class doc for why it is not a
140
+ * `Package`. */
141
+ scope: ConfigScope;
142
+ /**
143
+ * Where `config` sits in the whole config, for a caller handing over a **fragment**.
144
+ *
145
+ * Optional in the type and required in practice for a fragment: the path is what decides
146
+ * whether a function is a value to compute or a step to leave alone (`STEP_PATHS`). Without it
147
+ * a fragment starts at the root and matches nothing, so a function in a `version` hook was
148
+ * called while the hook was being *prepared* - it failed inside the user's own code with
149
+ * `path.join` receiving undefined.
150
+ */
151
+ at?: Path;
152
+ /** Config paths to leave entirely untouched, subtree and all. */
153
+ skip?: string[];
154
+ }
155
+ interface ResolveDeferredArgs<T = unknown> {
156
+ /** What `interpolate` returned - the marks it left are what this walks. */
157
+ config: T;
158
+ /** The scope with whatever pass one could not bind, `pkg.targetVersion` being the case this
159
+ * exists for. */
160
+ scope: ConfigScope;
161
+ at?: Path;
162
+ }
163
+ }
164
+ export {};