rman 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +3 -2
- package/commands/config.command.js +3 -2
- package/constants.js +1 -1
- package/core/config.d.ts +125 -6
- package/core/config.js +402 -50
- package/core/merge-config.d.ts +13 -0
- package/core/merge-config.js +47 -0
- package/core/repository.d.ts +15 -1
- package/core/repository.js +40 -8
- package/core/run-step.d.ts +75 -0
- package/core/run-step.js +1 -0
- package/index.d.ts +1 -0
- package/interfaces/rman-config.interface.d.ts +58 -17
- package/package.json +2 -1
- package/services/run.service.d.ts +66 -20
- package/services/run.service.js +171 -39
- package/services/version.service.d.ts +13 -3
- package/services/version.service.js +17 -11
- package/utils/printable-config.d.ts +15 -0
- package/utils/printable-config.js +42 -0
package/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import { assertNoBuiltinShadowing, loadCustomCommands } from './core/custom-comm
|
|
|
24
24
|
import { Repository } from './core/repository.js';
|
|
25
25
|
import { LOG_LEVELS, Logger, resolveRootLogLevel } from './utils/logger.js';
|
|
26
26
|
import { filterPackages, readPackageFilterOptions, readRootOption } from './utils/package-filter.js';
|
|
27
|
+
import { printableConfig } from './utils/printable-config.js';
|
|
27
28
|
import { runBin } from './utils/run-bin.js';
|
|
28
29
|
export async function runCli(options) {
|
|
29
30
|
try {
|
|
@@ -207,7 +208,7 @@ function printCommandConfig(repository, spec, args) {
|
|
|
207
208
|
console.log(`command: ${name}`);
|
|
208
209
|
const options = readOptions(args);
|
|
209
210
|
console.log(Object.keys(options).length
|
|
210
|
-
? `options:\n${indent(yaml.dump(options, { noRefs: true }).trimEnd())}`
|
|
211
|
+
? `options:\n${indent(yaml.dump(printableConfig(options), { noRefs: true }).trimEnd())}`
|
|
211
212
|
: `options: {}${comment(' # nothing but defaults')}`);
|
|
212
213
|
const targets = commandTargets(repository, args);
|
|
213
214
|
console.log(`packages: ${targets.length ? `[${targets.map(p => p.name).join(', ')}]` : '[]'}`);
|
|
@@ -225,7 +226,7 @@ function printCommandConfig(repository, spec, args) {
|
|
|
225
226
|
const config = {};
|
|
226
227
|
for (const pkg of shown)
|
|
227
228
|
config[pkg.name] = keys?.length ? pick(pkg.config, keys) : pkg.config;
|
|
228
|
-
console.log(indent(yaml.dump(config, { noRefs: true, lineWidth: 100 }).trimEnd()));
|
|
229
|
+
console.log(indent(yaml.dump(printableConfig(config), { noRefs: true, lineWidth: 100 }).trimEnd()));
|
|
229
230
|
if (!targets.includes(repository.rootPackage)) {
|
|
230
231
|
console.log(comment(`# "${repository.rootPackage.name}" is the root - listed because repo-wide keys are read there.`));
|
|
231
232
|
}
|
|
@@ -3,6 +3,7 @@ import colors from 'ansi-colors';
|
|
|
3
3
|
import * as yaml from 'js-yaml';
|
|
4
4
|
import { DEFERRED_PATHS } from '../core/config.js';
|
|
5
5
|
import { applyRootOption, readRootOption } from '../utils/package-filter.js';
|
|
6
|
+
import { printableConfig } from '../utils/printable-config.js';
|
|
6
7
|
export function initCli(repository, program) {
|
|
7
8
|
program.command({
|
|
8
9
|
command: 'config',
|
|
@@ -19,7 +20,7 @@ export function initCli(repository, program) {
|
|
|
19
20
|
handler: args => {
|
|
20
21
|
const target = (!readRootOption(args) && repository.currentPackage) || repository.rootPackage;
|
|
21
22
|
if (args.json) {
|
|
22
|
-
console.log(JSON.stringify(target.config, undefined, 2));
|
|
23
|
+
console.log(JSON.stringify(printableConfig(target.config), undefined, 2));
|
|
23
24
|
return;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
@@ -38,7 +39,7 @@ export function initCli(repository, program) {
|
|
|
38
39
|
/** `noRefs`: a value appearing twice in the config is the *same object* after merging, and
|
|
39
40
|
* js-yaml would otherwise emit the second as an `*anchor` reference - valid YAML that reads
|
|
40
41
|
* as a mistake in something meant to be looked at. */
|
|
41
|
-
console.log(yaml.dump(target.config, { noRefs: true, lineWidth: 100 }).trimEnd());
|
|
42
|
+
console.log(yaml.dump(printableConfig(target.config), { noRefs: true, lineWidth: 100 }).trimEnd());
|
|
42
43
|
},
|
|
43
44
|
});
|
|
44
45
|
}
|
package/constants.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '1.
|
|
1
|
+
export const version = '1.2.1';
|
package/core/config.d.ts
CHANGED
|
@@ -96,6 +96,17 @@ export declare function selectorToRegExp(key: string): RegExp;
|
|
|
96
96
|
* # first of these that exists, and an error naming the config path if none do
|
|
97
97
|
* exec: 'tsc -b ${{ file.exists("tsconfig-build.json") || file.resolve("tsconfig.json") }}'
|
|
98
98
|
* ```
|
|
99
|
+
*
|
|
100
|
+
* **Every member asks a question, and none may ever change anything - no `copy`, no `write`, no
|
|
101
|
+
* `mkdir`.** Not a matter of taste: this is evaluated when the config *resolves*, which every
|
|
102
|
+
* command does, so a member that acted would act on `rman list`, `rman info` and `rman config`.
|
|
103
|
+
*
|
|
104
|
+
* That has been tried, in the only way a missing function can be: a shared config reaching for a
|
|
105
|
+
* `file.copyMany(...)` that does not exist made **every** rman command exit 1 - and had it existed,
|
|
106
|
+
* the quieter outcome would have been files copied by `rman list`. Work belongs in a step
|
|
107
|
+
* (`run.<script>`'s slots, `version`'s hooks), which is the one place rman runs anything, and a
|
|
108
|
+
* step can now be a function - so there is nothing this would enable that is not already possible
|
|
109
|
+
* at the right moment.
|
|
99
110
|
*/
|
|
100
111
|
export interface FileScope {
|
|
101
112
|
/**
|
|
@@ -175,10 +186,6 @@ export interface RepositoryScope extends PackageScope {
|
|
|
175
186
|
packages: PackageScope[];
|
|
176
187
|
/** One package by name, or `undefined` - for reaching a sibling's directory. */
|
|
177
188
|
package(name: string): PackageScope | undefined;
|
|
178
|
-
/** Read from git only if an expression actually asks for it, then remembered: a repository that
|
|
179
|
-
* never mentions these pays nothing, and every command resolves config. All `undefined` outside
|
|
180
|
-
* a git checkout, which is a legitimate state rather than an error. */
|
|
181
|
-
git: GitScope;
|
|
182
189
|
}
|
|
183
190
|
export interface GitScope {
|
|
184
191
|
branch: string | undefined;
|
|
@@ -208,6 +215,23 @@ export interface ConfigScope {
|
|
|
208
215
|
repository: RepositoryScope;
|
|
209
216
|
/** Paths, resolved against the package the config was resolved for. */
|
|
210
217
|
file: FileScope;
|
|
218
|
+
/**
|
|
219
|
+
* The **contents** of a structured file - `${{ read('tsconfig.json').compilerOptions.outDir }}` -
|
|
220
|
+
* where `file` answers only where one is. Resolved against `pkg.dirname` like `file`, so a
|
|
221
|
+
* `"[*]"` block asks each package about its own; a repository-level file is reached through
|
|
222
|
+
* `read(path.join(repository.dirname, ...))`.
|
|
223
|
+
*
|
|
224
|
+
* `.json`, `.yml`/`.yaml` and `.ini` by extension, or name it for a file that does not say
|
|
225
|
+
* (`read('.npmrc', 'ini')`). **Throws** when the file is absent, as `file.resolve` does - compose
|
|
226
|
+
* with `file.exists` when its absence is a case to handle.
|
|
227
|
+
*
|
|
228
|
+
* **A manifest is `pkg.manifest`, not this.** `read('package.json')` works and is the wrong
|
|
229
|
+
* answer: which file a package's identity lives in belongs to the ecosystem, so that expression
|
|
230
|
+
* is already wrong in a Cargo package sitting beside a Node one.
|
|
231
|
+
*
|
|
232
|
+
* The result is **deeply frozen and shared** - see `readStructuredFile`. Spread it to change it.
|
|
233
|
+
*/
|
|
234
|
+
read: ReadFile;
|
|
211
235
|
env: Record<string, string | undefined>;
|
|
212
236
|
/** rman's own `semver`, for the arithmetic every release config eventually wants
|
|
213
237
|
* (`semver.major(pkg.version)`). */
|
|
@@ -221,6 +245,23 @@ export interface ConfigScope {
|
|
|
221
245
|
* genuinely needs one of them (a Docker image path, say, which is always posix).
|
|
222
246
|
*/
|
|
223
247
|
path: typeof path;
|
|
248
|
+
/**
|
|
249
|
+
* The checkout: branch, sha, whether the tree is dirty.
|
|
250
|
+
*
|
|
251
|
+
* **Top level, not `repository.git`** - which is where it used to be, and the move is the point.
|
|
252
|
+
* `repository` shares its shape with `pkg` because the repository root *is* a package, and its
|
|
253
|
+
* only other members (`monorepo`, `packages`, `package()`) say something about the repository as
|
|
254
|
+
* a container of packages. A branch name says nothing about any package; it describes the
|
|
255
|
+
* working tree every one of them happens to be sitting in - the same kind of ambient fact as
|
|
256
|
+
* `env`, and it belongs beside it.
|
|
257
|
+
*
|
|
258
|
+
* **Read from git only if an expression actually asks**, then remembered for the whole run: every
|
|
259
|
+
* command resolves config, and a repository that never mentions git must not pay for one. See
|
|
260
|
+
* `Repository.configScope` for the getter, and `interpolateConfig` for why the context is built
|
|
261
|
+
* from property descriptors rather than a spread - a spread would fire this getter on every
|
|
262
|
+
* command, which is exactly what moving it up here risked.
|
|
263
|
+
*/
|
|
264
|
+
git: GitScope;
|
|
224
265
|
}
|
|
225
266
|
/**
|
|
226
267
|
* Evaluates every `${{ ... }}` expression in **every** string value of a resolved config, against
|
|
@@ -264,9 +305,13 @@ export interface ConfigScope {
|
|
|
264
305
|
* A failing expression throws with the config path that holds it, rather than being left in place:
|
|
265
306
|
* silently passing through a mistake is how a config ends up quietly doing nothing.
|
|
266
307
|
*/
|
|
267
|
-
export
|
|
308
|
+
export interface InterpolateOptions {
|
|
309
|
+
/** Config paths to leave entirely untouched - `DEFERRED_PATHS`, when the whole config is walked. */
|
|
268
310
|
skip?: string[];
|
|
269
|
-
|
|
311
|
+
/** Where `config` sits in the whole config, for a caller interpolating a fragment. */
|
|
312
|
+
at?: string[];
|
|
313
|
+
}
|
|
314
|
+
export declare function interpolateConfig<T>(config: T, scope: ConfigScope, options?: InterpolateOptions): T;
|
|
270
315
|
/**
|
|
271
316
|
* Config paths left untouched when a repository's config is first resolved, and evaluated only by
|
|
272
317
|
* the command that runs them.
|
|
@@ -277,5 +322,79 @@ export declare function interpolateConfig<T>(config: T, scope: ConfigScope, opti
|
|
|
277
322
|
* command at all would fail on a config that mentions it.
|
|
278
323
|
*/
|
|
279
324
|
export declare const DEFERRED_PATHS: string[];
|
|
325
|
+
/**
|
|
326
|
+
* Paths whose value is a **step** - something to run later - rather than a setting to compute now.
|
|
327
|
+
* `*` matches one path segment (`run.<script>.exec`).
|
|
328
|
+
*
|
|
329
|
+
* This is what tells a step function from a value function, and the two live side by side in one
|
|
330
|
+
* config:
|
|
331
|
+
*
|
|
332
|
+
* ```js
|
|
333
|
+
* '[ws:*]': {
|
|
334
|
+
* clean: { include: ({ vars, value }) => [...value, vars.buildDir] }, // a value: called here
|
|
335
|
+
* run: { build: { after: ({ pkg }) => copyDocs(pkg) } }, // a step: called by `run`
|
|
336
|
+
* }
|
|
337
|
+
* ```
|
|
338
|
+
*
|
|
339
|
+
* **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
|
|
341
|
+
* where they sit. A function inherits the same rule, so nothing new has to be learned and no marker
|
|
342
|
+
* has to be remembered. The alternative was inspecting the function (arity, parameter names), which
|
|
343
|
+
* is the kind of guess `loadPlugins` refuses to make about a module's export for the same reason:
|
|
344
|
+
* guessing wrong here means running build-time code while merely loading the repository, or
|
|
345
|
+
* silently never running it.
|
|
346
|
+
*
|
|
347
|
+
* A **string** at one of these paths is still interpolated - `exec: 'tsc -b ${{ file.resolve(...) }}'`
|
|
348
|
+
* has to keep working - so this is narrower than `DEFERRED_PATHS`, which skips its paths entirely.
|
|
349
|
+
*/
|
|
350
|
+
export declare const STEP_PATHS: string[];
|
|
351
|
+
/**
|
|
352
|
+
* 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.
|
|
361
|
+
*/
|
|
362
|
+
export declare const CODE_SUBTREES: string[];
|
|
280
363
|
/** The `file` namespace for one package's directory - see `FileScope`. */
|
|
364
|
+
/**
|
|
365
|
+
* `read` in a `${{ ... }}` expression (and in a value function): a structured file's **contents**,
|
|
366
|
+
* parsed - where `file` answers only where a path is.
|
|
367
|
+
*
|
|
368
|
+
* ```yaml
|
|
369
|
+
* "[*]":
|
|
370
|
+
* run:
|
|
371
|
+
* build:
|
|
372
|
+
* exec: 'tsc --outDir ${{ read("tsconfig.json").compilerOptions.outDir }}'
|
|
373
|
+
* ```
|
|
374
|
+
*
|
|
375
|
+
* `cache` is shared across every package (see `Repository.configScope`) and keyed by what the file
|
|
376
|
+
* *is*, not merely where - so the same file read by twenty packages is parsed once, and a file rman
|
|
377
|
+
* itself rewrites mid-run is re-read rather than remembered. See `readStructuredFile`.
|
|
378
|
+
*/
|
|
379
|
+
export declare function createReadScope(dirname: string, cache: Map<string, CachedFile>): ReadFile;
|
|
380
|
+
/**
|
|
381
|
+
* What `read` can parse.
|
|
382
|
+
*
|
|
383
|
+
* **`.env` is deliberately absent, and that is the durable part of this list**: `env` is already in
|
|
384
|
+
* scope, and a `.env` file exists to be loaded *into* an environment by something else - a config
|
|
385
|
+
* reading one as data would mean two different things called the environment.
|
|
386
|
+
*
|
|
387
|
+
* Nothing else is excluded on principle. `xml` arrived because a `pom.xml` or a `.csproj` holds a
|
|
388
|
+
* version exactly the way a `package.json` does, and rman is language-agnostic; the earlier line
|
|
389
|
+
* ("no new parsers") did not survive it, since xmldom *is* a new one.
|
|
390
|
+
*/
|
|
391
|
+
export type FileFormat = 'json' | 'yaml' | 'ini' | 'xml';
|
|
392
|
+
/** `read(path)`, or `read(path, 'ini')` for a file whose name does not say what it is (`.npmrc`). */
|
|
393
|
+
export type ReadFile = (target: string, format?: FileFormat) => unknown;
|
|
394
|
+
/** One parsed file, kept against the identity of the bytes it came from - see `readStructuredFile`. */
|
|
395
|
+
export interface CachedFile {
|
|
396
|
+
/** `mtimeNs:size`. */
|
|
397
|
+
stamp: string;
|
|
398
|
+
value: unknown;
|
|
399
|
+
}
|
|
281
400
|
export declare function createFileScope(dirname: string): FileScope;
|